Refactor: Use BROWSER env var to run chrome-login script (#232)

* Use BROWSER env var to run chrome-login script

* Update acceptance-test-diagram.svg

* Added acceptance-test-diagram.svg
This commit is contained in:
Hidetake Iwata
2020-02-11 16:06:04 +09:00
committed by GitHub
parent 2fa306c348
commit 1b545e1c58
7 changed files with 111 additions and 158 deletions

View File

@@ -26,4 +26,5 @@ jobs:
# https://packages.ubuntu.com/xenial/libnss3-tools
- run: sudo apt install -y libnss3-tools
- run: echo '127.0.0.1 dex-server' | sudo tee -a /etc/hosts
- run: make -C acceptance_test -j3
- run: make -C acceptance_test -j3 setup
- run: make -C acceptance_test test

View File

@@ -5,21 +5,26 @@ KUBECONFIG := $(OUTPUT_DIR)/kubeconfig.yaml
export KUBECONFIG
.PHONY: test
test: build cluster add-dex-ca-cert-for-chrome
PATH=$(PATH):$(OUTPUT_DIR)/bin acceptance_test -test.v
test: build
PATH=$(PATH):$(OUTPUT_DIR)/bin BROWSER=chromelogin KUBECONFIG=$(KUBECONFIG):kubeconfig_oidc.yaml \
kubectl --user=oidc cluster-info
.PHONY: add-dex-ca-cert-for-chrome
add-dex-ca-cert-for-chrome: $(OUTPUT_DIR)/ca.crt
.PHONY: setup
setup: build dex cluster setup-chrome
.PHONY: setup-chrome
setup-chrome: $(OUTPUT_DIR)/ca.crt
# add the dex server certificate to the trust store
mkdir -p ~/.pki/nssdb
cd ~/.pki/nssdb && certutil -A -d sql:. -n dex -i $(OUTPUT_DIR)/ca.crt -t "TC,,"
# build binaries
.PHONY: build
build: $(OUTPUT_DIR)/bin/kubectl-oidc_login $(OUTPUT_DIR)/bin/acceptance_test
build: $(OUTPUT_DIR)/bin/kubectl-oidc_login $(OUTPUT_DIR)/bin/chromelogin
$(OUTPUT_DIR)/bin/kubectl-oidc_login:
go build -o $@ ..
$(OUTPUT_DIR)/bin/acceptance_test: acceptance_test.go
go test -c -o $@ .
$(OUTPUT_DIR)/bin/chromelogin: chromelogin/main.go
go build -o $@ ./chromelogin
# create a Dex server
.PHONY: dex

View File

@@ -19,8 +19,8 @@ It performs the test by the following steps:
1. Run kubectl.
1. kubectl automatically runs kubelogin.
1. Open the browser and navigate to `http://localhost:8000`.
1. Enter the username and password on the browser.
1. kubelogin automatically runs [chromelogin](chromelogin).
1. chromelogin opens the browser, navigates to `http://localhost:8000` and enter the username and password.
1. kubelogin gets an authorization code from the browser.
1. kubelogin gets a token.
1. kubectl accesses an API with the token.
@@ -66,12 +66,7 @@ As a result,
- Set the issuer URL to kubectl. See [`kubeconfig_oidc.yaml`](kubeconfig_oidc.yaml).
- Set the issuer URL to kube-apiserver. See [`cluster.yaml`](cluster.yaml).
### Test scenario
- Run `kubectl` and open the browser concurrently.
- It need to wait until `http://localhost:8000` is available. It prevents the browser error.
- It need to kill sub-processes finally, i.e. kubectl and kubelogin.
- Set `BROWSER` environment variable to run [`chromelogin`](chromelogin) by `xdg-open`.
## Run locally

View File

@@ -1,140 +0,0 @@
package acceptance_test
import (
"context"
"fmt"
"log"
"os"
"os/exec"
"strings"
"syscall"
"testing"
"time"
"github.com/chromedp/chromedp"
"golang.org/x/sync/errgroup"
)
const (
tokenCacheDir = "output/token-cache"
kubeconfigEnv = "KUBECONFIG=output/kubeconfig.yaml:kubeconfig_oidc.yaml"
)
func init() {
log.SetFlags(log.Lmicroseconds | log.Lshortfile)
}
func Test(t *testing.T) {
if _, err := os.Stat("output/kubeconfig.yaml"); err != nil {
t.Skipf("skip the test: %s", err)
}
if err := os.RemoveAll(tokenCacheDir); err != nil {
t.Fatalf("could not remove the token cache: %s", err)
}
ctx := context.TODO()
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error { return runKubectl(ctx, t, eg) })
eg.Go(func() error { return runBrowser(ctx) })
if err := eg.Wait(); err != nil {
t.Errorf("error: %s", err)
}
}
func runKubectl(ctx context.Context, t *testing.T, eg *errgroup.Group) error {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
cmd := exec.Command("kubectl", "--user=oidc", "--namespace=dex", "get", "deploy")
cmd.Env = append(os.Environ(), kubeconfigEnv)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
eg.Go(func() error {
<-ctx.Done()
if cmd.Process == nil {
log.Printf("process not started")
return nil
}
if cmd.ProcessState != nil && cmd.ProcessState.Exited() {
log.Printf("process terminated with exit code %d", cmd.ProcessState.ExitCode())
return nil
}
log.Printf("sending SIGTERM to pid %d", cmd.Process.Pid)
// kill the child processes
// https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773
if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGTERM); err != nil {
t.Errorf("could not send a signal: %s", err)
}
return nil
})
if err := cmd.Run(); err != nil {
return fmt.Errorf("could not run a command: %w", err)
}
return nil
}
func runBrowser(ctx context.Context) error {
execOpts := chromedp.DefaultExecAllocatorOptions[:]
execOpts = append(execOpts, chromedp.NoSandbox)
ctx, cancel := chromedp.NewExecAllocator(ctx, execOpts...)
defer cancel()
ctx, cancel = chromedp.NewContext(ctx, chromedp.WithLogf(log.Printf))
defer cancel()
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
defer cancel()
if err := openKubeloginAndLogInToDex(ctx); err != nil {
return fmt.Errorf("could not run the browser: %w", err)
}
return nil
}
func openKubeloginAndLogInToDex(ctx context.Context) error {
for {
var location string
err := chromedp.Run(ctx,
chromedp.Navigate(`http://localhost:8000`),
chromedp.Location(&location),
)
if err != nil {
return err
}
log.Printf("location: %s", location)
if strings.HasPrefix(location, `http://`) || strings.HasPrefix(location, `https://`) {
break
}
time.Sleep(2 * time.Second)
}
err := chromedp.Run(ctx,
// https://dex-server:10443/dex/auth/local
chromedp.WaitVisible(`#login`),
logPageMetadata(),
chromedp.SendKeys(`#login`, `admin@example.com`),
chromedp.SendKeys(`#password`, `password`),
chromedp.Submit(`#submit-login`),
// https://dex-server:10443/dex/approval
chromedp.WaitVisible(`.dex-btn.theme-btn--success`),
logPageMetadata(),
chromedp.Submit(`.dex-btn.theme-btn--success`),
// http://localhost:8000
chromedp.WaitReady(`body`),
logPageMetadata(),
)
if err != nil {
return err
}
return nil
}
func logPageMetadata() chromedp.Action {
var location string
var title string
return chromedp.Tasks{
chromedp.Location(&location),
chromedp.Title(&title),
chromedp.ActionFunc(func(ctx context.Context) error {
log.Printf("location: %s [%s]", location, title)
return nil
}),
}
}

