Add support for multible registrys (#23)
if normal settings `registry`, `username`... is set that's used as default, else first logins entry is used. ```yml settings: logins: - registry: https://codeberg.org username: "6543" password: geheim - registry: https://index.docker.io/v1/ username: a6543 password: anders_geheim ``` close #18 Reviewed-on: https://codeberg.org/woodpecker-plugins/plugin-docker-buildx/pulls/23 Reviewed-by: anbraten <anbraten@noreply.codeberg.org>
This commit is contained in:
parent
a7117600aa
commit
25ad50a579
@ -200,31 +200,38 @@ func settingsFlags(settings *plugin.Settings) []cli.Flag {
|
||||
EnvVars: []string{"PLUGIN_REGISTRY", "DOCKER_REGISTRY"},
|
||||
Usage: "sets docker registry to authenticate with",
|
||||
Value: "https://index.docker.io/v1/",
|
||||
Destination: &settings.Login.Registry,
|
||||
Destination: &settings.DefaultLogin.Registry,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "docker.username",
|
||||
EnvVars: []string{"PLUGIN_USERNAME", "DOCKER_USERNAME"},
|
||||
Usage: "sets username to authenticates with",
|
||||
Destination: &settings.Login.Username,
|
||||
Destination: &settings.DefaultLogin.Username,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "docker.password",
|
||||
EnvVars: []string{"PLUGIN_PASSWORD", "DOCKER_PASSWORD"},
|
||||
Usage: "sets password to authenticates with",
|
||||
Destination: &settings.Login.Password,
|
||||
Destination: &settings.DefaultLogin.Password,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "docker.email",
|
||||
EnvVars: []string{"PLUGIN_EMAIL", "DOCKER_EMAIL"},
|
||||
Usage: "sets email address to authenticates with",
|
||||
Destination: &settings.Login.Email,
|
||||
Destination: &settings.DefaultLogin.Email,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "docker.config",
|
||||
EnvVars: []string{"PLUGIN_CONFIG", "DOCKER_PLUGIN_CONFIG"},
|
||||
Usage: "sets content of the docker daemon json config",
|
||||
Destination: &settings.Login.Config,
|
||||
Destination: &settings.DefaultLogin.Config,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "logins",
|
||||
EnvVars: []string{"PLUGIN_LOGINS"},
|
||||
Usage: "list of login",
|
||||
Destination: &settings.LoginsRaw,
|
||||
Value: "[]",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "docker.purge",
|
||||
|
@ -1,3 +1,4 @@
|
||||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
package tools
|
||||
|
23
docs.md
23
docs.md
@ -16,11 +16,12 @@ Woodpecker CI plugin to build multiarch Docker images with buildx. This plugin i
|
||||
- Build without push
|
||||
- Use custom registries
|
||||
- Build based on existing tags when needed
|
||||
- Push to multible registries/repos
|
||||
|
||||
It will automatically generate buildkit configuration to use custom CA certificate if following conditions are met:
|
||||
|
||||
- Setting `buildkit_config` is not set
|
||||
- Custom `registry` value is provided
|
||||
- Custom `registry`/`logins` value is provided
|
||||
- File exists `/etc/docker/certs.d/<registry-value>/ca.crt`
|
||||
|
||||
> NB! To mount custom CA you can use Woodpecker CI runner configuration environment `WOODPECKER_BACKEND_DOCKER_VOLUMES` with value `/etc/ssl/certs:/etc/ssl/certs:ro,/etc/docker/certs.d:/etc/docker/certs.d:ro`. And have created file `/etc/docker/certs.d/<registry-value>/ca.crt` with CA certificate on runner server host.
|
||||
@ -112,3 +113,23 @@ It will automatically generate buildkit configuration to use custom CA certifica
|
||||
| `no_cache` | `false` | disables the usage of cached intermediate containers
|
||||
| `add_host` | *none* | sets additional host:ip mapping
|
||||
| `output` | *none* | sets build output in format `type=<type>[,<key>=<value>]`
|
||||
| `logins` | *none* | option to log into multible registrys
|
||||
|
||||
## Multi registry push example
|
||||
|
||||
Only supported with `woodpecker >= 1.0.0` (next-da997fa3).
|
||||
|
||||
```yml
|
||||
settings:
|
||||
repo: a6543/tmp,codeberg.org/6543/tmp
|
||||
tag: demo
|
||||
logins:
|
||||
- registry: https://index.docker.io/v1/
|
||||
username: a6543
|
||||
password:
|
||||
from_secret: docker_token
|
||||
- registry: https://codeberg.org
|
||||
username: "6543"
|
||||
password:
|
||||
from_secret: cb_token
|
||||
```
|
||||
|
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
||||
module codeberg.org/woodpecker-plugins/plugin-docker-buildx
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/coreos/go-semver v0.3.0
|
||||
|
18
go.sum
18
go.sum
@ -1,5 +1,4 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
@ -36,10 +35,8 @@ github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk=
|
||||
github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
@ -50,38 +47,23 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f h1:OKYpQQVE3DKSc3r3zHVzq46vq5YH7x8xpR3/k9ixmUg=
|
||||
golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
honnef.co/go/tools v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA=
|
||||
honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw=
|
||||
|
@ -10,19 +10,6 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// helper function to create the docker login command.
|
||||
func commandLogin(login Login) *exec.Cmd {
|
||||
if login.Email != "" {
|
||||
return commandLoginEmail(login)
|
||||
}
|
||||
return exec.Command(
|
||||
dockerExe, "login",
|
||||
"-u", login.Username,
|
||||
"-p", login.Password,
|
||||
login.Registry,
|
||||
)
|
||||
}
|
||||
|
||||
// helper to check if args match "docker pull <image>"
|
||||
func isCommandPull(args []string) bool {
|
||||
return len(args) > 2 && args[1] == "pull"
|
||||
|
@ -1,6 +1,7 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
@ -69,17 +70,49 @@ type Build struct {
|
||||
// Settings for the Plugin.
|
||||
type Settings struct {
|
||||
Daemon Daemon
|
||||
Login Login
|
||||
Logins []Login
|
||||
LoginsRaw string
|
||||
DefaultLogin Login
|
||||
Build Build
|
||||
Dryrun bool
|
||||
Cleanup bool
|
||||
}
|
||||
|
||||
// Validate handles the settings validation of the plugin.
|
||||
func (p *Plugin) Validate() error {
|
||||
func (l Login) anonymous() bool {
|
||||
return l.Username == "" || l.Password == ""
|
||||
}
|
||||
|
||||
// Init initialise plugin settings
|
||||
func (p *Plugin) InitSettings() error {
|
||||
if err := json.Unmarshal([]byte(p.settings.LoginsRaw), &p.settings.Logins); err != nil {
|
||||
return fmt.Errorf("Could not unmarshal logins: %v", err)
|
||||
}
|
||||
|
||||
p.settings.Build.Branch = p.pipeline.Repo.Branch
|
||||
p.settings.Build.Ref = p.pipeline.Commit.Ref
|
||||
p.settings.Daemon.Registry = p.settings.Login.Registry
|
||||
if p.settings.DefaultLogin.anonymous() {
|
||||
p.settings.Logins = append(p.settings.Logins, p.settings.DefaultLogin)
|
||||
} else {
|
||||
p.settings.Logins = prepend(p.settings.Logins, p.settings.DefaultLogin)
|
||||
}
|
||||
|
||||
p.settings.Daemon.Registry = p.settings.Logins[0].Registry
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate handles the settings validation of the plugin.
|
||||
func (p *Plugin) Validate() error {
|
||||
if err := p.InitSettings(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// beside the default login all other logins need to set a username and password
|
||||
for _, l := range p.settings.Logins[1:] {
|
||||
if l.anonymous() {
|
||||
return fmt.Errorf("beside the default login all other logins need to set a username and password")
|
||||
}
|
||||
}
|
||||
|
||||
if p.settings.Build.TagsAuto {
|
||||
// return true if tag event or default branch
|
||||
@ -120,10 +153,15 @@ func (p *Plugin) Validate() error {
|
||||
}
|
||||
|
||||
func (p *Plugin) writeBuildkitConfig() error {
|
||||
// no buildkit config, automatically generate buildkit configuration to use a custom CA certificate for each registry
|
||||
if p.settings.Daemon.BuildkitConfig == "" && p.settings.Daemon.Registry != "" {
|
||||
registry := p.settings.Daemon.Registry
|
||||
for _, login := range p.settings.Logins {
|
||||
if registry := login.Registry; registry != "" {
|
||||
u, err := url.Parse(registry)
|
||||
if err == nil && u.Host != "" {
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse registry address: %s: %v", registry, err)
|
||||
}
|
||||
if u.Host != "" {
|
||||
registry = u.Host
|
||||
}
|
||||
|
||||
@ -133,15 +171,20 @@ func (p *Plugin) writeBuildkitConfig() error {
|
||||
logrus.Warnf("error reading %s: %v", caPath, err)
|
||||
} else if err == nil {
|
||||
ca.Close()
|
||||
p.settings.Daemon.BuildkitConfig = fmt.Sprintf(buildkitConfigTemplate, registry, caPath)
|
||||
p.settings.Daemon.BuildkitConfig += fmt.Sprintf(buildkitConfigTemplate, registry, caPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save buildkit config as described
|
||||
if p.settings.Daemon.BuildkitConfig != "" {
|
||||
err := os.WriteFile(buildkitConfig, []byte(p.settings.Daemon.BuildkitConfig), 0o600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing buildkit.toml: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -164,23 +207,19 @@ func (p *Plugin) Execute() error {
|
||||
}
|
||||
|
||||
// Create Auth Config File
|
||||
if p.settings.Login.Config != "" {
|
||||
if p.settings.Logins[0].Config != "" {
|
||||
os.MkdirAll(dockerHome, 0o600)
|
||||
|
||||
path := filepath.Join(dockerHome, "config.json")
|
||||
err := os.WriteFile(path, []byte(p.settings.Login.Config), 0o600)
|
||||
err := os.WriteFile(path, []byte(p.settings.Logins[0].Config), 0o600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing config.json: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// login to the Docker registry
|
||||
if p.settings.Login.Password != "" {
|
||||
cmd := commandLogin(p.settings.Login)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error authenticating: %s", err)
|
||||
}
|
||||
if err := p.Login(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.writeBuildkitConfig(); err != nil {
|
||||
@ -188,9 +227,9 @@ func (p *Plugin) Execute() error {
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.settings.Login.Password != "":
|
||||
case p.settings.Logins[0].Password != "":
|
||||
fmt.Println("Detected registry credentials")
|
||||
case p.settings.Login.Config != "":
|
||||
case p.settings.Logins[0].Config != "":
|
||||
fmt.Println("Detected registry credentials file")
|
||||
default:
|
||||
fmt.Println("Registry credentials or Docker config not provided. Guest mode enabled.")
|
||||
@ -228,3 +267,7 @@ func (p *Plugin) Execute() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepend[Type any](slice []Type, elems ...Type) []Type {
|
||||
return append(elems, slice...)
|
||||
}
|
||||
|
36
plugin/login.go
Normal file
36
plugin/login.go
Normal file
@ -0,0 +1,36 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// login to the registrys
|
||||
func (p *Plugin) Login() error {
|
||||
registrys := make(map[string]bool)
|
||||
for _, login := range p.settings.Logins {
|
||||
if !registrys[login.Registry] && !login.anonymous() {
|
||||
// only log into a registry once
|
||||
registrys[login.Registry] = true
|
||||
cmd := commandLogin(login)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error authenticating: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// helper function to create the docker login command.
|
||||
func commandLogin(login Login) *exec.Cmd {
|
||||
if login.Email != "" {
|
||||
return commandLoginEmail(login)
|
||||
}
|
||||
return exec.Command(
|
||||
dockerExe, "login",
|
||||
"-u", login.Username,
|
||||
"-p", login.Password,
|
||||
login.Registry,
|
||||
)
|
||||
}
|
@ -27,7 +27,7 @@ func DefaultTagSuffix(ref, suffix string) ([]string, error) {
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func splitOff(input string, delim string) string {
|
||||
func splitOff(input, delim string) string {
|
||||
parts := strings.SplitN(input, delim, 2)
|
||||
|
||||
if len(parts) == 2 {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_stripTagPrefix(t *testing.T) {
|
||||
var tests = []struct {
|
||||
tests := []struct {
|
||||
Before string
|
||||
After string
|
||||
}{
|
||||
@ -24,7 +24,7 @@ func Test_stripTagPrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultTags(t *testing.T) {
|
||||
var tests = []struct {
|
||||
tests := []struct {
|
||||
Before string
|
||||
After []string
|
||||
}{
|
||||
@ -50,7 +50,7 @@ func TestDefaultTags(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultTagsError(t *testing.T) {
|
||||
var tests = []string{
|
||||
tests := []string{
|
||||
"refs/tags/x1.0.0",
|
||||
"refs/tags/20190203",
|
||||
}
|
||||
@ -64,7 +64,7 @@ func TestDefaultTagsError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultTagSuffix(t *testing.T) {
|
||||
var tests = []struct {
|
||||
tests := []struct {
|
||||
Before string
|
||||
Suffix string
|
||||
After []string
|
||||
|
Loading…
Reference in New Issue
Block a user