diff --git a/fix/fixer.go b/fix/fixer.go index e941d2e62..11bd4160c 100644 --- a/fix/fixer.go +++ b/fix/fixer.go @@ -48,6 +48,7 @@ func init() { "galaxy-command": new(FixerGalaxyCommand), "comm-config": new(FixerCommConfig), "ssh-wait-timeout": new(FixerSSHTimout), + "docker-tag-tags": new(FixerDockerTagtoTags), } FixerOrder = []string{ @@ -68,6 +69,7 @@ func init() { "amazon-private-ip", "amazon-temp-sec-cidrs", "docker-email", + "docker-tag-tags", "powershell-escapes", "vmware-compaction", "hyperv-deprecations", diff --git a/fix/fixer_pp_docker_tag_tags.go b/fix/fixer_pp_docker_tag_tags.go new file mode 100644 index 000000000..911459cae --- /dev/null +++ b/fix/fixer_pp_docker_tag_tags.go @@ -0,0 +1,108 @@ +package fix + +import ( + "strings" + + "github.com/mitchellh/mapstructure" +) + +// FixerDockerTagtoTags renames tag to tags +type FixerDockerTagtoTags struct{} + +func (FixerDockerTagtoTags) Fix(input map[string]interface{}) (map[string]interface{}, error) { + if input["post-processors"] == nil { + return input, nil + } + + // Our template type we'll use for this fixer only + type template struct { + PP `mapstructure:",squash"` + } + + // Decode the input into our structure, if we can + var tpl template + if err := mapstructure.Decode(input, &tpl); err != nil { + return nil, err + } + + // Go through each post-processor and get out all the complex configs + pps := tpl.ppList() + + for _, pp := range pps { + ppTypeRaw, ok := pp["type"] + if !ok { + continue + } + + if ppType, ok := ppTypeRaw.(string); !ok { + continue + } else if ppType != "docker-tag" { + continue + } + + // Create a []string to hold tag and tags values + allTags := []string{} + + tagRaw, ok := pp["tag"] + if ok { + // Gather all "tag" into the []string + switch t := tagRaw.(type) { + case []interface{}: + for _, tag := range t { + allTags = append(allTags, tag.(string)) + } + case []string: + allTags = append(allTags, t...) + case string: + tList := strings.Split(t, ",") + for _, tag := range tList { + allTags = append(allTags, strings.TrimSpace(tag)) + } + } + } + + // Now check to see if they already have the "tags" field + tagsRaw, ok := pp["tags"] + if ok { + // Gather all "tag" into the []string + switch t := tagsRaw.(type) { + case []interface{}: + for _, tag := range t { + allTags = append(allTags, tag.(string)) + } + case []string: + allTags = append(allTags, t...) + case string: + tList := strings.Split(t, ",") + for _, tag := range tList { + allTags = append(allTags, strings.TrimSpace(tag)) + } + } + } + + // Now deduplicate the tags in the list so we don't tag the same thing + // multiple times. + deduplicater := map[string]bool{} + for _, tag := range allTags { + if ok := deduplicater[tag]; !ok { + deduplicater[tag] = true + } + } + + finalTags := []string{} + for k := range deduplicater { + finalTags = append(finalTags, k) + } + + // Delete tag key, and set tags key to the final deduplicated list. + delete(pp, "tag") + pp["tags"] = finalTags + } + + input["post-processors"] = tpl.PostProcessors + return input, nil +} + +func (FixerDockerTagtoTags) Synopsis() string { + return `Updates "docker" post-processor so any "tag" field is renamed to "tags".` +} diff --git a/fix/fixer_pp_docker_tag_tags_test.go b/fix/fixer_pp_docker_tag_tags_test.go new file mode 100644 index 000000000..957f90ba9 --- /dev/null +++ b/fix/fixer_pp_docker_tag_tags_test.go @@ -0,0 +1,52 @@ +package fix + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFixerDockerTags(t *testing.T) { + var _ Fixer = new(FixerVagrantPPOverride) +} + +func TestFixerDockerTags_Fix(t *testing.T) { + var f FixerDockerTagtoTags + + input := map[string]interface{}{ + "post-processors": []interface{}{ + map[string]interface{}{ + "type": "docker-tag", + "tag": "foo", + "tags": []string{"foo", "bar"}, + }, + []interface{}{ + map[string]interface{}{ + "type": "docker-tag", + "tag": []string{"baz"}, + "tags": []string{"foo", "bar"}, + }, + }, + }, + } + + expected := map[string]interface{}{ + "post-processors": []interface{}{ + map[string]interface{}{ + "type": "docker-tag", + "tags": []string{"foo", "bar"}, + }, + []interface{}{ + map[string]interface{}{ + "type": "docker-tag", + "tags": []string{"baz", "foo", "bar"}, + }, + }, + }, + } + + output, err := f.Fix(input) + assert.NoError(t, err) + + assert.Equalf(t, expected, output, "should have removed tag from template") +} diff --git a/post-processor/docker-tag/post-processor.go b/post-processor/docker-tag/post-processor.go index b4b27350a..e7ed00c72 100644 --- a/post-processor/docker-tag/post-processor.go +++ b/post-processor/docker-tag/post-processor.go @@ -20,9 +20,11 @@ const BuilderId = "packer.post-processor.docker-tag" type Config struct { common.PackerConfig `mapstructure:",squash"` - Repository string `mapstructure:"repository"` - Tag []string `mapstructure:"tag"` - Force bool + Repository string `mapstructure:"repository"` + // Kept for backwards compatability + Tag []string `mapstructure:"tag"` + Tags []string `mapstructure:"tags"` + Force bool ctx interpolate.Context } @@ -47,11 +49,23 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { return err } + // combine Tag and Tags fields + allTags := p.config.Tags + allTags = append(allTags, p.config.Tag...) + + p.config.Tags = allTags + return nil } func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, bool, error) { + if len(p.config.Tag) > 0 { + ui.Say("Deprecation warning: \"tag\" option has been replaced with " + + "\"tags\". In future versions of Packer, this configuration may " + + "not work. Please call `packer fix` on your template to update.") + } + if artifact.BuilderId() != BuilderId && artifact.BuilderId() != dockerimport.BuilderId { err := fmt.Errorf( @@ -69,8 +83,9 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact importRepo := p.config.Repository var lastTaggedRepo = importRepo RepoTags := []string{} - if len(p.config.Tag) > 0 { - for _, tag := range p.config.Tag { + + if len(p.config.Tags) > 0 { + for _, tag := range p.config.Tags { local := importRepo + ":" + tag ui.Message("Tagging image: " + artifact.Id()) ui.Message("Repository: " + local) diff --git a/post-processor/docker-tag/post-processor.hcl2spec.go b/post-processor/docker-tag/post-processor.hcl2spec.go index 17e581821..5a93ef6de 100644 --- a/post-processor/docker-tag/post-processor.hcl2spec.go +++ b/post-processor/docker-tag/post-processor.hcl2spec.go @@ -18,6 +18,7 @@ type FlatConfig struct { PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` Repository *string `mapstructure:"repository" cty:"repository"` Tag []string `mapstructure:"tag" cty:"tag"` + Tags []string `mapstructure:"tags" cty:"tags"` Force *bool `cty:"force"` } @@ -42,6 +43,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, "repository": &hcldec.AttrSpec{Name: "repository", Type: cty.String, Required: false}, "tag": &hcldec.AttrSpec{Name: "tag", Type: cty.List(cty.String), Required: false}, + "tags": &hcldec.AttrSpec{Name: "tags", Type: cty.List(cty.String), Required: false}, "force": &hcldec.AttrSpec{Name: "force", Type: cty.Bool, Required: false}, } return s diff --git a/post-processor/docker-tag/post-processor_test.go b/post-processor/docker-tag/post-processor_test.go index 883eeb331..19d12c555 100644 --- a/post-processor/docker-tag/post-processor_test.go +++ b/post-processor/docker-tag/post-processor_test.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/packer/builder/docker" "github.com/hashicorp/packer/packer" dockerimport "github.com/hashicorp/packer/post-processor/docker-import" + "github.com/stretchr/testify/assert" ) func testConfig() map[string]interface{} { @@ -166,3 +167,30 @@ func TestPostProcessor_PostProcess_NoTag(t *testing.T) { t.Fatal("bad force") } } + +func TestPostProcessor_PostProcess_Tag_vs_Tags(t *testing.T) { + testCases := []map[string]interface{}{ + { + "tag": "bar,buzz", + "tags": []string{"bang"}, + }, + { + "tag": []string{"bar", "buzz"}, + "tags": []string{"bang"}, + }, + { + "tag": []string{"bar"}, + "tags": []string{"buzz", "bang"}, + }, + } + + for _, tc := range testCases { + var p PostProcessor + if err := p.Configure(tc); err != nil { + t.Fatalf("err: %s", err) + } + assert.ElementsMatchf(t, p.config.Tags, []string{"bar", "buzz", "bang"}, + "tag and tags fields should be combined into tags fields. Recieved: %#v", + p.config.Tags) + } +} diff --git a/website/pages/docs/post-processors/docker-tag.mdx b/website/pages/docs/post-processors/docker-tag.mdx index 7cc38643e..eea6a5c11 100644 --- a/website/pages/docs/post-processors/docker-tag.mdx +++ b/website/pages/docs/post-processors/docker-tag.mdx @@ -30,7 +30,9 @@ settings are optional. - `repository` (string) - The repository of the image. -- `tag` (array of strings) - The tag for the image. By default this is not set. +- `tags` (array of strings) - A list of tags for the image. By default this is + not set. Valid examples include: `"tags": "mytag"` or + `"tags": ["mytag-1", "mytag-2"]` - `force` (boolean) - If true, this post-processor forcibly tag the image even if tag name is collided. Default to `false`. But it will be ignored if