From 92231bc93056d88aa6419189c9de71109ef604a1 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 18 Jan 2018 17:40:44 -0200 Subject: [PATCH] feat: support multiple tag_templates for docker --- config/config.go | 19 ++--- pipeline/docker/docker.go | 56 +++++++------- pipeline/docker/docker_test.go | 135 +++++++++++++++++++-------------- 3 files changed, 116 insertions(+), 94 deletions(-) diff --git a/config/config.go b/config/config.go index b68d2fc92..523c45696 100644 --- a/config/config.go +++ b/config/config.go @@ -198,15 +198,16 @@ type Checksum struct { // Docker image config type Docker struct { - Binary string `yaml:",omitempty"` - Goos string `yaml:",omitempty"` - Goarch string `yaml:",omitempty"` - Goarm string `yaml:",omitempty"` - Image string `yaml:",omitempty"` - Dockerfile string `yaml:",omitempty"` - Latest bool `yaml:",omitempty"` - TagTemplate string `yaml:"tag_template,omitempty"` - Files []string `yaml:"extra_files,omitempty"` + Binary string `yaml:",omitempty"` + Goos string `yaml:",omitempty"` + Goarch string `yaml:",omitempty"` + Goarm string `yaml:",omitempty"` + Image string `yaml:",omitempty"` + Dockerfile string `yaml:",omitempty"` + Latest bool `yaml:",omitempty"` + OldTagTemplate string `yaml:"tag_template,omitempty"` + TagTemplates []string `yaml:"tag_templates,omitempty"` + Files []string `yaml:"extra_files,omitempty"` // Capture all undefined fields and should be empty after loading XXX map[string]interface{} `yaml:",inline"` diff --git a/pipeline/docker/docker.go b/pipeline/docker/docker.go index cd6e8c448..64f934219 100644 --- a/pipeline/docker/docker.go +++ b/pipeline/docker/docker.go @@ -34,8 +34,12 @@ func (Pipe) String() string { func (Pipe) Default(ctx *context.Context) error { for i := range ctx.Config.Dockers { var docker = &ctx.Config.Dockers[i] - if docker.TagTemplate == "" { - docker.TagTemplate = "{{ .Version }}" + if docker.OldTagTemplate != "" { + // TODO: deprecate docker.tag_template in favor of docker.tag_templates + docker.TagTemplates = append(docker.TagTemplates, docker.OldTagTemplate) + } + if len(docker.TagTemplates) == 0 { + docker.TagTemplates = append(docker.TagTemplates, "{{ .Version }}") } if docker.Goos == "" { docker.Goos = "linux" @@ -43,6 +47,10 @@ func (Pipe) Default(ctx *context.Context) error { if docker.Goarch == "" { docker.Goarch = "amd64" } + if docker.Latest { + // TODO: deprecate docker.Latest in favor of multiple tags? + docker.TagTemplates = append(docker.TagTemplates, "latest") + } } // only set defaults if there is exacly 1 docker setup in the config file. if len(ctx.Config.Dockers) != 1 { @@ -105,11 +113,9 @@ func doRun(ctx *context.Context) error { return g.Wait() } -func tagName(ctx *context.Context, docker config.Docker) (string, error) { +func tagName(ctx *context.Context, tagTemplate string) (string, error) { var out bytes.Buffer - t, err := template.New("tag"). - Option("missingkey=error"). - Parse(docker.TagTemplate) + t, err := template.New("tag").Option("missingkey=error").Parse(tagTemplate) if err != nil { return "", err } @@ -128,13 +134,14 @@ func tagName(ctx *context.Context, docker config.Docker) (string, error) { func process(ctx *context.Context, docker config.Docker, artifact artifact.Artifact) error { var root = filepath.Dir(artifact.Path) var dockerfile = filepath.Join(root, filepath.Base(docker.Dockerfile)) - tag, err := tagName(ctx, docker) - if err != nil { - return err + var images []string + for _, tagTemplate := range docker.TagTemplates { + tag, err := tagName(ctx, tagTemplate) + if err != nil { + return errors.Wrapf(err, "failed to execute tag template '%s'", tagTemplate) + } + images = append(images, fmt.Sprintf("%s:%s", docker.Image, tag)) } - var image = fmt.Sprintf("%s:%s", docker.Image, tag) - var latest = fmt.Sprintf("%s:latest", docker.Image) - if err := os.Link(docker.Dockerfile, dockerfile); err != nil { return errors.Wrap(err, "failed to link dockerfile") } @@ -143,16 +150,15 @@ func process(ctx *context.Context, docker config.Docker, artifact artifact.Artif return errors.Wrapf(err, "failed to link extra file '%s'", file) } } - if err := dockerBuild(ctx, root, dockerfile, image); err != nil { + if err := dockerBuild(ctx, root, dockerfile, images[0]); err != nil { return err } - if docker.Latest { - if err := dockerTag(ctx, image, latest); err != nil { + for _, img := range images[1:] { + if err := dockerTag(ctx, images[0], img); err != nil { return err } } - - return publish(ctx, docker, image, latest) + return publish(ctx, docker, images) } // walks the src, recreating dirs and hard-linking files @@ -178,21 +184,17 @@ func link(src, dest string) error { }) } -func publish(ctx *context.Context, docker config.Docker, image, latest string) error { +func publish(ctx *context.Context, docker config.Docker, images []string) error { if !ctx.Publish { log.Warn("skipping push because --skip-publish is set") return nil } - if err := dockerPush(ctx, docker, image); err != nil { - return err + for _, image := range images { + if err := dockerPush(ctx, docker, image); err != nil { + return err + } } - if !docker.Latest { - return nil - } - if err := dockerTag(ctx, image, latest); err != nil { - return err - } - return dockerPush(ctx, docker, latest) + return nil } func dockerBuild(ctx *context.Context, root, dockerfile, image string) error { diff --git a/pipeline/docker/docker_test.go b/pipeline/docker/docker_test.go index cb90f23ab..2e733c3e4 100644 --- a/pipeline/docker/docker_test.go +++ b/pipeline/docker/docker_test.go @@ -58,13 +58,15 @@ func TestRunPipe(t *testing.T) { "valid": { publish: true, docker: config.Docker{ - Image: registry + "goreleaser/test_run_pipe", - Goos: "linux", - Goarch: "amd64", - Dockerfile: "testdata/Dockerfile", - Binary: "mybin", - Latest: true, - TagTemplate: "{{.Tag}}-{{.Env.FOO}}", + Image: registry + "goreleaser/test_run_pipe", + Goos: "linux", + Goarch: "amd64", + Dockerfile: "testdata/Dockerfile", + Binary: "mybin", + TagTemplates: []string{ + "{{.Tag}}-{{.Env.FOO}}", + "latest", + }, Files: []string{ "testdata/extra_file.txt", }, @@ -78,13 +80,14 @@ func TestRunPipe(t *testing.T) { "valid_no_latest": { publish: true, docker: config.Docker{ - Image: registry + "goreleaser/test_run_pipe", - Goos: "linux", - Goarch: "amd64", - Dockerfile: "testdata/Dockerfile", - Binary: "mybin", - Latest: false, - TagTemplate: "{{.Version}}", + Image: registry + "goreleaser/test_run_pipe", + Goos: "linux", + Goarch: "amd64", + Dockerfile: "testdata/Dockerfile", + Binary: "mybin", + TagTemplates: []string{ + "{{.Version}}", + }, Files: []string{ "testdata/extra_file.txt", }, @@ -97,13 +100,15 @@ func TestRunPipe(t *testing.T) { "valid_dont_publish": { publish: false, docker: config.Docker{ - Image: registry + "goreleaser/test_run_pipe", - Goos: "linux", - Goarch: "amd64", - Dockerfile: "testdata/Dockerfile", - Binary: "mybin", - Latest: true, - TagTemplate: "{{.Tag}}-{{.Env.FOO}}", + Image: registry + "goreleaser/test_run_pipe", + Goos: "linux", + Goarch: "amd64", + Dockerfile: "testdata/Dockerfile", + Binary: "mybin", + TagTemplates: []string{ + "{{.Tag}}-{{.Env.FOO}}", + "latest", + }, Files: []string{ "testdata/extra_file.txt", }, @@ -117,51 +122,58 @@ func TestRunPipe(t *testing.T) { "bad_dockerfile": { publish: true, docker: config.Docker{ - Image: registry + "goreleaser/test_run_pipe", - Goos: "linux", - Goarch: "amd64", - Dockerfile: "testdata/Dockerfile.bad", - Binary: "mybin", - TagTemplate: "{{.Version}}", + Image: registry + "goreleaser/test_run_pipe", + Goos: "linux", + Goarch: "amd64", + Dockerfile: "testdata/Dockerfile.bad", + Binary: "mybin", + TagTemplates: []string{ + "{{.Version}}", + }, }, err: "pull access denied for nope, repository does not exist", }, "template_error": { publish: true, docker: config.Docker{ - Image: registry + "goreleaser/test_run_pipe", - Goos: "linux", - Goarch: "amd64", - Dockerfile: "testdata/Dockerfile", - Binary: "mybin", - Latest: true, - TagTemplate: "{{.Tag}", + Image: registry + "goreleaser/test_run_pipe", + Goos: "linux", + Goarch: "amd64", + Dockerfile: "testdata/Dockerfile", + Binary: "mybin", + TagTemplates: []string{ + "{{.Tag}", + }, }, err: `template: tag:1: unexpected "}" in operand`, }, "missing_env_on_template": { publish: true, docker: config.Docker{ - Image: registry + "goreleaser/test_run_pipe", - Goos: "linux", - Goarch: "amd64", - Dockerfile: "testdata/Dockerfile", - Binary: "mybin", - Latest: true, - TagTemplate: "{{.Env.NOPE}}", + Image: registry + "goreleaser/test_run_pipe", + Goos: "linux", + Goarch: "amd64", + Dockerfile: "testdata/Dockerfile", + Binary: "mybin", + TagTemplates: []string{ + "{{.Env.NOPE}}", + }, }, err: `template: tag:1:6: executing "tag" at <.Env.NOPE>: map has no entry for key "NOPE"`, }, "no_permissions": { publish: true, docker: config.Docker{ - Image: "docker.io/nope", - Goos: "linux", - Goarch: "amd64", - Binary: "mybin", - Dockerfile: "testdata/Dockerfile", - TagTemplate: "{{.Tag}}", - Latest: true, + Image: "docker.io/nope", + Goos: "linux", + Goarch: "amd64", + Binary: "mybin", + Dockerfile: "testdata/Dockerfile", + TagTemplates: []string{ + "{{.Tag}}", + "latest", + }, + Latest: true, }, expect: []string{ "docker.io/nope:latest", @@ -172,12 +184,14 @@ func TestRunPipe(t *testing.T) { "dockerfile_doesnt_exist": { publish: true, docker: config.Docker{ - Image: "whatever", - Goos: "linux", - Goarch: "amd64", - Binary: "mybin", - Dockerfile: "testdata/Dockerfilezzz", - TagTemplate: "{{.Tag}}", + Image: "whatever", + Goos: "linux", + Goarch: "amd64", + Binary: "mybin", + Dockerfile: "testdata/Dockerfilezzz", + TagTemplates: []string{ + "{{.Tag}}", + }, }, err: `failed to link dockerfile`, }, @@ -191,8 +205,10 @@ func TestRunPipe(t *testing.T) { Files: []string{ "testdata/nope.txt", }, - Dockerfile: "testdata/Dockerfile", - TagTemplate: "{{.Tag}}", + Dockerfile: "testdata/Dockerfile", + TagTemplates: []string{ + "{{.Tag}}", + }, }, err: `failed to link extra file 'testdata/nope.txt'`, }, @@ -339,7 +355,9 @@ func TestDefault(t *testing.T) { assert.Equal(t, "amd64", docker.Goarch) assert.Equal(t, ctx.Config.Builds[0].Binary, docker.Binary) assert.Equal(t, "Dockerfile", docker.Dockerfile) - assert.Equal(t, "{{ .Version }}", docker.TagTemplate) + assert.Empty(t, docker.OldTagTemplate) + assert.Equal(t, []string{"{{ .Version }}", "latest"}, docker.TagTemplates) + } func TestDefaultNoDockers(t *testing.T) { @@ -371,7 +389,8 @@ func TestDefaultSet(t *testing.T) { assert.Equal(t, "windows", docker.Goos) assert.Equal(t, "i386", docker.Goarch) assert.Equal(t, "bar", docker.Binary) - assert.Equal(t, "{{ .Version }}", docker.TagTemplate) + assert.Empty(t, docker.OldTagTemplate) + assert.Equal(t, []string{"{{ .Version }}"}, docker.TagTemplates) assert.Equal(t, "Dockerfile.foo", docker.Dockerfile) }