Add configurable tag for use with auto_tag (#36)

Implements #30, allows setting a (single) tag to override the default of `latest` allows for the woodpecker-ci usecase of setting `next` as the tag for the default branch.

Co-authored-by: Gapodo <gapodo@datenclown.at>
Reviewed-on: https://codeberg.org/woodpecker-plugins/plugin-docker-buildx/pulls/36
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: gapodo <gapodo@geekvoid.net>
Co-committed-by: gapodo <gapodo@geekvoid.net>
This commit is contained in:
gapodo 2022-11-05 03:25:44 +01:00 committed by 6543
parent f10f7b4c7f
commit fce3289743
6 changed files with 192 additions and 64 deletions

View File

@ -127,6 +127,13 @@ func settingsFlags(settings *plugin.Settings) []cli.Flag {
Usage: "generates tag names automatically based on git branch and git tag", Usage: "generates tag names automatically based on git branch and git tag",
Destination: &settings.Build.TagsAuto, 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{ &cli.StringFlag{
Name: "tags.suffix", Name: "tags.suffix",
EnvVars: []string{"PLUGIN_DEFAULT_SUFFIX", "PLUGIN_AUTO_TAG_SUFFIX"}, EnvVars: []string{"PLUGIN_DEFAULT_SUFFIX", "PLUGIN_AUTO_TAG_SUFFIX"},

View File

@ -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 | `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_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_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 `<name>=<value>` | `label`/`labels` | *none* | sets labels to use for the image in format `<name>=<value>`
| `default_labels`/`auto_labels` | `true` | sets docker image labels based on git information | `default_labels`/`auto_labels` | `true` | sets docker image labels based on git information
| `build_args` | *none* | sets custom build arguments for the build | `build_args` | *none* | sets custom build arguments for the build

View File

@ -49,6 +49,7 @@ type Build struct {
Dockerfile string // Docker build Dockerfile Dockerfile string // Docker build Dockerfile
Context string // Docker build context Context string // Docker build context
TagsAuto bool // Docker build auto tag TagsAuto bool // Docker build auto tag
TagsDefaultName string // Docker build auto tag name override
TagsSuffix string // Docker build tags with suffix TagsSuffix string // Docker build tags with suffix
Tags cli.StringSlice // Docker build tags Tags cli.StringSlice // Docker build tags
LabelsAuto bool // Docker build auto labels LabelsAuto bool // Docker build auto labels
@ -110,6 +111,10 @@ func (p *Plugin) Validate() error {
return err 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 // beside the default login all other logins need to set a username and password
for _, l := range p.settings.Logins[1:] { for _, l := range p.settings.Logins[1:] {
if l.anonymous() { if l.anonymous() {
@ -126,6 +131,7 @@ func (p *Plugin) Validate() error {
tag, err := DefaultTagSuffix( tag, err := DefaultTagSuffix(
p.settings.Build.Ref, p.settings.Build.Ref,
p.settings.Build.TagsSuffix, p.settings.Build.TagsSuffix,
p.settings.Build.TagsDefaultName,
) )
if err != nil { if err != nil {
logrus.Printf("cannot build docker image for %s, invalid semantic version", p.settings.Build.Ref) logrus.Printf("cannot build docker image for %s, invalid semantic version", p.settings.Build.Ref)

View File

@ -15,6 +15,7 @@ var defaultSettings = Settings{
Build: Build{ Build: Build{
Context: ".", Context: ".",
Tags: *cli.NewStringSlice("latest"), Tags: *cli.NewStringSlice("latest"),
TagsDefaultName: "latest",
LabelsAuto: true, LabelsAuto: true,
Pull: true, Pull: true,
}, },

View File

@ -2,6 +2,7 @@ package plugin
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"github.com/coreos/go-semver/semver" "github.com/coreos/go-semver/semver"
@ -9,8 +10,8 @@ import (
// DefaultTagSuffix returns a set of default suggested tags // DefaultTagSuffix returns a set of default suggested tags
// based on the commit ref with an attached suffix. // based on the commit ref with an attached suffix.
func DefaultTagSuffix(ref, suffix string) ([]string, error) { func DefaultTagSuffix(ref, defaultTag, suffix string) ([]string, error) {
tags, err := DefaultTags(ref) tags, err := DefaultTags(ref, defaultTag)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -18,7 +19,7 @@ func DefaultTagSuffix(ref, suffix string) ([]string, error) {
return tags, nil return tags, nil
} }
for i, tag := range tags { for i, tag := range tags {
if tag == "latest" { if tag == defaultTag {
tags[i] = suffix tags[i] = suffix
} else { } else {
tags[i] = fmt.Sprintf("%s-%s", tag, suffix) 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 // DefaultTags returns a set of default suggested tags based on
// the commit ref. // the commit ref.
func DefaultTags(ref string) ([]string, error) { func DefaultTags(ref, defaultTag string) ([]string, error) {
if !strings.HasPrefix(ref, "refs/tags/") { if !strings.HasPrefix(ref, "refs/tags/") {
return []string{"latest"}, nil return []string{defaultTag}, nil
} }
v := stripTagPrefix(ref) v := stripTagPrefix(ref)
version, err := semver.NewVersion(v) version, err := semver.NewVersion(v)
if err != nil { if err != nil {
return []string{"latest"}, err return []string{defaultTag}, err
} }
if version.PreRelease != "" || version.Metadata != "" { if version.PreRelease != "" || version.Metadata != "" {
return []string{ return []string{
@ -91,3 +92,8 @@ func stripTagPrefix(ref string) string {
ref = strings.TrimPrefix(ref, "v") ref = strings.TrimPrefix(ref, "v")
return ref 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
}

View File

@ -25,19 +25,20 @@ func Test_stripTagPrefix(t *testing.T) {
func TestDefaultTags(t *testing.T) { func TestDefaultTags(t *testing.T) {
tests := []struct { tests := []struct {
DefaultTag string
Before string Before string
After []string After []string
}{ }{
{"", []string{"latest"}}, {"latest", "", []string{"latest"}},
{"refs/heads/master", []string{"latest"}}, {"latest", "refs/heads/master", []string{"latest"}},
{"refs/tags/0.9.0", []string{"0.9", "0.9.0"}}, {"latest", "refs/tags/0.9.0", []string{"0.9", "0.9.0"}},
{"refs/tags/1.0.0", []string{"1", "1.0", "1.0.0"}}, {"latest", "refs/tags/1.0.0", []string{"1", "1.0", "1.0.0"}},
{"refs/tags/v1.0.0", []string{"1", "1.0", "1.0.0"}}, {"latest", "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", "refs/tags/v1.0.0-alpha.1", []string{"1.0.0-alpha.1"}},
} }
for _, test := range tests { for _, test := range tests {
tags, err := DefaultTags(test.Before) tags, err := DefaultTags(test.Before, test.DefaultTag)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue continue
@ -50,13 +51,22 @@ func TestDefaultTags(t *testing.T) {
} }
func TestDefaultTagsError(t *testing.T) { func TestDefaultTagsError(t *testing.T) {
tests := []string{ tests := []struct {
"refs/tags/x1.0.0", DefaultTag string
"refs/tags/20190203", Before string
}{
{
DefaultTag: "latest",
Before: "refs/tags/x1.0.0",
},
{
DefaultTag: "latest",
Before: "refs/tags/20190203",
},
} }
for _, test := range tests { for _, test := range tests {
_, err := DefaultTags(test) _, err := DefaultTags(test.Before, test.DefaultTag)
if err == nil { if err == nil {
t.Errorf("Expect tag error for %s", test) t.Errorf("Expect tag error for %s", test)
} }
@ -65,15 +75,25 @@ func TestDefaultTagsError(t *testing.T) {
func TestDefaultTagSuffix(t *testing.T) { func TestDefaultTagSuffix(t *testing.T) {
tests := []struct { tests := []struct {
Name string
Before string Before string
Suffix string Suffix string
After []string After []string
DefaultTag string
}{ }{
// without suffix
{ {
Name: "Default tag without suffix",
DefaultTag: "latest",
After: []string{"latest"}, After: []string{"latest"},
}, },
{ {
Name: "Overridden default tag without suffix",
DefaultTag: "next",
After: []string{"next"},
},
{
Name: "Generate version",
DefaultTag: "latest",
Before: "refs/tags/v1.0.0", Before: "refs/tags/v1.0.0",
After: []string{ After: []string{
"1", "1",
@ -81,12 +101,31 @@ func TestDefaultTagSuffix(t *testing.T) {
"1.0.0", "1.0.0",
}, },
}, },
// with suffix
{ {
Name: "Generate version with overridden default tag",
DefaultTag: "next",
Before: "refs/tags/v1.0.0",
After: []string{
"1",
"1.0",
"1.0.0",
},
},
{
Name: "Default tag with suffix (linux-amd64)",
DefaultTag: "latest",
Suffix: "linux-amd64", Suffix: "linux-amd64",
After: []string{"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", Before: "refs/tags/v1.0.0",
Suffix: "linux-amd64", Suffix: "linux-amd64",
After: []string{ After: []string{
@ -96,10 +135,31 @@ func TestDefaultTagSuffix(t *testing.T) {
}, },
}, },
{ {
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",
},
},
{
Name: "Default tag with suffix (nanoserver)",
DefaultTag: "latest",
Suffix: "nanoserver", Suffix: "nanoserver",
After: []string{"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", Before: "refs/tags/v1.9.2",
Suffix: "nanoserver", Suffix: "nanoserver",
After: []string{ After: []string{
@ -109,6 +169,30 @@ func TestDefaultTagSuffix(t *testing.T) {
}, },
}, },
{ {
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", Before: "refs/tags/v18.06.0",
Suffix: "nanoserver", Suffix: "nanoserver",
After: []string{ After: []string{
@ -120,14 +204,14 @@ func TestDefaultTagSuffix(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
tag, err := DefaultTagSuffix(test.Before, test.Suffix) tag, err := DefaultTagSuffix(test.Before, test.DefaultTag, test.Suffix)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue continue
} }
got, want := tag, test.After got, want := tag, test.After
if !reflect.DeepEqual(got, want) { 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)
}
}
}