diff --git a/cmd/docker-buildx/config.go b/cmd/docker-buildx/config.go index 3283140..562293c 100644 --- a/cmd/docker-buildx/config.go +++ b/cmd/docker-buildx/config.go @@ -127,6 +127,13 @@ func settingsFlags(settings *plugin.Settings) []cli.Flag { Usage: "generates tag names automatically based on git branch and git tag", Destination: &settings.Build.TagsAuto, }, + &cli.StringFlag{ + Name: "tags.defaultName", + EnvVars: []string{"PLUGIN_DEFAULT_TAG"}, + Usage: "allows setting an alternative to `latest` for the auto tag", + Destination: &settings.Build.TagsDefaultName, + Value: "latest", + }, &cli.StringFlag{ Name: "tags.suffix", EnvVars: []string{"PLUGIN_DEFAULT_SUFFIX", "PLUGIN_AUTO_TAG_SUFFIX"}, diff --git a/docs.md b/docs.md index 0782580..7bc68f9 100644 --- a/docs.md +++ b/docs.md @@ -99,6 +99,7 @@ It will automatically generate buildkit configuration to use custom CA certifica | `context` | `.` | sets the path of the build context to use | `default_tags`/`auto_tag` | `false` | generates tag names automatically based on git branch and git tag, tags supplied via `tags` are additionally added to the auto_tags without suffix | `default_suffix"`/`auto_tag_suffix`| *none* | generates tag names with the given suffix +| `default_tag` | `latest` | overrides the default tag name used when generating with `auto_tag` enabled | `label`/`labels` | *none* | sets labels to use for the image in format `=` | `default_labels`/`auto_labels` | `true` | sets docker image labels based on git information | `build_args` | *none* | sets custom build arguments for the build diff --git a/plugin/impl.go b/plugin/impl.go index fda375c..4db6bb1 100644 --- a/plugin/impl.go +++ b/plugin/impl.go @@ -43,28 +43,29 @@ type Login struct { // Build defines Docker build parameters. type Build struct { - Remote string // Git remote URL - Ref string // Git commit ref - Branch string // Git repository branch - Dockerfile string // Docker build Dockerfile - Context string // Docker build context - TagsAuto bool // Docker build auto tag - TagsSuffix string // Docker build tags with suffix - Tags cli.StringSlice // Docker build tags - LabelsAuto bool // Docker build auto labels - Labels cli.StringSlice // Docker build labels - Platforms cli.StringSlice // Docker build target platforms - Args cli.StringSlice // Docker build args - ArgsEnv cli.StringSlice // Docker build args from env - Target string // Docker build target - Output string // Docker build output - Pull bool // Docker build pull - CacheFrom cli.StringSlice // Docker build cache-from - Compress bool // Docker build compress - Repo cli.StringSlice // Docker build repository - NoCache bool // Docker build no-cache - AddHost cli.StringSlice // Docker build add-host - Quiet bool // Docker build quiet + Remote string // Git remote URL + Ref string // Git commit ref + Branch string // Git repository branch + Dockerfile string // Docker build Dockerfile + Context string // Docker build context + TagsAuto bool // Docker build auto tag + TagsDefaultName string // Docker build auto tag name override + TagsSuffix string // Docker build tags with suffix + Tags cli.StringSlice // Docker build tags + LabelsAuto bool // Docker build auto labels + Labels cli.StringSlice // Docker build labels + Platforms cli.StringSlice // Docker build target platforms + Args cli.StringSlice // Docker build args + ArgsEnv cli.StringSlice // Docker build args from env + Target string // Docker build target + Output string // Docker build output + Pull bool // Docker build pull + CacheFrom cli.StringSlice // Docker build cache-from + Compress bool // Docker build compress + Repo cli.StringSlice // Docker build repository + NoCache bool // Docker build no-cache + AddHost cli.StringSlice // Docker build add-host + Quiet bool // Docker build quiet } // Settings for the Plugin. @@ -110,6 +111,10 @@ func (p *Plugin) Validate() error { return err } + if !isSingleTag(p.settings.Build.TagsDefaultName) { + return fmt.Errorf("'%s' is not a valid, single tag", p.settings.Build.TagsDefaultName) + } + // beside the default login all other logins need to set a username and password for _, l := range p.settings.Logins[1:] { if l.anonymous() { @@ -126,6 +131,7 @@ func (p *Plugin) Validate() error { tag, err := DefaultTagSuffix( p.settings.Build.Ref, p.settings.Build.TagsSuffix, + p.settings.Build.TagsDefaultName, ) if err != nil { logrus.Printf("cannot build docker image for %s, invalid semantic version", p.settings.Build.Ref) diff --git a/plugin/impl_test.go b/plugin/impl_test.go index c311fd4..ebbbb47 100644 --- a/plugin/impl_test.go +++ b/plugin/impl_test.go @@ -13,10 +13,11 @@ var defaultSettings = Settings{ StoragePath: "/var/lib/docker", }, Build: Build{ - Context: ".", - Tags: *cli.NewStringSlice("latest"), - LabelsAuto: true, - Pull: true, + Context: ".", + Tags: *cli.NewStringSlice("latest"), + TagsDefaultName: "latest", + LabelsAuto: true, + Pull: true, }, DefaultLogin: Login{ Registry: "https://index.docker.io/v1/", diff --git a/plugin/tags.go b/plugin/tags.go index e5307ad..a141e89 100644 --- a/plugin/tags.go +++ b/plugin/tags.go @@ -2,6 +2,7 @@ package plugin import ( "fmt" + "regexp" "strings" "github.com/coreos/go-semver/semver" @@ -9,8 +10,8 @@ import ( // DefaultTagSuffix returns a set of default suggested tags // based on the commit ref with an attached suffix. -func DefaultTagSuffix(ref, suffix string) ([]string, error) { - tags, err := DefaultTags(ref) +func DefaultTagSuffix(ref, defaultTag, suffix string) ([]string, error) { + tags, err := DefaultTags(ref, defaultTag) if err != nil { return nil, err } @@ -18,7 +19,7 @@ func DefaultTagSuffix(ref, suffix string) ([]string, error) { return tags, nil } for i, tag := range tags { - if tag == "latest" { + if tag == defaultTag { tags[i] = suffix } else { tags[i] = fmt.Sprintf("%s-%s", tag, suffix) @@ -39,14 +40,14 @@ func splitOff(input, delim string) string { // DefaultTags returns a set of default suggested tags based on // the commit ref. -func DefaultTags(ref string) ([]string, error) { +func DefaultTags(ref, defaultTag string) ([]string, error) { if !strings.HasPrefix(ref, "refs/tags/") { - return []string{"latest"}, nil + return []string{defaultTag}, nil } v := stripTagPrefix(ref) version, err := semver.NewVersion(v) if err != nil { - return []string{"latest"}, err + return []string{defaultTag}, err } if version.PreRelease != "" || version.Metadata != "" { return []string{ @@ -91,3 +92,8 @@ func stripTagPrefix(ref string) string { ref = strings.TrimPrefix(ref, "v") return ref } + +func isSingleTag(tag string) bool { + // currently only filtering for seperators, this could be improoved... + return !regexp.MustCompile(`[,\s]+`).MatchString(tag) && len(tag) > 0 && len(tag) <= 128 +} diff --git a/plugin/tags_test.go b/plugin/tags_test.go index e9bffdc..8fa1d24 100644 --- a/plugin/tags_test.go +++ b/plugin/tags_test.go @@ -25,19 +25,20 @@ func Test_stripTagPrefix(t *testing.T) { func TestDefaultTags(t *testing.T) { tests := []struct { - Before string - After []string + DefaultTag string + Before string + After []string }{ - {"", []string{"latest"}}, - {"refs/heads/master", []string{"latest"}}, - {"refs/tags/0.9.0", []string{"0.9", "0.9.0"}}, - {"refs/tags/1.0.0", []string{"1", "1.0", "1.0.0"}}, - {"refs/tags/v1.0.0", []string{"1", "1.0", "1.0.0"}}, - {"refs/tags/v1.0.0-alpha.1", []string{"1.0.0-alpha.1"}}, + {"latest", "", []string{"latest"}}, + {"latest", "refs/heads/master", []string{"latest"}}, + {"latest", "refs/tags/0.9.0", []string{"0.9", "0.9.0"}}, + {"latest", "refs/tags/1.0.0", []string{"1", "1.0", "1.0.0"}}, + {"latest", "refs/tags/v1.0.0", []string{"1", "1.0", "1.0.0"}}, + {"latest", "refs/tags/v1.0.0-alpha.1", []string{"1.0.0-alpha.1"}}, } for _, test := range tests { - tags, err := DefaultTags(test.Before) + tags, err := DefaultTags(test.Before, test.DefaultTag) if err != nil { t.Error(err) continue @@ -50,13 +51,22 @@ func TestDefaultTags(t *testing.T) { } func TestDefaultTagsError(t *testing.T) { - tests := []string{ - "refs/tags/x1.0.0", - "refs/tags/20190203", + tests := []struct { + DefaultTag string + Before string + }{ + { + DefaultTag: "latest", + Before: "refs/tags/x1.0.0", + }, + { + DefaultTag: "latest", + Before: "refs/tags/20190203", + }, } for _, test := range tests { - _, err := DefaultTags(test) + _, err := DefaultTags(test.Before, test.DefaultTag) if err == nil { t.Errorf("Expect tag error for %s", test) } @@ -65,30 +75,59 @@ func TestDefaultTagsError(t *testing.T) { func TestDefaultTagSuffix(t *testing.T) { tests := []struct { - Before string - Suffix string - After []string + Name string + Before string + Suffix string + After []string + DefaultTag string }{ - // without suffix { - After: []string{"latest"}, + Name: "Default tag without suffix", + DefaultTag: "latest", + After: []string{"latest"}, }, { - Before: "refs/tags/v1.0.0", + Name: "Overridden default tag without suffix", + DefaultTag: "next", + After: []string{"next"}, + }, + { + Name: "Generate version", + DefaultTag: "latest", + Before: "refs/tags/v1.0.0", After: []string{ "1", "1.0", "1.0.0", }, }, - // with suffix { - Suffix: "linux-amd64", - After: []string{"linux-amd64"}, + Name: "Generate version with overridden default tag", + DefaultTag: "next", + Before: "refs/tags/v1.0.0", + After: []string{ + "1", + "1.0", + "1.0.0", + }, }, { - Before: "refs/tags/v1.0.0", - Suffix: "linux-amd64", + Name: "Default tag with suffix (linux-amd64)", + DefaultTag: "latest", + Suffix: "linux-amd64", + After: []string{"linux-amd64"}, + }, + { + Name: "Overridden default tag with suffix (linux-amd64)", + DefaultTag: "next", + Suffix: "linux-amd64", + After: []string{"linux-amd64"}, + }, + { + Name: "Generate version with suffix (linux-amd64)", + DefaultTag: "latest", + Before: "refs/tags/v1.0.0", + Suffix: "linux-amd64", After: []string{ "1-linux-amd64", "1.0-linux-amd64", @@ -96,12 +135,33 @@ func TestDefaultTagSuffix(t *testing.T) { }, }, { - Suffix: "nanoserver", - After: []string{"nanoserver"}, + Name: "Generate version with suffix (linux-amd64) and overridden default tag (next)", + DefaultTag: "next", + Before: "refs/tags/v1.0.0", + Suffix: "linux-amd64", + After: []string{ + "1-linux-amd64", + "1.0-linux-amd64", + "1.0.0-linux-amd64", + }, }, { - Before: "refs/tags/v1.9.2", - Suffix: "nanoserver", + Name: "Default tag with suffix (nanoserver)", + DefaultTag: "latest", + Suffix: "nanoserver", + After: []string{"nanoserver"}, + }, + { + Name: "Overridden default tag with suffix (nanoserver)", + DefaultTag: "next", + Suffix: "nanoserver", + After: []string{"nanoserver"}, + }, + { + Name: "Generate version with suffix (nanoserver)", + DefaultTag: "latest", + Before: "refs/tags/v1.9.2", + Suffix: "nanoserver", After: []string{ "1-nanoserver", "1.9-nanoserver", @@ -109,8 +169,32 @@ func TestDefaultTagSuffix(t *testing.T) { }, }, { - Before: "refs/tags/v18.06.0", - Suffix: "nanoserver", + Name: "Generate version with suffix (nanoserver) and overridden default tag (next)", + DefaultTag: "latest", + Before: "refs/tags/v1.9.2", + Suffix: "nanoserver", + After: []string{ + "1-nanoserver", + "1.9-nanoserver", + "1.9.2-nanoserver", + }, + }, + { + Name: "Generate version with suffix (zero-padded version)", + DefaultTag: "latest", + Before: "refs/tags/v18.06.0", + Suffix: "nanoserver", + After: []string{ + "18-nanoserver", + "18.06-nanoserver", + "18.06.0-nanoserver", + }, + }, + { + Name: "Generate version with suffix (zero-padded version) with overridden default tag (next)", + DefaultTag: "next", + Before: "refs/tags/v18.06.0", + Suffix: "nanoserver", After: []string{ "18-nanoserver", "18.06-nanoserver", @@ -120,14 +204,14 @@ func TestDefaultTagSuffix(t *testing.T) { } for _, test := range tests { - tag, err := DefaultTagSuffix(test.Before, test.Suffix) + tag, err := DefaultTagSuffix(test.Before, test.DefaultTag, test.Suffix) if err != nil { t.Error(err) continue } got, want := tag, test.After if !reflect.DeepEqual(got, want) { - t.Errorf("Got tag %v, want %v", got, want) + t.Errorf("%q. Got tag %v, want %v", test.Name, got, want) } } } @@ -195,3 +279,26 @@ func TestUseDefaultTag(t *testing.T) { } } } + +func Test_isSingleTag(t *testing.T) { + tests := []struct { + Tag string + IsValid bool + }{ + {"latest", true}, + {" latest", false}, + {"LaTest__Hi", true}, + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ__.-0123456789", true}, + {"_wierd.but-ok1", true}, + {"latest ", false}, + {"latest,next", false}, + // more tests to be added, once the validation is more powerful + } + + for _, test := range tests { + valid := isSingleTag(test.Tag) + if valid != test.IsValid { + t.Errorf("Tag verification '%s' tag %v, want %v", test.Tag, valid, test.IsValid) + } + } +}