From b980f3303b12da3ba3ca83b3b5857e34026a4429 Mon Sep 17 00:00:00 2001 From: Roman Khlystik Date: Fri, 12 Feb 2016 16:31:27 +0200 Subject: [PATCH 1/6] Moved to new rocker/imagename. Also updated other rocker libs --- main.go | 4 +- src/compose/client.go | 14 +- src/compose/client_test.go | 2 +- src/compose/compose.go | 2 +- src/compose/config/config.go | 4 +- src/compose/container.go | 2 +- src/compose/diff_test.go | 2 +- src/compose/docker.go | 6 +- .../rocker/src/rocker/dockerclient/auth.go | 143 ----- .../src/rocker/dockerclient/dockerclient.go | 221 -------- .../rocker/dockerclient/dockerclient_test.go | 224 -------- .../rocker/src/rocker/dockerclient/matrix.go | 132 ----- .../src/rocker/dockerclient/matrix_test.go | 52 -- .../src/rocker/dockerclient/registry.go | 277 --------- .../rocker/src/rocker/imagename/artifact.go | 74 --- .../rocker/src/rocker/imagename/imagename.go | 394 ------------- .../src/rocker/imagename/imagename_test.go | 526 ----------------- .../rocker/src/rocker/storage/s3/logger.go | 33 -- .../rocker/src/rocker/storage/s3/retryer.go | 95 ---- .../rocker/src/rocker/storage/s3/s3.go | 528 ------------------ .../rocker/src/rocker/storage/s3/s3_test.go | 47 -- .../rocker/src/rocker/storage/storage.go | 30 - .../rocker/src/rocker/template/LICENSE | 13 - .../rocker/src/rocker/template/Makefile | 32 -- .../rocker/src/rocker/template/README.md | 256 --------- .../rocker/src/rocker/template/shellarg.go | 52 -- .../src/rocker/template/shellarg_test.go | 51 -- .../rocker/src/rocker/template/template.go | 358 ------------ .../src/rocker/template/template_test.go | 228 -------- .../src/rocker/template/testdata/content.txt | 1 - .../rocker/src/rocker/template/vars.go | 295 ---------- .../rocker/src/rocker/template/vars_test.go | 320 ----------- vendor/manifest | 177 +++--- 33 files changed, 111 insertions(+), 4484 deletions(-) delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/dockerclient/auth.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/dockerclient/dockerclient.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/dockerclient/dockerclient_test.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/dockerclient/matrix.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/dockerclient/matrix_test.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/dockerclient/registry.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/imagename/artifact.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/imagename/imagename.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/imagename/imagename_test.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/storage/s3/logger.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/storage/s3/retryer.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/storage/s3/s3.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/storage/s3/s3_test.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/storage/storage.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/template/LICENSE delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/template/Makefile delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/template/README.md delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/template/shellarg.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/template/shellarg_test.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/template/template.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/template/template_test.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/template/testdata/content.txt delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/template/vars.go delete mode 100644 vendor/github.com/grammarly/rocker/src/rocker/template/vars_test.go diff --git a/main.go b/main.go index d330d32..1630f1e 100644 --- a/main.go +++ b/main.go @@ -36,10 +36,10 @@ import ( "github.com/codegangsta/cli" "github.com/fsouza/go-dockerclient" "github.com/go-yaml/yaml" + "github.com/grammarly/rocker/src/dockerclient" "github.com/grammarly/rocker/src/rocker/debugtrap" - "github.com/grammarly/rocker/src/rocker/dockerclient" - "github.com/grammarly/rocker/src/rocker/template" "github.com/grammarly/rocker/src/rocker/textformatter" + "github.com/grammarly/rocker/src/template" ) var ( diff --git a/src/compose/client.go b/src/compose/client.go index 620ac21..37df6e6 100644 --- a/src/compose/client.go +++ b/src/compose/client.go @@ -23,10 +23,10 @@ import ( "os" "time" - "github.com/grammarly/rocker/src/rocker/dockerclient" - "github.com/grammarly/rocker/src/rocker/imagename" - "github.com/grammarly/rocker/src/rocker/storage/s3" - "github.com/grammarly/rocker/src/rocker/template" + "github.com/grammarly/rocker/src/dockerclient" + "github.com/grammarly/rocker/src/imagename" + "github.com/grammarly/rocker/src/storage/s3" + "github.com/grammarly/rocker/src/template" "github.com/kr/pretty" log "github.com/Sirupsen/logrus" @@ -715,7 +715,7 @@ func (client *DockerClient) resolveVersions(local, hub bool, vars template.Vars, } // looking locally first - candidate := container.Image.ResolveVersion(images) + candidate := container.Image.ResolveVersion(images, true) // in case we want to include external images as well, pulling list of available // images from repository or central docker hub @@ -739,12 +739,14 @@ func (client *DockerClient) resolveVersions(local, hub bool, vars template.Vars, log.Debugf("remote: %v", remote) // Re-Resolve having hub tags - candidate = container.Image.ResolveVersion(append(images, remote...)) + candidate = container.Image.ResolveVersion(append(images, remote...), false) } if candidate == nil { err = fmt.Errorf("Image not found: %s", container.Image) return + } else { + candidate.IsOldS3Name = container.Image.IsOldS3Name } log.Infof("Resolve %s --> %s", container.Image, candidate.GetTag()) diff --git a/src/compose/client_test.go b/src/compose/client_test.go index c9d5253..5de8d0b 100644 --- a/src/compose/client_test.go +++ b/src/compose/client_test.go @@ -25,8 +25,8 @@ import ( log "github.com/Sirupsen/logrus" "github.com/fsouza/go-dockerclient" + "github.com/grammarly/rocker/src/imagename" "github.com/grammarly/rocker/src/rocker/dockerclient" - "github.com/grammarly/rocker/src/rocker/imagename" "github.com/grammarly/rocker/src/rocker/template" "github.com/grammarly/rocker/src/rocker/test" "github.com/kr/pretty" diff --git a/src/compose/compose.go b/src/compose/compose.go index 08b5fcc..5d8a53c 100644 --- a/src/compose/compose.go +++ b/src/compose/compose.go @@ -29,7 +29,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/fsouza/go-dockerclient" - "github.com/grammarly/rocker/src/rocker/template" + "github.com/grammarly/rocker/src/template" "github.com/kr/pretty" ) diff --git a/src/compose/config/config.go b/src/compose/config/config.go index ff3be44..f2a4d3b 100644 --- a/src/compose/config/config.go +++ b/src/compose/config/config.go @@ -33,8 +33,8 @@ import ( "regexp" "strings" - "github.com/grammarly/rocker/src/rocker/imagename" - "github.com/grammarly/rocker/src/rocker/template" + "github.com/grammarly/rocker/src/imagename" + "github.com/grammarly/rocker/src/template" "github.com/mitchellh/go-homedir" "github.com/fsouza/go-dockerclient" diff --git a/src/compose/container.go b/src/compose/container.go index da35d96..160d25b 100644 --- a/src/compose/container.go +++ b/src/compose/container.go @@ -23,7 +23,7 @@ import ( "time" "github.com/go-yaml/yaml" - "github.com/grammarly/rocker/src/rocker/imagename" + "github.com/grammarly/rocker/src/imagename" log "github.com/Sirupsen/logrus" "github.com/fsouza/go-dockerclient" diff --git a/src/compose/diff_test.go b/src/compose/diff_test.go index 31655da..8c26551 100644 --- a/src/compose/diff_test.go +++ b/src/compose/diff_test.go @@ -21,7 +21,7 @@ import ( "github.com/grammarly/rocker-compose/src/compose/config" "testing" - "github.com/grammarly/rocker/src/rocker/imagename" + "github.com/grammarly/rocker/src/imagename" "github.com/grammarly/rocker/src/rocker/template" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/src/compose/docker.go b/src/compose/docker.go index e151c41..a63fdba 100644 --- a/src/compose/docker.go +++ b/src/compose/docker.go @@ -25,9 +25,9 @@ import ( "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/term" "github.com/fsouza/go-dockerclient" - "github.com/grammarly/rocker/src/rocker/dockerclient" - "github.com/grammarly/rocker/src/rocker/imagename" - "github.com/grammarly/rocker/src/rocker/storage/s3" + "github.com/grammarly/rocker/src/dockerclient" + "github.com/grammarly/rocker/src/imagename" + "github.com/grammarly/rocker/src/storage/s3" ) const emptyImageName = "gliderlabs/alpine:3.2" diff --git a/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/auth.go b/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/auth.go deleted file mode 100644 index c9c9f8a..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/auth.go +++ /dev/null @@ -1,143 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dockerclient - -import ( - "encoding/base64" - "fmt" - "strings" - "sync" - - "github.com/grammarly/rocker/src/rocker/imagename" - - log "github.com/Sirupsen/logrus" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/ecr" - "github.com/fsouza/go-dockerclient" -) - -type ecrAuthCache struct { - tokens map[string]docker.AuthConfiguration - mu sync.Mutex -} - -var ( - _ecrAuthCache = ecrAuthCache{ - tokens: map[string]docker.AuthConfiguration{}, - } -) - -// GetAuthForRegistry extracts desired docker.AuthConfiguration object from the -// list of docker.AuthConfigurations by registry hostname -func GetAuthForRegistry(auth *docker.AuthConfigurations, image *imagename.ImageName) (result docker.AuthConfiguration, err error) { - - registry := image.Registry - - // The default registry is "index.docker.io" - if registry == "" || registry == "registry-1.docker.io" { - registry = "index.docker.io" - } - // Optionally override auth took via aws-sdk (through ENV vars) - if image.IsECR() { - if awsRegAuth, err := GetECRAuth(registry); err != nil && err != credentials.ErrNoValidProvidersFoundInChain { - return result, err - } else if awsRegAuth.Username != "" { - return awsRegAuth, nil - } - } - - if auth == nil { - return - } - - if result, ok := auth.Configs[registry]; ok { - return result, nil - } - if result, ok := auth.Configs["https://"+registry]; ok { - return result, nil - } - if result, ok := auth.Configs["https://"+registry+"/v1/"]; ok { - return result, nil - } - // not sure /v2/ is needed, but just in case - if result, ok := auth.Configs["https://"+registry+"/v2/"]; ok { - return result, nil - } - if result, ok := auth.Configs["*"]; ok { - return result, nil - } - return -} - -// GetECRAuth requests AWS ECR API to get docker.AuthConfiguration token -func GetECRAuth(registry string) (result docker.AuthConfiguration, err error) { - _ecrAuthCache.mu.Lock() - defer _ecrAuthCache.mu.Unlock() - - if token, ok := _ecrAuthCache.tokens[registry]; ok { - return token, nil - } - - defer func() { - _ecrAuthCache.tokens[registry] = result - }() - - // TODO: take region from the registry hostname? - cfg := &aws.Config{ - Region: aws.String("us-east-1"), - } - - if log.StandardLogger().Level >= log.DebugLevel { - cfg.LogLevel = aws.LogLevel(aws.LogDebugWithRequestErrors) - } - - split := strings.Split(registry, ".") - - svc := ecr.New(session.New(), cfg) - params := &ecr.GetAuthorizationTokenInput{ - RegistryIds: []*string{aws.String(split[0])}, - } - - res, err := svc.GetAuthorizationToken(params) - if err != nil { - return result, err - } - - if len(res.AuthorizationData) == 0 { - return result, nil - } - - data, err := base64.StdEncoding.DecodeString(*res.AuthorizationData[0].AuthorizationToken) - if err != nil { - return result, err - } - - userpass := strings.Split(string(data), ":") - if len(userpass) != 2 { - return result, fmt.Errorf("Cannot parse token got from ECR: %s", string(data)) - } - - result = docker.AuthConfiguration{ - Username: userpass[0], - Password: userpass[1], - ServerAddress: *res.AuthorizationData[0].ProxyEndpoint, - } - - return -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/dockerclient.go b/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/dockerclient.go deleted file mode 100644 index 76d77cf..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/dockerclient.go +++ /dev/null @@ -1,221 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Package dockerclient provides utilities for embedding docker client -// functionality to other tools. It provides configurable docker client -// connection functions, config struct, integration with codegangsta/cli. -package dockerclient - -import ( - "fmt" - "log" - "os" - "strconv" - "strings" - "time" - - "github.com/codegangsta/cli" - "github.com/fsouza/go-dockerclient" - "github.com/mitchellh/go-homedir" -) - -var ( - // DefaultEndpoint is the default address of Docker socket - DefaultEndpoint = "unix:///var/run/docker.sock" -) - -// Config represents docker client connection parameters -type Config struct { - Host string - Tlsverify bool - Tlscacert string - Tlscert string - Tlskey string -} - -// NewConfig returns new config with resolved options from current ENV -func NewConfig() *Config { - certPath := os.Getenv("DOCKER_CERT_PATH") - if certPath == "" { - homePath, err := homedir.Dir() - if err != nil { - log.Fatal(err) - } - certPath = homePath + "/.docker" - } - host := os.Getenv("DOCKER_HOST") - if host == "" { - host = DefaultEndpoint - } - // why NewConfigFromCli default value is not working - return &Config{ - Host: host, - Tlsverify: os.Getenv("DOCKER_TLS_VERIFY") == "1" || os.Getenv("DOCKER_TLS_VERIFY") == "yes", - Tlscacert: certPath + "/ca.pem", - Tlscert: certPath + "/cert.pem", - Tlskey: certPath + "/key.pem", - } -} - -// NewConfigFromCli returns new config with NewConfig overridden cli options -func NewConfigFromCli(c *cli.Context) *Config { - config := NewConfig() - config.Host = globalCliString(c, "host") - if c.GlobalIsSet("tlsverify") { - config.Tlsverify = c.GlobalBool("tlsverify") - config.Tlscacert = globalCliString(c, "tlscacert") - config.Tlscert = globalCliString(c, "tlscert") - config.Tlskey = globalCliString(c, "tlskey") - } - return config -} - -// New returns a new docker client connection with default config -func New() (*docker.Client, error) { - return NewFromConfig(NewConfig()) -} - -// NewFromConfig returns a new docker client connection with given config -func NewFromConfig(config *Config) (*docker.Client, error) { - if config.Tlsverify { - return docker.NewTLSClient(config.Host, config.Tlscert, config.Tlskey, config.Tlscacert) - } - return docker.NewClient(config.Host) -} - -// NewFromCli returns a new docker client connection with config built from cli params -func NewFromCli(c *cli.Context) (*docker.Client, error) { - return NewFromConfig(NewConfigFromCli(c)) -} - -// Ping pings docker client but with timeout -// The problem is that for some reason it's impossible to set the -// default timeout for the go-dockerclient Dialer, need to investigate -func Ping(client *docker.Client, timeoutMs int) error { - var ( - chErr = make(chan error) - timeout = time.Duration(timeoutMs) * time.Millisecond - ) - go func() { - chErr <- client.Ping() - }() - select { - case err := <-chErr: - return err - case <-time.After(timeout): - // TODO: can we kill the ping goroutine? - return fmt.Errorf("Failed to reach docker server, timeout %s", timeout) - } -} - -// GlobalCliParams returns global params that configures docker client connection -func GlobalCliParams() []cli.Flag { - return []cli.Flag{ - cli.StringFlag{ - Name: "host, H", - Value: DefaultEndpoint, - Usage: "Daemon socket(s) to connect to", - EnvVar: "DOCKER_HOST", - }, - cli.BoolFlag{ - Name: "tlsverify, tls", - Usage: "Use TLS and verify the remote", - }, - cli.StringFlag{ - Name: "tlscacert", - Value: "~/.docker/ca.pem", - Usage: "Trust certs signed only by this CA", - }, - cli.StringFlag{ - Name: "tlscert", - Value: "~/.docker/cert.pem", - Usage: "Path to TLS certificate file", - }, - cli.StringFlag{ - Name: "tlskey", - Value: "~/.docker/key.pem", - Usage: "Path to TLS key file", - }, - } -} - -// InfoCommandSpec returns specifications of the info comment for codegangsta/cli -func InfoCommandSpec() cli.Command { - return cli.Command{ - Name: "info", - Usage: "show docker info (check connectivity, versions, etc.)", - Action: infoCommand, - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "show advanced info", - }, - }, - } -} - -// infoCommand implements 'info' command that prints docker info (check connectivity, versions, etc.) -func infoCommand(c *cli.Context) { - config := NewConfigFromCli(c) - - fmt.Printf("Docker host: %s\n", config.Host) - fmt.Printf("Docker use TLS: %s\n", strconv.FormatBool(config.Tlsverify)) - if config.Tlsverify { - fmt.Printf(" TLS CA cert: %s\n", config.Tlscacert) - fmt.Printf(" TLS cert: %s\n", config.Tlscert) - fmt.Printf(" TLS key: %s\n", config.Tlskey) - } - - dockerClient, err := NewFromCli(c) - if err != nil { - log.Fatal(err) - } - - // TODO: golang randomizes maps every time, so the output is not consistent - // find out a way to sort it correctly - - version, err := dockerClient.Version() - if err != nil { - log.Fatal(err) - } - - for _, kv := range *version { - parts := strings.SplitN(kv, "=", 2) - fmt.Printf("Docker %s: %s\n", parts[0], parts[1]) - } - - if c.Bool("all") { - info, err := dockerClient.Info() - if err != nil { - log.Fatal(err) - } - - fmt.Printf("\nDocker advanced info:\n") - for key, val := range info.Map() { - fmt.Printf("%s: %s\n", key, val) - } - } -} - -// globalCliString fixes string arguments enclosed with double quotes -// 'docker-machine config' gives such arguments -func globalCliString(c *cli.Context, name string) string { - str := c.GlobalString(name) - if len(str) >= 2 && str[0] == '\u0022' && str[len(str)-1] == '\u0022' { - str = str[1 : len(str)-1] - } - return str -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/dockerclient_test.go b/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/dockerclient_test.go deleted file mode 100644 index eb5de81..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/dockerclient_test.go +++ /dev/null @@ -1,224 +0,0 @@ -// +build integration - -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dockerclient - -import ( - "bytes" - "fmt" - "testing" - - "github.com/fsouza/go-dockerclient" - "github.com/stretchr/testify/assert" -) - -func TestNewDockerClient(t *testing.T) { - cli, err := New() - if err != nil { - t.Fatal(err) - } - - info, err := cli.Info() - if err != nil { - t.Fatal(err) - } - - assert.IsType(t, &docker.Env{}, info) -} - -func TestEntrypointOverride(t *testing.T) { - t.Skip() - - cli, err := New() - if err != nil { - t.Fatal(err) - } - - container, err := cli.CreateContainer(docker.CreateContainerOptions{ - Config: &docker.Config{ - Image: "test-entrypoint-override", - Entrypoint: []string{}, - Cmd: []string{"/bin/ls"}, - AttachStdout: true, - AttachStderr: true, - }, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := cli.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID, Force: true}); err != nil { - t.Fatal(err) - } - }() - - success := make(chan struct{}) - var buf bytes.Buffer - - attachOpts := docker.AttachToContainerOptions{ - Container: container.ID, - OutputStream: &buf, - ErrorStream: &buf, - Stream: true, - Stdout: true, - Stderr: true, - Success: success, - } - go func() { - if err := cli.AttachToContainer(attachOpts); err != nil { - t.Fatal(fmt.Errorf("Attach container error: %s", err)) - } - }() - - success <- <-success - - if err := cli.StartContainer(container.ID, &docker.HostConfig{}); err != nil { - t.Fatal(fmt.Errorf("Failed to start container, error: %s", err)) - } - - statusCode, err := cli.WaitContainer(container.ID) - if err != nil { - t.Fatal(fmt.Errorf("Wait container error: %s", err)) - } - - println(buf.String()) - - if statusCode != 0 { - t.Fatal(fmt.Errorf("Failed to run container, exit with code %d", statusCode)) - } -} - -func TestNewVolumesBug(t *testing.T) { - cli, err := New() - if err != nil { - t.Fatal(err) - } - - c1, out, err := runContainer(t, cli, &docker.Config{ - Image: "alpine:3.2", - Cmd: []string{"touch", "/data/file"}, - Volumes: map[string]struct{}{ - "/data": struct{}{}, - }, - AttachStdout: true, - AttachStderr: true, - }, &docker.HostConfig{}) - defer func() { - if err := cli.RemoveContainer(docker.RemoveContainerOptions{ID: c1.ID, Force: true, RemoveVolumes: true}); err != nil { - t.Fatal(err) - } - }() - if err != nil { - t.Fatal(err) - } - - fmt.Printf("C1: %s out: %s err: %s\n", c1.ID, out, err) - - c2, out2, err := runContainer(t, cli, &docker.Config{ - Image: "alpine:3.2", - Cmd: []string{"ls", "/data/file"}, - AttachStdout: true, - AttachStderr: true, - }, &docker.HostConfig{ - Binds: []string{c1.Mounts[0].Source + ":/data:ro"}, - // VolumesFrom: []string{c1}, - }) - if err != nil { - t.Fatal(err) - } - - fmt.Printf("C2: %s out: %s err: %s\n", c2.ID, out2, err) - - if err := cli.RemoveContainer(docker.RemoveContainerOptions{ID: c2.ID, Force: true, RemoveVolumes: true}); err != nil { - t.Fatal(err) - } - - c3, out3, err := runContainer(t, cli, &docker.Config{ - Image: "alpine:3.2", - Cmd: []string{"ls", "/data/file"}, - AttachStdout: true, - AttachStderr: true, - }, &docker.HostConfig{ - Binds: []string{c1.Mounts[0].Source + ":/data:ro"}, - // VolumesFrom: []string{c1}, - }) - if err != nil { - t.Fatal(err) - } - - fmt.Printf("C3: %s out: %s err: %s\n", c3.ID, out3, err) - - if err := cli.RemoveContainer(docker.RemoveContainerOptions{ID: c3.ID, Force: true, RemoveVolumes: true}); err != nil { - t.Fatal(err) - } -} - -func runContainer(t *testing.T, cli *docker.Client, cfg *docker.Config, hostCfg *docker.HostConfig) (*docker.Container, string, error) { - container, err := cli.CreateContainer(docker.CreateContainerOptions{ - Config: cfg, - HostConfig: hostCfg, - }) - if err != nil { - return nil, "", err - } - - var ( - buf bytes.Buffer - success = make(chan struct{}) - - attachOpts = docker.AttachToContainerOptions{ - Container: container.ID, - OutputStream: &buf, - ErrorStream: &buf, - Stream: true, - Stdout: true, - Stderr: true, - Success: success, - } - ) - - go func() { - if err := cli.AttachToContainer(attachOpts); err != nil { - t.Errorf("Attach container error: %s", err) - } - }() - - success <- <-success - - if err := cli.StartContainer(container.ID, &docker.HostConfig{}); err != nil { - return container, "", err - } - - statusCode, err := cli.WaitContainer(container.ID) - if err != nil { - return container, "", err - } - - if statusCode != 0 { - err = fmt.Errorf("Failed to run container, exit with code %d", statusCode) - } - - c2, err := cli.InspectContainer(container.ID) - if err != nil { - return container, "", err - } - - // pretty.Println(c2) - - return c2, buf.String(), err -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/matrix.go b/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/matrix.go deleted file mode 100644 index 2f49fb4..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/matrix.go +++ /dev/null @@ -1,132 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dockerclient - -import ( - "fmt" - "os" - "path" - "path/filepath" - "strings" - - "github.com/grammarly/rocker/src/rocker/util" - - "github.com/fsouza/go-dockerclient" -) - -const ( - initFile = "/.dockerinit" -) - -// IsInMatrix returns true if current process is running inside of a docker container -func IsInMatrix() (bool, error) { - _, err := os.Stat(initFile) - if err != nil && os.IsNotExist(err) { - return false, nil - } - return true, err -} - -// MyDockerID returns id of the current container the process is running within, if any -func MyDockerID() (string, error) { - if _, err := os.Stat("/proc/self/cgroup"); os.IsNotExist(err) { - return "", nil - } - output, exitStatus, err := util.ExecPipe(&util.Cmd{ - Args: []string{"/bin/bash", "-c", `cat /proc/self/cgroup | grep "docker" | sed s/\\//\\n/g | tail -1`}, - }) - if err != nil { - return "", err - } - if exitStatus != 0 { - return "", fmt.Errorf("Failed to obtain docker id due error: %s", output) - } - - return strings.Trim(output, "\n"), nil -} - -// ResolveHostPath resolves any given path from the current context so -// it is mountable by any container. -// -// If the current process is executed in the container itself, this function -// resolves the given path according to the container's rootfs on the host -// machine. It also considers the mounted directories to the current container, so -// if given path is pointing to the mounted directory, it resolves correctly. -func ResolveHostPath(mountPath string, client *docker.Client) (string, error) { - // Accept only absolute path - if !filepath.IsAbs(mountPath) { - return "", fmt.Errorf("ResolveHostPath accepts only absolute paths, given: %s", mountPath) - } - - // In case we are running inside of a docker container - // we have to provide our fs path right from host machine - isMatrix, err := IsInMatrix() - if err != nil { - return "", err - } - // Not in a container, return the path as is - if !isMatrix { - return mountPath, nil - } - - myDockerID, err := MyDockerID() - if err != nil { - return "", err - } - - container, err := client.InspectContainer(myDockerID) - if err != nil { - return "", err - } - - // Check if the given path is inside some mounted volumes - for _, mount := range container.Mounts { - rel, err := filepath.Rel(mount.Destination, mountPath) - if err != nil { - return "", err - } - // The easiest way to check whether the `mountPath` is within the `mount.Destination` - if !strings.HasPrefix(rel, "..") { - // Resolve the mounted directory to the real host path - return strings.Replace(mountPath, mount.Destination, mount.Source, 1), nil - } - } - - // https://jpetazzo.github.io/assets/2015-03-03-not-so-deep-dive-into-docker-storage-drivers.html - // aufs: /var/lib/docker/aufs/mnt/$CONTAINER_ID/ - // devicemapper: /var/lib/docker/devicemapper/mnt/$CONTAINER_ID/ - // btrfs: /var/lib/docker/btrfs/subvolumes/$CONTAINER_OR_IMAGE_ID/ - // overlayfs: /var/lib/docker/overlay/$ID_OF_CONTAINER_OR_IMAGE/merged - - // Resolve docker root by using container's resolv.conf file path - dockerRoot := path.Join(path.Dir(container.ResolvConfPath), "../../") - - // Resolve the container mountpoint depending on the driver used - if container.Driver == "aufs" { - mountPath = path.Join(dockerRoot, "aufs/mnt", myDockerID, mountPath) - } else if container.Driver == "devicemapper" { - mountPath = path.Join(dockerRoot, "devicemapper/mnt", myDockerID, mountPath) - } else if container.Driver == "overlay" { - mountPath = path.Join(dockerRoot, "overlay", myDockerID, "merged", mountPath) - } else { - // NOTE: add support for other fs drivers is not a big deal, - // but need to have a test environment for that. - return "", fmt.Errorf("%s driver is not supported by rocker when using MOUNT from within a container", container.Driver) - } - - return mountPath, nil -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/matrix_test.go b/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/matrix_test.go deleted file mode 100644 index 571f42a..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/matrix_test.go +++ /dev/null @@ -1,52 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dockerclient - -import "testing" - -func TestDockerIsInMatrix(t *testing.T) { - result, err := IsInMatrix() - if err != nil { - t.Fatal(err) - } - - t.Logf("is matrix: %v", result) -} - -func TestDockerMyDockerId(t *testing.T) { - id, err := MyDockerID() - if err != nil { - t.Fatal(err) - } - - t.Logf("my docker id: %q", id) -} - -func TestResolveHostPath(t *testing.T) { - // we will need docker client to cleanup and do some cross-checks - client, err := New() - if err != nil { - t.Fatal(err) - } - - result, err := ResolveHostPath("/bin/rsync", client) - if err != nil { - t.Fatal(err) - } - - t.Logf("Result path: %s\n", result) -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/registry.go b/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/registry.go deleted file mode 100644 index 1e7cc2b..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/dockerclient/registry.go +++ /dev/null @@ -1,277 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dockerclient - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "strings" - - "github.com/grammarly/rocker/src/rocker/imagename" - - "github.com/fsouza/go-dockerclient" - - log "github.com/Sirupsen/logrus" -) - -type tags struct { - Name string `json:"name,omitempty"` - Tags []string `json:"tags,omitempty"` -} - -type bearer struct { - Realm string - Service string - Scope string -} - -// RegistryListTags returns the list of images instances obtained from all tags existing in the registry -func RegistryListTags(image *imagename.ImageName, auth *docker.AuthConfigurations) (images []*imagename.ImageName, err error) { - var ( - name = image.Name - registry = image.Registry - ) - - regAuth, err := GetAuthForRegistry(auth, image) - if err != nil { - return nil, fmt.Errorf("Failed to get auth token for registry: %s, make sure you are properly logged in using `docker login` or have AWS credentials set in case of using ECR", image) - } - - // XXX: AWS ECR Registry API v2 does not support listing tags - // wo we just return a single image tag if it exists and no wildcards used - if image.IsECR() { - log.Debugf("ECR detected %s", registry) - if !image.IsStrict() { - return nil, fmt.Errorf("Amazon ECR does not support tags listing, therefore image wildcards are not supported, sorry: %s", image) - } - if exists, err := ecrImageExists(image, regAuth); err != nil { - return nil, err - } else if exists { - log.Debugf("ECR image %s found in the registry", image) - images = append(images, image) - } - return - } - - if registry == "" { - registry = "registry-1.docker.io" - if !strings.Contains(name, "/") { - name = "library/" + name - } - } - - var ( - tg = tags{} - url = fmt.Sprintf("https://%s/v2/%s/tags/list?page_size=9999&page=1", registry, name) - ) - - log.Debugf("Listing image tags from the remote registry %s", url) - - if err := registryGet(url, regAuth, &tg); err != nil { - return nil, err - } - - log.Debugf("Got %d tags from the remote registry for image %s", len(tg.Tags), image) - - for _, t := range tg.Tags { - candidate := imagename.New(image.NameWithRegistry(), t) - if image.Contains(candidate) || image.Tag == candidate.Tag { - images = append(images, candidate) - } - } - - return -} - -// registryGet executes HTTP get to a given registry -func registryGet(uri string, auth docker.AuthConfiguration, obj interface{}) (err error) { - var ( - client = &http.Client{} - req *http.Request - res *http.Response - body []byte - ) - - if req, err = http.NewRequest("GET", uri, nil); err != nil { - return - } - - var ( - b *bearer - authTry bool - ) - - for { - if res, err = client.Do(req); err != nil { - return fmt.Errorf("Request to %s failed with %s\n", uri, err) - } - - b = parseBearer(res.Header.Get("Www-Authenticate")) - log.Debugf("Got HTTP %d for %s; tried auth: %t; has Bearer: %t, auth username: %q", res.StatusCode, uri, authTry, b != nil, auth.Username) - - if res.StatusCode == 401 && !authTry && b != nil { - token, err := getAuthToken(b, auth) - if err != nil { - return fmt.Errorf("Failed to authenticate to registry %s, error: %s", uri, err) - } - - req.Header.Add("Authorization", "Bearer "+token) - - authTry = true - continue - } - - break - } - - if res.StatusCode != 200 { - // TODO: maybe more descriptive error - return fmt.Errorf("GET %s status code %d", uri, res.StatusCode) - } - - if body, err = ioutil.ReadAll(res.Body); err != nil { - return fmt.Errorf("Response from %s cannot be read due to error %s\n", uri, err) - } - - if err = json.Unmarshal(body, obj); err != nil { - return fmt.Errorf("Response from %s cannot be unmarshalled due to error %s, response: %s\n", - uri, err, string(body)) - } - - return -} - -func getAuthToken(b *bearer, auth docker.AuthConfiguration) (token string, err error) { - type authRespType struct { - Token string - } - - var ( - req *http.Request - res *http.Response - body []byte - - client = &http.Client{} - authResp = &authRespType{} - ) - - uri, err := url.Parse(b.Realm) - if err != nil { - return "", fmt.Errorf("Failed to parse real url %s, error %s", b.Realm, err) - } - - // Add query params to the ream uri - q := uri.Query() - q.Set("service", b.Service) - q.Set("scope", b.Scope) - uri.RawQuery = q.Encode() - - if req, err = http.NewRequest("GET", uri.String(), nil); err != nil { - return "", err - } - - if auth.Username != "" { - req.SetBasicAuth(auth.Username, auth.Password) - } - - log.Debugf("Getting auth token from %s", uri) - - if res, err = client.Do(req); err != nil { - return "", fmt.Errorf("Failed to authenticate by realm url %s, error %s", uri, err) - } - - if res.StatusCode != 200 { - // TODO: maybe more descriptive error - return "", fmt.Errorf("GET %s status code %d", uri, res.StatusCode) - } - - if body, err = ioutil.ReadAll(res.Body); err != nil { - return "", fmt.Errorf("Response from %s cannot be read due to error %s\n", uri, err) - } - - if err := json.Unmarshal(body, authResp); err != nil { - return "", fmt.Errorf("Response from %s cannot be unmarshalled due to error %s, response: %s\n", - uri, err, string(body)) - } - - return authResp.Token, nil -} - -func ecrImageExists(image *imagename.ImageName, auth docker.AuthConfiguration) (exists bool, err error) { - var ( - req *http.Request - res *http.Response - client = &http.Client{} - ) - - uri := fmt.Sprintf("https://%s/v2/%s/manifests/%s", image.Registry, image.Name, image.Tag) - - if req, err = http.NewRequest("GET", uri, nil); err != nil { - return false, err - } - - req.SetBasicAuth(auth.Username, auth.Password) - - log.Debugf("Request ECR image %s with basic auth %s:****", uri, auth.Username) - - if res, err = client.Do(req); err != nil { - return false, fmt.Errorf("Failed to authenticate by realm url %s, error %s", uri, err) - } - - log.Debugf("Got status %d", res.StatusCode) - - if res.StatusCode == 404 { - return false, nil - } - - if res.StatusCode != 200 { - // TODO: maybe more descriptive error - return false, fmt.Errorf("GET %s status code %d", uri, res.StatusCode) - } - - return true, nil -} - -func parseBearer(hdr string) *bearer { - if !strings.HasPrefix(hdr, "Bearer ") { - return nil - } - - b := &bearer{} - hdr = strings.TrimPrefix(hdr, "Bearer ") - - // e.g. - // Www-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:me/alpine:pull" - for _, pair := range strings.Split(hdr, ",") { - kv := strings.SplitN(pair, "=", 2) - key, value := kv[0], strings.Trim(kv[1], "\"") - - switch key { - case "realm": - b.Realm = value - case "service": - b.Service = value - case "scope": - b.Scope = value - } - } - - return b -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/imagename/artifact.go b/vendor/github.com/grammarly/rocker/src/rocker/imagename/artifact.go deleted file mode 100644 index 85b26b0..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/imagename/artifact.go +++ /dev/null @@ -1,74 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package imagename - -import ( - "fmt" - "strings" - - "time" -) - -// Artifact represents the artifact that is the result of image build -// It holds information about the pushed image and may be saved as a file -type Artifact struct { - Name *ImageName `yaml:"Name"` - Pushed bool `yaml:"Pushed"` - Tag string `yaml:"Tag"` - Digest string `yaml:"Digest"` - ImageID string `yaml:"ImageID"` - Addressable string `yaml:"Addressable"` - BuildTime time.Time `yaml:"BuildTime"` -} - -// Artifacts is a collection of Artifact entities -type Artifacts struct { - RockerArtifacts []Artifact `yaml:"RockerArtifacts"` -} - -// GetFileName constructs the base file name out of the image info -func (a *Artifact) GetFileName() string { - imageName := strings.Replace(a.Name.Name, "/", "_", -1) - return fmt.Sprintf("%s_%s.yml", imageName, a.Name.GetTag()) -} - -// SetDigest sets the digest and forms the Addressable property -func (a *Artifact) SetDigest(digest string) { - a.Digest = digest - if strings.HasPrefix(a.Digest, "sha256:") { - // for digest sha256:blabla - a.Addressable = fmt.Sprintf("%s@%s", a.Name.NameWithRegistry(), a.Digest) - } else { - // for digest sha256-blabla (tag) - a.Addressable = fmt.Sprintf("%s:%s", a.Name.NameWithRegistry(), a.Digest) - } -} - -// Len returns the length of image tags -func (a *Artifacts) Len() int { - return len(a.RockerArtifacts) -} - -// Less returns true if item by index[i] is created after of item[j] -func (a *Artifacts) Less(i, j int) bool { - return a.RockerArtifacts[i].Name.Tag > a.RockerArtifacts[j].Name.Tag -} - -// Swap swaps items by indices [i] and [j] -func (a *Artifacts) Swap(i, j int) { - a.RockerArtifacts[i], a.RockerArtifacts[j] = a.RockerArtifacts[j], a.RockerArtifacts[i] -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/imagename/imagename.go b/vendor/github.com/grammarly/rocker/src/rocker/imagename/imagename.go deleted file mode 100644 index 92ba5bf..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/imagename/imagename.go +++ /dev/null @@ -1,394 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Package imagename provides docker data structure for docker image names -// It also provides a number of utility functions, related to image name resolving, -// comparison, and semver functionality. -package imagename - -import ( - "encoding/json" - "regexp" - "sort" - "strings" - - "github.com/wmark/semver" -) - -const ( - // Latest is :latest tag value - Latest = "latest" - - // Wildcards is wildcard value variants - Wildcards = "x*" -) - -const ( - // StorageRegistry is when docker registry is used as images storage - StorageRegistry = "registry" - - // StorageS3 is when s3 registry is used as images storage - StorageS3 = "s3" -) - -var ( - ecrRe = regexp.MustCompile("^\\d+\\.dkr\\.ecr\\.[^\\.]+\\.amazonaws\\.com$") -) - -// ImageName is the data structure with describes docker image name -type ImageName struct { - Registry string - Name string - Tag string - Storage string - Version *semver.Range -} - -// NewFromString parses a given string and returns ImageName -func NewFromString(image string) *ImageName { - name, tag := ParseRepositoryTag(image) - return New(name, tag) -} - -// New parses a given 'image' and 'tag' strings and returns ImageName -func New(image string, tag string) *ImageName { - dockerImage := &ImageName{} - - if tag != "" { - dockerImage.SetTag(tag) - } - - // default storage driver - dockerImage.Storage = StorageRegistry - - // In case storage is specified, e.g. s3://bucket-name/image-name - storages := []string{StorageRegistry, StorageS3} - firstIsHost := false - - for _, storage := range storages { - prefix := storage + ":" - - if strings.HasPrefix(image, prefix) { - image = strings.TrimPrefix(image, prefix) - dockerImage.Storage = storage - firstIsHost = true - break - } - } - - nameParts := strings.SplitN(image, "/", 2) - - firstIsHost = firstIsHost || - strings.Contains(nameParts[0], ".") || - strings.Contains(nameParts[0], ":") || - nameParts[0] == "localhost" - - if len(nameParts) == 1 || !firstIsHost { - dockerImage.Name = image - } else { - dockerImage.Registry = nameParts[0] - dockerImage.Name = nameParts[1] - } - - // Minor validation - if dockerImage.Storage == StorageS3 && dockerImage.Registry == "" { - panic("Image with S3 storage driver requires bucket name to be specified: " + image) - } - - return dockerImage -} - -// ParseRepositoryTag gets a repos name and returns the right reposName + tag|digest -// The tag can be confusing because of a port in a repository name. -// Ex: localhost.localdomain:5000/samalba/hipache:latest -// Digest ex: localhost:5000/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb -// NOTE: borrowed from Docker under Apache 2.0, Copyright 2013-2015 Docker, Inc. -func ParseRepositoryTag(repos string) (string, string) { - n := strings.Index(repos, "@") - if n >= 0 { - parts := strings.Split(repos, "@") - return parts[0], parts[1] - } - n = strings.LastIndex(repos, ":") - if n < 0 { - return repos, "" - } - if tag := repos[n+1:]; !strings.Contains(tag, "/") { - return repos[:n], tag - } - return repos, "" -} - -// String returns the string representation of the current image name -func (img ImageName) String() string { - if img.TagIsDigest() { - return img.NameWithRegistry() + "@" + img.GetTag() - } - return img.NameWithRegistry() + ":" + img.GetTag() -} - -// HasTag returns true if tags is specified for the image name -func (img ImageName) HasTag() bool { - return img.Tag != "" -} - -// TagIsSha returns true if the tag is content addressable sha256 but can also be a tag -// e.g. golang@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11 -// or golang:sha256-ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11 -func (img ImageName) TagIsSha() bool { - return strings.HasPrefix(img.Tag, "sha256:") || strings.HasPrefix(img.Tag, "sha256-") -} - -// TagIsDigest returns true if the tag is content addressable sha256 -// e.g. golang@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11 -func (img ImageName) TagIsDigest() bool { - return strings.HasPrefix(img.Tag, "sha256:") -} - -// GetTag returns the tag of the current image name -func (img ImageName) GetTag() string { - if img.HasTag() { - return img.Tag - } - return Latest -} - -// SetTag sets the new tag for the imagename -func (img *ImageName) SetTag(tag string) { - img.Version = nil - if rng, err := semver.NewRange(tag); err == nil && rng != nil { - img.Version = rng - } - img.Tag = tag -} - -// IsStrict returns true if tag of the current image is specified and contains no fuzzy rules -// Example: -// golang:latest == true -// golang:stable == true -// golang:1.5.1 == true -// golang:1.5.* == false -// golang == false -// -func (img ImageName) IsStrict() bool { - if img.HasVersionRange() { - return img.TagAsVersion() != nil - } - return img.Tag != "" -} - -// All returns true if tag of the current image refers to any version -// Example: -// golang:* == true -// golang == true -// golang:latest == false -func (img ImageName) All() bool { - return strings.Contains(Wildcards, img.Tag) -} - -// HasVersion returns true if tag of the current image refers to a semver format -// Example: -// golang:1.5.1 == true -// golang:1.5.* == false -// golang:stable == false -// golang:latest == false -func (img ImageName) HasVersion() bool { - return img.TagAsVersion() != nil -} - -// HasVersionRange returns true if tag of the current image refers to a semver format and is fuzzy -// Example: -// golang:1.5.1 == true -// golang:1.5.* == true -// golang:* == true -// golang:stable == false -// golang:latest == false -// golang == false -func (img ImageName) HasVersionRange() bool { - return img.Version != nil -} - -// IsECR returns true if the registry is AWS ECR -func (img ImageName) IsECR() bool { - return ecrRe.MatchString(img.Registry) -} - -// TagAsVersion return semver.Version of the current image tag in case it's in semver format -func (img ImageName) TagAsVersion() (ver *semver.Version) { - ver, _ = semver.NewVersion(strings.TrimPrefix(img.Tag, "v")) - return -} - -// IsSameKind returns true if current image and the given one are same but may have different versions (tags) -func (img ImageName) IsSameKind(b ImageName) bool { - return img.Registry == b.Registry && img.Name == b.Name -} - -// NameWithRegistry returns the [registry/]name of the current image name -func (img ImageName) NameWithRegistry() string { - registryPrefix := "" - if img.Registry != "" { - registryPrefix = img.Registry + "/" - } - if img.Storage != StorageRegistry { - registryPrefix = img.Storage + ":" + registryPrefix - } - return registryPrefix + img.Name -} - -// Contains returns true if the current image tag wildcard satisfies a given image version -func (img ImageName) Contains(b *ImageName) bool { - if b == nil { - return false - } - - if !img.IsSameKind(*b) { - return false - } - - // semver library has a bug with wildcards, so this checks are - // necessary: empty range (or wildcard range) cannot contain any version, it just fails - if img.All() { - return true - } - - if img.IsStrict() && img.Tag == b.Tag { - return true - } - - if img.HasVersionRange() && b.HasVersion() && img.Version.IsSatisfiedBy(b.TagAsVersion()) { - return true - } - - return img.Tag == "" && !img.HasVersionRange() -} - -// ResolveVersion finds an applicable tag for current image among the list of available tags -func (img *ImageName) ResolveVersion(list []*ImageName) (result *ImageName) { - for _, candidate := range list { - // If these are different images (different names/repos) - if !img.IsSameKind(*candidate) { - continue - } - - // If we have a strict equality - if img.HasTag() && candidate.HasTag() && img.Tag == candidate.Tag { - return candidate - } - - // If image is without tag, latest will be fine - if !img.HasTag() && candidate.GetTag() == Latest { - return candidate - } - - if !img.Contains(candidate) { - //this image is from the same name/registry but tag is not applicable - // e.g. ~1.2.3 contains 1.2.5, but it's not true for 1.3.0 - continue - } - - if result == nil { - result = candidate - continue - } - - // uncomparable candidate... skipping - if !candidate.HasVersion() { - continue - } - - // if both names has versions to compare, we cat safely compare them - if result.HasVersion() && candidate.HasVersion() && result.TagAsVersion().Less(candidate.TagAsVersion()) { - result = candidate - } - } - - return -} - -// UnmarshalJSON parses JSON string and returns ImageName -func (img *ImageName) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - *img = *NewFromString(s) - return nil -} - -// MarshalJSON serializes ImageName to JSON string -func (img ImageName) MarshalJSON() ([]byte, error) { - return json.Marshal(img.String()) -} - -// UnmarshalYAML parses YAML string and returns ImageName -func (img *ImageName) UnmarshalYAML(unmarshal func(interface{}) error) error { - var s string - if err := unmarshal(&s); err != nil { - return err - } - *img = *NewFromString(s) - return nil -} - -// MarshalYAML serializes ImageName to YAML string -func (img ImageName) MarshalYAML() (interface{}, error) { - return img.String(), nil -} - -// Tags is a structure used for cleaning images -// Sorts out old tags by creation date -type Tags struct { - Items []*Tag -} - -// Tag is a structure used for cleaning images -type Tag struct { - ID string - Name ImageName - Created int64 -} - -// Len returns the length of image tags -func (tags *Tags) Len() int { - return len(tags.Items) -} - -// Less returns true if item by index[i] is created after of item[j] -func (tags *Tags) Less(i, j int) bool { - return tags.Items[i].Created > tags.Items[j].Created -} - -// Swap swaps items by indices [i] and [j] -func (tags *Tags) Swap(i, j int) { - tags.Items[i], tags.Items[j] = tags.Items[j], tags.Items[i] -} - -// GetOld returns the list of items older then [keep] newest items sorted by Created date -func (tags *Tags) GetOld(keep int) []ImageName { - if len(tags.Items) < keep { - return nil - } - - sort.Sort(tags) - - result := []ImageName{} - for i := keep; i < len(tags.Items); i++ { - result = append(result, tags.Items[i].Name) - } - - return result -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/imagename/imagename_test.go b/vendor/github.com/grammarly/rocker/src/rocker/imagename/imagename_test.go deleted file mode 100644 index a42699a..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/imagename/imagename_test.go +++ /dev/null @@ -1,526 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package imagename - -import ( - "fmt" - "testing" - "time" - - "github.com/go-yaml/yaml" - "github.com/kr/pretty" - - "github.com/stretchr/testify/assert" - "github.com/wmark/semver" -) - -func TestImageParsingWithoutNamespace(t *testing.T) { - img := NewFromString("repo/name:1") - assert.Equal(t, "", img.Registry) - assert.Equal(t, "1", img.Tag) - assert.Equal(t, "repo/name", img.Name) - assert.True(t, img.Contains(NewFromString("repo/name:1"))) -} - -func TestWildcardNamespace(t *testing.T) { - img := NewFromString("repo/name:*") - assert.Empty(t, img.Registry) - assert.Equal(t, "*", img.Tag) - assert.Equal(t, "repo/name", img.Name) - assert.True(t, img.Contains(NewFromString("repo/name:1.0.0"))) -} - -func TestEnvironmentImageName(t *testing.T) { - img := NewFromString("repo/name:1.0.0") - assert.False(t, img.Contains(NewFromString("repo/name:1.0.123"))) -} - -func TestImageRealLifeNamingExample(t *testing.T) { - img := NewFromString("docker.io/tools/dockerize:v0.0.1") - assert.Equal(t, "docker.io", img.Registry) - assert.Equal(t, "tools/dockerize", img.Name) - assert.Equal(t, "v0.0.1", img.Tag) - assert.True(t, img.Contains(NewFromString("docker.io/tools/dockerize:v0.0.1"))) -} - -func TestRangeContainsPlainVersion(t *testing.T) { - img := NewFromString("docker.io/tools/dockerize:0.0.1") - expected, _ := semver.NewRange("0.0.1") - assert.Equal(t, "docker.io", img.Registry) - assert.Equal(t, "tools/dockerize", img.Name) - assert.Equal(t, "0.0.1", img.Tag) - assert.Equal(t, expected, img.Version) - - v, _ := semver.NewVersion("0.0.1") - assert.True(t, img.Version.Contains(v)) -} - -func TestUpperRangeBounds(t *testing.T) { - img := NewFromString("docker.io/tools/dockerize:~1.2.3") - assert.Equal(t, "docker.io", img.Registry) - assert.Equal(t, "tools/dockerize", img.Name) - assert.False(t, img.IsStrict()) - v, _ := semver.NewVersion("1.2.8") - assert.True(t, img.Version.Contains(v)) -} - -func TestWildcardRangeBounds(t *testing.T) { - img := NewFromString("docker.io/tools/dockerize:1.2.*") - assert.Equal(t, "docker.io", img.Registry) - assert.Equal(t, "tools/dockerize", img.Name) - assert.False(t, img.IsStrict()) - v, _ := semver.NewVersion("1.2.8") - assert.True(t, img.Version.Contains(v)) - v, _ = semver.NewVersion("1.2.0") - assert.True(t, img.Version.Contains(v)) -} - -func TestWildcardContains(t *testing.T) { - img1 := NewFromString("docker.io/tools/dockerize:1.2.*") - img2 := NewFromString("docker.io/tools/dockerize:1.2.1") - assert.False(t, img1.IsStrict()) - assert.True(t, img1.HasVersionRange()) - assert.True(t, img2.IsStrict()) - v, _ := semver.NewVersion("1.2.1") - assert.Equal(t, v, img2.TagAsVersion()) - - assert.True(t, img1.Contains(img2)) - assert.False(t, img2.Contains(img1)) -} - -func TestRangeContains(t *testing.T) { - img1 := NewFromString("docker.io/tools/dockerize:~1.2.1") - img2 := NewFromString("docker.io/tools/dockerize:1.2.999") - assert.True(t, img1.Contains(img2)) - assert.False(t, img2.Contains(img1)) -} - -func TestNilContains(t *testing.T) { - img1 := NewFromString("docker.io/tools/dockerize:~1.2.1") - assert.False(t, img1.Contains(nil)) -} - -func TestRangeNotContains(t *testing.T) { - img1 := NewFromString("docker.io/tools/dockerize:~1.2.1") - img2 := NewFromString("docker.io/tools/dockerize:1.3.1") - assert.False(t, img1.Contains(img2)) - assert.False(t, img2.Contains(img1)) - - img2 = NewFromString("docker.io/xxx/dockerize:1.2.1") - assert.False(t, img1.Contains(img2)) - - img2 = NewFromString("dockerhub.grammarly.com/tools/dockerize:1.2.1") - assert.False(t, img1.Contains(img2)) - - img2 = NewFromString("docker.io/tools/dockerize:1.2.1") - assert.True(t, img1.Contains(img2)) -} - -func TestVersionContains(t *testing.T) { - img1 := NewFromString("docker.io/tools/dockerize:1.2.1") - img2 := NewFromString("docker.io/tools/dockerize:1.2.1") - assert.True(t, img1.Contains(img2)) - assert.True(t, img2.Contains(img1)) -} - -func TestTagContains(t *testing.T) { - img1 := NewFromString("docker.io/tools/dockerize:test1") - img2 := NewFromString("docker.io/tools/dockerize:test1") - assert.True(t, img1.Contains(img2)) - assert.True(t, img2.Contains(img1)) -} - -func TestTagNotContains(t *testing.T) { - img1 := NewFromString("docker.io/tools/dockerize:test1") - img2 := NewFromString("docker.io/tools/dockerize:test2") - assert.False(t, img1.Contains(img2)) - assert.False(t, img2.Contains(img1)) -} - -func TestImageRealLifeNamingExampleWithCapi(t *testing.T) { - img := NewFromString("docker.io/common-api") - assert.Equal(t, "docker.io", img.Registry) - assert.Equal(t, "common-api", img.Name) - assert.Equal(t, false, img.HasTag()) - assert.Equal(t, "latest", img.GetTag()) - assert.Equal(t, "docker.io/common-api:latest", img.String()) -} - -func TestImageParsingWithNamespace(t *testing.T) { - img := NewFromString("hub/ns/name:1") - assert.Equal(t, "", img.Registry) - assert.Equal(t, "hub/ns/name", img.Name) - assert.Equal(t, "1", img.Tag) -} - -func TestImageParsingWithoutTag(t *testing.T) { - img := NewFromString("repo/name") - assert.Equal(t, "", img.Registry) - assert.Equal(t, "repo/name", img.Name) - assert.Equal(t, "latest", img.GetTag()) - assert.Equal(t, false, img.HasTag()) - assert.Equal(t, "repo/name:latest", img.String()) -} - -func TestImageWithDotsWithoutTag(t *testing.T) { - img := NewFromString("a.b.c.d") - assert.Equal(t, "", img.Registry) - assert.Equal(t, "a.b.c.d", img.Name) - assert.Equal(t, "latest", img.GetTag()) - assert.Equal(t, false, img.HasTag()) - assert.Equal(t, "a.b.c.d:latest", img.String()) -} - -func TestImageWithDotsWithTag(t *testing.T) { - img := NewFromString("a.b.c.d:snapshot") - assert.Equal(t, "", img.Registry) - assert.Equal(t, "a.b.c.d", img.Name) - assert.Equal(t, "snapshot", img.GetTag()) - assert.Equal(t, true, img.HasTag()) - assert.Equal(t, "a.b.c.d:snapshot", img.String()) -} - -func TestImageWithRegistryAndDotsAndTag(t *testing.T) { - img := NewFromString("hub.com/a.b.c.d:snapshot") - assert.Equal(t, "hub.com", img.Registry) - assert.Equal(t, "a.b.c.d", img.Name) - assert.Equal(t, "snapshot", img.GetTag()) - assert.Equal(t, true, img.HasTag()) - assert.Equal(t, "hub.com/a.b.c.d:snapshot", img.String()) -} - -func TestImageWithRegistryAndSlashAndDotsAndTag(t *testing.T) { - img := NewFromString("hub.com/a.b/c.d:snapshot") - assert.Equal(t, "hub.com", img.Registry) - assert.Equal(t, "a.b/c.d", img.Name) - assert.Equal(t, "snapshot", img.GetTag()) - assert.Equal(t, true, img.HasTag()) - assert.Equal(t, "hub.com/a.b/c.d:snapshot", img.String()) -} - -func TestImageLatest(t *testing.T) { - img := NewFromString("rocker-build:latest") - assert.Equal(t, "", img.Registry, "bag registry value") - assert.Equal(t, "rocker-build", img.Name, "bad image name") - assert.Equal(t, "latest", img.GetTag(), "bad image tag") -} - -func TestImageIpRegistry(t *testing.T) { - img := NewFromString("127.0.0.1:5000/golang:1.4") - assert.Equal(t, "127.0.0.1:5000", img.Registry, "bag registry value") - assert.Equal(t, "golang", img.Name, "bad image name") - assert.Equal(t, "1.4", img.GetTag(), "bad image tag") -} - -func TestImageTagSha(t *testing.T) { - img := NewFromString("golang@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11") - assert.Equal(t, "", img.Registry, "bag registry value") - assert.Equal(t, "golang", img.Name, "bad image name") - assert.Equal(t, "sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.GetTag(), "bad image tag") - assert.Equal(t, "golang@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.String()) -} - -func TestImageAll(t *testing.T) { - img := NewFromString("golang:1.*") - assert.False(t, img.All()) -} - -func TestImageTest(t *testing.T) { - t.Skip() - names := []string{ - "golang:latest", - "golang:stable", - "golang:1.5.1", - "golang:1.5.*", - "golang:*", - "golang", - } - for _, n := range names { - img := NewFromString(n) - m := [][2]interface{}{ - {"IsStrict()", img.IsStrict()}, - {"HasVersion()", img.HasVersion()}, - {"HasVersionRange()", img.HasVersionRange()}, - {"All()", img.All()}, - {"GetTag()", img.GetTag()}, - } - fmt.Printf("%s\t%# v\n", n, pretty.Formatter(m)) - } -} - -func TestImageResolveVersion_Strict(t *testing.T) { - img := NewFromString("golang:1.5.2") - list := []*ImageName{ - NewFromString("golang:1.5.1"), - NewFromString("golang:1.5.2"), - NewFromString("golang:1.5.3"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:1.5.2", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_Wildcard(t *testing.T) { - img := NewFromString("golang:1.5.*") - list := []*ImageName{ - NewFromString("golang:1.5.1"), - NewFromString("golang:1.5.2"), - NewFromString("golang:1.5.3"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:1.5.3", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_WildcardMulti(t *testing.T) { - img := NewFromString("golang:1.4.*") - list := []*ImageName{ - NewFromString("golang:1.4.1"), - NewFromString("golang:1.4.2"), - NewFromString("golang:1.5.1"), - NewFromString("golang:1.5.2"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:1.4.2", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_WildcardMatchX(t *testing.T) { - img := NewFromString("golang:1.4.x") - list := []*ImageName{ - NewFromString("golang:1.4.1"), - NewFromString("golang:1.4.x"), - NewFromString("golang:1.4.2"), - NewFromString("golang:1.5.1"), - NewFromString("golang:1.5.2"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:1.4.x", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_WildcardMatchX2(t *testing.T) { - img := NewFromString("golang:1.x") - list := []*ImageName{ - NewFromString("golang:1.4.1"), - NewFromString("golang:1.4.x"), - NewFromString("golang:1.4.2"), - NewFromString("golang:1.5.1"), - NewFromString("golang:1.5.2"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:1.5.2", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_WildcardMatchX3(t *testing.T) { - img := NewFromString("golang:1.x") - list := []*ImageName{ - NewFromString("golang:1.x"), - NewFromString("golang:1.4.1"), - NewFromString("golang:1.4.x"), - NewFromString("golang:1.4.2"), - NewFromString("golang:1.5.1"), - NewFromString("golang:1.5.2"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:1.x", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_All(t *testing.T) { - img := NewFromString("golang:*") - list := []*ImageName{ - NewFromString("golang:1.4.1"), - NewFromString("golang:1.5.1"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:1.5.1", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_Latest(t *testing.T) { - img := NewFromString("golang:latest") - list := []*ImageName{ - NewFromString("golang:1.4.1"), - NewFromString("golang:1.5.1"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:latest", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_OtherTag(t *testing.T) { - img := NewFromString("golang:stable") - list := []*ImageName{ - NewFromString("golang:1.4.1"), - NewFromString("golang:1.5.1"), - NewFromString("golang:stable"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:stable", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_NoTag(t *testing.T) { - img := NewFromString("golang") - list := []*ImageName{ - NewFromString("golang:1.4.1"), - NewFromString("golang:1.5.1"), - NewFromString("golang:stable"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:latest", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_NoTagOnlyLatest(t *testing.T) { - img := NewFromString("golang") - list := []*ImageName{ - NewFromString("golang:stable"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:latest", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_PatchExact(t *testing.T) { - img := NewFromString("golang:1.4.1") - list := []*ImageName{ - NewFromString("golang:1.4.1"), - NewFromString("golang:1.4.1-p2"), - NewFromString("golang:1.4.1-p1"), - NewFromString("golang:1.5.1"), - NewFromString("golang:1.5.2"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:1.4.1", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_PatchMatch(t *testing.T) { - img := NewFromString("golang:1.4.1") - list := []*ImageName{ - NewFromString("golang:1.4.1-p1"), - NewFromString("golang:1.4.1-p2"), - NewFromString("golang:1.5.1"), - NewFromString("golang:1.5.2"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:1.4.1-p2", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_PatchStrict(t *testing.T) { - img := NewFromString("golang:1.4.1-p1") - list := []*ImageName{ - NewFromString("golang:1.4.1"), - NewFromString("golang:1.4.1-p2"), - NewFromString("golang:1.4.1-p1"), - NewFromString("golang:1.5.1"), - NewFromString("golang:1.5.2"), - NewFromString("golang:latest"), - } - assert.Equal(t, "golang:1.4.1-p1", img.ResolveVersion(list).String()) -} - -func TestImageResolveVersion_NotFound(t *testing.T) { - img := NewFromString("golang:1.5.1") - list := []*ImageName{ - NewFromString("golang:1.4.1"), - NewFromString("golang:stable"), - NewFromString("golang:latest"), - } - assert.Nil(t, img.ResolveVersion(list)) -} - -func TestImageIsSameKind(t *testing.T) { - assert.True(t, NewFromString("rocker-build").IsSameKind(*NewFromString("rocker-build"))) - assert.True(t, NewFromString("rocker-build:latest").IsSameKind(*NewFromString("rocker-build:latest"))) - assert.True(t, NewFromString("rocker-build").IsSameKind(*NewFromString("rocker-build:1.2.1"))) - assert.True(t, NewFromString("rocker-build:1.2.1").IsSameKind(*NewFromString("rocker-build:1.2.1"))) - assert.True(t, NewFromString("grammarly/rocker-build").IsSameKind(*NewFromString("grammarly/rocker-build"))) - assert.True(t, NewFromString("grammarly/rocker-build:3.1").IsSameKind(*NewFromString("grammarly/rocker-build:3.1"))) - assert.True(t, NewFromString("grammarly/rocker-build").IsSameKind(*NewFromString("grammarly/rocker-build:3.1"))) - assert.True(t, NewFromString("grammarly/rocker-build:latest").IsSameKind(*NewFromString("grammarly/rocker-build:latest"))) - assert.True(t, NewFromString("quay.io/rocker-build").IsSameKind(*NewFromString("quay.io/rocker-build"))) - assert.True(t, NewFromString("quay.io/rocker-build:latest").IsSameKind(*NewFromString("quay.io/rocker-build:latest"))) - assert.True(t, NewFromString("quay.io/rocker-build:3.2").IsSameKind(*NewFromString("quay.io/rocker-build:3.2"))) - assert.True(t, NewFromString("quay.io/rocker-build").IsSameKind(*NewFromString("quay.io/rocker-build:3.2"))) - assert.True(t, NewFromString("quay.io/grammarly/rocker-build").IsSameKind(*NewFromString("quay.io/grammarly/rocker-build"))) - assert.True(t, NewFromString("quay.io/grammarly/rocker-build:latest").IsSameKind(*NewFromString("quay.io/grammarly/rocker-build:latest"))) - assert.True(t, NewFromString("quay.io/grammarly/rocker-build:1.2.1").IsSameKind(*NewFromString("quay.io/grammarly/rocker-build:1.2.1"))) - assert.True(t, NewFromString("quay.io/grammarly/rocker-build").IsSameKind(*NewFromString("quay.io/grammarly/rocker-build:1.2.1"))) - - assert.False(t, NewFromString("rocker-build").IsSameKind(*NewFromString("rocker-build2"))) - assert.False(t, NewFromString("rocker-build").IsSameKind(*NewFromString("rocker-compose"))) - assert.False(t, NewFromString("rocker-build").IsSameKind(*NewFromString("grammarly/rocker-build"))) - assert.False(t, NewFromString("rocker-build").IsSameKind(*NewFromString("quay.io/rocker-build"))) - assert.False(t, NewFromString("rocker-build").IsSameKind(*NewFromString("quay.io/grammarly/rocker-build"))) -} - -func TestTagsGetOld(t *testing.T) { - tags := Tags{ - Items: []*Tag{ - &Tag{"1", *NewFromString("hub/ns/name:1"), time.Unix(1, 0).Unix()}, - &Tag{"3", *NewFromString("hub/ns/name:3"), time.Unix(3, 0).Unix()}, - &Tag{"2", *NewFromString("hub/ns/name:2"), time.Unix(2, 0).Unix()}, - &Tag{"5", *NewFromString("hub/ns/name:5"), time.Unix(5, 0).Unix()}, - &Tag{"4", *NewFromString("hub/ns/name:4"), time.Unix(4, 0).Unix()}, - }, - } - old := tags.GetOld(2) - - assert.Equal(t, 3, len(old), "bad number of old tags") - assert.Equal(t, "hub/ns/name:3", old[0].String(), "bad old image 1") - assert.Equal(t, "hub/ns/name:2", old[1].String(), "bad old image 2") - assert.Equal(t, "hub/ns/name:1", old[2].String(), "bad old image 3") -} - -func TestImagename_ToYaml(t *testing.T) { - value := struct { - Name *ImageName - }{ - NewFromString("hub/ns/name:1"), - } - - data, err := yaml.Marshal(value) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "name: hub/ns/name:1\n", string(data)) -} - -func TestImagename_S3_Basic(t *testing.T) { - img := NewFromString("s3:bucket-name/image-name:1.2.3") - assert.Equal(t, "bucket-name", img.Registry) - assert.Equal(t, "image-name", img.Name) - assert.Equal(t, false, img.TagIsSha()) - assert.Equal(t, "1.2.3", img.GetTag()) - assert.Equal(t, "s3:bucket-name/image-name", img.NameWithRegistry()) - assert.Equal(t, "s3:bucket-name/image-name:1.2.3", img.String()) -} - -func TestImagename_S3_Digest(t *testing.T) { - img := NewFromString("s3:bucket-name/image-name@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11") - assert.Equal(t, "bucket-name", img.Registry) - assert.Equal(t, "image-name", img.Name) - assert.Equal(t, true, img.TagIsSha()) - assert.Equal(t, true, img.TagIsDigest()) - assert.Equal(t, "s3:bucket-name/image-name", img.NameWithRegistry()) - assert.Equal(t, "sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.GetTag()) - assert.Equal(t, "s3:bucket-name/image-name@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.String()) -} - -func TestImagename_S3_Sha(t *testing.T) { - img := NewFromString("s3:bucket-name/image-name:sha256-ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11") - assert.Equal(t, "bucket-name", img.Registry) - assert.Equal(t, "image-name", img.Name) - assert.Equal(t, true, img.TagIsSha()) - assert.Equal(t, false, img.TagIsDigest()) - assert.Equal(t, "s3:bucket-name/image-name", img.NameWithRegistry()) - assert.Equal(t, "sha256-ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.GetTag()) - assert.Equal(t, "s3:bucket-name/image-name:sha256-ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.String()) -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/storage/s3/logger.go b/vendor/github.com/grammarly/rocker/src/rocker/storage/s3/logger.go deleted file mode 100644 index 605a995..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/storage/s3/logger.go +++ /dev/null @@ -1,33 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3 - -import ( - log "github.com/Sirupsen/logrus" -) - -// Logger is an aws logger that prints entries to Logrus -type Logger struct{} - -// Log writes to logrus.Info -func (l *Logger) Log(args ...interface{}) { - if len(args) == 1 { - log.Infof(args[0].(string)) - } else { - log.Infof(args[0].(string), args[1:]...) - } -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/storage/s3/retryer.go b/vendor/github.com/grammarly/rocker/src/rocker/storage/s3/retryer.go deleted file mode 100644 index 45dad87..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/storage/s3/retryer.go +++ /dev/null @@ -1,95 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3 - -import ( - "math" - "math/rand" - "time" - - log "github.com/Sirupsen/logrus" - - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/client" - "github.com/aws/aws-sdk-go/aws/request" -) - -// Retryer is a custom aws retrier that logs retry attempts -type Retryer struct { - client.DefaultRetryer - - retryDelay int - retryMax int -} - -// NewRetryer returns the instance of Retryer object -func NewRetryer(retryDelay, retryMax int) *Retryer { - return &Retryer{ - retryDelay: retryDelay, - retryMax: retryMax, - } -} - -// MaxRetries returns the number of maximum returns the service will use to make -// an individual API request. -func (d Retryer) MaxRetries() int { - return d.retryMax -} - -// RetryRules returns the delay duration before retrying this request again -func (d Retryer) RetryRules(r *request.Request) time.Duration { - duration := d.getDuratoin(r.RetryCount) - - log.Errorf("%s/%s failed, will retry in %s, error %v", - r.ClientInfo.ServiceName, r.Operation.Name, duration, r.Error) - - return duration -} - -// Outer is for external stuff to handle retries for cases that are -// not covered by s3manager https://github.com/aws/aws-sdk-go/issues/466 -func (d Retryer) Outer(f func() error) error { - n := 0 - - for { - if err := f(); err != nil { - if n == d.retryMax { - log.Errorf("Max retries %d reached, error: %s", d.retryMax, err) - } - if _, ok := err.(awserr.Error); ok || n == d.retryMax { - return err - } - - duration := d.getDuratoin(n) - n = n + 1 - - log.Errorf("Retry %d/%d after %s, error: %s", n, d.retryMax, duration, err) - time.Sleep(duration) - - continue - } - - break - } - - return nil -} - -func (d Retryer) getDuratoin(n int) time.Duration { - delay := int(math.Pow(2, float64(n))) * (rand.Intn(d.retryDelay) + d.retryDelay) - return time.Duration(delay) * time.Millisecond -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/storage/s3/s3.go b/vendor/github.com/grammarly/rocker/src/rocker/storage/s3/s3.go deleted file mode 100644 index bb954a2..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/storage/s3/s3.go +++ /dev/null @@ -1,528 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3 - -import ( - "archive/tar" - "crypto/sha256" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/grammarly/rocker/src/rocker/imagename" - - log "github.com/Sirupsen/logrus" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/aws/aws-sdk-go/service/s3/s3manager" - "github.com/docker/docker/pkg/units" - "github.com/fsouza/go-dockerclient" -) - -const ( - cacheDir = "_digests" -) - -// Repositories is a struct that serializes to a "repositories" file -type Repositories map[string]Repository - -// Repository is an entity of Repositories struct -type Repository map[string]string - -// StorageS3 is a storage driver that implements storing docker images directly on S3 -type StorageS3 struct { - client *docker.Client - cacheRoot string - s3 *s3.S3 - retryer *Retryer -} - -// New makes an instance of StorageS3 storage driver -func New(client *docker.Client, cacheRoot string) *StorageS3 { - retryer := NewRetryer(400, 6) - - // TODO: configure region? - cfg := &aws.Config{ - Region: aws.String("us-east-1"), - Retryer: retryer, - Logger: &Logger{}, - } - - if log.StandardLogger().Level >= log.DebugLevel { - cfg.LogLevel = aws.LogLevel(aws.LogDebugWithRequestErrors) - } - - return &StorageS3{ - client: client, - cacheRoot: cacheRoot, - s3: s3.New(session.New(), cfg), - retryer: retryer, - } -} - -// Push pushes image tarball directly to S3 -func (s *StorageS3) Push(imageName string) (digest string, err error) { - img := imagename.NewFromString(imageName) - - if img.Storage != imagename.StorageS3 { - return "", fmt.Errorf("Can only push images with s3 storage specified, got: %s", img) - } - - if img.Registry == "" { - return "", fmt.Errorf("Cannot push image to S3, missing bucket name, got: %s", img) - } - - var image *docker.Image - if image, err = s.client.InspectImage(img.String()); err != nil { - return "", err - } - - if digest, err = s.CacheGet(image.ID); err != nil { - return "", err - } - - var tmpf string - - defer func() { - if tmpf != "" { - os.Remove(tmpf) - } - }() - - // Not cached, make tar - if digest == "" { - if tmpf, digest, err = s.MakeTar(imageName); err != nil { - return "", err - } - } - - var ( - ext = ".tar" - imgPathDigest = fmt.Sprintf("%s/%s%s", img.Name, digest, ext) - imgPathTag = fmt.Sprintf("%s/%s%s", img.Name, img.Tag, ext) - ) - - // Make HEAD request to s3 and check if image already uploaded - _, headErr := s.s3.HeadObject(&s3.HeadObjectInput{ - Bucket: aws.String(img.Registry), - Key: aws.String(imgPathDigest), - }) - - // Object not found, need to store - if headErr != nil { - // Other error, raise then - if e, ok := headErr.(awserr.RequestFailure); !ok || e.StatusCode() != 404 { - return "", headErr - } - // In case we do not have archive - if tmpf == "" { - var digest2 string - if tmpf, digest2, err = s.MakeTar(imageName); err != nil { - return "", err - } - // Verify digest (TODO: remote this check in future?) - if digest != digest2 { - return "", fmt.Errorf("The new digest does no equal old one (shouldn't happen) %s != %s", digest, digest2) - } - } - - uploader := s3manager.NewUploaderWithClient(s.s3, func(u *s3manager.Uploader) { - u.PartSize = 64 * 1024 * 1024 // 64MB per part - }) - - fd, err := os.Open(tmpf) - if err != nil { - return "", err - } - defer fd.Close() - - log.Infof("| Uploading image to s3://%s/%s", img.Registry, imgPathDigest) - - uploadParams := &s3manager.UploadInput{ - Bucket: aws.String(img.Registry), - Key: aws.String(imgPathDigest), - ContentType: aws.String("application/x-tar"), - Body: fd, - Metadata: map[string]*string{ - "Tag": aws.String(img.Tag), - "ImageID": aws.String(image.ID), - "Digest": aws.String(digest), - }, - } - - if err := s.retryer.Outer(func() error { - _, err := uploader.Upload(uploadParams) - return err - }); err != nil { - return "", fmt.Errorf("Failed to upload object to S3, error: %s", err) - } - } - - // Make a content addressable copy of an image file - copyParams := &s3.CopyObjectInput{ - Bucket: aws.String(img.Registry), - CopySource: aws.String(img.Registry + "/" + imgPathDigest), - Key: aws.String(imgPathTag), - } - - log.Infof("| Make alias s3://%s/%s", img.Registry, imgPathTag) - - if _, err = s.s3.CopyObject(copyParams); err != nil { - return "", fmt.Errorf("Failed to PUT object to S3, error: %s", err) - } - - return digest, nil -} - -// Pull imports docker image from tar artifact stored on S3 -func (s *StorageS3) Pull(name string) error { - img := imagename.NewFromString(name) - - if img.Storage != imagename.StorageS3 { - return fmt.Errorf("Can only pull images with s3 storage specified, got: %s", img) - } - - if img.Registry == "" { - return fmt.Errorf("Cannot pull image from S3, missing bucket name, got: %s", img) - } - - // TODO: here we use tmp file, but we can stream from S3 directly to Docker - tmpf, err := ioutil.TempFile("", "rocker_image_") - if err != nil { - return err - } - defer os.Remove(tmpf.Name()) - - var ( - // Create a downloader with the s3 client and custom options - downloader = s3manager.NewDownloaderWithClient(s.s3, func(d *s3manager.Downloader) { - d.PartSize = 64 * 1024 * 1024 // 64MB per part - }) - - imgPath = img.Name + "/" + img.Tag + ".tar" - - downloadParams = &s3.GetObjectInput{ - Bucket: aws.String(img.Registry), - Key: aws.String(imgPath), - } - ) - - log.Infof("| Import s3://%s/%s to %s", img.Registry, imgPath, tmpf.Name()) - - if err := s.retryer.Outer(func() error { - _, err := downloader.Download(tmpf, downloadParams) - return err - }); err != nil { - return fmt.Errorf("Failed to download object from S3, error: %s", err) - } - - fd, err := os.Open(tmpf.Name()) - if err != nil { - return err - } - defer fd.Close() - - // Read through tar reader to patch repositories file since we might - // mave a different tag property - var ( - pipeReader, pipeWriter = io.Pipe() - tr = tar.NewReader(fd) - tw = tar.NewWriter(pipeWriter) - errch = make(chan error, 1) - - loadOptions = docker.LoadImageOptions{ - InputStream: pipeReader, - } - ) - - go func() { - errch <- s.client.LoadImage(loadOptions) - }() - - // Iterate through the files in the archive. - for { - hdr, err := tr.Next() - if err == io.EOF { - break - } - if err != nil { - return fmt.Errorf("Failed to read tar content, error: %s", err) - } - - // Skip "repositories" file, we will write our own - if hdr.Name == "repositories" { - // Read repositories file and pass to JSON decoder - r1 := Repositories{} - data, err := ioutil.ReadAll(tr) - if err != nil { - return fmt.Errorf("Failed to read `repositories` file content, error: %s", err) - } - if err := json.Unmarshal(data, &r1); err != nil { - return fmt.Errorf("Failed to parse `repositories` file json, error: %s", err) - } - - var imageID string - - // Read first key from repositories - for _, tags := range r1 { - for _, id := range tags { - imageID = id - break - } - break - } - - // Make a new repositories struct - r2 := Repositories{ - img.NameWithRegistry(): { - img.GetTag(): imageID, - }, - } - - // Write repositories file to the stream - reposBody, err := json.Marshal(r2) - if err != nil { - return fmt.Errorf("Failed to marshal `repositories` file json, error: %s", err) - } - - hdr := &tar.Header{ - Name: "repositories", - Mode: 0644, - Size: int64(len(reposBody)), - } - if err := tw.WriteHeader(hdr); err != nil { - return fmt.Errorf("Failed to write `repositories` file tar header, error: %s", err) - } - if _, err := tw.Write(reposBody); err != nil { - return fmt.Errorf("Failed to write `repositories` file to tar, error: %s", err) - } - - continue - } - - // Passthrough other files as is - if err := tw.WriteHeader(hdr); err != nil { - return fmt.Errorf("Failed to passthough tar header, error: %s", err) - } - if _, err := io.Copy(tw, tr); err != nil { - return fmt.Errorf("Failed to passthough tar content, error: %s", err) - } - } - - // Finish tar - if err := tw.Close(); err != nil { - return fmt.Errorf("Failed to close tar writer, error: %s", err) - } - - // Close pipeWriter - if err := pipeWriter.Close(); err != nil { - return fmt.Errorf("Failed to close tar pipeWriter, error: %s", err) - } - - if err := <-errch; err != nil { - errch <- fmt.Errorf("Failed to import image, error: %s", err) - } - - return nil -} - -// MakeTar makes a tar out of docker image and gives a temporary file and a digest -func (s *StorageS3) MakeTar(imageName string) (tmpfile string, digest string, err error) { - img := imagename.NewFromString(imageName) - - var image *docker.Image - if image, err = s.client.InspectImage(img.String()); err != nil { - return "", "", err - } - - tmpf, err := ioutil.TempFile("", "rocker_image_") - if err != nil { - return "", "", err - } - defer tmpf.Close() - - var ( - cleanup = func() { - os.Remove(tmpf.Name()) - } - - humanSize = units.HumanSize(float64(image.VirtualSize)) - errch = make(chan error, 1) - - pipeReader, pipeWriter = io.Pipe() - tr = tar.NewReader(pipeReader) - tw = tar.NewWriter(tmpf) - hash = sha256.New() - tarHashStream = io.MultiWriter(tw, hash) - - exportParams = docker.ExportImageOptions{ - Name: img.String(), - OutputStream: pipeWriter, - } - ) - - defer pipeWriter.Close() - - log.Infof("| Buffering image to a file %s (%s)", tmpf.Name(), humanSize) - - go func() { - errch <- s.client.ExportImage(exportParams) - }() - - // Iterate through the files in the archive. - for { - hdr, err := tr.Next() - if err == io.EOF { - break - } - if err != nil { - cleanup() - return "", "", err - } - - // Skip "repositories" file, we will write our own - if hdr.Name == "repositories" { - io.Copy(ioutil.Discard, tr) - continue - } - - // Write any other file - tw.WriteHeader(hdr) - if _, err := io.Copy(tarHashStream, tr); err != nil { - cleanup() - return "", "", err - } - } - - // Write "repositories" file - digest = fmt.Sprintf("sha256-%x", hash.Sum(nil)) - - reposBody, err := json.Marshal(Repositories{ - img.NameWithRegistry(): { - img.Tag: image.ID, - digest: image.ID, - }, - }) - if err != nil { - cleanup() - return "", "", err - } - - hdr := &tar.Header{ - Name: "repositories", - Mode: 0644, - Size: int64(len(reposBody)), - } - if err := tw.WriteHeader(hdr); err != nil { - cleanup() - return "", "", err - } - if _, err := tw.Write(reposBody); err != nil { - cleanup() - return "", "", err - } - - // Finish tar - - if err := tw.Close(); err != nil { - cleanup() - return "", "", err - } - - if err := <-errch; err != nil { - cleanup() - return "", "", fmt.Errorf("Failed to export docker image %s, error: %s", img, err) - } - - // Cache digest by image ID - if err := s.CachePut(image.ID, digest); err != nil { - return "", "", fmt.Errorf("Failed to save digest cache, error: %s", err) - } - - return tmpf.Name(), digest, nil -} - -// ListTags returns the list of parsed tags existing for given image name on S3 -func (s *StorageS3) ListTags(imageName string) (images []*imagename.ImageName, err error) { - image := imagename.NewFromString(imageName) - - params := &s3.ListObjectsInput{ - Bucket: aws.String(image.Registry), - MaxKeys: aws.Int64(1000), - Prefix: aws.String(image.Name), - } - - resp, err := s.s3.ListObjects(params) - if err != nil { - return nil, err - } - - for _, s3Obj := range resp.Contents { - split := strings.Split(*s3Obj.Key, "/") - if len(split) < 2 { - continue - } - - imgName := strings.Join(split[:len(split)-1], "/") - imgName = fmt.Sprintf("s3:%s/%s", image.Registry, imgName) - - tag := strings.TrimSuffix(split[len(split)-1], ".tar") - candidate := imagename.New(imgName, tag) - - if candidate.Name != image.Name { - continue - } - - if image.Contains(candidate) || image.Tag == candidate.Tag { - images = append(images, candidate) - } - } - - return -} - -// CacheGet returns cached digest of the image -func (s *StorageS3) CacheGet(imageID string) (digest string, err error) { - fileName := filepath.Join(s.cacheRoot, cacheDir, imageID) - - data, err := ioutil.ReadFile(fileName) - if err != nil && os.IsNotExist(err) { - return "", nil - } - if err != nil { - return "", err - } - - return string(data), nil -} - -// CachePut stores digest cache of the image -func (s *StorageS3) CachePut(imageID, digest string) error { - fileName := filepath.Join(s.cacheRoot, cacheDir, imageID) - - if err := os.MkdirAll(filepath.Dir(fileName), 0755); err != nil { - return err - } - - return ioutil.WriteFile(fileName, []byte(digest), 0644) -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/storage/s3/s3_test.go b/vendor/github.com/grammarly/rocker/src/rocker/storage/s3/s3_test.go deleted file mode 100644 index 85b6b2c..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/storage/s3/s3_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// +build integration - -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Run the test like this: -// GOPATH=`pwd`:`pwd`/vendor go test -v rocker/storage/s3 -tags="integration" - -package s3 - -import ( - "os" - "rocker/dockerclient" - "testing" - - "github.com/kr/pretty" -) - -func TestStorageS3_Basic(t *testing.T) { - client, err := dockerclient.New() - if err != nil { - t.Fatal(err) - } - - s := New(client) - - tmpf, digest, err := s.MakeTar("alpine-s3:0.2") - if err != nil { - t.Fatal(err) - } - defer os.Remove(tmpf) - - pretty.Println(digest, tmpf) -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/storage/storage.go b/vendor/github.com/grammarly/rocker/src/rocker/storage/storage.go deleted file mode 100644 index 1945917..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/storage/storage.go +++ /dev/null @@ -1,30 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package storage - -import ( - "rocker/imagename" - - "github.com/fsouza/go-dockerclient" -) - -// Interface describes an interface of docker image storage driver -type Interface interface { - Push(imageName string) (digest string, err error) - Pull(imageName string) (image *docker.Image, err error) - ListTags(imageName string) (images []*imagename.ImageName, err error) -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/template/LICENSE b/vendor/github.com/grammarly/rocker/src/rocker/template/LICENSE deleted file mode 100644 index 7c699b1..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/template/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -(c) Copyright 2015 Grammarly, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/vendor/github.com/grammarly/rocker/src/rocker/template/Makefile b/vendor/github.com/grammarly/rocker/src/rocker/template/Makefile deleted file mode 100644 index 89a2b6c..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/template/Makefile +++ /dev/null @@ -1,32 +0,0 @@ - -SRCS = $(shell find . -name '*.go' | grep -v '^./vendor/') -PKGS := $(foreach pkg, $(sort $(dir $(SRCS))), $(pkg)) - -TESTARGS ?= - -deps: - @ go get github.com/kr/pretty - -testdeps: deps - @ go get github.com/GeertJohan/fgt - @ go get github.com/stretchr/testify/assert - -fmtcheck: - $(foreach file,$(SRCS),gofmt $(file) | diff -u $(file) - || exit;) - -lint: - @ go get github.com/golang/lint/golint - $(foreach file,$(SRCS),fgt golint $(file) || exit;) - -vet: - @ go get golang.org/x/tools/cmd/vet - $(foreach pkg,$(PKGS),fgt go vet $(pkg) || exit;) - -gocyclo: - @ go get github.com/fzipp/gocyclo - gocyclo -over 25 ./src - -test: testdeps fmtcheck vet lint - go test - -.PHONY: test fmtcheck lint vet gocyclo diff --git a/vendor/github.com/grammarly/rocker/src/rocker/template/README.md b/vendor/github.com/grammarly/rocker/src/rocker/template/README.md deleted file mode 100644 index f7cf4bf..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/template/README.md +++ /dev/null @@ -1,256 +0,0 @@ -# rocker/template - -Template renderer with additional helpers based on Go's [text/template](http://golang.org/pkg/text/template/) used by [rocker](https://github.com/grammarly/rocker) and [rocker-compose](https://github.com/grammarly/rocker-compose). - -# Helpers - -### {{ seq *To* }} or {{ seq *From* *To* }} or {{ seq *From* *To* *Step* }} -Sequence generator. Returns an array of integers of a given sequence. Useful when you need to duplicate some configuration, for example scale containers of the same type. Mostly used in combination with `range`: -``` -{{ range $i := seq 1 5 2 }} -container-$i -{{ end }} -``` - -This template will yield: -``` -container-1 -container-3 -container-5 -``` - -### String functions -`rocker/template` exposes some Go's native functions from [strings](http://golang.org/pkg/strings/) package. Here is the list of them: - -* `compare` - [strings.Compare](http://golang.org/pkg/strings/#Compare) -* `contains` - [strings.Contains](http://golang.org/pkg/strings/#Contains) -* `containsAny` - [strings.ContainsAny](http://golang.org/pkg/strings/#ContainsAny) -* `count` - [strings.Count](http://golang.org/pkg/strings/#Count) -* `equalFold` - [strings.EqualFold](http://golang.org/pkg/strings/#EqualFold) -* `hasPrefix` - [strings.HasPrefix](http://golang.org/pkg/strings/#HasPrefix) -* `hasSuffix` - [strings.HasSuffix](http://golang.org/pkg/strings/#HasSuffix) -* `index` - [strings.Index](http://golang.org/pkg/strings/#Index) -* `indexAny` - [strings.IndexAny](http://golang.org/pkg/strings/#IndexAny) -* `join` - [strings.Join](http://golang.org/pkg/strings/#Join) -* `lastIndex` - [strings.LastIndex](http://golang.org/pkg/strings/#LastIndex) -* `lastIndexAny` - [strings.LastIndexAny](http://golang.org/pkg/strings/#LastIndexAny) -* `repeat` - [strings.Repeat](http://golang.org/pkg/strings/#Repeat) -* `replace` - [strings.Replace](http://golang.org/pkg/strings/#Replace) -* `split` - [strings.Split](http://golang.org/pkg/strings/#Split) -* `splitAfter` - [strings.SplitAfter](http://golang.org/pkg/strings/#SplitAfter) -* `splitAfterN` - [strings.SplitAfterN](http://golang.org/pkg/strings/#SplitAfterN) -* `splitN` - [strings.SplitN](http://golang.org/pkg/strings/#SplitN) -* `title` - [strings.Title](http://golang.org/pkg/strings/#Title) -* `toLower` - [strings.ToLower](http://golang.org/pkg/strings/#ToLower) -* `toTitle` - [strings.ToTitle](http://golang.org/pkg/strings/#ToTitle) -* `toUpper` - [strings.ToUpper](http://golang.org/pkg/strings/#ToUpper) -* `trim` - [strings.Trim](http://golang.org/pkg/strings/#Trim) -* `trimLeft` - [strings.TrimLeft](http://golang.org/pkg/strings/#TrimLeft) -* `trimPrefix` - [strings.TrimPrefix](http://golang.org/pkg/strings/#TrimPrefix) -* `trimRight` - [strings.TrimRight](http://golang.org/pkg/strings/#TrimRight) -* `trimSpace` - [strings.TrimSpace](http://golang.org/pkg/strings/#TrimSpace) -* `trimSuffix` - [strings.TrimSuffix](http://golang.org/pkg/strings/#TrimSuffix) - -Example: -``` -{{ replace "www.google.com" "google" "grammarly" -1 }} -``` - -Will yield: -``` -www.grammarly.com -``` - -### {{ json *anything* }} or {{ *anything* | json }} -Marshals given input to JSON. - -Example: -``` -ENV={{ .Env | json }} -``` - -This template will yield: -``` -ENV={"USER":"johnsnow","DOCKER_MACHINE_NAME":"dev","PATH":"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin",...} -``` - -### {{ yaml *anything* }} or {{ *anything* | yaml }} -Marshals given input to YAML. - -Example: -``` -{{ .Env | yaml }} -``` - -This template will yield: -``` -USER: johnsnow -DOCKER_MACHINE_NAME: dev -PATH: /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin -``` - -### {{ *anything* | yaml *N* }} where *N* is indentation level -Useful if you want to nest a yaml struct into another yaml file: - -```yaml -foo: - bar: - {{ .bar | yaml 2 }} -``` - -Will indent yaml-encoded `.bar` into two levels: - -```yaml -foo: - bar: - a: 1 - b: 2 -``` - -### {{ shell *string* }} or {{ *string* | shell }} -Escapes given string so it can be substituted to a shell command. - -Example: -```Dockerfile -RUN echo {{ "hello\nworld" | shell }} -``` - -This template will yield: -```Dockerfile -RUN echo $'hello\nworld' -``` - -### {{ dump *anything* }} -Pretty-prints any variable. Useful for debugging. - -Example: -``` -{{ dump .Env }} -``` - -This template will yield: -``` -template.Vars{ - "USER": "johnsnow", - "DOCKER_MACHINE_NAME": "dev", - "PATH": "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin", - ... -} -``` - -### {{ assert *expression* }} -Raises an error if given expression is false. *Positive* value is an existing non-nil value, non-empty slice, non-empty string, and non-zero number. - -For example `assert` is useful to check that passed variables are present. - -``` -{{ assert .Version }} -``` - -If the `Version` variable is not given, then template processing will fail with the following error: - -``` -Error executing template TEMPLATE_NAME, error: template: TEMPLATE_NAME:1:3: executing \"TEMPLATE_NAME\" at : error calling assert: Assertion failed -``` - -### {{ image *docker_image_name_with_tag* }} or {{ image *docker_image_name* *tag* }} -Wrapper that is used to substitute images of particular versions derived by artifacts *(TODO: link to artifacts doc)*. - -Example: -```Dockerfile -FROM {{ image "ubuntu" }} -# OR -FROM {{ image "ubuntu:latest" }} -# OR -FROM {{ image "ubuntu" "latest" }} -``` - -Without any additional arguments it will resolve into this: -```Dockerfile -FROM ubuntu:latest -``` - -But if you have an artifact that is resulted by a previous rocker build, that can be fed back to rocker as variable, the artifact will be substituted: -```yaml -# shorten version of an artifact by rocker -RockerArtifacts: -- Name: ubuntu:latest - Digest: sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11 -``` - -```Dockerfile -# rocker build -vars artifacts/* -FROM ubuntu@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11 -``` - -This feature is useful when you have a continuous integration pipeline and you want to build images on top of each other with guaranteed immutability. Also, this trick can be used with [rocker-compose](https://github.com/grammarly/rocker-compose) to run images of particular versions devired by the artifacts. - -*TODO: also describe semver matching behavior* - -# Variables -`rocker/template` automatically populates [os.Environ](https://golang.org/pkg/os/#Environ) to the template along with the variables that are passed from the outside. All environment variables are available under `.Env`. - -Example: -``` -HOME={{ .Env.HOME }} -``` - -# Load file content to a variable -This template engine also supports loading files content to a variables. `rocker` and `rocker-compose` support this through a command line parameters: - -```bash -rocker build -var key=@key.pem -rocker-compose run -var key=@key.pem -``` - -If the file path is relative, it will be resolved according to the current working directory. - -**Usage options:** - -``` -key=@relative/file/path.txt -key=@../another/relative/file/path.txt -key=@/absolute/file/path.txt -key=@~/.bash_history -key=\@keep_value_as_is -``` - -# Development - -Please install pre-push git hook that will run tests before every push: - -```bash -cd rocker-template -``` - -To run tests manually: - -```bash -make test -``` - -Or to test something particular: - -```bash -go test -run TestProcessConfigTemplate_Seq -``` - -# Authors - -- Yura Bogdanov - -# License - -(c) Copyright 2015 Grammarly, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/vendor/github.com/grammarly/rocker/src/rocker/template/shellarg.go b/vendor/github.com/grammarly/rocker/src/rocker/template/shellarg.go deleted file mode 100644 index 57c662f..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/template/shellarg.go +++ /dev/null @@ -1,52 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package template - -import ( - "regexp" - "strings" -) - -var ( - complexShellArgRegex = regexp.MustCompile("(?i:[^a-z\\d_\\/:=-])") - leadingSingleQuotesRegex = regexp.MustCompile("^(?:'')+") -) - -// EscapeShellarg escapes any string so it can be safely passed to a shell -func EscapeShellarg(value string) string { - // Nothing to escape, return as is - if !complexShellArgRegex.MatchString(value) { - return value - } - - // escape all single quotes - value = "'" + strings.Replace(value, "'", "'\\''", -1) + "'" - - // remove duplicated single quotes at the beginning - value = leadingSingleQuotesRegex.ReplaceAllString(value, "") - - // remove non-escaped single-quote if there are enclosed between 2 escaped - value = strings.Replace(value, "\\'''", "\\'", -1) - - // if the string contains new lines, then use bash $'string' representation - // to have the newline escape character - if strings.Contains(value, "\n") { - value = "$" + strings.Replace(value, "\n", "\\n", -1) - } - - return value -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/template/shellarg_test.go b/vendor/github.com/grammarly/rocker/src/rocker/template/shellarg_test.go deleted file mode 100644 index 6c6fea9..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/template/shellarg_test.go +++ /dev/null @@ -1,51 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package template - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestEscapeShellarg_Basic(t *testing.T) { - t.Parallel() - assert.Equal(t, "Testing", EscapeShellarg("Testing")) - assert.Equal(t, "'Testing;'", EscapeShellarg("Testing;")) -} - -func TestEscapeShellarg_Advanced(t *testing.T) { - t.Parallel() - - assertions := map[string]string{ - "hello\\nworld": "'hello\\nworld'", - "hello:world": "hello:world", - "--hello=world": "--hello=world", - "hello\\tworld": "'hello\\tworld'", - "hello\nworld": "$'hello\\nworld'", - "\thello\nworld'": "$'\thello\\nworld'\\'", - "hello world": "'hello world'", - "hello\\\\'": "'hello\\\\'\\'", - "'\\\\'world": "\\''\\\\'\\''world'", - "world\\": "'world\\'", - "'single'": "\\''single'\\'", - } - - for k, v := range assertions { - assert.Equal(t, v, EscapeShellarg(k)) - } -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/template/template.go b/vendor/github.com/grammarly/rocker/src/rocker/template/template.go deleted file mode 100644 index 6e51654..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/template/template.go +++ /dev/null @@ -1,358 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package template - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "os" - "reflect" - "sort" - "strconv" - "strings" - "text/template" - - "github.com/grammarly/rocker/src/rocker/imagename" - - "github.com/go-yaml/yaml" - "github.com/kr/pretty" - - log "github.com/Sirupsen/logrus" -) - -// Funs is the list of additional helpers that may be given to the template -type Funs map[string]interface{} - -// Process renders config through the template processor. -// vars and additional functions are acceptable. -func Process(name string, reader io.Reader, vars Vars, funs Funs) (*bytes.Buffer, error) { - - var buf bytes.Buffer - // read template - data, err := ioutil.ReadAll(reader) - if err != nil { - return nil, fmt.Errorf("Error reading template %s, error: %s", name, err) - } - - // Copy the vars struct because we don't want to modify the original struct - vars = Vars{}.Merge(vars) - - // merge OS environment variables with the given Vars map - // todo: maybe, we need to make it configurable - vars["Env"] = ParseKvPairs(os.Environ()) - - // Populate functions - funcMap := map[string]interface{}{ - "seq": seq, - "dump": dump, - "assert": assertFn, - "json": jsonFn, - "shell": EscapeShellarg, - "yaml": yamlFn, - "image": makeImageHelper(vars), // `image` helper needs to make a closure on Vars - - // strings functions - "compare": strings.Compare, - "contains": strings.Contains, - "containsAny": strings.ContainsAny, - "count": strings.Count, - "equalFold": strings.EqualFold, - "hasPrefix": strings.HasPrefix, - "hasSuffix": strings.HasSuffix, - "indexOf": strings.Index, - "indexAny": strings.IndexAny, - "join": strings.Join, - "lastIndex": strings.LastIndex, - "lastIndexAny": strings.LastIndexAny, - "repeat": strings.Repeat, - "replace": strings.Replace, - "split": strings.Split, - "splitAfter": strings.SplitAfter, - "splitAfterN": strings.SplitAfterN, - "splitN": strings.SplitN, - "title": strings.Title, - "toLower": strings.ToLower, - "toTitle": strings.ToTitle, - "toUpper": strings.ToUpper, - "trim": strings.Trim, - "trimLeft": strings.TrimLeft, - "trimPrefix": strings.TrimPrefix, - "trimRight": strings.TrimRight, - "trimSpace": strings.TrimSpace, - "trimSuffix": strings.TrimSuffix, - } - for k, f := range funs { - funcMap[k] = f - } - - tmpl, err := template.New(name).Funcs(funcMap).Parse(string(data)) - if err != nil { - return nil, fmt.Errorf("Error parsing template %s, error: %s", name, err) - } - - if err := tmpl.Execute(&buf, vars); err != nil { - return nil, fmt.Errorf("Error executing template %s, error: %s", name, err) - } - - return &buf, nil -} - -// seq produces a sequence slice of a given length. See README.md for more info. -func seq(args ...interface{}) ([]int, error) { - l := len(args) - if l == 0 || l > 3 { - return nil, fmt.Errorf("seq helper expects from 1 to 3 arguments, %d given", l) - } - intArgs := make([]int, l) - for i, v := range args { - n, err := interfaceToInt(v) - if err != nil { - return nil, err - } - intArgs[i] = n - } - return doSeq(intArgs[0], intArgs[1:]...) -} - -func doSeq(n int, args ...int) ([]int, error) { - var ( - from, to, step int - - i = 0 - ) - - switch len(args) { - // {{ seq To }} - case 0: - // {{ seq 0 }} - if n == 0 { - return []int{}, nil - } - if n > 0 { - // {{ seq 15 }} - from, to, step = 1, n, 1 - } else { - // {{ seq -15 }} - from, to, step = -1, n, 1 - } - // {{ seq From To }} - case 1: - from, to, step = n, args[0], 1 - - // {{ seq From To Step }} - case 2: - from, to, step = n, args[0], args[1] - } - - if step <= 0 { - return nil, fmt.Errorf("step should be a positive integer, `%#v` given", step) - } - - // reverse order - if from > to { - res := make([]int, ((from-to)/step)+1) - for k := from; k >= to; k = k - step { - res[i] = k - i++ - } - return res, nil - } - - // straight order - res := make([]int, ((to-from)/step)+1) - for k := from; k <= to; k = k + step { - res[i] = k - i++ - } - return res, nil -} - -func dump(v interface{}) string { - return fmt.Sprintf("% #v", pretty.Formatter(v)) -} - -func assertFn(v interface{}) (string, error) { - t, _ := isTrue(reflect.ValueOf(v)) - if t { - return "", nil - } - return "", fmt.Errorf("Assertion failed") -} - -func jsonFn(v interface{}) (string, error) { - data, err := json.Marshal(v) - if err != nil { - return "", err - } - return string(data), nil -} - -func yamlFn(args ...interface{}) (result string, err error) { - var ( - i = 0 - input interface{} - ) - - if len(args) == 1 { - input = args[0] - } else if len(args) == 2 { - if i, err = interfaceToInt(args[0]); err != nil { - return "", err - } - input = args[1] - } else { - return "", fmt.Errorf("yaml helper expects from 1 to 2 arguments, %d given", len(args)) - } - - data, err := yaml.Marshal(input) - if err != nil { - return "", err - } - result = string(data) - - if i > 0 { - result = indent(strings.Repeat(" ", i), result) - } - - return result, nil -} - -func indent(prefix, s string) string { - var res []string - for _, line := range strings.Split(s, "\n") { - if line != "" { - line = prefix + line - } - res = append(res, line) - } - return strings.Join(res, "\n") -} - -func makeImageHelper(vars Vars) func(string, ...string) (string, error) { - // Sort artifacts so we match semver on latest item - var ( - artifacts = &imagename.Artifacts{} - ok bool - ) - - if artifacts.RockerArtifacts, ok = vars["RockerArtifacts"].([]imagename.Artifact); !ok { - artifacts.RockerArtifacts = []imagename.Artifact{} - } - - sort.Sort(artifacts) - - log.Debugf("`image` helper got artifacts: %# v", pretty.Formatter(artifacts)) - - return func(img string, args ...string) (string, error) { - var ( - matched bool - ok bool - shouldMatch bool - image = imagename.NewFromString(img) - ) - - if len(args) > 0 { - image = imagename.New(img, args[0]) - } - - for _, a := range artifacts.RockerArtifacts { - if !image.IsSameKind(*a.Name) { - continue - } - - if image.HasVersionRange() { - if !image.Contains(a.Name) { - log.Debugf("Skipping artifact %s because it is not suitable for %s", a.Name, image) - continue - } - } else if image.GetTag() != a.Name.GetTag() { - log.Debugf("Skipping artifact %s because it is not suitable for %s", a.Name, image) - continue - } - - if a.Digest != "" { - log.Infof("Apply artifact digest %s for image %s", a.Digest, image) - image.SetTag(a.Digest) - matched = true - break - } - if a.Name.HasTag() { - log.Infof("Apply artifact tag %s for image %s", a.Name.GetTag(), image) - image.SetTag(a.Name.GetTag()) - matched = true - break - } - } - - if shouldMatch, ok = vars["DemandArtifacts"].(bool); ok && shouldMatch && !matched { - return "", fmt.Errorf("Cannot find suitable artifact for image %s", image) - } - - return image.String(), nil - } -} - -func interfaceToInt(v interface{}) (int, error) { - switch v.(type) { - case int: - return v.(int), nil - case string: - n, err := strconv.ParseInt(v.(string), 10, 64) - if err != nil { - return 0, err - } - return (int)(n), nil - default: - return 0, fmt.Errorf("Cannot receive %#v, int or string is expected", v) - } -} - -// isTrue reports whether the value is 'true', in the sense of not the zero of its type, -// and whether the value has a meaningful truth value. -// -// NOTE: Borrowed from Go sources: http://golang.org/src/text/template/exec.go -// Copyright (c) 2012 The Go Authors. All rights reserved. -func isTrue(val reflect.Value) (truth, ok bool) { - if !val.IsValid() { - // Something like var x interface{}, never set. It's a form of nil. - return false, true - } - switch val.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - truth = val.Len() > 0 - case reflect.Bool: - truth = val.Bool() - case reflect.Complex64, reflect.Complex128: - truth = val.Complex() != 0 - case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface: - truth = !val.IsNil() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - truth = val.Int() != 0 - case reflect.Float32, reflect.Float64: - truth = val.Float() != 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - truth = val.Uint() != 0 - case reflect.Struct: - truth = true // Struct values are always true. - default: - return - } - return truth, true -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/template/template_test.go b/vendor/github.com/grammarly/rocker/src/rocker/template/template_test.go deleted file mode 100644 index 654b4ff..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/template/template_test.go +++ /dev/null @@ -1,228 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package template - -import ( - "fmt" - "os" - "strings" - "testing" - - "github.com/grammarly/rocker/src/rocker/imagename" - - "github.com/stretchr/testify/assert" -) - -var ( - configTemplateVars = Vars{ - "mykey": "myval", - "n": "5", - "data": map[string]string{ - "foo": "bar", - }, - "RockerArtifacts": []imagename.Artifact{ - imagename.Artifact{ - Name: imagename.NewFromString("alpine:3.2"), - Tag: "3.2", - }, - imagename.Artifact{ - Name: imagename.NewFromString("golang:1.5"), - Tag: "1.5", - Digest: "sha256:ead434", - }, - imagename.Artifact{ - Name: imagename.NewFromString("data:master"), - Tag: "master", - Digest: "sha256:fafe14", - }, - imagename.Artifact{ - Name: imagename.NewFromString("ssh:latest"), - Tag: "latest", - Digest: "sha256:ba41cd", - }, - }, - } -) - -func TestProcess_Basic(t *testing.T) { - result, err := Process("test", strings.NewReader("this is a test {{.mykey}}"), configTemplateVars, map[string]interface{}{}) - if err != nil { - t.Fatal(err) - } - // fmt.Printf("Template result: %s\n", result) - assert.Equal(t, "this is a test myval", result.String(), "template should be rendered") -} - -func TestProcess_Seq(t *testing.T) { - assert.Equal(t, "[1 2 3 4 5]", processTemplate(t, "{{ seq 1 5 1 }}")) - assert.Equal(t, "[0 1 2 3 4]", processTemplate(t, "{{ seq 0 4 1 }}")) - assert.Equal(t, "[1 3 5]", processTemplate(t, "{{ seq 1 5 2 }}")) - assert.Equal(t, "[1 4]", processTemplate(t, "{{ seq 1 5 3 }}")) - assert.Equal(t, "[1 5]", processTemplate(t, "{{ seq 1 5 4 }}")) - assert.Equal(t, "[1]", processTemplate(t, "{{ seq 1 5 5 }}")) - - assert.Equal(t, "[1]", processTemplate(t, "{{ seq 1 1 1 }}")) - assert.Equal(t, "[1]", processTemplate(t, "{{ seq 1 1 5 }}")) - - assert.Equal(t, "[5 4 3 2 1]", processTemplate(t, "{{ seq 5 1 1 }}")) - assert.Equal(t, "[5 3 1]", processTemplate(t, "{{ seq 5 1 2 }}")) - assert.Equal(t, "[5 2]", processTemplate(t, "{{ seq 5 1 3 }}")) - assert.Equal(t, "[5 1]", processTemplate(t, "{{ seq 5 1 4 }}")) - assert.Equal(t, "[5]", processTemplate(t, "{{ seq 5 1 5 }}")) - - assert.Equal(t, "[1 2 3 4 5]", processTemplate(t, "{{ seq 5 }}")) - assert.Equal(t, "[1]", processTemplate(t, "{{ seq 1 }}")) - assert.Equal(t, "[]", processTemplate(t, "{{ seq 0 }}")) - assert.Equal(t, "[-1 -2 -3 -4 -5]", processTemplate(t, "{{ seq -5 }}")) - - assert.Equal(t, "[1 2 3 4 5]", processTemplate(t, "{{ seq 1 5 }}")) - assert.Equal(t, "[1]", processTemplate(t, "{{ seq 1 1 }}")) - assert.Equal(t, "[0]", processTemplate(t, "{{ seq 0 0 }}")) - assert.Equal(t, "[-1 -2 -3 -4 -5]", processTemplate(t, "{{ seq -1 -5 }}")) - - // Test string param - assert.Equal(t, "[1 2 3 4 5]", processTemplate(t, "{{ seq .n }}")) -} - -func TestProcess_Replace(t *testing.T) { - assert.Equal(t, "url-com-", processTemplate(t, `{{ replace "url.com." "." "-" -1 }}`)) - assert.Equal(t, "url", processTemplate(t, `{{ replace "url" "*" "l" -1 }}`)) - assert.Equal(t, "krl", processTemplate(t, `{{ replace "url" "u" "k" -1 }}`)) -} - -func TestProcess_Env(t *testing.T) { - env := os.Environ() - kv := strings.SplitN(env[0], "=", 2) - assert.Equal(t, kv[1], processTemplate(t, fmt.Sprintf("{{ .Env.%s }}", kv[0]))) -} - -func TestProcess_Dump(t *testing.T) { - assert.Equal(t, `map[string]string{"foo":"bar"}`, processTemplate(t, "{{ dump .data }}")) -} - -func TestProcess_AssertSuccess(t *testing.T) { - assert.Equal(t, "output", processTemplate(t, "{{ assert true }}output")) -} - -func TestProcess_AssertFail(t *testing.T) { - tpl := "{{ assert .Version }}lololo" - _, err := Process("test", strings.NewReader(tpl), configTemplateVars, map[string]interface{}{}) - errStr := "Error executing template test, error: template: test:1:3: executing \"test\" at : error calling assert: Assertion failed" - assert.Equal(t, errStr, err.Error()) -} - -func TestProcess_Json(t *testing.T) { - assert.Equal(t, "key: {\"foo\":\"bar\"}", processTemplate(t, "key: {{ .data | json }}")) -} - -func TestProcess_Shellarg(t *testing.T) { - assert.Equal(t, "echo 'hello world'", processTemplate(t, "echo {{ \"hello world\" | shell }}")) -} - -func TestProcess_Yaml(t *testing.T) { - assert.Equal(t, "key: foo: bar\n", processTemplate(t, "key: {{ .data | yaml }}")) - assert.Equal(t, "key: myval\n", processTemplate(t, "key: {{ .mykey | yaml }}")) - assert.Equal(t, "key: |-\n hello\n world\n", processTemplate(t, "key: {{ \"hello\\nworld\" | yaml }}")) -} - -func TestProcess_YamlIndent(t *testing.T) { - assert.Equal(t, "key:\n foo: bar\n", processTemplate(t, "key:\n{{ .data | yaml 1 }}")) -} - -func TestProcess_Image_Simple(t *testing.T) { - tests := []struct { - tpl string - result string - message string - }{ - {"{{ image `debian:7.7` }}", "debian:7.7", "should not alter the tag that is not in artifacts"}, - {"{{ image `debian` `7.7` }}", "debian:7.7", "should be possible to specify tag as a separate argument"}, - {"{{ image `debian` `sha256:afa` }}", "debian@sha256:afa", "should be possible to specify digest as a separate argument"}, - } - - for _, test := range tests { - assert.Equal(t, test.result, processTemplate(t, test.tpl), test.message) - } -} - -func TestProcess_Image_Advanced(t *testing.T) { - tests := []struct { - in string - result string - shouldMatch bool - message string - }{ - {"debian:7.7", "debian:7.7", false, "should not alter the tag that is not in artifacts"}, - {"debian:7.*", "debian:7.*", false, "should not alter the semver tag that is not in artifacts"}, - {"debian", "debian:latest", false, "should not match anything when no tag given (:latest) and no artifact"}, - {"alpine:3.1", "alpine:3.1", false, "should not match artifact with different version"}, - {"alpine:4.1", "alpine:4.1", false, "should not match artifact with different version"}, - {"alpine:3.*", "alpine:3.2", true, "should match artifact with version wildcard"}, - {"alpine", "alpine:latest", false, "should not match artifact when no tag given (:latest by default)"}, - {"alpine:latest", "alpine:latest", false, "should not match on a :latest tag"}, - {"alpine:snapshot", "alpine:snapshot", false, "should not match on a named tag"}, - {"golang:1.5", "golang@sha256:ead434", true, "should match semver tag and use digest"}, - {"golang:1.*", "golang@sha256:ead434", true, "should match on wildcard semver tag and use digest"}, - {"golang:1", "golang@sha256:ead434", true, "should match on prefix semver tag and use digest"}, - {"golang:1.4", "golang:1.4", false, "should not match on different semver tag"}, - {"golang:master", "golang:master", false, "should not match on a named tag"}, - {"data:1.2", "data:1.2", false, "should not match on a version tag against named artifact"}, - {"data:snapshot", "data:snapshot", false, "should not match on a different named tag against named artifact"}, - {"data:master", "data@sha256:fafe14", true, "should match on a same named tag against named artifact"}, - {"ssh:latest", "ssh@sha256:ba41cd", true, "should match on a :latest tag against :latest artifact"}, - {"ssh", "ssh@sha256:ba41cd", true, "should match on non-tagged tag against :latest artifact"}, - {"ssh:master", "ssh:master", false, "should match with other tag against :latest artifact"}, - {"ssh:1.2", "ssh:1.2", false, "should match with semver tag against :latest artifact"}, - } - - for _, test := range tests { - tpl := fmt.Sprintf("{{ image `%s` }}", test.in) - assert.Equal(t, test.result, processTemplate(t, tpl), test.message) - } - - // Now test the same but with DemandArtifact On - configTemplateVars["DemandArtifacts"] = true - defer func() { - configTemplateVars["DemandArtifacts"] = false - }() - - for _, test := range tests { - tpl := fmt.Sprintf("{{ image `%s` }}", test.in) - if test.shouldMatch { - assert.Equal(t, test.result, processTemplate(t, tpl), test.message) - } else { - err := processTemplateReturnError(t, tpl) - assert.Error(t, err, fmt.Sprintf("should give an error for test case: %s", test.message)) - if err != nil { - assert.Contains(t, err.Error(), fmt.Sprintf("Cannot find suitable artifact for image %s", test.in), test.message) - } - } - } -} - -func processTemplate(t *testing.T, tpl string) string { - result, err := Process("test", strings.NewReader(tpl), configTemplateVars, map[string]interface{}{}) - if err != nil { - t.Fatal(err) - } - return result.String() -} - -func processTemplateReturnError(t *testing.T, tpl string) error { - _, err := Process("test", strings.NewReader(tpl), configTemplateVars, map[string]interface{}{}) - return err -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/template/testdata/content.txt b/vendor/github.com/grammarly/rocker/src/rocker/template/testdata/content.txt deleted file mode 100644 index ce01362..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/template/testdata/content.txt +++ /dev/null @@ -1 +0,0 @@ -hello diff --git a/vendor/github.com/grammarly/rocker/src/rocker/template/vars.go b/vendor/github.com/grammarly/rocker/src/rocker/template/vars.go deleted file mode 100644 index 52323ce..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/template/vars.go +++ /dev/null @@ -1,295 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package template - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path" - "path/filepath" - "reflect" - "regexp" - "sort" - "strings" - - "github.com/grammarly/rocker/src/rocker/imagename" - - "github.com/go-yaml/yaml" - - log "github.com/Sirupsen/logrus" -) - -// Vars describes the data structure of the build variables -type Vars map[string]interface{} - -// Merge the current Vars structure with the list of other Vars structs -func (vars Vars) Merge(varsList ...Vars) Vars { - for _, mergeWith := range varsList { - for k, v := range mergeWith { - // We want to merge slices of the same type by appending them to each other - // instead of overwriting - rv1 := reflect.ValueOf(vars[k]) - rv2 := reflect.ValueOf(v) - - if rv1.Kind() == reflect.Slice && rv2.Kind() == reflect.Slice && rv1.Type() == rv2.Type() { - vars[k] = reflect.AppendSlice(rv1, rv2).Interface() - } else { - vars[k] = v - } - } - } - return vars -} - -// IsSet returns true if the given key is set -func (vars Vars) IsSet(key string) bool { - _, ok := vars[key] - return ok -} - -// ToStrings converts Vars to a slice of strings line []string{"KEY=VALUE"} -func (vars Vars) ToStrings() (result []string) { - for k, v := range vars { - result = append(result, fmt.Sprintf("%s=%s", k, v)) - } - sort.Strings(result) - return result -} - -// ToMapOfInterface casts Vars to map[string]interface{} -func (vars Vars) ToMapOfInterface() map[string]interface{} { - result := map[string]interface{}{} - for k, v := range vars { - result[k] = v - } - return result -} - -// MarshalJSON serialize Vars to JSON -func (vars Vars) MarshalJSON() ([]byte, error) { - return json.Marshal(vars.ToStrings()) -} - -// UnmarshalJSON unserialize Vars from JSON string -func (vars *Vars) UnmarshalJSON(data []byte) (err error) { - // try unmarshal map to keep backward compatibility - maps := map[string]interface{}{} - if err = json.Unmarshal(data, &maps); err == nil { - *vars = (Vars)(maps) - return nil - } - // unmarshal slice of strings - strings := []string{} - if err = json.Unmarshal(data, &strings); err != nil { - return err - } - if *vars, err = VarsFromStrings(strings); err != nil { - return err - } - return nil -} - -// UnmarshalYAML parses YAML string and returns Vars -func (vars *Vars) UnmarshalYAML(unmarshal func(interface{}) error) (err error) { - // try unmarshal RockerArtifacts type - var artifacts imagename.Artifacts - if err = unmarshal(&artifacts); err != nil { - return err - } - - var value map[string]interface{} - if err = unmarshal(&value); err != nil { - return err - } - - // Fill artifacts if present - if len(artifacts.RockerArtifacts) > 0 { - value["RockerArtifacts"] = artifacts.RockerArtifacts - } - - *vars = value - - return nil -} - -// VarsFromStrings parses Vars through ParseKvPairs and then loads content from files -// for vars values with "@" prefix -func VarsFromStrings(pairs []string) (vars Vars, err error) { - vars = ParseKvPairs(pairs) - for k, v := range vars { - // We care only about strings - switch v := v.(type) { - case string: - // Read variable content from a file if "@" prefix is given - if strings.HasPrefix(v, "@") { - f := v[1:] - if vars[k], err = loadFileContent(f); err != nil { - return vars, fmt.Errorf("Failed to read file '%s' for variable %s, error: %s", f, k, err) - } - } - // Unescape "\@" - if strings.HasPrefix(v, "\\@") { - vars[k] = v[1:] - } - } - } - return vars, nil -} - -// VarsFromFile reads variables from either JSON or YAML file -func VarsFromFile(filename string) (vars Vars, err error) { - log.Debugf("Load vars from file %s", filename) - - if filename, err = resolveFileName(filename); err != nil { - return nil, err - } - - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - vars = Vars{} - - switch filepath.Ext(filename) { - case ".yaml", ".yml", ".": - if err := yaml.Unmarshal(data, &vars); err != nil { - return nil, err - } - case ".json": - if err := json.Unmarshal(data, &vars); err != nil { - return nil, err - } - } - - return vars, nil -} - -// VarsFromFileMulti reads multiple files and merge vars -func VarsFromFileMulti(files []string) (Vars, error) { - var ( - varsList = []Vars{} - matches []string - vars Vars - err error - ) - - for _, pat := range files { - matches = []string{pat} - - if containsWildcards(pat) { - if matches, err = filepath.Glob(pat); err != nil { - return nil, err - } - } - - for _, f := range matches { - if vars, err = VarsFromFile(f); err != nil { - return nil, err - } - varsList = append(varsList, vars) - } - } - - return Vars{}.Merge(varsList...), nil -} - -// ParseKvPairs parses Vars from a slice of strings e.g. []string{"KEY=VALUE"} -func ParseKvPairs(pairs []string) (vars Vars) { - vars = make(Vars) - for _, varPair := range pairs { - tmp := strings.SplitN(varPair, "=", 2) - vars[tmp[0]] = tmp[1] - } - return vars -} - -func loadFileContent(f string) (content string, err error) { - if f, err = resolveFileName(f); err != nil { - return "", err - } - data, err := ioutil.ReadFile(f) - if err != nil { - return "", err - } - return string(data), nil -} - -func resolveFileName(f string) (string, error) { - if f == "~" || strings.HasPrefix(f, "~/") { - f = strings.Replace(f, "~", os.Getenv("HOME"), 1) - } - if !filepath.IsAbs(f) { - wd, err := os.Getwd() - if err != nil { - return "", err - } - f = path.Join(wd, f) - } - return f, nil -} - -// Code borrowed from https://github.com/docker/docker/blob/df0e0c76831bed08cf5e08ac9a1abebf6739da23/builder/support.go -var ( - // `\\\\+|[^\\]|\b|\A` - match any number of "\\" (ie, properly-escaped backslashes), or a single non-backslash character, or a word boundary, or beginning-of-line - // `\$` - match literal $ - // `[[:alnum:]_]+` - match things like `$SOME_VAR` - // `{[[:alnum:]_]+}` - match things like `${SOME_VAR}` - tokenVarsInterpolation = regexp.MustCompile(`(\\|\\\\+|[^\\]|\b|\A)\$([[:alnum:]_]+|{[[:alnum:]_]+})`) - // this intentionally punts on more exotic interpolations like ${SOME_VAR%suffix} and lets the shell handle those directly -) - -// ReplaceString handle vars replacement -func (vars Vars) ReplaceString(str string) string { - for _, match := range tokenVarsInterpolation.FindAllString(str, -1) { - idx := strings.Index(match, "\\$") - if idx != -1 { - if idx+2 >= len(match) { - str = strings.Replace(str, match, "\\$", -1) - continue - } - - prefix := match[:idx] - stripped := match[idx+2:] - str = strings.Replace(str, match, prefix+"$"+stripped, -1) - continue - } - - match = match[strings.Index(match, "$"):] - matchKey := strings.Trim(match, "${}") - - if val, ok := vars[matchKey].(string); ok { - str = strings.Replace(str, match, val, -1) - } - } - - return str -} - -func containsWildcards(name string) bool { - for i := 0; i < len(name); i++ { - ch := name[i] - if ch == '\\' { - i++ - } else if ch == '*' || ch == '?' || ch == '[' { - return true - } - } - return false -} diff --git a/vendor/github.com/grammarly/rocker/src/rocker/template/vars_test.go b/vendor/github.com/grammarly/rocker/src/rocker/template/vars_test.go deleted file mode 100644 index a942a99..0000000 --- a/vendor/github.com/grammarly/rocker/src/rocker/template/vars_test.go +++ /dev/null @@ -1,320 +0,0 @@ -/*- - * Copyright 2015 Grammarly, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package template - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path" - "testing" - - "github.com/grammarly/rocker/src/rocker/imagename" - "github.com/grammarly/rocker/src/rocker/test" - - "github.com/stretchr/testify/assert" -) - -func TestVars_MergeSlices(t *testing.T) { - v1 := Vars{ - "fruits": []string{"banana", "apple"}, - } - v2 := Vars{ - "fruits": []string{"pear", "orange"}, - } - v3 := v1.Merge(v2) - - assert.Equal(t, []string{"banana", "apple", "pear", "orange"}, v3["fruits"].([]string)) -} - -func TestVarsToStrings(t *testing.T) { - t.Parallel() - - type assertion struct { - vars Vars - expectation []string - } - - tests := []assertion{ - assertion{ - Vars{"FOO": "bar", "XYZ": "oqoq"}, - []string{"FOO=bar", "XYZ=oqoq"}, - }, - assertion{ - Vars{"": "bar", "XYZ": "oqoq"}, - []string{"=bar", "XYZ=oqoq"}, - }, - assertion{ - Vars{"asd": "qwe"}, - []string{"asd=qwe"}, - }, - assertion{ - Vars{"asd": "", "haha": "hehe"}, - []string{"asd=", "haha=hehe"}, - }, - } - - for _, a := range tests { - result := a.vars.ToStrings() - assert.Equal(t, len(a.vars), len(result), "resulting number of strings not match number of vars keys") - for _, expectation := range a.expectation { - assert.Contains(t, result, expectation, "failed to narrow down vars to list of strings") - } - } -} - -func TestVarsFromStrings(t *testing.T) { - t.Parallel() - - type assertion struct { - input []string - expectation Vars - } - - tests := []assertion{ - assertion{ - []string{"FOO=bar", "XYZ=oqoq"}, - Vars{"FOO": "bar", "XYZ": "oqoq"}, - }, - assertion{ - []string{"=bar", "XYZ=oqoq"}, - Vars{"": "bar", "XYZ": "oqoq"}, - }, - assertion{ - []string{"asd=qwe"}, - Vars{"asd": "qwe"}, - }, - assertion{ - []string{"asd=", "haha=hehe"}, - Vars{"asd": "", "haha": "hehe"}, - }, - } - - for _, a := range tests { - result, err := VarsFromStrings(a.input) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, len(a.input), len(result), "resulting number of strings not match number of vars keys") - } -} - -// TODO: test VarsFromFileMulti - -func TestVarsFromFile_Yaml(t *testing.T) { - tempDir, rm := tplMkFiles(t, map[string]string{ - "vars.yml": ` -Foo: x -Bar: yes -`, - }) - defer rm() - - vars, err := VarsFromFile(tempDir + "/vars.yml") - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "x", vars["Foo"]) - assert.Equal(t, true, vars["Bar"]) -} - -func TestVarsFromFile_Yaml_Artifacts(t *testing.T) { - tempDir, rm := tplMkFiles(t, map[string]string{ - "vars.yml": ` -Foo: x -Bar: yes -RockerArtifacts: -- Name: golang:1.5 - Tag: "1.5" -`, - }) - defer rm() - - vars, err := VarsFromFile(tempDir + "/vars.yml") - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "x", vars["Foo"]) - assert.Equal(t, true, vars["Bar"]) - assert.IsType(t, []imagename.Artifact{}, vars["RockerArtifacts"]) -} - -func TestVarsFromFile_Json(t *testing.T) { - tempDir, rm := tplMkFiles(t, map[string]string{ - "vars.json": ` -{"Foo": "x", "Bar": true} -`, - }) - defer rm() - - vars, err := VarsFromFile(tempDir + "/vars.json") - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "x", vars["Foo"]) - assert.Equal(t, true, vars["Bar"]) -} - -func TestVarsReplaceString(t *testing.T) { - t.Parallel() - - type assertion struct { - vars Vars - input string - expectation string - } - - tests := []assertion{ - assertion{ - Vars{"FOO": "bar"}, - "Hello, this is $FOO", - "Hello, this is bar", - }, - assertion{ - Vars{"FOO": "bar"}, - "Hello, this is ${FOO}", - "Hello, this is bar", - }, - assertion{ - Vars{"FOO": ""}, - "Hello, this is $FOO", - "Hello, this is ", - }, - assertion{ - Vars{"GREETING": "Hello", "NAME": "Hadiyah"}, - "$GREETING,\n$NAME!", - "Hello,\nHadiyah!", - }, - assertion{ - Vars{}, - "$GREETING,\n$NAME!", - "$GREETING,\n$NAME!", - }, - } - - for _, a := range tests { - result := a.vars.ReplaceString(a.input) - assert.Equal(t, a.expectation, result, "failed to substitute variables to a string") - } -} - -func TestVarsJsonMarshal(t *testing.T) { - v := Vars{"foo": "bar", "asd": "qwe"} - data, err := json.Marshal(v) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, `["asd=qwe","foo=bar"]`, string(data), "bad Vars encoded to json") - - v2 := Vars{"asd": "qwe", "foo": "bar"} - data2, err := json.Marshal(v2) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, `["asd=qwe","foo=bar"]`, string(data2), "bad Vars encoded to json (order)") - - v3 := Vars{} - if err := json.Unmarshal(data2, &v3); err != nil { - t.Fatal(err) - } - - assert.Equal(t, 2, len(v3), "bad decoded vars length") - assert.Equal(t, "qwe", v3["asd"], "bad decoded vars element") - assert.Equal(t, "bar", v3["foo"], "bad decoded vars element") - - // Test unmarshal map to keep backward capatibility - m := map[string]string{ - "foo": "bar", - "asd": "qwe", - } - data3, err := json.Marshal(m) - if err != nil { - t.Fatal(err) - } - - v4 := Vars{} - if err := json.Unmarshal(data3, &v4); err != nil { - t.Fatal(err) - } - - assert.Equal(t, 2, len(v4), "bad decoded vars length") - assert.Equal(t, "qwe", v4["asd"], "bad decoded vars element") - assert.Equal(t, "bar", v4["foo"], "bad decoded vars element") -} - -func TestVarsFileContent(t *testing.T) { - t.Parallel() - - wd, err := os.Getwd() - if err != nil { - t.Fatal(err) - } - - // Test absolute - result, err := VarsFromStrings([]string{fmt.Sprintf("FOO=@%s/testdata/content.txt", wd)}) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "hello\n", result["FOO"]) - - // Test relative - result2, err := VarsFromStrings([]string{"FOO=@testdata/content.txt"}) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "hello\n", result2["FOO"]) - - // Test escaped @ - result3, err := VarsFromStrings([]string{"FOO=\\@testdata/content.txt"}) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "@testdata/content.txt", result3["FOO"]) - - // Test HOME - os.Setenv("HOME", path.Join(wd, "testdata")) - - result4, err := VarsFromStrings([]string{"FOO=@~/content.txt"}) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "hello\n", result4["FOO"]) -} - -func tplMkFiles(t *testing.T, files map[string]string) (string, func()) { - tempDir, err := ioutil.TempDir("", "rocker-vars-test") - if err != nil { - t.Fatal(err) - } - - if err = test.MakeFiles(tempDir, files); err != nil { - os.RemoveAll(tempDir) - t.Fatal(err) - } - - return tempDir, func() { - os.RemoveAll(tempDir) - } -} diff --git a/vendor/manifest b/vendor/manifest index 38a3940..d2c0a4c 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -1,42 +1,18 @@ { "version": 0, "dependencies": [ - { - "importpath": "github.com/go-yaml/yaml", - "repository": "https://github.com/go-yaml/yaml", - "revision": "7ad95dd0798a40da1ccdff6dff35fd177b5edf40", - "branch": "v2" - }, - { - "importpath": "github.com/stretchr/testify", - "repository": "https://github.com/stretchr/testify", - "revision": "089c7181b8c728499929ff09b62d3fdd8df8adff", - "branch": "master" - }, - { - "importpath": "github.com/kr/pretty", - "repository": "https://github.com/kr/pretty", - "revision": "e6ac2fc51e89a3249e82157fa0bb7a18ef9dd5bb", - "branch": "master" - }, - { - "importpath": "github.com/kr/text", - "repository": "https://github.com/kr/text", - "revision": "e373e137fafd8abd480af49182dea0513914adb4", - "branch": "master" - }, - { - "importpath": "github.com/stretchr/objx", - "repository": "https://github.com/stretchr/objx", - "revision": "cbeaeb16a013161a98496fad62933b1d21786672", - "branch": "master" - }, { "importpath": "github.com/Sirupsen/logrus", "repository": "https://github.com/Sirupsen/logrus", "revision": "93a1736895ca25a01a739e0394bf7f672299a27d", "branch": "master" }, + { + "importpath": "github.com/aws/aws-sdk-go", + "repository": "https://github.com/aws/aws-sdk-go", + "revision": "c924893c38ecc04b18d7aab8a7aa561cb8b4c4cc", + "branch": "master" + }, { "importpath": "github.com/codegangsta/cli", "repository": "https://github.com/codegangsta/cli", @@ -50,6 +26,13 @@ "branch": "master", "path": "/pkg/jsonmessage" }, + { + "importpath": "github.com/docker/docker/pkg/signal", + "repository": "https://github.com/docker/docker", + "revision": "92487d7fb4963c0333c409d053ff694e619c538d", + "branch": "master", + "path": "/pkg/signal" + }, { "importpath": "github.com/docker/docker/pkg/term", "repository": "https://github.com/docker/docker", @@ -72,30 +55,50 @@ "path": "/pkg/units" }, { - "importpath": "github.com/grammarly/rocker/src/rocker/test", + "importpath": "github.com/fsouza/go-dockerclient", + "repository": "https://github.com/fsouza/go-dockerclient", + "revision": "c9ad0ce23f68428421adfc6ced9e6123f54788a5", + "branch": "HEAD" + }, + { + "importpath": "github.com/go-ini/ini", + "repository": "https://github.com/go-ini/ini", + "revision": "9f4d2712cf687b68fad24366f81eaee163079090", + "branch": "v1" + }, + { + "importpath": "github.com/go-yaml/yaml", + "repository": "https://github.com/go-yaml/yaml", + "revision": "7ad95dd0798a40da1ccdff6dff35fd177b5edf40", + "branch": "v2" + }, + { + "importpath": "github.com/grammarly/rocker/src/dockerclient", "repository": "https://github.com/grammarly/rocker", - "revision": "79bcadfcce121a8d9fa0f84e231314c6374bf8f3", + "revision": "ec7c40b0d139303db89add1fbdde15d321142e53", "branch": "master", - "path": "/src/rocker/test" + "path": "/src/dockerclient" }, { - "importpath": "github.com/grammarly/rocker/src/rocker/util", + "importpath": "github.com/grammarly/rocker/src/imagename", "repository": "https://github.com/grammarly/rocker", - "revision": "79bcadfcce121a8d9fa0f84e231314c6374bf8f3", + "revision": "ec7c40b0d139303db89add1fbdde15d321142e53", "branch": "master", - "path": "/src/rocker/util" + "path": "/src/imagename" }, { - "importpath": "github.com/fsouza/go-dockerclient", - "repository": "https://github.com/fsouza/go-dockerclient", - "revision": "c9ad0ce23f68428421adfc6ced9e6123f54788a5", - "branch": "HEAD" + "importpath": "github.com/grammarly/rocker/src/rocker/debugtrap", + "repository": "https://github.com/grammarly/rocker", + "revision": "d6a4bbfc503169324de3462a2ae2bb5a5dc2f041", + "branch": "v1", + "path": "/src/rocker/debugtrap" }, { - "importpath": "github.com/wmark/semver", - "repository": "https://github.com/wmark/semver", - "revision": "461c06b538be53cc0339815001a398e29ace025b", - "branch": "master" + "importpath": "github.com/grammarly/rocker/src/rocker/test", + "repository": "https://github.com/grammarly/rocker", + "revision": "79bcadfcce121a8d9fa0f84e231314c6374bf8f3", + "branch": "master", + "path": "/src/rocker/test" }, { "importpath": "github.com/grammarly/rocker/src/rocker/textformatter", @@ -105,37 +108,32 @@ "path": "/src/rocker/textformatter" }, { - "importpath": "github.com/grammarly/rocker/src/rocker/template", + "importpath": "github.com/grammarly/rocker/src/rocker/util", "repository": "https://github.com/grammarly/rocker", - "revision": "3b5905678b83b1b5cc4f22557bed523ff4d2438e", - "branch": "v1", - "path": "/src/rocker/template" - }, - { - "importpath": "github.com/mitchellh/go-homedir", - "repository": "https://github.com/mitchellh/go-homedir", - "revision": "d682a8f0cf139663a984ff12528da460ca963de9", - "branch": "master" + "revision": "79bcadfcce121a8d9fa0f84e231314c6374bf8f3", + "branch": "master", + "path": "/src/rocker/util" }, { - "importpath": "github.com/docker/docker/pkg/signal", - "repository": "https://github.com/docker/docker", - "revision": "92487d7fb4963c0333c409d053ff694e619c538d", + "importpath": "github.com/grammarly/rocker/src/storage", + "repository": "https://github.com/grammarly/rocker", + "revision": "ec7c40b0d139303db89add1fbdde15d321142e53", "branch": "master", - "path": "/pkg/signal" + "path": "/src/storage" }, { - "importpath": "github.com/grammarly/rocker/src/rocker/debugtrap", + "importpath": "github.com/grammarly/rocker/src/template", "repository": "https://github.com/grammarly/rocker", - "revision": "d6a4bbfc503169324de3462a2ae2bb5a5dc2f041", - "branch": "v1", - "path": "/src/rocker/debugtrap" + "revision": "ec7c40b0d139303db89add1fbdde15d321142e53", + "branch": "master", + "path": "/src/template" }, { - "importpath": "github.com/go-ini/ini", - "repository": "https://github.com/go-ini/ini", - "revision": "9f4d2712cf687b68fad24366f81eaee163079090", - "branch": "v1" + "importpath": "github.com/grammarly/rocker/src/util", + "repository": "https://github.com/grammarly/rocker", + "revision": "ec7c40b0d139303db89add1fbdde15d321142e53", + "branch": "master", + "path": "/src/util" }, { "importpath": "github.com/jmespath/go-jmespath", @@ -144,31 +142,40 @@ "branch": "master" }, { - "importpath": "github.com/grammarly/rocker/src/rocker/storage", - "repository": "https://github.com/grammarly/rocker", - "revision": "d0220cddfa1bcd107db138498c0f4de5d9f0ecc2", - "branch": "f-s3", - "path": "/src/rocker/storage" + "importpath": "github.com/kr/pretty", + "repository": "https://github.com/kr/pretty", + "revision": "e6ac2fc51e89a3249e82157fa0bb7a18ef9dd5bb", + "branch": "master" }, { - "importpath": "github.com/grammarly/rocker/src/rocker/imagename", - "repository": "https://github.com/grammarly/rocker", - "revision": "fb14fe236b5675a3491e9c948ba01c43432252db", - "branch": "dev", - "path": "/src/rocker/imagename" + "importpath": "github.com/kr/text", + "repository": "https://github.com/kr/text", + "revision": "e373e137fafd8abd480af49182dea0513914adb4", + "branch": "master" }, { - "importpath": "github.com/grammarly/rocker/src/rocker/dockerclient", - "repository": "https://github.com/grammarly/rocker", - "revision": "fb14fe236b5675a3491e9c948ba01c43432252db", - "branch": "dev", - "path": "/src/rocker/dockerclient" + "importpath": "github.com/mitchellh/go-homedir", + "repository": "https://github.com/mitchellh/go-homedir", + "revision": "d682a8f0cf139663a984ff12528da460ca963de9", + "branch": "master" }, { - "importpath": "github.com/aws/aws-sdk-go", - "repository": "https://github.com/aws/aws-sdk-go", - "revision": "c924893c38ecc04b18d7aab8a7aa561cb8b4c4cc", + "importpath": "github.com/stretchr/objx", + "repository": "https://github.com/stretchr/objx", + "revision": "cbeaeb16a013161a98496fad62933b1d21786672", + "branch": "master" + }, + { + "importpath": "github.com/stretchr/testify", + "repository": "https://github.com/stretchr/testify", + "revision": "089c7181b8c728499929ff09b62d3fdd8df8adff", + "branch": "master" + }, + { + "importpath": "github.com/wmark/semver", + "repository": "https://github.com/wmark/semver", + "revision": "461c06b538be53cc0339815001a398e29ace025b", "branch": "master" } ] -} +} \ No newline at end of file From ccbf5c83eed0450d2cc3483a6c7bb50f5a87fee3 Mon Sep 17 00:00:00 2001 From: Roman Khlystik Date: Fri, 12 Feb 2016 16:32:04 +0200 Subject: [PATCH 2/6] Added new rocker libs --- .../grammarly/rocker/src/dockerclient/auth.go | 142 +++++ .../rocker/src/dockerclient/dockerclient.go | 221 +++++++ .../src/dockerclient/dockerclient_test.go | 224 +++++++ .../rocker/src/dockerclient/matrix.go | 131 ++++ .../rocker/src/dockerclient/matrix_test.go | 52 ++ .../rocker/src/dockerclient/registry.go | 276 +++++++++ .../rocker/src/imagename/artifact.go | 74 +++ .../rocker/src/imagename/imagename.go | 426 +++++++++++++ .../rocker/src/imagename/imagename_test.go | 558 ++++++++++++++++++ .../grammarly/rocker/src/storage/s3/logger.go | 33 ++ .../rocker/src/storage/s3/retryer.go | 95 +++ .../grammarly/rocker/src/storage/s3/s3.go | 527 +++++++++++++++++ .../grammarly/rocker/src/storage/storage.go | 30 + .../grammarly/rocker/src/template/LICENSE | 13 + .../grammarly/rocker/src/template/Makefile | 32 + .../grammarly/rocker/src/template/README.md | 256 ++++++++ .../grammarly/rocker/src/template/shellarg.go | 52 ++ .../rocker/src/template/shellarg_test.go | 51 ++ .../grammarly/rocker/src/template/template.go | 357 +++++++++++ .../rocker/src/template/template_test.go | 227 +++++++ .../rocker/src/template/testdata/content.txt | 1 + .../grammarly/rocker/src/template/vars.go | 294 +++++++++ .../rocker/src/template/vars_test.go | 319 ++++++++++ .../grammarly/rocker/src/util/cmd.go | 125 ++++ .../grammarly/rocker/src/util/cmd_test.go | 91 +++ .../grammarly/rocker/src/util/doc.go | 19 + .../grammarly/rocker/src/util/filepath.go | 78 +++ .../grammarly/rocker/src/util/io.go | 40 ++ .../grammarly/rocker/src/util/testdata/prog | 8 + 29 files changed, 4752 insertions(+) create mode 100644 vendor/github.com/grammarly/rocker/src/dockerclient/auth.go create mode 100644 vendor/github.com/grammarly/rocker/src/dockerclient/dockerclient.go create mode 100644 vendor/github.com/grammarly/rocker/src/dockerclient/dockerclient_test.go create mode 100644 vendor/github.com/grammarly/rocker/src/dockerclient/matrix.go create mode 100644 vendor/github.com/grammarly/rocker/src/dockerclient/matrix_test.go create mode 100644 vendor/github.com/grammarly/rocker/src/dockerclient/registry.go create mode 100644 vendor/github.com/grammarly/rocker/src/imagename/artifact.go create mode 100644 vendor/github.com/grammarly/rocker/src/imagename/imagename.go create mode 100644 vendor/github.com/grammarly/rocker/src/imagename/imagename_test.go create mode 100644 vendor/github.com/grammarly/rocker/src/storage/s3/logger.go create mode 100644 vendor/github.com/grammarly/rocker/src/storage/s3/retryer.go create mode 100644 vendor/github.com/grammarly/rocker/src/storage/s3/s3.go create mode 100644 vendor/github.com/grammarly/rocker/src/storage/storage.go create mode 100644 vendor/github.com/grammarly/rocker/src/template/LICENSE create mode 100644 vendor/github.com/grammarly/rocker/src/template/Makefile create mode 100644 vendor/github.com/grammarly/rocker/src/template/README.md create mode 100644 vendor/github.com/grammarly/rocker/src/template/shellarg.go create mode 100644 vendor/github.com/grammarly/rocker/src/template/shellarg_test.go create mode 100644 vendor/github.com/grammarly/rocker/src/template/template.go create mode 100644 vendor/github.com/grammarly/rocker/src/template/template_test.go create mode 100644 vendor/github.com/grammarly/rocker/src/template/testdata/content.txt create mode 100644 vendor/github.com/grammarly/rocker/src/template/vars.go create mode 100644 vendor/github.com/grammarly/rocker/src/template/vars_test.go create mode 100644 vendor/github.com/grammarly/rocker/src/util/cmd.go create mode 100644 vendor/github.com/grammarly/rocker/src/util/cmd_test.go create mode 100644 vendor/github.com/grammarly/rocker/src/util/doc.go create mode 100644 vendor/github.com/grammarly/rocker/src/util/filepath.go create mode 100644 vendor/github.com/grammarly/rocker/src/util/io.go create mode 100644 vendor/github.com/grammarly/rocker/src/util/testdata/prog diff --git a/vendor/github.com/grammarly/rocker/src/dockerclient/auth.go b/vendor/github.com/grammarly/rocker/src/dockerclient/auth.go new file mode 100644 index 0000000..d94b23f --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/dockerclient/auth.go @@ -0,0 +1,142 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dockerclient + +import ( + "encoding/base64" + "fmt" + "github.com/grammarly/rocker/src/imagename" + "strings" + "sync" + + log "github.com/Sirupsen/logrus" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ecr" + "github.com/fsouza/go-dockerclient" +) + +type ecrAuthCache struct { + tokens map[string]docker.AuthConfiguration + mu sync.Mutex +} + +var ( + _ecrAuthCache = ecrAuthCache{ + tokens: map[string]docker.AuthConfiguration{}, + } +) + +// GetAuthForRegistry extracts desired docker.AuthConfiguration object from the +// list of docker.AuthConfigurations by registry hostname +func GetAuthForRegistry(auth *docker.AuthConfigurations, image *imagename.ImageName) (result docker.AuthConfiguration, err error) { + + registry := image.Registry + + // The default registry is "index.docker.io" + if registry == "" || registry == "registry-1.docker.io" { + registry = "index.docker.io" + } + // Optionally override auth took via aws-sdk (through ENV vars) + if image.IsECR() { + if awsRegAuth, err := GetECRAuth(registry); err != nil && err != credentials.ErrNoValidProvidersFoundInChain { + return result, err + } else if awsRegAuth.Username != "" { + return awsRegAuth, nil + } + } + + if auth == nil { + return + } + + if result, ok := auth.Configs[registry]; ok { + return result, nil + } + if result, ok := auth.Configs["https://"+registry]; ok { + return result, nil + } + if result, ok := auth.Configs["https://"+registry+"/v1/"]; ok { + return result, nil + } + // not sure /v2/ is needed, but just in case + if result, ok := auth.Configs["https://"+registry+"/v2/"]; ok { + return result, nil + } + if result, ok := auth.Configs["*"]; ok { + return result, nil + } + return +} + +// GetECRAuth requests AWS ECR API to get docker.AuthConfiguration token +func GetECRAuth(registry string) (result docker.AuthConfiguration, err error) { + _ecrAuthCache.mu.Lock() + defer _ecrAuthCache.mu.Unlock() + + if token, ok := _ecrAuthCache.tokens[registry]; ok { + return token, nil + } + + defer func() { + _ecrAuthCache.tokens[registry] = result + }() + + // TODO: take region from the registry hostname? + cfg := &aws.Config{ + Region: aws.String("us-east-1"), + } + + if log.StandardLogger().Level >= log.DebugLevel { + cfg.LogLevel = aws.LogLevel(aws.LogDebugWithRequestErrors) + } + + split := strings.Split(registry, ".") + + svc := ecr.New(session.New(), cfg) + params := &ecr.GetAuthorizationTokenInput{ + RegistryIds: []*string{aws.String(split[0])}, + } + + res, err := svc.GetAuthorizationToken(params) + if err != nil { + return result, err + } + + if len(res.AuthorizationData) == 0 { + return result, nil + } + + data, err := base64.StdEncoding.DecodeString(*res.AuthorizationData[0].AuthorizationToken) + if err != nil { + return result, err + } + + userpass := strings.Split(string(data), ":") + if len(userpass) != 2 { + return result, fmt.Errorf("Cannot parse token got from ECR: %s", string(data)) + } + + result = docker.AuthConfiguration{ + Username: userpass[0], + Password: userpass[1], + ServerAddress: *res.AuthorizationData[0].ProxyEndpoint, + } + + return +} diff --git a/vendor/github.com/grammarly/rocker/src/dockerclient/dockerclient.go b/vendor/github.com/grammarly/rocker/src/dockerclient/dockerclient.go new file mode 100644 index 0000000..76d77cf --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/dockerclient/dockerclient.go @@ -0,0 +1,221 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package dockerclient provides utilities for embedding docker client +// functionality to other tools. It provides configurable docker client +// connection functions, config struct, integration with codegangsta/cli. +package dockerclient + +import ( + "fmt" + "log" + "os" + "strconv" + "strings" + "time" + + "github.com/codegangsta/cli" + "github.com/fsouza/go-dockerclient" + "github.com/mitchellh/go-homedir" +) + +var ( + // DefaultEndpoint is the default address of Docker socket + DefaultEndpoint = "unix:///var/run/docker.sock" +) + +// Config represents docker client connection parameters +type Config struct { + Host string + Tlsverify bool + Tlscacert string + Tlscert string + Tlskey string +} + +// NewConfig returns new config with resolved options from current ENV +func NewConfig() *Config { + certPath := os.Getenv("DOCKER_CERT_PATH") + if certPath == "" { + homePath, err := homedir.Dir() + if err != nil { + log.Fatal(err) + } + certPath = homePath + "/.docker" + } + host := os.Getenv("DOCKER_HOST") + if host == "" { + host = DefaultEndpoint + } + // why NewConfigFromCli default value is not working + return &Config{ + Host: host, + Tlsverify: os.Getenv("DOCKER_TLS_VERIFY") == "1" || os.Getenv("DOCKER_TLS_VERIFY") == "yes", + Tlscacert: certPath + "/ca.pem", + Tlscert: certPath + "/cert.pem", + Tlskey: certPath + "/key.pem", + } +} + +// NewConfigFromCli returns new config with NewConfig overridden cli options +func NewConfigFromCli(c *cli.Context) *Config { + config := NewConfig() + config.Host = globalCliString(c, "host") + if c.GlobalIsSet("tlsverify") { + config.Tlsverify = c.GlobalBool("tlsverify") + config.Tlscacert = globalCliString(c, "tlscacert") + config.Tlscert = globalCliString(c, "tlscert") + config.Tlskey = globalCliString(c, "tlskey") + } + return config +} + +// New returns a new docker client connection with default config +func New() (*docker.Client, error) { + return NewFromConfig(NewConfig()) +} + +// NewFromConfig returns a new docker client connection with given config +func NewFromConfig(config *Config) (*docker.Client, error) { + if config.Tlsverify { + return docker.NewTLSClient(config.Host, config.Tlscert, config.Tlskey, config.Tlscacert) + } + return docker.NewClient(config.Host) +} + +// NewFromCli returns a new docker client connection with config built from cli params +func NewFromCli(c *cli.Context) (*docker.Client, error) { + return NewFromConfig(NewConfigFromCli(c)) +} + +// Ping pings docker client but with timeout +// The problem is that for some reason it's impossible to set the +// default timeout for the go-dockerclient Dialer, need to investigate +func Ping(client *docker.Client, timeoutMs int) error { + var ( + chErr = make(chan error) + timeout = time.Duration(timeoutMs) * time.Millisecond + ) + go func() { + chErr <- client.Ping() + }() + select { + case err := <-chErr: + return err + case <-time.After(timeout): + // TODO: can we kill the ping goroutine? + return fmt.Errorf("Failed to reach docker server, timeout %s", timeout) + } +} + +// GlobalCliParams returns global params that configures docker client connection +func GlobalCliParams() []cli.Flag { + return []cli.Flag{ + cli.StringFlag{ + Name: "host, H", + Value: DefaultEndpoint, + Usage: "Daemon socket(s) to connect to", + EnvVar: "DOCKER_HOST", + }, + cli.BoolFlag{ + Name: "tlsverify, tls", + Usage: "Use TLS and verify the remote", + }, + cli.StringFlag{ + Name: "tlscacert", + Value: "~/.docker/ca.pem", + Usage: "Trust certs signed only by this CA", + }, + cli.StringFlag{ + Name: "tlscert", + Value: "~/.docker/cert.pem", + Usage: "Path to TLS certificate file", + }, + cli.StringFlag{ + Name: "tlskey", + Value: "~/.docker/key.pem", + Usage: "Path to TLS key file", + }, + } +} + +// InfoCommandSpec returns specifications of the info comment for codegangsta/cli +func InfoCommandSpec() cli.Command { + return cli.Command{ + Name: "info", + Usage: "show docker info (check connectivity, versions, etc.)", + Action: infoCommand, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "show advanced info", + }, + }, + } +} + +// infoCommand implements 'info' command that prints docker info (check connectivity, versions, etc.) +func infoCommand(c *cli.Context) { + config := NewConfigFromCli(c) + + fmt.Printf("Docker host: %s\n", config.Host) + fmt.Printf("Docker use TLS: %s\n", strconv.FormatBool(config.Tlsverify)) + if config.Tlsverify { + fmt.Printf(" TLS CA cert: %s\n", config.Tlscacert) + fmt.Printf(" TLS cert: %s\n", config.Tlscert) + fmt.Printf(" TLS key: %s\n", config.Tlskey) + } + + dockerClient, err := NewFromCli(c) + if err != nil { + log.Fatal(err) + } + + // TODO: golang randomizes maps every time, so the output is not consistent + // find out a way to sort it correctly + + version, err := dockerClient.Version() + if err != nil { + log.Fatal(err) + } + + for _, kv := range *version { + parts := strings.SplitN(kv, "=", 2) + fmt.Printf("Docker %s: %s\n", parts[0], parts[1]) + } + + if c.Bool("all") { + info, err := dockerClient.Info() + if err != nil { + log.Fatal(err) + } + + fmt.Printf("\nDocker advanced info:\n") + for key, val := range info.Map() { + fmt.Printf("%s: %s\n", key, val) + } + } +} + +// globalCliString fixes string arguments enclosed with double quotes +// 'docker-machine config' gives such arguments +func globalCliString(c *cli.Context, name string) string { + str := c.GlobalString(name) + if len(str) >= 2 && str[0] == '\u0022' && str[len(str)-1] == '\u0022' { + str = str[1 : len(str)-1] + } + return str +} diff --git a/vendor/github.com/grammarly/rocker/src/dockerclient/dockerclient_test.go b/vendor/github.com/grammarly/rocker/src/dockerclient/dockerclient_test.go new file mode 100644 index 0000000..eb5de81 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/dockerclient/dockerclient_test.go @@ -0,0 +1,224 @@ +// +build integration + +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dockerclient + +import ( + "bytes" + "fmt" + "testing" + + "github.com/fsouza/go-dockerclient" + "github.com/stretchr/testify/assert" +) + +func TestNewDockerClient(t *testing.T) { + cli, err := New() + if err != nil { + t.Fatal(err) + } + + info, err := cli.Info() + if err != nil { + t.Fatal(err) + } + + assert.IsType(t, &docker.Env{}, info) +} + +func TestEntrypointOverride(t *testing.T) { + t.Skip() + + cli, err := New() + if err != nil { + t.Fatal(err) + } + + container, err := cli.CreateContainer(docker.CreateContainerOptions{ + Config: &docker.Config{ + Image: "test-entrypoint-override", + Entrypoint: []string{}, + Cmd: []string{"/bin/ls"}, + AttachStdout: true, + AttachStderr: true, + }, + }) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := cli.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID, Force: true}); err != nil { + t.Fatal(err) + } + }() + + success := make(chan struct{}) + var buf bytes.Buffer + + attachOpts := docker.AttachToContainerOptions{ + Container: container.ID, + OutputStream: &buf, + ErrorStream: &buf, + Stream: true, + Stdout: true, + Stderr: true, + Success: success, + } + go func() { + if err := cli.AttachToContainer(attachOpts); err != nil { + t.Fatal(fmt.Errorf("Attach container error: %s", err)) + } + }() + + success <- <-success + + if err := cli.StartContainer(container.ID, &docker.HostConfig{}); err != nil { + t.Fatal(fmt.Errorf("Failed to start container, error: %s", err)) + } + + statusCode, err := cli.WaitContainer(container.ID) + if err != nil { + t.Fatal(fmt.Errorf("Wait container error: %s", err)) + } + + println(buf.String()) + + if statusCode != 0 { + t.Fatal(fmt.Errorf("Failed to run container, exit with code %d", statusCode)) + } +} + +func TestNewVolumesBug(t *testing.T) { + cli, err := New() + if err != nil { + t.Fatal(err) + } + + c1, out, err := runContainer(t, cli, &docker.Config{ + Image: "alpine:3.2", + Cmd: []string{"touch", "/data/file"}, + Volumes: map[string]struct{}{ + "/data": struct{}{}, + }, + AttachStdout: true, + AttachStderr: true, + }, &docker.HostConfig{}) + defer func() { + if err := cli.RemoveContainer(docker.RemoveContainerOptions{ID: c1.ID, Force: true, RemoveVolumes: true}); err != nil { + t.Fatal(err) + } + }() + if err != nil { + t.Fatal(err) + } + + fmt.Printf("C1: %s out: %s err: %s\n", c1.ID, out, err) + + c2, out2, err := runContainer(t, cli, &docker.Config{ + Image: "alpine:3.2", + Cmd: []string{"ls", "/data/file"}, + AttachStdout: true, + AttachStderr: true, + }, &docker.HostConfig{ + Binds: []string{c1.Mounts[0].Source + ":/data:ro"}, + // VolumesFrom: []string{c1}, + }) + if err != nil { + t.Fatal(err) + } + + fmt.Printf("C2: %s out: %s err: %s\n", c2.ID, out2, err) + + if err := cli.RemoveContainer(docker.RemoveContainerOptions{ID: c2.ID, Force: true, RemoveVolumes: true}); err != nil { + t.Fatal(err) + } + + c3, out3, err := runContainer(t, cli, &docker.Config{ + Image: "alpine:3.2", + Cmd: []string{"ls", "/data/file"}, + AttachStdout: true, + AttachStderr: true, + }, &docker.HostConfig{ + Binds: []string{c1.Mounts[0].Source + ":/data:ro"}, + // VolumesFrom: []string{c1}, + }) + if err != nil { + t.Fatal(err) + } + + fmt.Printf("C3: %s out: %s err: %s\n", c3.ID, out3, err) + + if err := cli.RemoveContainer(docker.RemoveContainerOptions{ID: c3.ID, Force: true, RemoveVolumes: true}); err != nil { + t.Fatal(err) + } +} + +func runContainer(t *testing.T, cli *docker.Client, cfg *docker.Config, hostCfg *docker.HostConfig) (*docker.Container, string, error) { + container, err := cli.CreateContainer(docker.CreateContainerOptions{ + Config: cfg, + HostConfig: hostCfg, + }) + if err != nil { + return nil, "", err + } + + var ( + buf bytes.Buffer + success = make(chan struct{}) + + attachOpts = docker.AttachToContainerOptions{ + Container: container.ID, + OutputStream: &buf, + ErrorStream: &buf, + Stream: true, + Stdout: true, + Stderr: true, + Success: success, + } + ) + + go func() { + if err := cli.AttachToContainer(attachOpts); err != nil { + t.Errorf("Attach container error: %s", err) + } + }() + + success <- <-success + + if err := cli.StartContainer(container.ID, &docker.HostConfig{}); err != nil { + return container, "", err + } + + statusCode, err := cli.WaitContainer(container.ID) + if err != nil { + return container, "", err + } + + if statusCode != 0 { + err = fmt.Errorf("Failed to run container, exit with code %d", statusCode) + } + + c2, err := cli.InspectContainer(container.ID) + if err != nil { + return container, "", err + } + + // pretty.Println(c2) + + return c2, buf.String(), err +} diff --git a/vendor/github.com/grammarly/rocker/src/dockerclient/matrix.go b/vendor/github.com/grammarly/rocker/src/dockerclient/matrix.go new file mode 100644 index 0000000..9c557f6 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/dockerclient/matrix.go @@ -0,0 +1,131 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dockerclient + +import ( + "fmt" + "github.com/grammarly/rocker/src/util" + "os" + "path" + "path/filepath" + "strings" + + "github.com/fsouza/go-dockerclient" +) + +const ( + initFile = "/.dockerinit" +) + +// IsInMatrix returns true if current process is running inside of a docker container +func IsInMatrix() (bool, error) { + _, err := os.Stat(initFile) + if err != nil && os.IsNotExist(err) { + return false, nil + } + return true, err +} + +// MyDockerID returns id of the current container the process is running within, if any +func MyDockerID() (string, error) { + if _, err := os.Stat("/proc/self/cgroup"); os.IsNotExist(err) { + return "", nil + } + output, exitStatus, err := util.ExecPipe(&util.Cmd{ + Args: []string{"/bin/bash", "-c", `cat /proc/self/cgroup | grep "docker" | sed s/\\//\\n/g | tail -1`}, + }) + if err != nil { + return "", err + } + if exitStatus != 0 { + return "", fmt.Errorf("Failed to obtain docker id due error: %s", output) + } + + return strings.Trim(output, "\n"), nil +} + +// ResolveHostPath resolves any given path from the current context so +// it is mountable by any container. +// +// If the current process is executed in the container itself, this function +// resolves the given path according to the container's rootfs on the host +// machine. It also considers the mounted directories to the current container, so +// if given path is pointing to the mounted directory, it resolves correctly. +func ResolveHostPath(mountPath string, client *docker.Client) (string, error) { + // Accept only absolute path + if !filepath.IsAbs(mountPath) { + return "", fmt.Errorf("ResolveHostPath accepts only absolute paths, given: %s", mountPath) + } + + // In case we are running inside of a docker container + // we have to provide our fs path right from host machine + isMatrix, err := IsInMatrix() + if err != nil { + return "", err + } + // Not in a container, return the path as is + if !isMatrix { + return mountPath, nil + } + + myDockerID, err := MyDockerID() + if err != nil { + return "", err + } + + container, err := client.InspectContainer(myDockerID) + if err != nil { + return "", err + } + + // Check if the given path is inside some mounted volumes + for _, mount := range container.Mounts { + rel, err := filepath.Rel(mount.Destination, mountPath) + if err != nil { + return "", err + } + // The easiest way to check whether the `mountPath` is within the `mount.Destination` + if !strings.HasPrefix(rel, "..") { + // Resolve the mounted directory to the real host path + return strings.Replace(mountPath, mount.Destination, mount.Source, 1), nil + } + } + + // https://jpetazzo.github.io/assets/2015-03-03-not-so-deep-dive-into-docker-storage-drivers.html + // aufs: /var/lib/docker/aufs/mnt/$CONTAINER_ID/ + // devicemapper: /var/lib/docker/devicemapper/mnt/$CONTAINER_ID/ + // btrfs: /var/lib/docker/btrfs/subvolumes/$CONTAINER_OR_IMAGE_ID/ + // overlayfs: /var/lib/docker/overlay/$ID_OF_CONTAINER_OR_IMAGE/merged + + // Resolve docker root by using container's resolv.conf file path + dockerRoot := path.Join(path.Dir(container.ResolvConfPath), "../../") + + // Resolve the container mountpoint depending on the driver used + if container.Driver == "aufs" { + mountPath = path.Join(dockerRoot, "aufs/mnt", myDockerID, mountPath) + } else if container.Driver == "devicemapper" { + mountPath = path.Join(dockerRoot, "devicemapper/mnt", myDockerID, mountPath) + } else if container.Driver == "overlay" { + mountPath = path.Join(dockerRoot, "overlay", myDockerID, "merged", mountPath) + } else { + // NOTE: add support for other fs drivers is not a big deal, + // but need to have a test environment for that. + return "", fmt.Errorf("%s driver is not supported by rocker when using MOUNT from within a container", container.Driver) + } + + return mountPath, nil +} diff --git a/vendor/github.com/grammarly/rocker/src/dockerclient/matrix_test.go b/vendor/github.com/grammarly/rocker/src/dockerclient/matrix_test.go new file mode 100644 index 0000000..571f42a --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/dockerclient/matrix_test.go @@ -0,0 +1,52 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dockerclient + +import "testing" + +func TestDockerIsInMatrix(t *testing.T) { + result, err := IsInMatrix() + if err != nil { + t.Fatal(err) + } + + t.Logf("is matrix: %v", result) +} + +func TestDockerMyDockerId(t *testing.T) { + id, err := MyDockerID() + if err != nil { + t.Fatal(err) + } + + t.Logf("my docker id: %q", id) +} + +func TestResolveHostPath(t *testing.T) { + // we will need docker client to cleanup and do some cross-checks + client, err := New() + if err != nil { + t.Fatal(err) + } + + result, err := ResolveHostPath("/bin/rsync", client) + if err != nil { + t.Fatal(err) + } + + t.Logf("Result path: %s\n", result) +} diff --git a/vendor/github.com/grammarly/rocker/src/dockerclient/registry.go b/vendor/github.com/grammarly/rocker/src/dockerclient/registry.go new file mode 100644 index 0000000..01b9491 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/dockerclient/registry.go @@ -0,0 +1,276 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dockerclient + +import ( + "encoding/json" + "fmt" + "github.com/grammarly/rocker/src/imagename" + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/fsouza/go-dockerclient" + + log "github.com/Sirupsen/logrus" +) + +type tags struct { + Name string `json:"name,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +type bearer struct { + Realm string + Service string + Scope string +} + +// RegistryListTags returns the list of images instances obtained from all tags existing in the registry +func RegistryListTags(image *imagename.ImageName, auth *docker.AuthConfigurations) (images []*imagename.ImageName, err error) { + var ( + name = image.Name + registry = image.Registry + ) + + regAuth, err := GetAuthForRegistry(auth, image) + if err != nil { + return nil, fmt.Errorf("Failed to get auth token for registry: %s, make sure you are properly logged in using `docker login` or have AWS credentials set in case of using ECR", image) + } + + // XXX: AWS ECR Registry API v2 does not support listing tags + // wo we just return a single image tag if it exists and no wildcards used + if image.IsECR() { + log.Debugf("ECR detected %s", registry) + if !image.IsStrict() { + return nil, fmt.Errorf("Amazon ECR does not support tags listing, therefore image wildcards are not supported, sorry: %s", image) + } + if exists, err := ecrImageExists(image, regAuth); err != nil { + return nil, err + } else if exists { + log.Debugf("ECR image %s found in the registry", image) + images = append(images, image) + } + return + } + + if registry == "" { + registry = "registry-1.docker.io" + if !strings.Contains(name, "/") { + name = "library/" + name + } + } + + var ( + tg = tags{} + url = fmt.Sprintf("https://%s/v2/%s/tags/list?page_size=9999&page=1", registry, name) + ) + + log.Debugf("Listing image tags from the remote registry %s", url) + + if err := registryGet(url, regAuth, &tg); err != nil { + return nil, err + } + + log.Debugf("Got %d tags from the remote registry for image %s", len(tg.Tags), image) + + for _, t := range tg.Tags { + candidate := imagename.New(image.NameWithRegistry(), t) + if image.Contains(candidate) || image.Tag == candidate.Tag { + images = append(images, candidate) + } + } + + return +} + +// registryGet executes HTTP get to a given registry +func registryGet(uri string, auth docker.AuthConfiguration, obj interface{}) (err error) { + var ( + client = &http.Client{} + req *http.Request + res *http.Response + body []byte + ) + + if req, err = http.NewRequest("GET", uri, nil); err != nil { + return + } + + var ( + b *bearer + authTry bool + ) + + for { + if res, err = client.Do(req); err != nil { + return fmt.Errorf("Request to %s failed with %s\n", uri, err) + } + + b = parseBearer(res.Header.Get("Www-Authenticate")) + log.Debugf("Got HTTP %d for %s; tried auth: %t; has Bearer: %t, auth username: %q", res.StatusCode, uri, authTry, b != nil, auth.Username) + + if res.StatusCode == 401 && !authTry && b != nil { + token, err := getAuthToken(b, auth) + if err != nil { + return fmt.Errorf("Failed to authenticate to registry %s, error: %s", uri, err) + } + + req.Header.Add("Authorization", "Bearer "+token) + + authTry = true + continue + } + + break + } + + if res.StatusCode != 200 { + // TODO: maybe more descriptive error + return fmt.Errorf("GET %s status code %d", uri, res.StatusCode) + } + + if body, err = ioutil.ReadAll(res.Body); err != nil { + return fmt.Errorf("Response from %s cannot be read due to error %s\n", uri, err) + } + + if err = json.Unmarshal(body, obj); err != nil { + return fmt.Errorf("Response from %s cannot be unmarshalled due to error %s, response: %s\n", + uri, err, string(body)) + } + + return +} + +func getAuthToken(b *bearer, auth docker.AuthConfiguration) (token string, err error) { + type authRespType struct { + Token string + } + + var ( + req *http.Request + res *http.Response + body []byte + + client = &http.Client{} + authResp = &authRespType{} + ) + + uri, err := url.Parse(b.Realm) + if err != nil { + return "", fmt.Errorf("Failed to parse real url %s, error %s", b.Realm, err) + } + + // Add query params to the ream uri + q := uri.Query() + q.Set("service", b.Service) + q.Set("scope", b.Scope) + uri.RawQuery = q.Encode() + + if req, err = http.NewRequest("GET", uri.String(), nil); err != nil { + return "", err + } + + if auth.Username != "" { + req.SetBasicAuth(auth.Username, auth.Password) + } + + log.Debugf("Getting auth token from %s", uri) + + if res, err = client.Do(req); err != nil { + return "", fmt.Errorf("Failed to authenticate by realm url %s, error %s", uri, err) + } + + if res.StatusCode != 200 { + // TODO: maybe more descriptive error + return "", fmt.Errorf("GET %s status code %d", uri, res.StatusCode) + } + + if body, err = ioutil.ReadAll(res.Body); err != nil { + return "", fmt.Errorf("Response from %s cannot be read due to error %s\n", uri, err) + } + + if err := json.Unmarshal(body, authResp); err != nil { + return "", fmt.Errorf("Response from %s cannot be unmarshalled due to error %s, response: %s\n", + uri, err, string(body)) + } + + return authResp.Token, nil +} + +func ecrImageExists(image *imagename.ImageName, auth docker.AuthConfiguration) (exists bool, err error) { + var ( + req *http.Request + res *http.Response + client = &http.Client{} + ) + + uri := fmt.Sprintf("https://%s/v2/%s/manifests/%s", image.Registry, image.Name, image.Tag) + + if req, err = http.NewRequest("GET", uri, nil); err != nil { + return false, err + } + + req.SetBasicAuth(auth.Username, auth.Password) + + log.Debugf("Request ECR image %s with basic auth %s:****", uri, auth.Username) + + if res, err = client.Do(req); err != nil { + return false, fmt.Errorf("Failed to authenticate by realm url %s, error %s", uri, err) + } + + log.Debugf("Got status %d", res.StatusCode) + + if res.StatusCode == 404 { + return false, nil + } + + if res.StatusCode != 200 { + // TODO: maybe more descriptive error + return false, fmt.Errorf("GET %s status code %d", uri, res.StatusCode) + } + + return true, nil +} + +func parseBearer(hdr string) *bearer { + if !strings.HasPrefix(hdr, "Bearer ") { + return nil + } + + b := &bearer{} + hdr = strings.TrimPrefix(hdr, "Bearer ") + + // e.g. + // Www-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:me/alpine:pull" + for _, pair := range strings.Split(hdr, ",") { + kv := strings.SplitN(pair, "=", 2) + key, value := kv[0], strings.Trim(kv[1], "\"") + + switch key { + case "realm": + b.Realm = value + case "service": + b.Service = value + case "scope": + b.Scope = value + } + } + + return b +} diff --git a/vendor/github.com/grammarly/rocker/src/imagename/artifact.go b/vendor/github.com/grammarly/rocker/src/imagename/artifact.go new file mode 100644 index 0000000..85b26b0 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/imagename/artifact.go @@ -0,0 +1,74 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package imagename + +import ( + "fmt" + "strings" + + "time" +) + +// Artifact represents the artifact that is the result of image build +// It holds information about the pushed image and may be saved as a file +type Artifact struct { + Name *ImageName `yaml:"Name"` + Pushed bool `yaml:"Pushed"` + Tag string `yaml:"Tag"` + Digest string `yaml:"Digest"` + ImageID string `yaml:"ImageID"` + Addressable string `yaml:"Addressable"` + BuildTime time.Time `yaml:"BuildTime"` +} + +// Artifacts is a collection of Artifact entities +type Artifacts struct { + RockerArtifacts []Artifact `yaml:"RockerArtifacts"` +} + +// GetFileName constructs the base file name out of the image info +func (a *Artifact) GetFileName() string { + imageName := strings.Replace(a.Name.Name, "/", "_", -1) + return fmt.Sprintf("%s_%s.yml", imageName, a.Name.GetTag()) +} + +// SetDigest sets the digest and forms the Addressable property +func (a *Artifact) SetDigest(digest string) { + a.Digest = digest + if strings.HasPrefix(a.Digest, "sha256:") { + // for digest sha256:blabla + a.Addressable = fmt.Sprintf("%s@%s", a.Name.NameWithRegistry(), a.Digest) + } else { + // for digest sha256-blabla (tag) + a.Addressable = fmt.Sprintf("%s:%s", a.Name.NameWithRegistry(), a.Digest) + } +} + +// Len returns the length of image tags +func (a *Artifacts) Len() int { + return len(a.RockerArtifacts) +} + +// Less returns true if item by index[i] is created after of item[j] +func (a *Artifacts) Less(i, j int) bool { + return a.RockerArtifacts[i].Name.Tag > a.RockerArtifacts[j].Name.Tag +} + +// Swap swaps items by indices [i] and [j] +func (a *Artifacts) Swap(i, j int) { + a.RockerArtifacts[i], a.RockerArtifacts[j] = a.RockerArtifacts[j], a.RockerArtifacts[i] +} diff --git a/vendor/github.com/grammarly/rocker/src/imagename/imagename.go b/vendor/github.com/grammarly/rocker/src/imagename/imagename.go new file mode 100644 index 0000000..6d5e312 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/imagename/imagename.go @@ -0,0 +1,426 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package imagename provides docker data structure for docker image names +// It also provides a number of utility functions, related to image name resolving, +// comparison, and semver functionality. +package imagename + +import ( + "encoding/json" + "fmt" + "regexp" + "sort" + "strings" + + "github.com/wmark/semver" +) + +const ( + // Latest is :latest tag value + Latest = "latest" + + // Wildcards is wildcard value variants + Wildcards = "x*" +) + +const ( + // StorageRegistry is when docker registry is used as images storage + StorageRegistry = "registry" + + // StorageS3 is when s3 registry is used as images storage + StorageS3 = "s3" +) + +const ( + s3Prefix = "s3.amazonaws.com/" + s3OldPrefix = "s3:" +) + +var ( + ecrRe = regexp.MustCompile("^\\d+\\.dkr\\.ecr\\.[^\\.]+\\.amazonaws\\.com$") +) + +// ImageName is the data structure with describes docker image name +type ImageName struct { + Registry string + Name string + Tag string + Storage string + Version *semver.Range + + IsOldS3Name bool +} + +// NewFromString parses a given string and returns ImageName +func NewFromString(image string) *ImageName { + name, tag := ParseRepositoryTag(image) + return New(name, tag) +} + +// WarnIfOldS3ImageName Check whether old image format is used. Also return warning message if yes +func WarnIfOldS3ImageName(imageName string) (bool, string) { + if !strings.HasPrefix(imageName, s3OldPrefix) { + return false, "" + } + + warning := fmt.Sprintf("Your image '%s' is using old name style (s3:/) for s3 images."+ + " This style isn't supported by docker 1.10 and would be removed from rocker in the future as well."+ + " Please consider changing to the new schema (s3.amazonaws.com//).", imageName) + + return true, warning +} + +// New parses a given 'image' and 'tag' strings and returns ImageName +func New(image string, tag string) *ImageName { + dockerImage := &ImageName{} + + if tag != "" { + dockerImage.SetTag(tag) + } + + // default storage driver + dockerImage.Storage = StorageRegistry + + firstIsHost := false + prefix := "" + + if strings.HasPrefix(image, s3Prefix) { + dockerImage.IsOldS3Name = false + prefix = s3Prefix + + } else if strings.HasPrefix(image, s3OldPrefix) { + dockerImage.IsOldS3Name = true + prefix = s3OldPrefix + } + + if strings.HasPrefix(image, s3Prefix) || strings.HasPrefix(image, s3OldPrefix) { + image = strings.TrimPrefix(image, prefix) + dockerImage.Storage = StorageS3 + firstIsHost = true + } + + nameParts := strings.SplitN(image, "/", 2) + + firstIsHost = firstIsHost || + strings.Contains(nameParts[0], ".") || + strings.Contains(nameParts[0], ":") || + nameParts[0] == "localhost" + + if len(nameParts) == 1 || !firstIsHost { + dockerImage.Name = image + } else { + dockerImage.Registry = nameParts[0] + dockerImage.Name = nameParts[1] + } + + // Minor validation + if dockerImage.Storage == StorageS3 && dockerImage.Registry == "" { + panic("Image with S3 storage driver requires bucket name to be specified: " + image) + } + + return dockerImage +} + +// ParseRepositoryTag gets a repos name and returns the right reposName + tag|digest +// The tag can be confusing because of a port in a repository name. +// Ex: localhost.localdomain:5000/samalba/hipache:latest +// Digest ex: localhost:5000/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb +// NOTE: borrowed from Docker under Apache 2.0, Copyright 2013-2015 Docker, Inc. +func ParseRepositoryTag(repos string) (string, string) { + n := strings.Index(repos, "@") + if n >= 0 { + parts := strings.Split(repos, "@") + return parts[0], parts[1] + } + n = strings.LastIndex(repos, ":") + if n < 0 { + return repos, "" + } + if tag := repos[n+1:]; !strings.Contains(tag, "/") { + return repos[:n], tag + } + return repos, "" +} + +// String returns the string representation of the current image name +func (img ImageName) String() string { + if img.TagIsDigest() { + return img.NameWithRegistry() + "@" + img.GetTag() + } + return img.NameWithRegistry() + ":" + img.GetTag() +} + +// HasTag returns true if tags is specified for the image name +func (img ImageName) HasTag() bool { + return img.Tag != "" +} + +// TagIsSha returns true if the tag is content addressable sha256 but can also be a tag +// e.g. golang@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11 +// or golang:sha256-ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11 +func (img ImageName) TagIsSha() bool { + return strings.HasPrefix(img.Tag, "sha256:") || strings.HasPrefix(img.Tag, "sha256-") +} + +// TagIsDigest returns true if the tag is content addressable sha256 +// e.g. golang@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11 +func (img ImageName) TagIsDigest() bool { + return strings.HasPrefix(img.Tag, "sha256:") +} + +// GetTag returns the tag of the current image name +func (img ImageName) GetTag() string { + if img.HasTag() { + return img.Tag + } + return Latest +} + +// SetTag sets the new tag for the imagename +func (img *ImageName) SetTag(tag string) { + img.Version = nil + if rng, err := semver.NewRange(tag); err == nil && rng != nil { + img.Version = rng + } + img.Tag = tag +} + +// IsStrict returns true if tag of the current image is specified and contains no fuzzy rules +// Example: +// golang:latest == true +// golang:stable == true +// golang:1.5.1 == true +// golang:1.5.* == false +// golang == false +// +func (img ImageName) IsStrict() bool { + if img.HasVersionRange() { + return img.TagAsVersion() != nil + } + return img.Tag != "" +} + +// All returns true if tag of the current image refers to any version +// Example: +// golang:* == true +// golang == true +// golang:latest == false +func (img ImageName) All() bool { + return strings.Contains(Wildcards, img.Tag) +} + +// HasVersion returns true if tag of the current image refers to a semver format +// Example: +// golang:1.5.1 == true +// golang:1.5.* == false +// golang:stable == false +// golang:latest == false +func (img ImageName) HasVersion() bool { + return img.TagAsVersion() != nil +} + +// HasVersionRange returns true if tag of the current image refers to a semver format and is fuzzy +// Example: +// golang:1.5.1 == true +// golang:1.5.* == true +// golang:* == true +// golang:stable == false +// golang:latest == false +// golang == false +func (img ImageName) HasVersionRange() bool { + return img.Version != nil +} + +// IsECR returns true if the registry is AWS ECR +func (img ImageName) IsECR() bool { + return ecrRe.MatchString(img.Registry) +} + +// TagAsVersion return semver.Version of the current image tag in case it's in semver format +func (img ImageName) TagAsVersion() (ver *semver.Version) { + ver, _ = semver.NewVersion(strings.TrimPrefix(img.Tag, "v")) + return +} + +// IsSameKind returns true if current image and the given one are same but may have different versions (tags) +func (img ImageName) IsSameKind(b ImageName) bool { + return img.Registry == b.Registry && img.Name == b.Name +} + +// NameWithRegistry returns the [registry/]name of the current image name +func (img ImageName) NameWithRegistry() string { + registryPrefix := "" + if img.Registry != "" { + registryPrefix = img.Registry + "/" + } + if img.Storage == StorageS3 { + if img.IsOldS3Name { + registryPrefix = s3OldPrefix + registryPrefix + } else { + registryPrefix = s3Prefix + registryPrefix + } + } + return registryPrefix + img.Name +} + +// Contains returns true if the current image tag wildcard satisfies a given image version +func (img ImageName) Contains(b *ImageName) bool { + if b == nil { + return false + } + + if !img.IsSameKind(*b) { + return false + } + + // semver library has a bug with wildcards, so this checks are + // necessary: empty range (or wildcard range) cannot contain any version, it just fails + if img.All() { + return true + } + + if img.IsStrict() && img.Tag == b.Tag { + return true + } + + if img.HasVersionRange() && b.HasVersion() && img.Version.IsSatisfiedBy(b.TagAsVersion()) { + return true + } + + return img.Tag == "" && !img.HasVersionRange() +} + +// ResolveVersion finds an applicable tag for current image among the list of available tags +func (img *ImageName) ResolveVersion(list []*ImageName, strictS3Match bool) (result *ImageName) { + for _, candidate := range list { + // If these are different images (different names/repos) + if !img.IsSameKind(*candidate) { + continue + } + + if strictS3Match && img.IsOldS3Name != candidate.IsOldS3Name { + continue + } + + // If we have a strict equality + if img.HasTag() && candidate.HasTag() && img.Tag == candidate.Tag { + return candidate + } + + // If image is without tag, latest will be fine + if !img.HasTag() && candidate.GetTag() == Latest { + return candidate + } + + if !img.Contains(candidate) { + //this image is from the same name/registry but tag is not applicable + // e.g. ~1.2.3 contains 1.2.5, but it's not true for 1.3.0 + continue + } + + if result == nil { + result = candidate + continue + } + + // uncomparable candidate... skipping + if !candidate.HasVersion() { + continue + } + + // if both names has versions to compare, we cat safely compare them + if result.HasVersion() && candidate.HasVersion() && result.TagAsVersion().Less(candidate.TagAsVersion()) { + result = candidate + } + } + + return +} + +// UnmarshalJSON parses JSON string and returns ImageName +func (img *ImageName) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + *img = *NewFromString(s) + return nil +} + +// MarshalJSON serializes ImageName to JSON string +func (img ImageName) MarshalJSON() ([]byte, error) { + return json.Marshal(img.String()) +} + +// UnmarshalYAML parses YAML string and returns ImageName +func (img *ImageName) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + if err := unmarshal(&s); err != nil { + return err + } + *img = *NewFromString(s) + return nil +} + +// MarshalYAML serializes ImageName to YAML string +func (img ImageName) MarshalYAML() (interface{}, error) { + return img.String(), nil +} + +// Tags is a structure used for cleaning images +// Sorts out old tags by creation date +type Tags struct { + Items []*Tag +} + +// Tag is a structure used for cleaning images +type Tag struct { + ID string + Name ImageName + Created int64 +} + +// Len returns the length of image tags +func (tags *Tags) Len() int { + return len(tags.Items) +} + +// Less returns true if item by index[i] is created after of item[j] +func (tags *Tags) Less(i, j int) bool { + return tags.Items[i].Created > tags.Items[j].Created +} + +// Swap swaps items by indices [i] and [j] +func (tags *Tags) Swap(i, j int) { + tags.Items[i], tags.Items[j] = tags.Items[j], tags.Items[i] +} + +// GetOld returns the list of items older then [keep] newest items sorted by Created date +func (tags *Tags) GetOld(keep int) []ImageName { + if len(tags.Items) < keep { + return nil + } + + sort.Sort(tags) + + result := []ImageName{} + for i := keep; i < len(tags.Items); i++ { + result = append(result, tags.Items[i].Name) + } + + return result +} diff --git a/vendor/github.com/grammarly/rocker/src/imagename/imagename_test.go b/vendor/github.com/grammarly/rocker/src/imagename/imagename_test.go new file mode 100644 index 0000000..0371e63 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/imagename/imagename_test.go @@ -0,0 +1,558 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package imagename + +import ( + "fmt" + "testing" + "time" + + "github.com/go-yaml/yaml" + "github.com/kr/pretty" + + "github.com/stretchr/testify/assert" + "github.com/wmark/semver" +) + +func TestImageParsingWithoutNamespace(t *testing.T) { + img := NewFromString("repo/name:1") + assert.Equal(t, "", img.Registry) + assert.Equal(t, "1", img.Tag) + assert.Equal(t, "repo/name", img.Name) + assert.True(t, img.Contains(NewFromString("repo/name:1"))) +} + +func TestWildcardNamespace(t *testing.T) { + img := NewFromString("repo/name:*") + assert.Empty(t, img.Registry) + assert.Equal(t, "*", img.Tag) + assert.Equal(t, "repo/name", img.Name) + assert.True(t, img.Contains(NewFromString("repo/name:1.0.0"))) +} + +func TestEnvironmentImageName(t *testing.T) { + img := NewFromString("repo/name:1.0.0") + assert.False(t, img.Contains(NewFromString("repo/name:1.0.123"))) +} + +func TestImageRealLifeNamingExample(t *testing.T) { + img := NewFromString("docker.io/tools/dockerize:v0.0.1") + assert.Equal(t, "docker.io", img.Registry) + assert.Equal(t, "tools/dockerize", img.Name) + assert.Equal(t, "v0.0.1", img.Tag) + assert.True(t, img.Contains(NewFromString("docker.io/tools/dockerize:v0.0.1"))) +} + +func TestRangeContainsPlainVersion(t *testing.T) { + img := NewFromString("docker.io/tools/dockerize:0.0.1") + expected, _ := semver.NewRange("0.0.1") + assert.Equal(t, "docker.io", img.Registry) + assert.Equal(t, "tools/dockerize", img.Name) + assert.Equal(t, "0.0.1", img.Tag) + assert.Equal(t, expected, img.Version) + + v, _ := semver.NewVersion("0.0.1") + assert.True(t, img.Version.Contains(v)) +} + +func TestUpperRangeBounds(t *testing.T) { + img := NewFromString("docker.io/tools/dockerize:~1.2.3") + assert.Equal(t, "docker.io", img.Registry) + assert.Equal(t, "tools/dockerize", img.Name) + assert.False(t, img.IsStrict()) + v, _ := semver.NewVersion("1.2.8") + assert.True(t, img.Version.Contains(v)) +} + +func TestWildcardRangeBounds(t *testing.T) { + img := NewFromString("docker.io/tools/dockerize:1.2.*") + assert.Equal(t, "docker.io", img.Registry) + assert.Equal(t, "tools/dockerize", img.Name) + assert.False(t, img.IsStrict()) + v, _ := semver.NewVersion("1.2.8") + assert.True(t, img.Version.Contains(v)) + v, _ = semver.NewVersion("1.2.0") + assert.True(t, img.Version.Contains(v)) +} + +func TestWildcardContains(t *testing.T) { + img1 := NewFromString("docker.io/tools/dockerize:1.2.*") + img2 := NewFromString("docker.io/tools/dockerize:1.2.1") + assert.False(t, img1.IsStrict()) + assert.True(t, img1.HasVersionRange()) + assert.True(t, img2.IsStrict()) + v, _ := semver.NewVersion("1.2.1") + assert.Equal(t, v, img2.TagAsVersion()) + + assert.True(t, img1.Contains(img2)) + assert.False(t, img2.Contains(img1)) +} + +func TestRangeContains(t *testing.T) { + img1 := NewFromString("docker.io/tools/dockerize:~1.2.1") + img2 := NewFromString("docker.io/tools/dockerize:1.2.999") + assert.True(t, img1.Contains(img2)) + assert.False(t, img2.Contains(img1)) +} + +func TestNilContains(t *testing.T) { + img1 := NewFromString("docker.io/tools/dockerize:~1.2.1") + assert.False(t, img1.Contains(nil)) +} + +func TestRangeNotContains(t *testing.T) { + img1 := NewFromString("docker.io/tools/dockerize:~1.2.1") + img2 := NewFromString("docker.io/tools/dockerize:1.3.1") + assert.False(t, img1.Contains(img2)) + assert.False(t, img2.Contains(img1)) + + img2 = NewFromString("docker.io/xxx/dockerize:1.2.1") + assert.False(t, img1.Contains(img2)) + + img2 = NewFromString("dockerhub.grammarly.com/tools/dockerize:1.2.1") + assert.False(t, img1.Contains(img2)) + + img2 = NewFromString("docker.io/tools/dockerize:1.2.1") + assert.True(t, img1.Contains(img2)) +} + +func TestVersionContains(t *testing.T) { + img1 := NewFromString("docker.io/tools/dockerize:1.2.1") + img2 := NewFromString("docker.io/tools/dockerize:1.2.1") + assert.True(t, img1.Contains(img2)) + assert.True(t, img2.Contains(img1)) +} + +func TestTagContains(t *testing.T) { + img1 := NewFromString("docker.io/tools/dockerize:test1") + img2 := NewFromString("docker.io/tools/dockerize:test1") + assert.True(t, img1.Contains(img2)) + assert.True(t, img2.Contains(img1)) +} + +func TestTagNotContains(t *testing.T) { + img1 := NewFromString("docker.io/tools/dockerize:test1") + img2 := NewFromString("docker.io/tools/dockerize:test2") + assert.False(t, img1.Contains(img2)) + assert.False(t, img2.Contains(img1)) +} + +func TestImageRealLifeNamingExampleWithCapi(t *testing.T) { + img := NewFromString("docker.io/common-api") + assert.Equal(t, "docker.io", img.Registry) + assert.Equal(t, "common-api", img.Name) + assert.Equal(t, false, img.HasTag()) + assert.Equal(t, "latest", img.GetTag()) + assert.Equal(t, "docker.io/common-api:latest", img.String()) +} + +func TestImageParsingWithNamespace(t *testing.T) { + img := NewFromString("hub/ns/name:1") + assert.Equal(t, "", img.Registry) + assert.Equal(t, "hub/ns/name", img.Name) + assert.Equal(t, "1", img.Tag) +} + +func TestImageParsingWithoutTag(t *testing.T) { + img := NewFromString("repo/name") + assert.Equal(t, "", img.Registry) + assert.Equal(t, "repo/name", img.Name) + assert.Equal(t, "latest", img.GetTag()) + assert.Equal(t, false, img.HasTag()) + assert.Equal(t, "repo/name:latest", img.String()) +} + +func TestImageWithDotsWithoutTag(t *testing.T) { + img := NewFromString("a.b.c.d") + assert.Equal(t, "", img.Registry) + assert.Equal(t, "a.b.c.d", img.Name) + assert.Equal(t, "latest", img.GetTag()) + assert.Equal(t, false, img.HasTag()) + assert.Equal(t, "a.b.c.d:latest", img.String()) +} + +func TestImageWithDotsWithTag(t *testing.T) { + img := NewFromString("a.b.c.d:snapshot") + assert.Equal(t, "", img.Registry) + assert.Equal(t, "a.b.c.d", img.Name) + assert.Equal(t, "snapshot", img.GetTag()) + assert.Equal(t, true, img.HasTag()) + assert.Equal(t, "a.b.c.d:snapshot", img.String()) +} + +func TestImageWithRegistryAndDotsAndTag(t *testing.T) { + img := NewFromString("hub.com/a.b.c.d:snapshot") + assert.Equal(t, "hub.com", img.Registry) + assert.Equal(t, "a.b.c.d", img.Name) + assert.Equal(t, "snapshot", img.GetTag()) + assert.Equal(t, true, img.HasTag()) + assert.Equal(t, "hub.com/a.b.c.d:snapshot", img.String()) +} + +func TestImageWithRegistryAndSlashAndDotsAndTag(t *testing.T) { + img := NewFromString("hub.com/a.b/c.d:snapshot") + assert.Equal(t, "hub.com", img.Registry) + assert.Equal(t, "a.b/c.d", img.Name) + assert.Equal(t, "snapshot", img.GetTag()) + assert.Equal(t, true, img.HasTag()) + assert.Equal(t, "hub.com/a.b/c.d:snapshot", img.String()) +} + +func TestImageLatest(t *testing.T) { + img := NewFromString("rocker-build:latest") + assert.Equal(t, "", img.Registry, "bag registry value") + assert.Equal(t, "rocker-build", img.Name, "bad image name") + assert.Equal(t, "latest", img.GetTag(), "bad image tag") +} + +func TestImageIpRegistry(t *testing.T) { + img := NewFromString("127.0.0.1:5000/golang:1.4") + assert.Equal(t, "127.0.0.1:5000", img.Registry, "bag registry value") + assert.Equal(t, "golang", img.Name, "bad image name") + assert.Equal(t, "1.4", img.GetTag(), "bad image tag") +} + +func TestImageTagSha(t *testing.T) { + img := NewFromString("golang@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11") + assert.Equal(t, "", img.Registry, "bag registry value") + assert.Equal(t, "golang", img.Name, "bad image name") + assert.Equal(t, "sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.GetTag(), "bad image tag") + assert.Equal(t, "golang@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.String()) +} + +func TestImageAll(t *testing.T) { + img := NewFromString("golang:1.*") + assert.False(t, img.All()) +} + +func TestImageTest(t *testing.T) { + t.Skip() + names := []string{ + "golang:latest", + "golang:stable", + "golang:1.5.1", + "golang:1.5.*", + "golang:*", + "golang", + } + for _, n := range names { + img := NewFromString(n) + m := [][2]interface{}{ + {"IsStrict()", img.IsStrict()}, + {"HasVersion()", img.HasVersion()}, + {"HasVersionRange()", img.HasVersionRange()}, + {"All()", img.All()}, + {"GetTag()", img.GetTag()}, + } + fmt.Printf("%s\t%# v\n", n, pretty.Formatter(m)) + } +} + +func TestImageResolveVersion_Strict(t *testing.T) { + img := NewFromString("golang:1.5.2") + list := []*ImageName{ + NewFromString("golang:1.5.1"), + NewFromString("golang:1.5.2"), + NewFromString("golang:1.5.3"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:1.5.2", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_Wildcard(t *testing.T) { + img := NewFromString("golang:1.5.*") + list := []*ImageName{ + NewFromString("golang:1.5.1"), + NewFromString("golang:1.5.2"), + NewFromString("golang:1.5.3"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:1.5.3", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_WildcardMulti(t *testing.T) { + img := NewFromString("golang:1.4.*") + list := []*ImageName{ + NewFromString("golang:1.4.1"), + NewFromString("golang:1.4.2"), + NewFromString("golang:1.5.1"), + NewFromString("golang:1.5.2"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:1.4.2", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_WildcardMatchX(t *testing.T) { + img := NewFromString("golang:1.4.x") + list := []*ImageName{ + NewFromString("golang:1.4.1"), + NewFromString("golang:1.4.x"), + NewFromString("golang:1.4.2"), + NewFromString("golang:1.5.1"), + NewFromString("golang:1.5.2"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:1.4.x", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_WildcardMatchX2(t *testing.T) { + img := NewFromString("golang:1.x") + list := []*ImageName{ + NewFromString("golang:1.4.1"), + NewFromString("golang:1.4.x"), + NewFromString("golang:1.4.2"), + NewFromString("golang:1.5.1"), + NewFromString("golang:1.5.2"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:1.5.2", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_WildcardMatchX3(t *testing.T) { + img := NewFromString("golang:1.x") + list := []*ImageName{ + NewFromString("golang:1.x"), + NewFromString("golang:1.4.1"), + NewFromString("golang:1.4.x"), + NewFromString("golang:1.4.2"), + NewFromString("golang:1.5.1"), + NewFromString("golang:1.5.2"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:1.x", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_All(t *testing.T) { + img := NewFromString("golang:*") + list := []*ImageName{ + NewFromString("golang:1.4.1"), + NewFromString("golang:1.5.1"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:1.5.1", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_Latest(t *testing.T) { + img := NewFromString("golang:latest") + list := []*ImageName{ + NewFromString("golang:1.4.1"), + NewFromString("golang:1.5.1"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:latest", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_OtherTag(t *testing.T) { + img := NewFromString("golang:stable") + list := []*ImageName{ + NewFromString("golang:1.4.1"), + NewFromString("golang:1.5.1"), + NewFromString("golang:stable"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:stable", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_NoTag(t *testing.T) { + img := NewFromString("golang") + list := []*ImageName{ + NewFromString("golang:1.4.1"), + NewFromString("golang:1.5.1"), + NewFromString("golang:stable"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:latest", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_NoTagOnlyLatest(t *testing.T) { + img := NewFromString("golang") + list := []*ImageName{ + NewFromString("golang:stable"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:latest", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_PatchExact(t *testing.T) { + img := NewFromString("golang:1.4.1") + list := []*ImageName{ + NewFromString("golang:1.4.1"), + NewFromString("golang:1.4.1-p2"), + NewFromString("golang:1.4.1-p1"), + NewFromString("golang:1.5.1"), + NewFromString("golang:1.5.2"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:1.4.1", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_PatchMatch(t *testing.T) { + img := NewFromString("golang:1.4.1") + list := []*ImageName{ + NewFromString("golang:1.4.1-p1"), + NewFromString("golang:1.4.1-p2"), + NewFromString("golang:1.5.1"), + NewFromString("golang:1.5.2"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:1.4.1-p2", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_PatchStrict(t *testing.T) { + img := NewFromString("golang:1.4.1-p1") + list := []*ImageName{ + NewFromString("golang:1.4.1"), + NewFromString("golang:1.4.1-p2"), + NewFromString("golang:1.4.1-p1"), + NewFromString("golang:1.5.1"), + NewFromString("golang:1.5.2"), + NewFromString("golang:latest"), + } + assert.Equal(t, "golang:1.4.1-p1", img.ResolveVersion(list, false).String()) +} + +func TestImageResolveVersion_NotFound(t *testing.T) { + img := NewFromString("golang:1.5.1") + list := []*ImageName{ + NewFromString("golang:1.4.1"), + NewFromString("golang:stable"), + NewFromString("golang:latest"), + } + assert.Nil(t, img.ResolveVersion(list, false)) +} + +func TestImageIsSameKind(t *testing.T) { + assert.True(t, NewFromString("rocker-build").IsSameKind(*NewFromString("rocker-build"))) + assert.True(t, NewFromString("rocker-build:latest").IsSameKind(*NewFromString("rocker-build:latest"))) + assert.True(t, NewFromString("rocker-build").IsSameKind(*NewFromString("rocker-build:1.2.1"))) + assert.True(t, NewFromString("rocker-build:1.2.1").IsSameKind(*NewFromString("rocker-build:1.2.1"))) + assert.True(t, NewFromString("grammarly/rocker-build").IsSameKind(*NewFromString("grammarly/rocker-build"))) + assert.True(t, NewFromString("grammarly/rocker-build:3.1").IsSameKind(*NewFromString("grammarly/rocker-build:3.1"))) + assert.True(t, NewFromString("grammarly/rocker-build").IsSameKind(*NewFromString("grammarly/rocker-build:3.1"))) + assert.True(t, NewFromString("grammarly/rocker-build:latest").IsSameKind(*NewFromString("grammarly/rocker-build:latest"))) + assert.True(t, NewFromString("quay.io/rocker-build").IsSameKind(*NewFromString("quay.io/rocker-build"))) + assert.True(t, NewFromString("quay.io/rocker-build:latest").IsSameKind(*NewFromString("quay.io/rocker-build:latest"))) + assert.True(t, NewFromString("quay.io/rocker-build:3.2").IsSameKind(*NewFromString("quay.io/rocker-build:3.2"))) + assert.True(t, NewFromString("quay.io/rocker-build").IsSameKind(*NewFromString("quay.io/rocker-build:3.2"))) + assert.True(t, NewFromString("quay.io/grammarly/rocker-build").IsSameKind(*NewFromString("quay.io/grammarly/rocker-build"))) + assert.True(t, NewFromString("quay.io/grammarly/rocker-build:latest").IsSameKind(*NewFromString("quay.io/grammarly/rocker-build:latest"))) + assert.True(t, NewFromString("quay.io/grammarly/rocker-build:1.2.1").IsSameKind(*NewFromString("quay.io/grammarly/rocker-build:1.2.1"))) + assert.True(t, NewFromString("quay.io/grammarly/rocker-build").IsSameKind(*NewFromString("quay.io/grammarly/rocker-build:1.2.1"))) + + assert.False(t, NewFromString("rocker-build").IsSameKind(*NewFromString("rocker-build2"))) + assert.False(t, NewFromString("rocker-build").IsSameKind(*NewFromString("rocker-compose"))) + assert.False(t, NewFromString("rocker-build").IsSameKind(*NewFromString("grammarly/rocker-build"))) + assert.False(t, NewFromString("rocker-build").IsSameKind(*NewFromString("quay.io/rocker-build"))) + assert.False(t, NewFromString("rocker-build").IsSameKind(*NewFromString("quay.io/grammarly/rocker-build"))) +} + +func TestTagsGetOld(t *testing.T) { + tags := Tags{ + Items: []*Tag{ + &Tag{"1", *NewFromString("hub/ns/name:1"), time.Unix(1, 0).Unix()}, + &Tag{"3", *NewFromString("hub/ns/name:3"), time.Unix(3, 0).Unix()}, + &Tag{"2", *NewFromString("hub/ns/name:2"), time.Unix(2, 0).Unix()}, + &Tag{"5", *NewFromString("hub/ns/name:5"), time.Unix(5, 0).Unix()}, + &Tag{"4", *NewFromString("hub/ns/name:4"), time.Unix(4, 0).Unix()}, + }, + } + old := tags.GetOld(2) + + assert.Equal(t, 3, len(old), "bad number of old tags") + assert.Equal(t, "hub/ns/name:3", old[0].String(), "bad old image 1") + assert.Equal(t, "hub/ns/name:2", old[1].String(), "bad old image 2") + assert.Equal(t, "hub/ns/name:1", old[2].String(), "bad old image 3") +} + +func TestImagename_ToYaml(t *testing.T) { + value := struct { + Name *ImageName + }{ + NewFromString("hub/ns/name:1"), + } + + data, err := yaml.Marshal(value) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "name: hub/ns/name:1\n", string(data)) +} + +func TestImagename_S3_Basic_Old(t *testing.T) { + img := NewFromString("s3:bucket-name/image-name:1.2.3") + assert.Equal(t, "bucket-name", img.Registry) + assert.Equal(t, "image-name", img.Name) + assert.Equal(t, false, img.TagIsSha()) + assert.Equal(t, "1.2.3", img.GetTag()) + assert.Equal(t, "s3:bucket-name/image-name", img.NameWithRegistry()) + assert.Equal(t, "s3:bucket-name/image-name:1.2.3", img.String()) +} + +func TestImagename_S3_Digest_Old(t *testing.T) { + img := NewFromString("s3:bucket-name/image-name@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11") + assert.Equal(t, "bucket-name", img.Registry) + assert.Equal(t, "image-name", img.Name) + assert.Equal(t, true, img.TagIsSha()) + assert.Equal(t, true, img.TagIsDigest()) + assert.Equal(t, "s3:bucket-name/image-name", img.NameWithRegistry()) + assert.Equal(t, "sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.GetTag()) + assert.Equal(t, "s3:bucket-name/image-name@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.String()) +} + +func TestImagename_S3_Sha_Old(t *testing.T) { + img := NewFromString("s3:bucket-name/image-name:sha256-ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11") + assert.Equal(t, "bucket-name", img.Registry) + assert.Equal(t, "image-name", img.Name) + assert.Equal(t, true, img.TagIsSha()) + assert.Equal(t, false, img.TagIsDigest()) + assert.Equal(t, "s3:bucket-name/image-name", img.NameWithRegistry()) + assert.Equal(t, "sha256-ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.GetTag()) + assert.Equal(t, "s3:bucket-name/image-name:sha256-ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.String()) +} + +func TestImagename_S3_Basic(t *testing.T) { + img := NewFromString("s3.amazonaws.com/bucket-name/image-name:1.2.3") + assert.Equal(t, "bucket-name", img.Registry) + assert.Equal(t, "image-name", img.Name) + assert.Equal(t, false, img.TagIsSha()) + assert.Equal(t, "1.2.3", img.GetTag()) + assert.Equal(t, "s3.amazonaws.com/bucket-name/image-name", img.NameWithRegistry()) + assert.Equal(t, "s3.amazonaws.com/bucket-name/image-name:1.2.3", img.String()) +} + +func TestImagename_S3_Digest(t *testing.T) { + img := NewFromString("s3.amazonaws.com/bucket-name/image-name@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11") + assert.Equal(t, "bucket-name", img.Registry) + assert.Equal(t, "image-name", img.Name) + assert.Equal(t, true, img.TagIsSha()) + assert.Equal(t, true, img.TagIsDigest()) + assert.Equal(t, "s3.amazonaws.com/bucket-name/image-name", img.NameWithRegistry()) + assert.Equal(t, "sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.GetTag()) + assert.Equal(t, "s3.amazonaws.com/bucket-name/image-name@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.String()) +} + +func TestImagename_S3_Sha(t *testing.T) { + img := NewFromString("s3.amazonaws.com/bucket-name/image-name:sha256-ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11") + assert.Equal(t, "bucket-name", img.Registry) + assert.Equal(t, "image-name", img.Name) + assert.Equal(t, true, img.TagIsSha()) + assert.Equal(t, false, img.TagIsDigest()) + assert.Equal(t, "s3.amazonaws.com/bucket-name/image-name", img.NameWithRegistry()) + assert.Equal(t, "sha256-ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.GetTag()) + assert.Equal(t, "s3.amazonaws.com/bucket-name/image-name:sha256-ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11", img.String()) +} diff --git a/vendor/github.com/grammarly/rocker/src/storage/s3/logger.go b/vendor/github.com/grammarly/rocker/src/storage/s3/logger.go new file mode 100644 index 0000000..605a995 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/storage/s3/logger.go @@ -0,0 +1,33 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package s3 + +import ( + log "github.com/Sirupsen/logrus" +) + +// Logger is an aws logger that prints entries to Logrus +type Logger struct{} + +// Log writes to logrus.Info +func (l *Logger) Log(args ...interface{}) { + if len(args) == 1 { + log.Infof(args[0].(string)) + } else { + log.Infof(args[0].(string), args[1:]...) + } +} diff --git a/vendor/github.com/grammarly/rocker/src/storage/s3/retryer.go b/vendor/github.com/grammarly/rocker/src/storage/s3/retryer.go new file mode 100644 index 0000000..45dad87 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/storage/s3/retryer.go @@ -0,0 +1,95 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package s3 + +import ( + "math" + "math/rand" + "time" + + log "github.com/Sirupsen/logrus" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/client" + "github.com/aws/aws-sdk-go/aws/request" +) + +// Retryer is a custom aws retrier that logs retry attempts +type Retryer struct { + client.DefaultRetryer + + retryDelay int + retryMax int +} + +// NewRetryer returns the instance of Retryer object +func NewRetryer(retryDelay, retryMax int) *Retryer { + return &Retryer{ + retryDelay: retryDelay, + retryMax: retryMax, + } +} + +// MaxRetries returns the number of maximum returns the service will use to make +// an individual API request. +func (d Retryer) MaxRetries() int { + return d.retryMax +} + +// RetryRules returns the delay duration before retrying this request again +func (d Retryer) RetryRules(r *request.Request) time.Duration { + duration := d.getDuratoin(r.RetryCount) + + log.Errorf("%s/%s failed, will retry in %s, error %v", + r.ClientInfo.ServiceName, r.Operation.Name, duration, r.Error) + + return duration +} + +// Outer is for external stuff to handle retries for cases that are +// not covered by s3manager https://github.com/aws/aws-sdk-go/issues/466 +func (d Retryer) Outer(f func() error) error { + n := 0 + + for { + if err := f(); err != nil { + if n == d.retryMax { + log.Errorf("Max retries %d reached, error: %s", d.retryMax, err) + } + if _, ok := err.(awserr.Error); ok || n == d.retryMax { + return err + } + + duration := d.getDuratoin(n) + n = n + 1 + + log.Errorf("Retry %d/%d after %s, error: %s", n, d.retryMax, duration, err) + time.Sleep(duration) + + continue + } + + break + } + + return nil +} + +func (d Retryer) getDuratoin(n int) time.Duration { + delay := int(math.Pow(2, float64(n))) * (rand.Intn(d.retryDelay) + d.retryDelay) + return time.Duration(delay) * time.Millisecond +} diff --git a/vendor/github.com/grammarly/rocker/src/storage/s3/s3.go b/vendor/github.com/grammarly/rocker/src/storage/s3/s3.go new file mode 100644 index 0000000..83c334a --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/storage/s3/s3.go @@ -0,0 +1,527 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package s3 + +import ( + "archive/tar" + "crypto/sha256" + "encoding/json" + "fmt" + "github.com/grammarly/rocker/src/imagename" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + log "github.com/Sirupsen/logrus" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3manager" + "github.com/docker/docker/pkg/units" + "github.com/fsouza/go-dockerclient" +) + +const ( + cacheDir = "_digests" +) + +// Repositories is a struct that serializes to a "repositories" file +type Repositories map[string]Repository + +// Repository is an entity of Repositories struct +type Repository map[string]string + +// StorageS3 is a storage driver that implements storing docker images directly on S3 +type StorageS3 struct { + client *docker.Client + cacheRoot string + s3 *s3.S3 + retryer *Retryer +} + +// New makes an instance of StorageS3 storage driver +func New(client *docker.Client, cacheRoot string) *StorageS3 { + retryer := NewRetryer(400, 6) + + // TODO: configure region? + cfg := &aws.Config{ + Region: aws.String("us-east-1"), + Retryer: retryer, + Logger: &Logger{}, + } + + if log.StandardLogger().Level >= log.DebugLevel { + cfg.LogLevel = aws.LogLevel(aws.LogDebugWithRequestErrors) + } + + return &StorageS3{ + client: client, + cacheRoot: cacheRoot, + s3: s3.New(session.New(), cfg), + retryer: retryer, + } +} + +// Push pushes image tarball directly to S3 +func (s *StorageS3) Push(imageName string) (digest string, err error) { + img := imagename.NewFromString(imageName) + + if img.Storage != imagename.StorageS3 { + return "", fmt.Errorf("Can only push images with s3 storage specified, got: %s", img) + } + + if img.Registry == "" { + return "", fmt.Errorf("Cannot push image to S3, missing bucket name, got: %s", img) + } + + var image *docker.Image + if image, err = s.client.InspectImage(img.String()); err != nil { + return "", err + } + + if digest, err = s.CacheGet(image.ID); err != nil { + return "", err + } + + var tmpf string + + defer func() { + if tmpf != "" { + os.Remove(tmpf) + } + }() + + // Not cached, make tar + if digest == "" { + if tmpf, digest, err = s.MakeTar(imageName); err != nil { + return "", err + } + } + + var ( + ext = ".tar" + imgPathDigest = fmt.Sprintf("%s/%s%s", img.Name, digest, ext) + imgPathTag = fmt.Sprintf("%s/%s%s", img.Name, img.Tag, ext) + ) + + // Make HEAD request to s3 and check if image already uploaded + _, headErr := s.s3.HeadObject(&s3.HeadObjectInput{ + Bucket: aws.String(img.Registry), + Key: aws.String(imgPathDigest), + }) + + // Object not found, need to store + if headErr != nil { + // Other error, raise then + if e, ok := headErr.(awserr.RequestFailure); !ok || e.StatusCode() != 404 { + return "", headErr + } + // In case we do not have archive + if tmpf == "" { + var digest2 string + if tmpf, digest2, err = s.MakeTar(imageName); err != nil { + return "", err + } + // Verify digest (TODO: remote this check in future?) + if digest != digest2 { + return "", fmt.Errorf("The new digest does no equal old one (shouldn't happen) %s != %s", digest, digest2) + } + } + + uploader := s3manager.NewUploaderWithClient(s.s3, func(u *s3manager.Uploader) { + u.PartSize = 64 * 1024 * 1024 // 64MB per part + }) + + fd, err := os.Open(tmpf) + if err != nil { + return "", err + } + defer fd.Close() + + log.Infof("| Uploading image to s3.amazonaws.com/%s/%s", img.Registry, imgPathDigest) + + uploadParams := &s3manager.UploadInput{ + Bucket: aws.String(img.Registry), + Key: aws.String(imgPathDigest), + ContentType: aws.String("application/x-tar"), + Body: fd, + Metadata: map[string]*string{ + "Tag": aws.String(img.Tag), + "ImageID": aws.String(image.ID), + "Digest": aws.String(digest), + }, + } + + if err := s.retryer.Outer(func() error { + _, err := uploader.Upload(uploadParams) + return err + }); err != nil { + return "", fmt.Errorf("Failed to upload object to S3, error: %s", err) + } + } + + // Make a content addressable copy of an image file + copyParams := &s3.CopyObjectInput{ + Bucket: aws.String(img.Registry), + CopySource: aws.String(img.Registry + "/" + imgPathDigest), + Key: aws.String(imgPathTag), + } + + log.Infof("| Make alias s3.amazonaws.com/%s/%s", img.Registry, imgPathTag) + + if _, err = s.s3.CopyObject(copyParams); err != nil { + return "", fmt.Errorf("Failed to PUT object to S3, error: %s", err) + } + + return digest, nil +} + +// Pull imports docker image from tar artifact stored on S3 +func (s *StorageS3) Pull(name string) error { + img := imagename.NewFromString(name) + + if img.Storage != imagename.StorageS3 { + return fmt.Errorf("Can only pull images with s3 storage specified, got: %s", img) + } + + if img.Registry == "" { + return fmt.Errorf("Cannot pull image from S3, missing bucket name, got: %s", img) + } + + // TODO: here we use tmp file, but we can stream from S3 directly to Docker + tmpf, err := ioutil.TempFile("", "rocker_image_") + if err != nil { + return err + } + defer os.Remove(tmpf.Name()) + + var ( + // Create a downloader with the s3 client and custom options + downloader = s3manager.NewDownloaderWithClient(s.s3, func(d *s3manager.Downloader) { + d.PartSize = 64 * 1024 * 1024 // 64MB per part + }) + + imgPath = img.Name + "/" + img.Tag + ".tar" + + downloadParams = &s3.GetObjectInput{ + Bucket: aws.String(img.Registry), + Key: aws.String(imgPath), + } + ) + + log.Infof("| Import %s/%s.tar to %s", img.NameWithRegistry(), img.Tag, tmpf.Name()) + + if err := s.retryer.Outer(func() error { + _, err := downloader.Download(tmpf, downloadParams) + return err + }); err != nil { + return fmt.Errorf("Failed to download object from S3, error: %s", err) + } + + fd, err := os.Open(tmpf.Name()) + if err != nil { + return err + } + defer fd.Close() + + // Read through tar reader to patch repositories file since we might + // mave a different tag property + var ( + pipeReader, pipeWriter = io.Pipe() + tr = tar.NewReader(fd) + tw = tar.NewWriter(pipeWriter) + errch = make(chan error, 1) + + loadOptions = docker.LoadImageOptions{ + InputStream: pipeReader, + } + ) + + go func() { + errch <- s.client.LoadImage(loadOptions) + }() + + // Iterate through the files in the archive. + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return fmt.Errorf("Failed to read tar content, error: %s", err) + } + + // Skip "repositories" file, we will write our own + if hdr.Name == "repositories" { + // Read repositories file and pass to JSON decoder + r1 := Repositories{} + data, err := ioutil.ReadAll(tr) + if err != nil { + return fmt.Errorf("Failed to read `repositories` file content, error: %s", err) + } + if err := json.Unmarshal(data, &r1); err != nil { + return fmt.Errorf("Failed to parse `repositories` file json, error: %s", err) + } + + var imageID string + + // Read first key from repositories + for _, tags := range r1 { + for _, id := range tags { + imageID = id + break + } + break + } + + // Make a new repositories struct + r2 := Repositories{ + img.NameWithRegistry(): { + img.GetTag(): imageID, + }, + } + + // Write repositories file to the stream + reposBody, err := json.Marshal(r2) + if err != nil { + return fmt.Errorf("Failed to marshal `repositories` file json, error: %s", err) + } + + hdr := &tar.Header{ + Name: "repositories", + Mode: 0644, + Size: int64(len(reposBody)), + } + if err := tw.WriteHeader(hdr); err != nil { + return fmt.Errorf("Failed to write `repositories` file tar header, error: %s", err) + } + if _, err := tw.Write(reposBody); err != nil { + return fmt.Errorf("Failed to write `repositories` file to tar, error: %s", err) + } + + continue + } + + // Passthrough other files as is + if err := tw.WriteHeader(hdr); err != nil { + return fmt.Errorf("Failed to passthough tar header, error: %s", err) + } + if _, err := io.Copy(tw, tr); err != nil { + return fmt.Errorf("Failed to passthough tar content, error: %s", err) + } + } + + // Finish tar + if err := tw.Close(); err != nil { + return fmt.Errorf("Failed to close tar writer, error: %s", err) + } + + // Close pipeWriter + if err := pipeWriter.Close(); err != nil { + return fmt.Errorf("Failed to close tar pipeWriter, error: %s", err) + } + + if err := <-errch; err != nil { + errch <- fmt.Errorf("Failed to import image, error: %s", err) + } + + return nil +} + +// MakeTar makes a tar out of docker image and gives a temporary file and a digest +func (s *StorageS3) MakeTar(imageName string) (tmpfile string, digest string, err error) { + img := imagename.NewFromString(imageName) + + var image *docker.Image + if image, err = s.client.InspectImage(img.String()); err != nil { + return "", "", err + } + + tmpf, err := ioutil.TempFile("", "rocker_image_") + if err != nil { + return "", "", err + } + defer tmpf.Close() + + var ( + cleanup = func() { + os.Remove(tmpf.Name()) + } + + humanSize = units.HumanSize(float64(image.VirtualSize)) + errch = make(chan error, 1) + + pipeReader, pipeWriter = io.Pipe() + tr = tar.NewReader(pipeReader) + tw = tar.NewWriter(tmpf) + hash = sha256.New() + tarHashStream = io.MultiWriter(tw, hash) + + exportParams = docker.ExportImageOptions{ + Name: img.String(), + OutputStream: pipeWriter, + } + ) + + defer pipeWriter.Close() + + log.Infof("| Buffering image to a file %s (%s)", tmpf.Name(), humanSize) + + go func() { + errch <- s.client.ExportImage(exportParams) + }() + + // Iterate through the files in the archive. + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + cleanup() + return "", "", err + } + + // Skip "repositories" file, we will write our own + if hdr.Name == "repositories" { + io.Copy(ioutil.Discard, tr) + continue + } + + // Write any other file + tw.WriteHeader(hdr) + if _, err := io.Copy(tarHashStream, tr); err != nil { + cleanup() + return "", "", err + } + } + + // Write "repositories" file + digest = fmt.Sprintf("sha256-%x", hash.Sum(nil)) + + reposBody, err := json.Marshal(Repositories{ + img.NameWithRegistry(): { + img.Tag: image.ID, + digest: image.ID, + }, + }) + if err != nil { + cleanup() + return "", "", err + } + + hdr := &tar.Header{ + Name: "repositories", + Mode: 0644, + Size: int64(len(reposBody)), + } + if err := tw.WriteHeader(hdr); err != nil { + cleanup() + return "", "", err + } + if _, err := tw.Write(reposBody); err != nil { + cleanup() + return "", "", err + } + + // Finish tar + + if err := tw.Close(); err != nil { + cleanup() + return "", "", err + } + + if err := <-errch; err != nil { + cleanup() + return "", "", fmt.Errorf("Failed to export docker image %s, error: %s", img, err) + } + + // Cache digest by image ID + if err := s.CachePut(image.ID, digest); err != nil { + return "", "", fmt.Errorf("Failed to save digest cache, error: %s", err) + } + + return tmpf.Name(), digest, nil +} + +// ListTags returns the list of parsed tags existing for given image name on S3 +func (s *StorageS3) ListTags(imageName string) (images []*imagename.ImageName, err error) { + image := imagename.NewFromString(imageName) + + params := &s3.ListObjectsInput{ + Bucket: aws.String(image.Registry), + MaxKeys: aws.Int64(1000), + Prefix: aws.String(image.Name), + } + + resp, err := s.s3.ListObjects(params) + if err != nil { + return nil, err + } + + for _, s3Obj := range resp.Contents { + split := strings.Split(*s3Obj.Key, "/") + if len(split) < 2 { + continue + } + + imgName := strings.Join(split[:len(split)-1], "/") + imgName = fmt.Sprintf("s3.amazonaws.com/%s/%s", image.Registry, imgName) + + tag := strings.TrimSuffix(split[len(split)-1], ".tar") + candidate := imagename.New(imgName, tag) + + if candidate.Name != image.Name { + continue + } + + if image.Contains(candidate) || image.Tag == candidate.Tag { + images = append(images, candidate) + } + } + + return +} + +// CacheGet returns cached digest of the image +func (s *StorageS3) CacheGet(imageID string) (digest string, err error) { + fileName := filepath.Join(s.cacheRoot, cacheDir, imageID) + + data, err := ioutil.ReadFile(fileName) + if err != nil && os.IsNotExist(err) { + return "", nil + } + if err != nil { + return "", err + } + + return string(data), nil +} + +// CachePut stores digest cache of the image +func (s *StorageS3) CachePut(imageID, digest string) error { + fileName := filepath.Join(s.cacheRoot, cacheDir, imageID) + + if err := os.MkdirAll(filepath.Dir(fileName), 0755); err != nil { + return err + } + + return ioutil.WriteFile(fileName, []byte(digest), 0644) +} diff --git a/vendor/github.com/grammarly/rocker/src/storage/storage.go b/vendor/github.com/grammarly/rocker/src/storage/storage.go new file mode 100644 index 0000000..6b16b81 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/storage/storage.go @@ -0,0 +1,30 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package storage + +import ( + "github.com/grammarly/rocker/src/imagename" + + "github.com/fsouza/go-dockerclient" +) + +// Interface describes an interface of docker image storage driver +type Interface interface { + Push(imageName string) (digest string, err error) + Pull(imageName string) (image *docker.Image, err error) + ListTags(imageName string) (images []*imagename.ImageName, err error) +} diff --git a/vendor/github.com/grammarly/rocker/src/template/LICENSE b/vendor/github.com/grammarly/rocker/src/template/LICENSE new file mode 100644 index 0000000..7c699b1 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/template/LICENSE @@ -0,0 +1,13 @@ +(c) Copyright 2015 Grammarly, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/github.com/grammarly/rocker/src/template/Makefile b/vendor/github.com/grammarly/rocker/src/template/Makefile new file mode 100644 index 0000000..89a2b6c --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/template/Makefile @@ -0,0 +1,32 @@ + +SRCS = $(shell find . -name '*.go' | grep -v '^./vendor/') +PKGS := $(foreach pkg, $(sort $(dir $(SRCS))), $(pkg)) + +TESTARGS ?= + +deps: + @ go get github.com/kr/pretty + +testdeps: deps + @ go get github.com/GeertJohan/fgt + @ go get github.com/stretchr/testify/assert + +fmtcheck: + $(foreach file,$(SRCS),gofmt $(file) | diff -u $(file) - || exit;) + +lint: + @ go get github.com/golang/lint/golint + $(foreach file,$(SRCS),fgt golint $(file) || exit;) + +vet: + @ go get golang.org/x/tools/cmd/vet + $(foreach pkg,$(PKGS),fgt go vet $(pkg) || exit;) + +gocyclo: + @ go get github.com/fzipp/gocyclo + gocyclo -over 25 ./src + +test: testdeps fmtcheck vet lint + go test + +.PHONY: test fmtcheck lint vet gocyclo diff --git a/vendor/github.com/grammarly/rocker/src/template/README.md b/vendor/github.com/grammarly/rocker/src/template/README.md new file mode 100644 index 0000000..f7cf4bf --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/template/README.md @@ -0,0 +1,256 @@ +# rocker/template + +Template renderer with additional helpers based on Go's [text/template](http://golang.org/pkg/text/template/) used by [rocker](https://github.com/grammarly/rocker) and [rocker-compose](https://github.com/grammarly/rocker-compose). + +# Helpers + +### {{ seq *To* }} or {{ seq *From* *To* }} or {{ seq *From* *To* *Step* }} +Sequence generator. Returns an array of integers of a given sequence. Useful when you need to duplicate some configuration, for example scale containers of the same type. Mostly used in combination with `range`: +``` +{{ range $i := seq 1 5 2 }} +container-$i +{{ end }} +``` + +This template will yield: +``` +container-1 +container-3 +container-5 +``` + +### String functions +`rocker/template` exposes some Go's native functions from [strings](http://golang.org/pkg/strings/) package. Here is the list of them: + +* `compare` - [strings.Compare](http://golang.org/pkg/strings/#Compare) +* `contains` - [strings.Contains](http://golang.org/pkg/strings/#Contains) +* `containsAny` - [strings.ContainsAny](http://golang.org/pkg/strings/#ContainsAny) +* `count` - [strings.Count](http://golang.org/pkg/strings/#Count) +* `equalFold` - [strings.EqualFold](http://golang.org/pkg/strings/#EqualFold) +* `hasPrefix` - [strings.HasPrefix](http://golang.org/pkg/strings/#HasPrefix) +* `hasSuffix` - [strings.HasSuffix](http://golang.org/pkg/strings/#HasSuffix) +* `index` - [strings.Index](http://golang.org/pkg/strings/#Index) +* `indexAny` - [strings.IndexAny](http://golang.org/pkg/strings/#IndexAny) +* `join` - [strings.Join](http://golang.org/pkg/strings/#Join) +* `lastIndex` - [strings.LastIndex](http://golang.org/pkg/strings/#LastIndex) +* `lastIndexAny` - [strings.LastIndexAny](http://golang.org/pkg/strings/#LastIndexAny) +* `repeat` - [strings.Repeat](http://golang.org/pkg/strings/#Repeat) +* `replace` - [strings.Replace](http://golang.org/pkg/strings/#Replace) +* `split` - [strings.Split](http://golang.org/pkg/strings/#Split) +* `splitAfter` - [strings.SplitAfter](http://golang.org/pkg/strings/#SplitAfter) +* `splitAfterN` - [strings.SplitAfterN](http://golang.org/pkg/strings/#SplitAfterN) +* `splitN` - [strings.SplitN](http://golang.org/pkg/strings/#SplitN) +* `title` - [strings.Title](http://golang.org/pkg/strings/#Title) +* `toLower` - [strings.ToLower](http://golang.org/pkg/strings/#ToLower) +* `toTitle` - [strings.ToTitle](http://golang.org/pkg/strings/#ToTitle) +* `toUpper` - [strings.ToUpper](http://golang.org/pkg/strings/#ToUpper) +* `trim` - [strings.Trim](http://golang.org/pkg/strings/#Trim) +* `trimLeft` - [strings.TrimLeft](http://golang.org/pkg/strings/#TrimLeft) +* `trimPrefix` - [strings.TrimPrefix](http://golang.org/pkg/strings/#TrimPrefix) +* `trimRight` - [strings.TrimRight](http://golang.org/pkg/strings/#TrimRight) +* `trimSpace` - [strings.TrimSpace](http://golang.org/pkg/strings/#TrimSpace) +* `trimSuffix` - [strings.TrimSuffix](http://golang.org/pkg/strings/#TrimSuffix) + +Example: +``` +{{ replace "www.google.com" "google" "grammarly" -1 }} +``` + +Will yield: +``` +www.grammarly.com +``` + +### {{ json *anything* }} or {{ *anything* | json }} +Marshals given input to JSON. + +Example: +``` +ENV={{ .Env | json }} +``` + +This template will yield: +``` +ENV={"USER":"johnsnow","DOCKER_MACHINE_NAME":"dev","PATH":"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin",...} +``` + +### {{ yaml *anything* }} or {{ *anything* | yaml }} +Marshals given input to YAML. + +Example: +``` +{{ .Env | yaml }} +``` + +This template will yield: +``` +USER: johnsnow +DOCKER_MACHINE_NAME: dev +PATH: /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin +``` + +### {{ *anything* | yaml *N* }} where *N* is indentation level +Useful if you want to nest a yaml struct into another yaml file: + +```yaml +foo: + bar: + {{ .bar | yaml 2 }} +``` + +Will indent yaml-encoded `.bar` into two levels: + +```yaml +foo: + bar: + a: 1 + b: 2 +``` + +### {{ shell *string* }} or {{ *string* | shell }} +Escapes given string so it can be substituted to a shell command. + +Example: +```Dockerfile +RUN echo {{ "hello\nworld" | shell }} +``` + +This template will yield: +```Dockerfile +RUN echo $'hello\nworld' +``` + +### {{ dump *anything* }} +Pretty-prints any variable. Useful for debugging. + +Example: +``` +{{ dump .Env }} +``` + +This template will yield: +``` +template.Vars{ + "USER": "johnsnow", + "DOCKER_MACHINE_NAME": "dev", + "PATH": "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin", + ... +} +``` + +### {{ assert *expression* }} +Raises an error if given expression is false. *Positive* value is an existing non-nil value, non-empty slice, non-empty string, and non-zero number. + +For example `assert` is useful to check that passed variables are present. + +``` +{{ assert .Version }} +``` + +If the `Version` variable is not given, then template processing will fail with the following error: + +``` +Error executing template TEMPLATE_NAME, error: template: TEMPLATE_NAME:1:3: executing \"TEMPLATE_NAME\" at : error calling assert: Assertion failed +``` + +### {{ image *docker_image_name_with_tag* }} or {{ image *docker_image_name* *tag* }} +Wrapper that is used to substitute images of particular versions derived by artifacts *(TODO: link to artifacts doc)*. + +Example: +```Dockerfile +FROM {{ image "ubuntu" }} +# OR +FROM {{ image "ubuntu:latest" }} +# OR +FROM {{ image "ubuntu" "latest" }} +``` + +Without any additional arguments it will resolve into this: +```Dockerfile +FROM ubuntu:latest +``` + +But if you have an artifact that is resulted by a previous rocker build, that can be fed back to rocker as variable, the artifact will be substituted: +```yaml +# shorten version of an artifact by rocker +RockerArtifacts: +- Name: ubuntu:latest + Digest: sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11 +``` + +```Dockerfile +# rocker build -vars artifacts/* +FROM ubuntu@sha256:ead434cd278824865d6e3b67e5d4579ded02eb2e8367fc165efa21138b225f11 +``` + +This feature is useful when you have a continuous integration pipeline and you want to build images on top of each other with guaranteed immutability. Also, this trick can be used with [rocker-compose](https://github.com/grammarly/rocker-compose) to run images of particular versions devired by the artifacts. + +*TODO: also describe semver matching behavior* + +# Variables +`rocker/template` automatically populates [os.Environ](https://golang.org/pkg/os/#Environ) to the template along with the variables that are passed from the outside. All environment variables are available under `.Env`. + +Example: +``` +HOME={{ .Env.HOME }} +``` + +# Load file content to a variable +This template engine also supports loading files content to a variables. `rocker` and `rocker-compose` support this through a command line parameters: + +```bash +rocker build -var key=@key.pem +rocker-compose run -var key=@key.pem +``` + +If the file path is relative, it will be resolved according to the current working directory. + +**Usage options:** + +``` +key=@relative/file/path.txt +key=@../another/relative/file/path.txt +key=@/absolute/file/path.txt +key=@~/.bash_history +key=\@keep_value_as_is +``` + +# Development + +Please install pre-push git hook that will run tests before every push: + +```bash +cd rocker-template +``` + +To run tests manually: + +```bash +make test +``` + +Or to test something particular: + +```bash +go test -run TestProcessConfigTemplate_Seq +``` + +# Authors + +- Yura Bogdanov + +# License + +(c) Copyright 2015 Grammarly, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/github.com/grammarly/rocker/src/template/shellarg.go b/vendor/github.com/grammarly/rocker/src/template/shellarg.go new file mode 100644 index 0000000..57c662f --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/template/shellarg.go @@ -0,0 +1,52 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package template + +import ( + "regexp" + "strings" +) + +var ( + complexShellArgRegex = regexp.MustCompile("(?i:[^a-z\\d_\\/:=-])") + leadingSingleQuotesRegex = regexp.MustCompile("^(?:'')+") +) + +// EscapeShellarg escapes any string so it can be safely passed to a shell +func EscapeShellarg(value string) string { + // Nothing to escape, return as is + if !complexShellArgRegex.MatchString(value) { + return value + } + + // escape all single quotes + value = "'" + strings.Replace(value, "'", "'\\''", -1) + "'" + + // remove duplicated single quotes at the beginning + value = leadingSingleQuotesRegex.ReplaceAllString(value, "") + + // remove non-escaped single-quote if there are enclosed between 2 escaped + value = strings.Replace(value, "\\'''", "\\'", -1) + + // if the string contains new lines, then use bash $'string' representation + // to have the newline escape character + if strings.Contains(value, "\n") { + value = "$" + strings.Replace(value, "\n", "\\n", -1) + } + + return value +} diff --git a/vendor/github.com/grammarly/rocker/src/template/shellarg_test.go b/vendor/github.com/grammarly/rocker/src/template/shellarg_test.go new file mode 100644 index 0000000..6c6fea9 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/template/shellarg_test.go @@ -0,0 +1,51 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package template + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEscapeShellarg_Basic(t *testing.T) { + t.Parallel() + assert.Equal(t, "Testing", EscapeShellarg("Testing")) + assert.Equal(t, "'Testing;'", EscapeShellarg("Testing;")) +} + +func TestEscapeShellarg_Advanced(t *testing.T) { + t.Parallel() + + assertions := map[string]string{ + "hello\\nworld": "'hello\\nworld'", + "hello:world": "hello:world", + "--hello=world": "--hello=world", + "hello\\tworld": "'hello\\tworld'", + "hello\nworld": "$'hello\\nworld'", + "\thello\nworld'": "$'\thello\\nworld'\\'", + "hello world": "'hello world'", + "hello\\\\'": "'hello\\\\'\\'", + "'\\\\'world": "\\''\\\\'\\''world'", + "world\\": "'world\\'", + "'single'": "\\''single'\\'", + } + + for k, v := range assertions { + assert.Equal(t, v, EscapeShellarg(k)) + } +} diff --git a/vendor/github.com/grammarly/rocker/src/template/template.go b/vendor/github.com/grammarly/rocker/src/template/template.go new file mode 100644 index 0000000..e47d76a --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/template/template.go @@ -0,0 +1,357 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package template + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/grammarly/rocker/src/imagename" + "io" + "io/ioutil" + "os" + "reflect" + "sort" + "strconv" + "strings" + "text/template" + + "github.com/go-yaml/yaml" + "github.com/kr/pretty" + + log "github.com/Sirupsen/logrus" +) + +// Funs is the list of additional helpers that may be given to the template +type Funs map[string]interface{} + +// Process renders config through the template processor. +// vars and additional functions are acceptable. +func Process(name string, reader io.Reader, vars Vars, funs Funs) (*bytes.Buffer, error) { + + var buf bytes.Buffer + // read template + data, err := ioutil.ReadAll(reader) + if err != nil { + return nil, fmt.Errorf("Error reading template %s, error: %s", name, err) + } + + // Copy the vars struct because we don't want to modify the original struct + vars = Vars{}.Merge(vars) + + // merge OS environment variables with the given Vars map + // todo: maybe, we need to make it configurable + vars["Env"] = ParseKvPairs(os.Environ()) + + // Populate functions + funcMap := map[string]interface{}{ + "seq": seq, + "dump": dump, + "assert": assertFn, + "json": jsonFn, + "shell": EscapeShellarg, + "yaml": yamlFn, + "image": makeImageHelper(vars), // `image` helper needs to make a closure on Vars + + // strings functions + "compare": strings.Compare, + "contains": strings.Contains, + "containsAny": strings.ContainsAny, + "count": strings.Count, + "equalFold": strings.EqualFold, + "hasPrefix": strings.HasPrefix, + "hasSuffix": strings.HasSuffix, + "indexOf": strings.Index, + "indexAny": strings.IndexAny, + "join": strings.Join, + "lastIndex": strings.LastIndex, + "lastIndexAny": strings.LastIndexAny, + "repeat": strings.Repeat, + "replace": strings.Replace, + "split": strings.Split, + "splitAfter": strings.SplitAfter, + "splitAfterN": strings.SplitAfterN, + "splitN": strings.SplitN, + "title": strings.Title, + "toLower": strings.ToLower, + "toTitle": strings.ToTitle, + "toUpper": strings.ToUpper, + "trim": strings.Trim, + "trimLeft": strings.TrimLeft, + "trimPrefix": strings.TrimPrefix, + "trimRight": strings.TrimRight, + "trimSpace": strings.TrimSpace, + "trimSuffix": strings.TrimSuffix, + } + for k, f := range funs { + funcMap[k] = f + } + + tmpl, err := template.New(name).Funcs(funcMap).Parse(string(data)) + if err != nil { + return nil, fmt.Errorf("Error parsing template %s, error: %s", name, err) + } + + if err := tmpl.Execute(&buf, vars); err != nil { + return nil, fmt.Errorf("Error executing template %s, error: %s", name, err) + } + + return &buf, nil +} + +// seq produces a sequence slice of a given length. See README.md for more info. +func seq(args ...interface{}) ([]int, error) { + l := len(args) + if l == 0 || l > 3 { + return nil, fmt.Errorf("seq helper expects from 1 to 3 arguments, %d given", l) + } + intArgs := make([]int, l) + for i, v := range args { + n, err := interfaceToInt(v) + if err != nil { + return nil, err + } + intArgs[i] = n + } + return doSeq(intArgs[0], intArgs[1:]...) +} + +func doSeq(n int, args ...int) ([]int, error) { + var ( + from, to, step int + + i = 0 + ) + + switch len(args) { + // {{ seq To }} + case 0: + // {{ seq 0 }} + if n == 0 { + return []int{}, nil + } + if n > 0 { + // {{ seq 15 }} + from, to, step = 1, n, 1 + } else { + // {{ seq -15 }} + from, to, step = -1, n, 1 + } + // {{ seq From To }} + case 1: + from, to, step = n, args[0], 1 + + // {{ seq From To Step }} + case 2: + from, to, step = n, args[0], args[1] + } + + if step <= 0 { + return nil, fmt.Errorf("step should be a positive integer, `%#v` given", step) + } + + // reverse order + if from > to { + res := make([]int, ((from-to)/step)+1) + for k := from; k >= to; k = k - step { + res[i] = k + i++ + } + return res, nil + } + + // straight order + res := make([]int, ((to-from)/step)+1) + for k := from; k <= to; k = k + step { + res[i] = k + i++ + } + return res, nil +} + +func dump(v interface{}) string { + return fmt.Sprintf("% #v", pretty.Formatter(v)) +} + +func assertFn(v interface{}) (string, error) { + t, _ := isTrue(reflect.ValueOf(v)) + if t { + return "", nil + } + return "", fmt.Errorf("Assertion failed") +} + +func jsonFn(v interface{}) (string, error) { + data, err := json.Marshal(v) + if err != nil { + return "", err + } + return string(data), nil +} + +func yamlFn(args ...interface{}) (result string, err error) { + var ( + i = 0 + input interface{} + ) + + if len(args) == 1 { + input = args[0] + } else if len(args) == 2 { + if i, err = interfaceToInt(args[0]); err != nil { + return "", err + } + input = args[1] + } else { + return "", fmt.Errorf("yaml helper expects from 1 to 2 arguments, %d given", len(args)) + } + + data, err := yaml.Marshal(input) + if err != nil { + return "", err + } + result = string(data) + + if i > 0 { + result = indent(strings.Repeat(" ", i), result) + } + + return result, nil +} + +func indent(prefix, s string) string { + var res []string + for _, line := range strings.Split(s, "\n") { + if line != "" { + line = prefix + line + } + res = append(res, line) + } + return strings.Join(res, "\n") +} + +func makeImageHelper(vars Vars) func(string, ...string) (string, error) { + // Sort artifacts so we match semver on latest item + var ( + artifacts = &imagename.Artifacts{} + ok bool + ) + + if artifacts.RockerArtifacts, ok = vars["RockerArtifacts"].([]imagename.Artifact); !ok { + artifacts.RockerArtifacts = []imagename.Artifact{} + } + + sort.Sort(artifacts) + + log.Debugf("`image` helper got artifacts: %# v", pretty.Formatter(artifacts)) + + return func(img string, args ...string) (string, error) { + var ( + matched bool + ok bool + shouldMatch bool + image = imagename.NewFromString(img) + ) + + if len(args) > 0 { + image = imagename.New(img, args[0]) + } + + for _, a := range artifacts.RockerArtifacts { + if !image.IsSameKind(*a.Name) { + continue + } + + if image.HasVersionRange() { + if !image.Contains(a.Name) { + log.Debugf("Skipping artifact %s because it is not suitable for %s", a.Name, image) + continue + } + } else if image.GetTag() != a.Name.GetTag() { + log.Debugf("Skipping artifact %s because it is not suitable for %s", a.Name, image) + continue + } + + if a.Digest != "" { + log.Infof("Apply artifact digest %s for image %s", a.Digest, image) + image.SetTag(a.Digest) + matched = true + break + } + if a.Name.HasTag() { + log.Infof("Apply artifact tag %s for image %s", a.Name.GetTag(), image) + image.SetTag(a.Name.GetTag()) + matched = true + break + } + } + + if shouldMatch, ok = vars["DemandArtifacts"].(bool); ok && shouldMatch && !matched { + return "", fmt.Errorf("Cannot find suitable artifact for image %s", image) + } + + return image.String(), nil + } +} + +func interfaceToInt(v interface{}) (int, error) { + switch v.(type) { + case int: + return v.(int), nil + case string: + n, err := strconv.ParseInt(v.(string), 10, 64) + if err != nil { + return 0, err + } + return (int)(n), nil + default: + return 0, fmt.Errorf("Cannot receive %#v, int or string is expected", v) + } +} + +// isTrue reports whether the value is 'true', in the sense of not the zero of its type, +// and whether the value has a meaningful truth value. +// +// NOTE: Borrowed from Go sources: http://golang.org/src/text/template/exec.go +// Copyright (c) 2012 The Go Authors. All rights reserved. +func isTrue(val reflect.Value) (truth, ok bool) { + if !val.IsValid() { + // Something like var x interface{}, never set. It's a form of nil. + return false, true + } + switch val.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + truth = val.Len() > 0 + case reflect.Bool: + truth = val.Bool() + case reflect.Complex64, reflect.Complex128: + truth = val.Complex() != 0 + case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface: + truth = !val.IsNil() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + truth = val.Int() != 0 + case reflect.Float32, reflect.Float64: + truth = val.Float() != 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + truth = val.Uint() != 0 + case reflect.Struct: + truth = true // Struct values are always true. + default: + return + } + return truth, true +} diff --git a/vendor/github.com/grammarly/rocker/src/template/template_test.go b/vendor/github.com/grammarly/rocker/src/template/template_test.go new file mode 100644 index 0000000..c2af074 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/template/template_test.go @@ -0,0 +1,227 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package template + +import ( + "fmt" + "github.com/grammarly/rocker/src/imagename" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + configTemplateVars = Vars{ + "mykey": "myval", + "n": "5", + "data": map[string]string{ + "foo": "bar", + }, + "RockerArtifacts": []imagename.Artifact{ + imagename.Artifact{ + Name: imagename.NewFromString("alpine:3.2"), + Tag: "3.2", + }, + imagename.Artifact{ + Name: imagename.NewFromString("golang:1.5"), + Tag: "1.5", + Digest: "sha256:ead434", + }, + imagename.Artifact{ + Name: imagename.NewFromString("data:master"), + Tag: "master", + Digest: "sha256:fafe14", + }, + imagename.Artifact{ + Name: imagename.NewFromString("ssh:latest"), + Tag: "latest", + Digest: "sha256:ba41cd", + }, + }, + } +) + +func TestProcess_Basic(t *testing.T) { + result, err := Process("test", strings.NewReader("this is a test {{.mykey}}"), configTemplateVars, map[string]interface{}{}) + if err != nil { + t.Fatal(err) + } + // fmt.Printf("Template result: %s\n", result) + assert.Equal(t, "this is a test myval", result.String(), "template should be rendered") +} + +func TestProcess_Seq(t *testing.T) { + assert.Equal(t, "[1 2 3 4 5]", processTemplate(t, "{{ seq 1 5 1 }}")) + assert.Equal(t, "[0 1 2 3 4]", processTemplate(t, "{{ seq 0 4 1 }}")) + assert.Equal(t, "[1 3 5]", processTemplate(t, "{{ seq 1 5 2 }}")) + assert.Equal(t, "[1 4]", processTemplate(t, "{{ seq 1 5 3 }}")) + assert.Equal(t, "[1 5]", processTemplate(t, "{{ seq 1 5 4 }}")) + assert.Equal(t, "[1]", processTemplate(t, "{{ seq 1 5 5 }}")) + + assert.Equal(t, "[1]", processTemplate(t, "{{ seq 1 1 1 }}")) + assert.Equal(t, "[1]", processTemplate(t, "{{ seq 1 1 5 }}")) + + assert.Equal(t, "[5 4 3 2 1]", processTemplate(t, "{{ seq 5 1 1 }}")) + assert.Equal(t, "[5 3 1]", processTemplate(t, "{{ seq 5 1 2 }}")) + assert.Equal(t, "[5 2]", processTemplate(t, "{{ seq 5 1 3 }}")) + assert.Equal(t, "[5 1]", processTemplate(t, "{{ seq 5 1 4 }}")) + assert.Equal(t, "[5]", processTemplate(t, "{{ seq 5 1 5 }}")) + + assert.Equal(t, "[1 2 3 4 5]", processTemplate(t, "{{ seq 5 }}")) + assert.Equal(t, "[1]", processTemplate(t, "{{ seq 1 }}")) + assert.Equal(t, "[]", processTemplate(t, "{{ seq 0 }}")) + assert.Equal(t, "[-1 -2 -3 -4 -5]", processTemplate(t, "{{ seq -5 }}")) + + assert.Equal(t, "[1 2 3 4 5]", processTemplate(t, "{{ seq 1 5 }}")) + assert.Equal(t, "[1]", processTemplate(t, "{{ seq 1 1 }}")) + assert.Equal(t, "[0]", processTemplate(t, "{{ seq 0 0 }}")) + assert.Equal(t, "[-1 -2 -3 -4 -5]", processTemplate(t, "{{ seq -1 -5 }}")) + + // Test string param + assert.Equal(t, "[1 2 3 4 5]", processTemplate(t, "{{ seq .n }}")) +} + +func TestProcess_Replace(t *testing.T) { + assert.Equal(t, "url-com-", processTemplate(t, `{{ replace "url.com." "." "-" -1 }}`)) + assert.Equal(t, "url", processTemplate(t, `{{ replace "url" "*" "l" -1 }}`)) + assert.Equal(t, "krl", processTemplate(t, `{{ replace "url" "u" "k" -1 }}`)) +} + +func TestProcess_Env(t *testing.T) { + env := os.Environ() + kv := strings.SplitN(env[0], "=", 2) + assert.Equal(t, kv[1], processTemplate(t, fmt.Sprintf("{{ .Env.%s }}", kv[0]))) +} + +func TestProcess_Dump(t *testing.T) { + assert.Equal(t, `map[string]string{"foo":"bar"}`, processTemplate(t, "{{ dump .data }}")) +} + +func TestProcess_AssertSuccess(t *testing.T) { + assert.Equal(t, "output", processTemplate(t, "{{ assert true }}output")) +} + +func TestProcess_AssertFail(t *testing.T) { + tpl := "{{ assert .Version }}lololo" + _, err := Process("test", strings.NewReader(tpl), configTemplateVars, map[string]interface{}{}) + errStr := "Error executing template test, error: template: test:1:3: executing \"test\" at : error calling assert: Assertion failed" + assert.Equal(t, errStr, err.Error()) +} + +func TestProcess_Json(t *testing.T) { + assert.Equal(t, "key: {\"foo\":\"bar\"}", processTemplate(t, "key: {{ .data | json }}")) +} + +func TestProcess_Shellarg(t *testing.T) { + assert.Equal(t, "echo 'hello world'", processTemplate(t, "echo {{ \"hello world\" | shell }}")) +} + +func TestProcess_Yaml(t *testing.T) { + assert.Equal(t, "key: foo: bar\n", processTemplate(t, "key: {{ .data | yaml }}")) + assert.Equal(t, "key: myval\n", processTemplate(t, "key: {{ .mykey | yaml }}")) + assert.Equal(t, "key: |-\n hello\n world\n", processTemplate(t, "key: {{ \"hello\\nworld\" | yaml }}")) +} + +func TestProcess_YamlIndent(t *testing.T) { + assert.Equal(t, "key:\n foo: bar\n", processTemplate(t, "key:\n{{ .data | yaml 1 }}")) +} + +func TestProcess_Image_Simple(t *testing.T) { + tests := []struct { + tpl string + result string + message string + }{ + {"{{ image `debian:7.7` }}", "debian:7.7", "should not alter the tag that is not in artifacts"}, + {"{{ image `debian` `7.7` }}", "debian:7.7", "should be possible to specify tag as a separate argument"}, + {"{{ image `debian` `sha256:afa` }}", "debian@sha256:afa", "should be possible to specify digest as a separate argument"}, + } + + for _, test := range tests { + assert.Equal(t, test.result, processTemplate(t, test.tpl), test.message) + } +} + +func TestProcess_Image_Advanced(t *testing.T) { + tests := []struct { + in string + result string + shouldMatch bool + message string + }{ + {"debian:7.7", "debian:7.7", false, "should not alter the tag that is not in artifacts"}, + {"debian:7.*", "debian:7.*", false, "should not alter the semver tag that is not in artifacts"}, + {"debian", "debian:latest", false, "should not match anything when no tag given (:latest) and no artifact"}, + {"alpine:3.1", "alpine:3.1", false, "should not match artifact with different version"}, + {"alpine:4.1", "alpine:4.1", false, "should not match artifact with different version"}, + {"alpine:3.*", "alpine:3.2", true, "should match artifact with version wildcard"}, + {"alpine", "alpine:latest", false, "should not match artifact when no tag given (:latest by default)"}, + {"alpine:latest", "alpine:latest", false, "should not match on a :latest tag"}, + {"alpine:snapshot", "alpine:snapshot", false, "should not match on a named tag"}, + {"golang:1.5", "golang@sha256:ead434", true, "should match semver tag and use digest"}, + {"golang:1.*", "golang@sha256:ead434", true, "should match on wildcard semver tag and use digest"}, + {"golang:1", "golang@sha256:ead434", true, "should match on prefix semver tag and use digest"}, + {"golang:1.4", "golang:1.4", false, "should not match on different semver tag"}, + {"golang:master", "golang:master", false, "should not match on a named tag"}, + {"data:1.2", "data:1.2", false, "should not match on a version tag against named artifact"}, + {"data:snapshot", "data:snapshot", false, "should not match on a different named tag against named artifact"}, + {"data:master", "data@sha256:fafe14", true, "should match on a same named tag against named artifact"}, + {"ssh:latest", "ssh@sha256:ba41cd", true, "should match on a :latest tag against :latest artifact"}, + {"ssh", "ssh@sha256:ba41cd", true, "should match on non-tagged tag against :latest artifact"}, + {"ssh:master", "ssh:master", false, "should match with other tag against :latest artifact"}, + {"ssh:1.2", "ssh:1.2", false, "should match with semver tag against :latest artifact"}, + } + + for _, test := range tests { + tpl := fmt.Sprintf("{{ image `%s` }}", test.in) + assert.Equal(t, test.result, processTemplate(t, tpl), test.message) + } + + // Now test the same but with DemandArtifact On + configTemplateVars["DemandArtifacts"] = true + defer func() { + configTemplateVars["DemandArtifacts"] = false + }() + + for _, test := range tests { + tpl := fmt.Sprintf("{{ image `%s` }}", test.in) + if test.shouldMatch { + assert.Equal(t, test.result, processTemplate(t, tpl), test.message) + } else { + err := processTemplateReturnError(t, tpl) + assert.Error(t, err, fmt.Sprintf("should give an error for test case: %s", test.message)) + if err != nil { + assert.Contains(t, err.Error(), fmt.Sprintf("Cannot find suitable artifact for image %s", test.in), test.message) + } + } + } +} + +func processTemplate(t *testing.T, tpl string) string { + result, err := Process("test", strings.NewReader(tpl), configTemplateVars, map[string]interface{}{}) + if err != nil { + t.Fatal(err) + } + return result.String() +} + +func processTemplateReturnError(t *testing.T, tpl string) error { + _, err := Process("test", strings.NewReader(tpl), configTemplateVars, map[string]interface{}{}) + return err +} diff --git a/vendor/github.com/grammarly/rocker/src/template/testdata/content.txt b/vendor/github.com/grammarly/rocker/src/template/testdata/content.txt new file mode 100644 index 0000000..ce01362 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/template/testdata/content.txt @@ -0,0 +1 @@ +hello diff --git a/vendor/github.com/grammarly/rocker/src/template/vars.go b/vendor/github.com/grammarly/rocker/src/template/vars.go new file mode 100644 index 0000000..a14b248 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/template/vars.go @@ -0,0 +1,294 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package template + +import ( + "encoding/json" + "fmt" + "github.com/grammarly/rocker/src/imagename" + "io/ioutil" + "os" + "path" + "path/filepath" + "reflect" + "regexp" + "sort" + "strings" + + "github.com/go-yaml/yaml" + + log "github.com/Sirupsen/logrus" +) + +// Vars describes the data structure of the build variables +type Vars map[string]interface{} + +// Merge the current Vars structure with the list of other Vars structs +func (vars Vars) Merge(varsList ...Vars) Vars { + for _, mergeWith := range varsList { + for k, v := range mergeWith { + // We want to merge slices of the same type by appending them to each other + // instead of overwriting + rv1 := reflect.ValueOf(vars[k]) + rv2 := reflect.ValueOf(v) + + if rv1.Kind() == reflect.Slice && rv2.Kind() == reflect.Slice && rv1.Type() == rv2.Type() { + vars[k] = reflect.AppendSlice(rv1, rv2).Interface() + } else { + vars[k] = v + } + } + } + return vars +} + +// IsSet returns true if the given key is set +func (vars Vars) IsSet(key string) bool { + _, ok := vars[key] + return ok +} + +// ToStrings converts Vars to a slice of strings line []string{"KEY=VALUE"} +func (vars Vars) ToStrings() (result []string) { + for k, v := range vars { + result = append(result, fmt.Sprintf("%s=%s", k, v)) + } + sort.Strings(result) + return result +} + +// ToMapOfInterface casts Vars to map[string]interface{} +func (vars Vars) ToMapOfInterface() map[string]interface{} { + result := map[string]interface{}{} + for k, v := range vars { + result[k] = v + } + return result +} + +// MarshalJSON serialize Vars to JSON +func (vars Vars) MarshalJSON() ([]byte, error) { + return json.Marshal(vars.ToStrings()) +} + +// UnmarshalJSON unserialize Vars from JSON string +func (vars *Vars) UnmarshalJSON(data []byte) (err error) { + // try unmarshal map to keep backward compatibility + maps := map[string]interface{}{} + if err = json.Unmarshal(data, &maps); err == nil { + *vars = (Vars)(maps) + return nil + } + // unmarshal slice of strings + strings := []string{} + if err = json.Unmarshal(data, &strings); err != nil { + return err + } + if *vars, err = VarsFromStrings(strings); err != nil { + return err + } + return nil +} + +// UnmarshalYAML parses YAML string and returns Vars +func (vars *Vars) UnmarshalYAML(unmarshal func(interface{}) error) (err error) { + // try unmarshal RockerArtifacts type + var artifacts imagename.Artifacts + if err = unmarshal(&artifacts); err != nil { + return err + } + + var value map[string]interface{} + if err = unmarshal(&value); err != nil { + return err + } + + // Fill artifacts if present + if len(artifacts.RockerArtifacts) > 0 { + value["RockerArtifacts"] = artifacts.RockerArtifacts + } + + *vars = value + + return nil +} + +// VarsFromStrings parses Vars through ParseKvPairs and then loads content from files +// for vars values with "@" prefix +func VarsFromStrings(pairs []string) (vars Vars, err error) { + vars = ParseKvPairs(pairs) + for k, v := range vars { + // We care only about strings + switch v := v.(type) { + case string: + // Read variable content from a file if "@" prefix is given + if strings.HasPrefix(v, "@") { + f := v[1:] + if vars[k], err = loadFileContent(f); err != nil { + return vars, fmt.Errorf("Failed to read file '%s' for variable %s, error: %s", f, k, err) + } + } + // Unescape "\@" + if strings.HasPrefix(v, "\\@") { + vars[k] = v[1:] + } + } + } + return vars, nil +} + +// VarsFromFile reads variables from either JSON or YAML file +func VarsFromFile(filename string) (vars Vars, err error) { + log.Debugf("Load vars from file %s", filename) + + if filename, err = resolveFileName(filename); err != nil { + return nil, err + } + + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + vars = Vars{} + + switch filepath.Ext(filename) { + case ".yaml", ".yml", ".": + if err := yaml.Unmarshal(data, &vars); err != nil { + return nil, err + } + case ".json": + if err := json.Unmarshal(data, &vars); err != nil { + return nil, err + } + } + + return vars, nil +} + +// VarsFromFileMulti reads multiple files and merge vars +func VarsFromFileMulti(files []string) (Vars, error) { + var ( + varsList = []Vars{} + matches []string + vars Vars + err error + ) + + for _, pat := range files { + matches = []string{pat} + + if containsWildcards(pat) { + if matches, err = filepath.Glob(pat); err != nil { + return nil, err + } + } + + for _, f := range matches { + if vars, err = VarsFromFile(f); err != nil { + return nil, err + } + varsList = append(varsList, vars) + } + } + + return Vars{}.Merge(varsList...), nil +} + +// ParseKvPairs parses Vars from a slice of strings e.g. []string{"KEY=VALUE"} +func ParseKvPairs(pairs []string) (vars Vars) { + vars = make(Vars) + for _, varPair := range pairs { + tmp := strings.SplitN(varPair, "=", 2) + vars[tmp[0]] = tmp[1] + } + return vars +} + +func loadFileContent(f string) (content string, err error) { + if f, err = resolveFileName(f); err != nil { + return "", err + } + data, err := ioutil.ReadFile(f) + if err != nil { + return "", err + } + return string(data), nil +} + +func resolveFileName(f string) (string, error) { + if f == "~" || strings.HasPrefix(f, "~/") { + f = strings.Replace(f, "~", os.Getenv("HOME"), 1) + } + if !filepath.IsAbs(f) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + f = path.Join(wd, f) + } + return f, nil +} + +// Code borrowed from https://github.com/docker/docker/blob/df0e0c76831bed08cf5e08ac9a1abebf6739da23/builder/support.go +var ( + // `\\\\+|[^\\]|\b|\A` - match any number of "\\" (ie, properly-escaped backslashes), or a single non-backslash character, or a word boundary, or beginning-of-line + // `\$` - match literal $ + // `[[:alnum:]_]+` - match things like `$SOME_VAR` + // `{[[:alnum:]_]+}` - match things like `${SOME_VAR}` + tokenVarsInterpolation = regexp.MustCompile(`(\\|\\\\+|[^\\]|\b|\A)\$([[:alnum:]_]+|{[[:alnum:]_]+})`) + // this intentionally punts on more exotic interpolations like ${SOME_VAR%suffix} and lets the shell handle those directly +) + +// ReplaceString handle vars replacement +func (vars Vars) ReplaceString(str string) string { + for _, match := range tokenVarsInterpolation.FindAllString(str, -1) { + idx := strings.Index(match, "\\$") + if idx != -1 { + if idx+2 >= len(match) { + str = strings.Replace(str, match, "\\$", -1) + continue + } + + prefix := match[:idx] + stripped := match[idx+2:] + str = strings.Replace(str, match, prefix+"$"+stripped, -1) + continue + } + + match = match[strings.Index(match, "$"):] + matchKey := strings.Trim(match, "${}") + + if val, ok := vars[matchKey].(string); ok { + str = strings.Replace(str, match, val, -1) + } + } + + return str +} + +func containsWildcards(name string) bool { + for i := 0; i < len(name); i++ { + ch := name[i] + if ch == '\\' { + i++ + } else if ch == '*' || ch == '?' || ch == '[' { + return true + } + } + return false +} diff --git a/vendor/github.com/grammarly/rocker/src/template/vars_test.go b/vendor/github.com/grammarly/rocker/src/template/vars_test.go new file mode 100644 index 0000000..cc71893 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/template/vars_test.go @@ -0,0 +1,319 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package template + +import ( + "encoding/json" + "fmt" + "github.com/grammarly/rocker/src/imagename" + "github.com/grammarly/rocker/src/test" + "io/ioutil" + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVars_MergeSlices(t *testing.T) { + v1 := Vars{ + "fruits": []string{"banana", "apple"}, + } + v2 := Vars{ + "fruits": []string{"pear", "orange"}, + } + v3 := v1.Merge(v2) + + assert.Equal(t, []string{"banana", "apple", "pear", "orange"}, v3["fruits"].([]string)) +} + +func TestVarsToStrings(t *testing.T) { + t.Parallel() + + type assertion struct { + vars Vars + expectation []string + } + + tests := []assertion{ + assertion{ + Vars{"FOO": "bar", "XYZ": "oqoq"}, + []string{"FOO=bar", "XYZ=oqoq"}, + }, + assertion{ + Vars{"": "bar", "XYZ": "oqoq"}, + []string{"=bar", "XYZ=oqoq"}, + }, + assertion{ + Vars{"asd": "qwe"}, + []string{"asd=qwe"}, + }, + assertion{ + Vars{"asd": "", "haha": "hehe"}, + []string{"asd=", "haha=hehe"}, + }, + } + + for _, a := range tests { + result := a.vars.ToStrings() + assert.Equal(t, len(a.vars), len(result), "resulting number of strings not match number of vars keys") + for _, expectation := range a.expectation { + assert.Contains(t, result, expectation, "failed to narrow down vars to list of strings") + } + } +} + +func TestVarsFromStrings(t *testing.T) { + t.Parallel() + + type assertion struct { + input []string + expectation Vars + } + + tests := []assertion{ + assertion{ + []string{"FOO=bar", "XYZ=oqoq"}, + Vars{"FOO": "bar", "XYZ": "oqoq"}, + }, + assertion{ + []string{"=bar", "XYZ=oqoq"}, + Vars{"": "bar", "XYZ": "oqoq"}, + }, + assertion{ + []string{"asd=qwe"}, + Vars{"asd": "qwe"}, + }, + assertion{ + []string{"asd=", "haha=hehe"}, + Vars{"asd": "", "haha": "hehe"}, + }, + } + + for _, a := range tests { + result, err := VarsFromStrings(a.input) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(a.input), len(result), "resulting number of strings not match number of vars keys") + } +} + +// TODO: test VarsFromFileMulti + +func TestVarsFromFile_Yaml(t *testing.T) { + tempDir, rm := tplMkFiles(t, map[string]string{ + "vars.yml": ` +Foo: x +Bar: yes +`, + }) + defer rm() + + vars, err := VarsFromFile(tempDir + "/vars.yml") + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "x", vars["Foo"]) + assert.Equal(t, true, vars["Bar"]) +} + +func TestVarsFromFile_Yaml_Artifacts(t *testing.T) { + tempDir, rm := tplMkFiles(t, map[string]string{ + "vars.yml": ` +Foo: x +Bar: yes +RockerArtifacts: +- Name: golang:1.5 + Tag: "1.5" +`, + }) + defer rm() + + vars, err := VarsFromFile(tempDir + "/vars.yml") + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "x", vars["Foo"]) + assert.Equal(t, true, vars["Bar"]) + assert.IsType(t, []imagename.Artifact{}, vars["RockerArtifacts"]) +} + +func TestVarsFromFile_Json(t *testing.T) { + tempDir, rm := tplMkFiles(t, map[string]string{ + "vars.json": ` +{"Foo": "x", "Bar": true} +`, + }) + defer rm() + + vars, err := VarsFromFile(tempDir + "/vars.json") + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "x", vars["Foo"]) + assert.Equal(t, true, vars["Bar"]) +} + +func TestVarsReplaceString(t *testing.T) { + t.Parallel() + + type assertion struct { + vars Vars + input string + expectation string + } + + tests := []assertion{ + assertion{ + Vars{"FOO": "bar"}, + "Hello, this is $FOO", + "Hello, this is bar", + }, + assertion{ + Vars{"FOO": "bar"}, + "Hello, this is ${FOO}", + "Hello, this is bar", + }, + assertion{ + Vars{"FOO": ""}, + "Hello, this is $FOO", + "Hello, this is ", + }, + assertion{ + Vars{"GREETING": "Hello", "NAME": "Hadiyah"}, + "$GREETING,\n$NAME!", + "Hello,\nHadiyah!", + }, + assertion{ + Vars{}, + "$GREETING,\n$NAME!", + "$GREETING,\n$NAME!", + }, + } + + for _, a := range tests { + result := a.vars.ReplaceString(a.input) + assert.Equal(t, a.expectation, result, "failed to substitute variables to a string") + } +} + +func TestVarsJsonMarshal(t *testing.T) { + v := Vars{"foo": "bar", "asd": "qwe"} + data, err := json.Marshal(v) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, `["asd=qwe","foo=bar"]`, string(data), "bad Vars encoded to json") + + v2 := Vars{"asd": "qwe", "foo": "bar"} + data2, err := json.Marshal(v2) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, `["asd=qwe","foo=bar"]`, string(data2), "bad Vars encoded to json (order)") + + v3 := Vars{} + if err := json.Unmarshal(data2, &v3); err != nil { + t.Fatal(err) + } + + assert.Equal(t, 2, len(v3), "bad decoded vars length") + assert.Equal(t, "qwe", v3["asd"], "bad decoded vars element") + assert.Equal(t, "bar", v3["foo"], "bad decoded vars element") + + // Test unmarshal map to keep backward capatibility + m := map[string]string{ + "foo": "bar", + "asd": "qwe", + } + data3, err := json.Marshal(m) + if err != nil { + t.Fatal(err) + } + + v4 := Vars{} + if err := json.Unmarshal(data3, &v4); err != nil { + t.Fatal(err) + } + + assert.Equal(t, 2, len(v4), "bad decoded vars length") + assert.Equal(t, "qwe", v4["asd"], "bad decoded vars element") + assert.Equal(t, "bar", v4["foo"], "bad decoded vars element") +} + +func TestVarsFileContent(t *testing.T) { + t.Parallel() + + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + + // Test absolute + result, err := VarsFromStrings([]string{fmt.Sprintf("FOO=@%s/testdata/content.txt", wd)}) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "hello\n", result["FOO"]) + + // Test relative + result2, err := VarsFromStrings([]string{"FOO=@testdata/content.txt"}) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "hello\n", result2["FOO"]) + + // Test escaped @ + result3, err := VarsFromStrings([]string{"FOO=\\@testdata/content.txt"}) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "@testdata/content.txt", result3["FOO"]) + + // Test HOME + os.Setenv("HOME", path.Join(wd, "testdata")) + + result4, err := VarsFromStrings([]string{"FOO=@~/content.txt"}) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "hello\n", result4["FOO"]) +} + +func tplMkFiles(t *testing.T, files map[string]string) (string, func()) { + tempDir, err := ioutil.TempDir("", "rocker-vars-test") + if err != nil { + t.Fatal(err) + } + + if err = test.MakeFiles(tempDir, files); err != nil { + os.RemoveAll(tempDir) + t.Fatal(err) + } + + return tempDir, func() { + os.RemoveAll(tempDir) + } +} diff --git a/vendor/github.com/grammarly/rocker/src/util/cmd.go b/vendor/github.com/grammarly/rocker/src/util/cmd.go new file mode 100644 index 0000000..25124ce --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/util/cmd.go @@ -0,0 +1,125 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +// TODO: this stuff is smelling and should be refactored + +import ( + "bufio" + "bytes" + "fmt" + "io" + "os/exec" + "strings" + "syscall" +) + +// Cmd is a wrapper over os/exec and provides extra convenience stuff +type Cmd struct { + Args []string + Env []string + Dir string + Stream io.Writer + Reader *io.PipeReader + Writer *io.PipeWriter + ExitStatus int + Error error +} + +// Pipe pipes cmd stdout/stderr to a given io.Writer +func (cmd *Cmd) Pipe(writer io.Writer) error { + scanner := bufio.NewScanner(cmd.Reader) + for scanner.Scan() { + writer.Write(scanner.Bytes()) + writer.Write([]byte("\n")) + } + return scanner.Err() +} + +// String returns debug representation of the Cmd +func (cmd *Cmd) String() string { + if len(cmd.Env) > 0 { + return fmt.Sprintf("%s [Env %s] [Dir %s]", strings.Join(cmd.Args, " "), strings.Join(cmd.Env, " "), cmd.Dir) + } + return fmt.Sprintf("%s [Dir %s]", strings.Join(cmd.Args, " "), cmd.Dir) +} + +// ExecPipe executes the command and returns its output, exit code and error +func ExecPipe(cmd *Cmd) (string, int, error) { + var output bytes.Buffer + cmd, err := Exec(cmd) + if err != nil { + return "", 0, err + } + err = cmd.Pipe(&output) + if err != nil { + return "", 0, err + } + return output.String(), cmd.ExitStatus, cmd.Error +} + +// Exec runs the command and grabs exit code at the end +// If Stream property is present, then it also pipes stdout/stderr to it +func Exec(cmd *Cmd) (*Cmd, error) { + reader, writer := io.Pipe() + + cmd.Reader = reader + cmd.Writer = writer + + execCmd := &exec.Cmd{ + Path: cmd.Args[0], + Args: cmd.Args, + Env: cmd.Env, + Dir: cmd.Dir, + } + + execCmd.Stdout = cmd.Writer + execCmd.Stderr = cmd.Writer + + go func() { + defer cmd.Writer.Close() + err := execCmd.Start() + if err != nil { + cmd.Error = err + return + } + if err = execCmd.Wait(); err != nil { + if exiterr, ok := err.(*exec.ExitError); ok { + // The program has exited with an exit code != 0 + + // This works on both Unix and Windows. Although package + // syscall is generally platform dependent, WaitStatus is + // defined for both Unix and Windows and in both cases has + // an ExitStatus() method with the same signature. + if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { + cmd.ExitStatus = status.ExitStatus() + } + } else { + cmd.Error = err + } + } + }() + + if cmd.Stream != nil { + err := cmd.Pipe(cmd.Stream) + if err != nil { + return cmd, err + } + } + + return cmd, nil +} diff --git a/vendor/github.com/grammarly/rocker/src/util/cmd_test.go b/vendor/github.com/grammarly/rocker/src/util/cmd_test.go new file mode 100644 index 0000000..1a15a4c --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/util/cmd_test.go @@ -0,0 +1,91 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExecBase(t *testing.T) { + cmd, err := Exec(&Cmd{ + Args: []string{"testdata/prog"}, + }) + if err != nil { + t.Fatal(err) + } + + var buf bytes.Buffer + err = cmd.Pipe(&buf) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "1 stdout\n1 stderr\n2 stdout\n2 stderr\n3 stdout\n3 stderr\n4 stdout\n4 stderr\n5 stdout\n5 stderr\n", buf.String()) + assert.Equal(t, 0, cmd.ExitStatus) +} + +func TestExecStream(t *testing.T) { + var buf bytes.Buffer + + cmd, err := Exec(&Cmd{ + Args: []string{"testdata/prog"}, + Stream: &buf, + }) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "1 stdout\n1 stderr\n2 stdout\n2 stderr\n3 stdout\n3 stderr\n4 stdout\n4 stderr\n5 stdout\n5 stderr\n", buf.String()) + assert.Equal(t, 0, cmd.ExitStatus) +} + +func TestExecPipe(t *testing.T) { + output, exitStatus, err := ExecPipe(&Cmd{ + Args: []string{"testdata/prog"}, + }) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "1 stdout\n1 stderr\n2 stdout\n2 stderr\n3 stdout\n3 stderr\n4 stdout\n4 stderr\n5 stdout\n5 stderr\n", output) + assert.Equal(t, 0, exitStatus) +} + +func TestExecPipeError(t *testing.T) { + output, exitStatus, err := ExecPipe(&Cmd{ + Args: []string{"klwemlwkemw"}, + }) + + assert.Equal(t, "", output, "expected error to be") + assert.Equal(t, "fork/exec klwemlwkemw: no such file or directory", err.Error(), "expected error to be") + assert.Equal(t, 0, exitStatus) +} + +func TestExecPipeExitStatus(t *testing.T) { + output, exitStatus, err := ExecPipe(&Cmd{ + Args: []string{"testdata/prog", "1"}, + }) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "1 stdout\n1 stderr\n2 stdout\n2 stderr\n3 stdout\n3 stderr\n4 stdout\n4 stderr\n5 stdout\n5 stderr\n", output) + assert.Equal(t, 1, exitStatus) +} diff --git a/vendor/github.com/grammarly/rocker/src/util/doc.go b/vendor/github.com/grammarly/rocker/src/util/doc.go new file mode 100644 index 0000000..b577df0 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/util/doc.go @@ -0,0 +1,19 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package util provide different utilities that are used by rocker +// TODO: this should be refactored to a different packages +package util diff --git a/vendor/github.com/grammarly/rocker/src/util/filepath.go b/vendor/github.com/grammarly/rocker/src/util/filepath.go new file mode 100644 index 0000000..2e02d92 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/util/filepath.go @@ -0,0 +1,78 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +import ( + "fmt" + "os" + "os/user" + "path" + "path/filepath" + "strings" +) + +// ResolvePath resolves the subPath from baseDir such that the resultig path cannot +// go outside the baseDir +func ResolvePath(baseDir, subPath string) (resultPath string, err error) { + resultPath = path.Join(baseDir, subPath) + + // path.Join cleans the path and removes trailing slash if it's not the root path + // but we want to preserve trailing slash instead + if subPath[len(subPath)-1:] == "/" && resultPath[len(resultPath)-1:] != "/" { + resultPath = resultPath + "/" + } + + if resultPath == baseDir { + return resultPath, nil + } + + if !strings.HasPrefix(resultPath, baseDir+"/") { + return resultPath, fmt.Errorf("Invalid path: %s", subPath) + } + + return resultPath, nil +} + +// MakeAbsolute makes any path absolute, either according to a HOME or from a working directory +func MakeAbsolute(path string) (result string, err error) { + result = filepath.Clean(path) + if filepath.IsAbs(result) { + return result, nil + } + + if strings.HasPrefix(result, "~/") || result == "~" { + home := os.Getenv("HOME") + + // fallback to system user info + if home == "" { + usr, err := user.Current() + if err != nil { + return "", err + } + home = usr.HomeDir + } + + return home + result[1:], nil + } + + wd, err := os.Getwd() + if err != nil { + return "", err + } + + return filepath.Join(wd, path), nil +} diff --git a/vendor/github.com/grammarly/rocker/src/util/io.go b/vendor/github.com/grammarly/rocker/src/util/io.go new file mode 100644 index 0000000..9876542 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/util/io.go @@ -0,0 +1,40 @@ +/*- + * Copyright 2015 Grammarly, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +import ( + "bufio" + "fmt" + "io" +) + +// PrefixPipe creates an io wrapper that will add [prefix] to every line written +func PrefixPipe(prefix string, writer io.Writer) io.Writer { + reader, proxy := io.Pipe() + + go func(prefix string, reader io.Reader, writer io.Writer) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + writer.Write([]byte(prefix + scanner.Text() + "\n")) + } + if scannererr := scanner.Err(); scannererr != nil { + fmt.Fprint(writer, scannererr) + } + }(prefix, reader, writer) + + return proxy +} diff --git a/vendor/github.com/grammarly/rocker/src/util/testdata/prog b/vendor/github.com/grammarly/rocker/src/util/testdata/prog new file mode 100644 index 0000000..d809216 --- /dev/null +++ b/vendor/github.com/grammarly/rocker/src/util/testdata/prog @@ -0,0 +1,8 @@ +#!/bin/bash +for i in {1..5}; do + echo "$i stdout" + >&2 echo "$i stderr" + sleep .05 +done + +exit $1 From e921983fa716b36616122888e0850027c8f3079b Mon Sep 17 00:00:00 2001 From: Roman Khlystik Date: Fri, 12 Feb 2016 16:34:36 +0200 Subject: [PATCH 3/6] This `else` isn't required --- src/compose/client.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compose/client.go b/src/compose/client.go index 37df6e6..3f9a6c8 100644 --- a/src/compose/client.go +++ b/src/compose/client.go @@ -745,9 +745,8 @@ func (client *DockerClient) resolveVersions(local, hub bool, vars template.Vars, if candidate == nil { err = fmt.Errorf("Image not found: %s", container.Image) return - } else { - candidate.IsOldS3Name = container.Image.IsOldS3Name } + candidate.IsOldS3Name = container.Image.IsOldS3Name log.Infof("Resolve %s --> %s", container.Image, candidate.GetTag()) From f54623c1df884cd9d87a1551c01f9466346e0272 Mon Sep 17 00:00:00 2001 From: Roman Khlystik Date: Fri, 12 Feb 2016 17:01:46 +0200 Subject: [PATCH 4/6] Fixed tests --- src/compose/client_test.go | 4 +- src/compose/config/config_test.go | 2 +- src/compose/diff_test.go | 62 +++++++++++++++---------------- src/compose/docker_test.go | 2 +- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/compose/client_test.go b/src/compose/client_test.go index 5de8d0b..40e470a 100644 --- a/src/compose/client_test.go +++ b/src/compose/client_test.go @@ -25,10 +25,10 @@ import ( log "github.com/Sirupsen/logrus" "github.com/fsouza/go-dockerclient" + "github.com/grammarly/rocker/src/dockerclient" "github.com/grammarly/rocker/src/imagename" - "github.com/grammarly/rocker/src/rocker/dockerclient" - "github.com/grammarly/rocker/src/rocker/template" "github.com/grammarly/rocker/src/rocker/test" + "github.com/grammarly/rocker/src/template" "github.com/kr/pretty" "github.com/stretchr/testify/assert" ) diff --git a/src/compose/config/config_test.go b/src/compose/config/config_test.go index 0fd7f83..33d04a9 100644 --- a/src/compose/config/config_test.go +++ b/src/compose/config/config_test.go @@ -20,7 +20,7 @@ import ( "strings" "testing" - "github.com/grammarly/rocker/src/rocker/template" + "github.com/grammarly/rocker/src/template" "github.com/stretchr/testify/assert" ) diff --git a/src/compose/diff_test.go b/src/compose/diff_test.go index 8c26551..8e19bf7 100644 --- a/src/compose/diff_test.go +++ b/src/compose/diff_test.go @@ -22,7 +22,7 @@ import ( "testing" "github.com/grammarly/rocker/src/imagename" - "github.com/grammarly/rocker/src/rocker/template" + "github.com/grammarly/rocker/src/template" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -38,9 +38,9 @@ func TestComparatorSameValue(t *testing.T) { func TestDiffCreateAll(t *testing.T) { cmp := NewDiff("test") containers := []*Container{} - c1 := newContainer("test", "1", config.ContainerName{"test", "2"}, config.ContainerName{"test", "3"}) - c2 := newContainer("test", "2", config.ContainerName{"test", "4"}) - c3 := newContainer("test", "3", config.ContainerName{"test", "4"}) + c1 := newContainer("test", "1", config.ContainerName{Namespace: "test", Name: "2"}, config.ContainerName{Namespace: "test", Name: "3"}) + c2 := newContainer("test", "2", config.ContainerName{Namespace: "test", Name: "4"}) + c3 := newContainer("test", "3", config.ContainerName{Namespace: "test", Name: "4"}) c4 := newContainer("test", "4") containers = append(containers, c1, c2, c3, c4) actions, _ := cmp.Diff(containers, []*Container{}) @@ -157,8 +157,8 @@ func TestDiffEnsureFewExternalDependencies(t *testing.T) { c1 := newContainer("metrics", "1") c2 := newContainer("metrics", "2") c3 := newContainer("metrics", "3") - c4 := newContainer("test", "1", config.ContainerName{"metrics", "1"}, - config.ContainerName{"metrics", "2"}, config.ContainerName{"metrics", "3"}) + c4 := newContainer("test", "1", config.ContainerName{Namespace: "metrics", Name: "1"}, + config.ContainerName{Namespace: "metrics", Name: "2"}, config.ContainerName{Namespace: "metrics", Name: "3"}) actions, _ := cmp.Diff([]*Container{c4}, []*Container{c1, c2, c3}) mock := clientMock{} mock.On("EnsureContainerExist", c1).Return(nil) @@ -188,9 +188,9 @@ func TestDiffFailInMiddle(t *testing.T) { func TestDiffFailInDependent(t *testing.T) { cmp := NewDiff("test") - c1 := newContainer("test", "1", config.ContainerName{"test", "2"}) + c1 := newContainer("test", "1", config.ContainerName{Namespace: "test", Name: "2"}) c2 := newContainer("test", "2") - c3 := newContainer("test", "3", config.ContainerName{"test", "2"}) + c3 := newContainer("test", "3", config.ContainerName{Namespace: "test", Name: "2"}) actions, _ := cmp.Diff([]*Container{c1, c2, c3}, []*Container{}) mock := clientMock{} mock.On("RunContainer", c2).Return(fmt.Errorf("fail")) @@ -201,7 +201,7 @@ func TestDiffFailInDependent(t *testing.T) { func TestDiffInDependent(t *testing.T) { cmp := NewDiff("test") - c1 := newContainer("test", "1", config.ContainerName{"test", "2"}) + c1 := newContainer("test", "1", config.ContainerName{Namespace: "test", Name: "2"}) c2 := newContainer("test", "2") c2x := newContainer("test", "2") c2x.Config.Labels = map[string]string{"test": "test2"} @@ -224,17 +224,17 @@ func TestDiffInDependentNet(t *testing.T) { } c1 := &Container{ State: &ContainerState{Running: true}, - Name: &config.ContainerName{"test", "1"}, + Name: &config.ContainerName{Namespace: "test", Name: "1"}, Config: &config.Container{Net: c2NetName}, } c2 := &Container{ State: &ContainerState{Running: true}, - Name: &config.ContainerName{"test", "2"}, + Name: &config.ContainerName{Namespace: "test", Name: "2"}, Config: &config.Container{}, } c2x := &Container{ State: &ContainerState{Running: true}, - Name: &config.ContainerName{"test", "2"}, + Name: &config.ContainerName{Namespace: "test", Name: "2"}, Config: &config.Container{Labels: map[string]string{"test": "test2"}}, } actions, _ := cmp.Diff([]*Container{c1, c2x}, []*Container{c1, c2}) @@ -256,7 +256,7 @@ func TestDiffInDependentExternalNet(t *testing.T) { } c1 := &Container{ State: &ContainerState{Running: true}, - Name: &config.ContainerName{"test", "1"}, + Name: &config.ContainerName{Namespace: "test", Name: "1"}, Config: &config.Container{Net: c2NetName}, } c2 := newContainer("external", "2") @@ -271,9 +271,9 @@ func TestDiffInDependentExternalNet(t *testing.T) { func TestDiffForCycles(t *testing.T) { cmp := NewDiff("test") containers := []*Container{} - c1 := newContainer("test", "1", config.ContainerName{"test", "2"}) - c2 := newContainer("test", "2", config.ContainerName{"test", "3"}) - c3 := newContainer("test", "3", config.ContainerName{"test", "1"}) + c1 := newContainer("test", "1", config.ContainerName{Namespace: "test", Name: "2"}) + c2 := newContainer("test", "2", config.ContainerName{Namespace: "test", Name: "3"}) + c3 := newContainer("test", "3", config.ContainerName{Namespace: "test", Name: "1"}) containers = append(containers, c1, c2, c3) _, err := cmp.Diff(containers, []*Container{c1, c3}) assert.Error(t, err) @@ -286,12 +286,12 @@ func TestDiffDifferentConfig(t *testing.T) { cpusetCpus2 := "0-4" c1x := &Container{ State: &ContainerState{Running: true}, - Name: &config.ContainerName{"test", "1"}, + Name: &config.ContainerName{Namespace: "test", Name: "1"}, Config: &config.Container{CpusetCpus: &cpusetCpus1}, } c1y := &Container{ State: &ContainerState{Running: true}, - Name: &config.ContainerName{"test", "1"}, + Name: &config.ContainerName{Namespace: "test", Name: "1"}, Config: &config.Container{CpusetCpus: &cpusetCpus2}, } containers = append(containers, c1x) @@ -308,7 +308,7 @@ func TestDiffForExternalDependencies(t *testing.T) { cmp := NewDiff("test") containers := []*Container{} c1 := newContainer("test", "1") - c2 := newContainer("test", "2", config.ContainerName{"metrics", "1"}) + c2 := newContainer("test", "2", config.ContainerName{Namespace: "metrics", Name: "1"}) m1 := newContainer("metrics", "1") containers = append(containers, c1, c2) actions, _ := cmp.Diff(containers, []*Container{m1}) @@ -324,9 +324,9 @@ func TestDiffForExternalDependencies(t *testing.T) { func TestDiffCreateRemoving(t *testing.T) { cmp := NewDiff("test") containers := []*Container{} - c1 := newContainer("test", "1", config.ContainerName{"test", "2"}, config.ContainerName{"test", "3"}) - c2 := newContainer("test", "2", config.ContainerName{"test", "4"}) - c3 := newContainer("test", "3", config.ContainerName{"test", "4"}) + c1 := newContainer("test", "1", config.ContainerName{Namespace: "test", Name: "2"}, config.ContainerName{Namespace: "test", Name: "3"}) + c2 := newContainer("test", "2", config.ContainerName{Namespace: "test", Name: "4"}) + c3 := newContainer("test", "3", config.ContainerName{Namespace: "test", Name: "4"}) c4 := newContainer("test", "4") c5 := newContainer("test", "5") containers = append(containers, c1, c2, c3, c4) @@ -345,9 +345,9 @@ func TestDiffCreateRemoving(t *testing.T) { func TestDiffCreateSome(t *testing.T) { cmp := NewDiff("test") containers := []*Container{} - c1 := newContainer("test", "1", config.ContainerName{"test", "2"}, config.ContainerName{"test", "3"}) - c2 := newContainer("test", "2", config.ContainerName{"test", "4"}) - c3 := newContainer("test", "3", config.ContainerName{"test", "4"}) + c1 := newContainer("test", "1", config.ContainerName{Namespace: "test", Name: "2"}, config.ContainerName{Namespace: "test", Name: "3"}) + c2 := newContainer("test", "2", config.ContainerName{Namespace: "test", Name: "4"}) + c3 := newContainer("test", "3", config.ContainerName{Namespace: "test", Name: "4"}) c4 := newContainer("test", "4") containers = append(containers, c1, c2, c3, c4) actions, _ := cmp.Diff(containers, []*Container{c1}) @@ -362,7 +362,7 @@ func TestDiffCreateSome(t *testing.T) { func TestWaitForStart(t *testing.T) { cmp := NewDiff("test") - c1 := newContainerWaitFor("test", "1", config.ContainerName{"test", "2"}) + c1 := newContainerWaitFor("test", "1", config.ContainerName{Namespace: "test", Name: "2"}) c2 := newContainer("test", "2") actions, _ := cmp.Diff([]*Container{c1, c2}, []*Container{}) mock := clientMock{} @@ -376,7 +376,7 @@ func TestWaitForStart(t *testing.T) { func TestWaitForNotRestart(t *testing.T) { cmp := NewDiff("test") - c1 := newContainerWaitFor("test", "1", config.ContainerName{"test", "2"}) + c1 := newContainerWaitFor("test", "1", config.ContainerName{Namespace: "test", Name: "2"}) c2 := newContainer("test", "2") c2x := newContainer("test", "2") c2x.Config.Labels = map[string]string{"test": "test2"} @@ -394,12 +394,12 @@ func TestDiffRecovery(t *testing.T) { cmp := NewDiff("") c1x := &Container{ State: &ContainerState{Running: true}, - Name: &config.ContainerName{"test", "1"}, + Name: &config.ContainerName{Namespace: "test", Name: "1"}, Config: &config.Container{}, } c1y := &Container{ State: &ContainerState{Running: false}, - Name: &config.ContainerName{"test", "1"}, + Name: &config.ContainerName{Namespace: "test", Name: "1"}, Config: &config.Container{}, } actions, _ := cmp.Diff([]*Container{c1x}, []*Container{c1y}) @@ -415,7 +415,7 @@ func newContainer(namespace string, name string, dependencies ...config.Containe State: &ContainerState{ Running: true, }, - Name: &config.ContainerName{namespace, name}, + Name: &config.ContainerName{Namespace: namespace, Name: name}, Config: &config.Container{ VolumesFrom: dependencies, }} @@ -426,7 +426,7 @@ func newContainerWaitFor(namespace string, name string, dependencies ...config.C State: &ContainerState{ Running: true, }, - Name: &config.ContainerName{namespace, name}, + Name: &config.ContainerName{Namespace: namespace, Name: name}, Config: &config.Container{ WaitFor: dependencies, }} diff --git a/src/compose/docker_test.go b/src/compose/docker_test.go index f8e37cc..fe49325 100644 --- a/src/compose/docker_test.go +++ b/src/compose/docker_test.go @@ -22,7 +22,7 @@ import ( "testing" "github.com/fsouza/go-dockerclient" - "github.com/grammarly/rocker/src/rocker/dockerclient" + "github.com/grammarly/rocker/src/dockerclient" ) func TestEntrypointOverride(t *testing.T) { From bad334bab3c8a4dcbe673e4a433e2d4ffd0f6eae Mon Sep 17 00:00:00 2001 From: Roman Khlystik Date: Fri, 12 Feb 2016 18:10:31 +0200 Subject: [PATCH 5/6] Increaased version up to 0.1.5 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 845639e..9faa1b7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.4 +0.1.5 From d1e77df0259f63d1b87a0b8f385171294150044a Mon Sep 17 00:00:00 2001 From: Roman Khlystik Date: Fri, 12 Feb 2016 18:13:27 +0200 Subject: [PATCH 6/6] Updated changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c882f97..c8c84be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## [0.1.5](https://github.com/grammarly/rocker-compose/tree/0.1.5) (2016-02-12) + +[Full Changelog](https://github.com/grammarly/rocker-compose/compare/0.1.4...0.1.5) + +**Implemented enhancements:** + +- New S3 naming schema support [\#42](https://github.com/grammarly/rocker-compose/pull/42) + ## [0.1.4](https://github.com/grammarly/rocker-compose/tree/HEAD) (2016-02-08) [Full Changelog](https://github.com/grammarly/rocker-compose/compare/0.1.3...0.1.4)