View File

@@ -0,0 +1,93 @@
package main
import (
"context"
"fmt"
"log"
"os"
"strings"
"time"
"github.com/chromedp/chromedp"
)
func init() {
log.SetFlags(log.Lmicroseconds | log.Lshortfile)
}
func main() {
if len(os.Args) != 2 {
log.Fatalf("usage: %s URL", os.Args[0])
return
}
url := os.Args[1]
if err := runBrowser(context.Background(), url); err != nil {
log.Fatalf("error: %s", err)
}
}
func runBrowser(ctx context.Context, url string) error {
execOpts := chromedp.DefaultExecAllocatorOptions[:]
execOpts = append(execOpts, chromedp.NoSandbox)
ctx, cancel := chromedp.NewExecAllocator(ctx, execOpts...)
defer cancel()
ctx, cancel = chromedp.NewContext(ctx, chromedp.WithLogf(log.Printf))
defer cancel()
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
defer cancel()
if err := logInToDex(ctx, url); err != nil {
return fmt.Errorf("could not run the browser: %w", err)
}
return nil
}
func logInToDex(ctx context.Context, url string) error {
for {
var location string
err := chromedp.Run(ctx,
chromedp.Navigate(url),
chromedp.Location(&location),
)
if err != nil {
return err
}
log.Printf("location: %s", location)
if strings.HasPrefix(location, `http://`) || strings.HasPrefix(location, `https://`) {
break
}
time.Sleep(1 * time.Second)
}
err := chromedp.Run(ctx,
// https://dex-server:10443/dex/auth/local
chromedp.WaitVisible(`#login`),
logPageMetadata(),
chromedp.SendKeys(`#login`, `admin@example.com`),
chromedp.SendKeys(`#password`, `password`),
chromedp.Submit(`#submit-login`),
// https://dex-server:10443/dex/approval
chromedp.WaitVisible(`.dex-btn.theme-btn--success`),
logPageMetadata(),
chromedp.Submit(`.dex-btn.theme-btn--success`),
// http://localhost:8000
chromedp.WaitReady(`body`),
logPageMetadata(),
)
if err != nil {
return err
}
return nil
}
func logPageMetadata() chromedp.Action {
var location string
var title string
return chromedp.Tasks{
chromedp.Location(&location),
chromedp.Title(&title),
chromedp.ActionFunc(func(ctx context.Context) error {
log.Printf("location: %s [%s]", location, title)
return nil
}),
}
}

View File

@@ -15,7 +15,6 @@ users:
- --certificate-authority=output/ca.crt
- --token-cache-dir=output/token-cache
- --listen-address=127.0.0.1:8000
- --skip-open-browser
- -v1
command: kubectl
contexts:

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 28 KiB