From aa124e3ecb31fa411a350b9744f307d3f01481eb Mon Sep 17 00:00:00 2001 From: Jess Frazelle Date: Mon, 5 Jun 2017 20:15:21 -0400 Subject: [PATCH] update tests Signed-off-by: Jess Frazelle --- main_test.go | 4 +- testutils/configs/basicauth.yml | 21 ++++ testutils/configs/htpasswd | 1 + testutils/configs/noauth.yml | 17 +++ testutils/snakeoil/cert.pem | 33 +++--- testutils/snakeoil/key.pem | 50 ++++----- testutils/testutils.go | 180 ++++++++++++++------------------ 7 files changed, 164 insertions(+), 142 deletions(-) create mode 100644 testutils/configs/basicauth.yml create mode 100644 testutils/configs/htpasswd create mode 100644 testutils/configs/noauth.yml diff --git a/main_test.go b/main_test.go index 13f222cf..165285bc 100644 --- a/main_test.go +++ b/main_test.go @@ -45,7 +45,7 @@ func TestMain(m *testing.M) { } // start registry - regID, addr, err := testutils.StartRegistry(dcli) + regID, addr, err := testutils.StartRegistry(dcli, "basicauth.yml", "admin", "testing") if err != nil { testutils.RemoveContainer(dcli, regID) panic(fmt.Errorf("starting registry container failed: %v", err)) @@ -78,7 +78,7 @@ func run(args ...string) (string, error) { func TestList(t *testing.T) { out, err := run("ls") if err != nil { - t.Fatal(err) + t.Fatalf("output: %s, error: %v", string(out), err) } expected := `Repositories for localhost:5000 REPO TAGS diff --git a/testutils/configs/basicauth.yml b/testutils/configs/basicauth.yml new file mode 100644 index 00000000..4c8f77d4 --- /dev/null +++ b/testutils/configs/basicauth.yml @@ -0,0 +1,21 @@ +version: 0.1 +log: + level: debug + formatter: text + fields: + service: registry +storage: + filesystem: + rootdirectory: /var/lib/registry +http: + addr: 0.0.0.0:5000 + headers: + X-Content-Type-Options: [nosniff] + host: https://localhost:5000 + tls: + certificate: /etc/docker/registry/ssl/cert.pem + key: /etc/docker/registry/ssl/key.pem +auth: + htpasswd: + realm: basic-realm + path: /etc/docker/registry/htpasswd diff --git a/testutils/configs/htpasswd b/testutils/configs/htpasswd new file mode 100644 index 00000000..0b499bdb --- /dev/null +++ b/testutils/configs/htpasswd @@ -0,0 +1 @@ +admin:$2y$05$TpcLzA8b5hMLptNGRIRWXuOI7KmAOqIuRhHAv15qHNrJaxuyIhCg6 diff --git a/testutils/configs/noauth.yml b/testutils/configs/noauth.yml new file mode 100644 index 00000000..253cd141 --- /dev/null +++ b/testutils/configs/noauth.yml @@ -0,0 +1,17 @@ +version: 0.1 +log: + level: debug + formatter: text + fields: + service: registry +storage: + filesystem: + rootdirectory: /var/lib/registry +http: + addr: 0.0.0.0:5000 + headers: + X-Content-Type-Options: [nosniff] + host: https://localhost:5000 + tls: + certificate: /etc/docker/registry/ssl/cert.pem + key: /etc/docker/registry/ssl/key.pem diff --git a/testutils/snakeoil/cert.pem b/testutils/snakeoil/cert.pem index afc8de1b..5955bcd3 100644 --- a/testutils/snakeoil/cert.pem +++ b/testutils/snakeoil/cert.pem @@ -1,18 +1,19 @@ -----BEGIN CERTIFICATE----- -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 +CxG31l+z -----END CERTIFICATE----- diff --git a/testutils/snakeoil/key.pem b/testutils/snakeoil/key.pem index 43f9fce3..326be163 100644 --- a/testutils/snakeoil/key.pem +++ b/testutils/snakeoil/key.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/testutils/testutils.go b/testutils/testutils.go index 5257935c..a7dcbe14 100644 --- a/testutils/testutils.go +++ b/testutils/testutils.go @@ -2,13 +2,17 @@ package testutils import ( "context" + "crypto/tls" + "crypto/x509" "encoding/base64" "encoding/json" "errors" "fmt" "io/ioutil" "net" + "net/http" "os" + "os/exec" "path/filepath" "runtime" "time" @@ -20,73 +24,29 @@ import ( "github.com/docker/docker/pkg/term" ) -const ( - registryConfig = `version: 0.1 -log: - level: debug - formatter: text - fields: - service: registry -storage: - filesystem: - rootdirectory: /var/lib/registry -http: - addr: 0.0.0.0:5000 - headers: - X-Content-Type-Options: [nosniff] - host: https://localhost:5000 - tls: - certificate: /etc/docker/registry/ssl/cert.pem - key: /etc/docker/registry/ssl/key.pem` - - // admin:testing - htpasswd = "admin:$apr1$2a7OBK4C$pZEqDfaxN3Qaywsi5hMKt1\n" -) - // StartRegistry starts a new registry container. -func StartRegistry(dcli *client.Client) (string, string, error) { - image := "registry:2" - - hostConfig, err := createRegistryConfig() - if err != nil { - return "", "", err - } - - hostHtpasswd, err := createRegistryHtpasswd() - if err != nil { - return "", "", err - } - - if err := pullDockerImage(dcli, image); err != nil { - return "", "", err - } - +func StartRegistry(dcli *client.Client, config, username, password string) (string, string, error) { _, filename, _, ok := runtime.Caller(0) if !ok { return "", "", errors.New("No caller information") } - fmt.Println(filename) + image := "registry:2" + + if err := pullDockerImage(dcli, image); err != nil { + return "", "", err + } r, err := dcli.ContainerCreate( context.Background(), &container.Config{ Image: image, - /*ExposedPorts: map[nat.Port]struct{}{ - "5000": {}, - },*/ }, &container.HostConfig{ - /* PortBindings: map[nat.Port][]nat.PortBinding{ - "5000": []nat.PortBinding{{ - HostIP: "0.0.0.0", - HostPort: "5000", - }}, - },*/ NetworkMode: "host", Binds: []string{ - hostConfig + ":" + "/etc/docker/registry/config.yml" + ":ro", - hostHtpasswd + ":" + "/etc/docker/registry/htpasswd" + ":ro", + filepath.Join(filepath.Dir(filename), "configs", config) + ":" + "/etc/docker/registry/config.yml" + ":ro", + filepath.Join(filepath.Dir(filename), "configs", "htpasswd") + ":" + "/etc/docker/registry/htpasswd" + ":ro", filepath.Join(filepath.Dir(filename), "snakeoil") + ":" + "/etc/docker/registry/ssl" + ":ro", }, }, @@ -100,19 +60,18 @@ func StartRegistry(dcli *client.Client) (string, string, error) { return r.ID, "", err } - // get the container's IP - /*info, err := dcli.ContainerInspect(context.Background(), r.ID) - if err != nil { - return r.ID, "", err - }*/ port := ":5000" - // addr := "http://" + info.NetworkSettings.IPAddress + port addr := "https://localhost" + port - // waitForConn(info.NetworkSettings.IPAddress + port) - waitForConn("localhost" + port) + if err := waitForConn(addr, filepath.Join(filepath.Dir(filename), "snakeoil", "cert.pem"), filepath.Join(filepath.Dir(filename), "snakeoil", "key.pem")); err != nil { + return r.ID, addr, err + } - if err := prefillRegistry(dcli, "localhost"+port); err != nil { + if err := dockerLogin("localhost"+port, username, password); err != nil { + return r.ID, addr, err + } + + if err := prefillRegistry(dcli, "localhost"+port, username, password); err != nil { return r.ID, addr, err } @@ -132,8 +91,18 @@ func RemoveContainer(dcli *client.Client, ctrID string) error { return nil } +// dockerLogin logins via the command line to a docker registry +func dockerLogin(addr, username, password string) error { + cmd := exec.Command("docker", "login", addr, "--username", username, "--password", password) + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("docker login failed with output %q and error: %v", string(out), err) + } + return nil +} + // prefillRegistry adds images to a registry. -func prefillRegistry(dcli *client.Client, addr string) error { +func prefillRegistry(dcli *client.Client, addr, username, password string) error { image := "alpine:latest" if err := pullDockerImage(dcli, image); err != nil { @@ -144,7 +113,7 @@ func prefillRegistry(dcli *client.Client, addr string) error { return err } - auth, err := constructRegistryAuth("admin", "testing") + auth, err := constructRegistryAuth(username, password) if err != nil { return err } @@ -196,57 +165,70 @@ func imageExists(dcli *client.Client, image string) (bool, error) { return false, err } -func createRegistryConfig() (string, error) { - tmpfile, err := ioutil.TempFile("", "registry") - if err != nil { - return "", err - } - - if _, err := tmpfile.WriteString(registryConfig); err != nil { - return "", err - } - if err := tmpfile.Close(); err != nil { - return "", err - } - - return tmpfile.Name(), nil -} - -func createRegistryHtpasswd() (string, error) { - tmpfile, err := ioutil.TempFile("", "registry-htpasswd") - if err != nil { - return "", err - } - - if _, err := tmpfile.WriteString(htpasswd); err != nil { - return "", err - } - if err := tmpfile.Close(); err != nil { - return "", err - } - - return tmpfile.Name(), nil -} - // waitForConn takes a tcp addr and waits until it is reachable -func waitForConn(addr string) { +func waitForConn(addr, cert, key string) error { + tlsCert, err := tls.LoadX509KeyPair(cert, key) + if err != nil { + return fmt.Errorf("Could not load X509 key pair: %v. Make sure the key is not encrypted", err) + } + + certPool, err := x509.SystemCertPool() + if err != nil { + return fmt.Errorf("failed to read system certificates: %v", err) + } + pem, err := ioutil.ReadFile(cert) + if err != nil { + return fmt.Errorf("could not read CA certificate %s: %v", cert, err) + } + if !certPool.AppendCertsFromPEM(pem) { + return fmt.Errorf("failed to append certificates from PEM file: %s", cert) + } + + c := http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }).DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + TLSClientConfig: &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + MinVersion: tls.VersionTLS12, + CipherSuites: []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }, + RootCAs: certPool, + }, + }, + } + n := 0 max := 10 for n < max { - if _, err := net.Dial("tcp", addr); err != nil { - fmt.Printf("try number %d to dial %s: %v\n", n, addr, err) + if _, err := c.Get(addr + "/v2/"); err != nil { + fmt.Printf("try number %d to %s: %v\n", n, addr, err) n++ if n != max { fmt.Println("sleeping for 1 second then will try again...") time.Sleep(time.Second) } else { - fmt.Printf("[WHOOPS]: maximum retries for %s exceeded\n", addr) + return fmt.Errorf("[WHOOPS]: maximum retries for %s exceeded\n", addr) } continue } else { break } } + + return nil } // constructRegistryAuth serializes the auth configuration as JSON base64 payload.