auth: add option to load docker config for specific repo/scope
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
@@ -23,12 +23,10 @@ import (
|
|||||||
"github.com/docker/buildx/util/buildflags"
|
"github.com/docker/buildx/util/buildflags"
|
||||||
"github.com/docker/buildx/util/platformutil"
|
"github.com/docker/buildx/util/platformutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/cli/cli/config"
|
|
||||||
dockeropts "github.com/docker/cli/opts"
|
dockeropts "github.com/docker/cli/opts"
|
||||||
hcl "github.com/hashicorp/hcl/v2"
|
hcl "github.com/hashicorp/hcl/v2"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/client/llb"
|
"github.com/moby/buildkit/client/llb"
|
||||||
"github.com/moby/buildkit/session/auth/authprovider"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
@@ -1256,18 +1254,12 @@ func (t *Target) GetName(ectx *hcl.EvalContext, block *hcl.Block, loadDeps func(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
|
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
|
||||||
// make sure local credentials are loaded multiple times for different targets
|
|
||||||
authProvider := authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
|
|
||||||
ConfigFile: config.LoadDefaultConfigFile(os.Stderr),
|
|
||||||
})
|
|
||||||
|
|
||||||
m2 := make(map[string]build.Options, len(m))
|
m2 := make(map[string]build.Options, len(m))
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
bo, err := toBuildOpt(v, inp)
|
bo, err := toBuildOpt(v, inp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
bo.Session = append(bo.Session, authProvider)
|
|
||||||
m2[k] = *bo
|
m2[k] = *bo
|
||||||
}
|
}
|
||||||
return m2, nil
|
return m2, nil
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ import (
|
|||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/desktop"
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/buildx/util/dockerutil"
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
|
"github.com/docker/buildx/util/dockerutil/dockerconfig"
|
||||||
"github.com/docker/buildx/util/osutil"
|
"github.com/docker/buildx/util/osutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/buildx/util/tracing"
|
"github.com/docker/buildx/util/tracing"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/identity"
|
"github.com/moby/buildkit/identity"
|
||||||
|
"github.com/moby/buildkit/session/auth/authprovider"
|
||||||
"github.com/moby/buildkit/util/progress/progressui"
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -252,6 +254,16 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure local credentials aren't loaded multiple times for different targets
|
||||||
|
authProvider := authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
|
||||||
|
AuthConfigProvider: dockerconfig.LoadAuthConfig(dockerCli),
|
||||||
|
})
|
||||||
|
|
||||||
|
for k, opt := range bo {
|
||||||
|
opt.Session = append(opt.Session, authProvider)
|
||||||
|
bo[k] = opt
|
||||||
|
}
|
||||||
|
|
||||||
def := struct {
|
def := struct {
|
||||||
Group map[string]*bake.Group `json:"group,omitempty"`
|
Group map[string]*bake.Group `json:"group,omitempty"`
|
||||||
Target map[string]*bake.Target `json:"target"`
|
Target map[string]*bake.Target `json:"target"`
|
||||||
|
|||||||
+2
-1
@@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/desktop"
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/buildx/util/dockerutil"
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
|
"github.com/docker/buildx/util/dockerutil/dockerconfig"
|
||||||
"github.com/docker/buildx/util/ioset"
|
"github.com/docker/buildx/util/ioset"
|
||||||
"github.com/docker/buildx/util/metricutil"
|
"github.com/docker/buildx/util/metricutil"
|
||||||
"github.com/docker/buildx/util/osutil"
|
"github.com/docker/buildx/util/osutil"
|
||||||
@@ -1019,7 +1020,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *BuildOptions, inSt
|
|||||||
opts.Platforms = platforms
|
opts.Platforms = platforms
|
||||||
|
|
||||||
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
|
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
|
||||||
ConfigFile: dockerCli.ConfigFile(),
|
AuthConfigProvider: dockerconfig.LoadAuthConfig(dockerCli),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
secrets, err := build.CreateSecrets(in.Secrets)
|
secrets, err := build.CreateSecrets(in.Secrets)
|
||||||
|
|||||||
@@ -0,0 +1,254 @@
|
|||||||
|
package dockerconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/config"
|
||||||
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
|
"github.com/docker/cli/cli/config/types"
|
||||||
|
"github.com/moby/buildkit/session/auth/authprovider"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadAuthConfig(cli command.Cli) authprovider.AuthConfigProvider {
|
||||||
|
acp := &authConfigProvider{
|
||||||
|
buildxConfig: confutil.NewConfig(cli),
|
||||||
|
defaultConfig: cli.ConfigFile(),
|
||||||
|
authConfigCache: map[string]authConfigCacheEntry{},
|
||||||
|
}
|
||||||
|
return acp.load
|
||||||
|
}
|
||||||
|
|
||||||
|
type authConfigProvider struct {
|
||||||
|
initOnce sync.Once
|
||||||
|
defaultConfig *configfile.ConfigFile
|
||||||
|
buildxConfig *confutil.Config
|
||||||
|
authConfigCache map[string]authConfigCacheEntry
|
||||||
|
mu sync.Mutex // mutex for authConfigCache
|
||||||
|
alternativeConfigs []*alternativeConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *authConfigProvider) load(ctx context.Context, host string, scopes []string, cacheExpireCheck authprovider.ExpireCachedAuthCheck) (types.AuthConfig, error) {
|
||||||
|
ap.initOnce.Do(func() {
|
||||||
|
ap.init()
|
||||||
|
})
|
||||||
|
|
||||||
|
ap.mu.Lock()
|
||||||
|
defer ap.mu.Unlock()
|
||||||
|
|
||||||
|
candidates := []*alternativeConfig{}
|
||||||
|
parsedScopes := parseScopes(scopes)
|
||||||
|
|
||||||
|
if len(parsedScopes) == 1 {
|
||||||
|
for _, cfg := range ap.alternativeConfigs {
|
||||||
|
if cfg.host != host {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cfg.matchesScopes(parsedScopes) {
|
||||||
|
candidates = append(candidates, cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key := host
|
||||||
|
cfg := ap.defaultConfig
|
||||||
|
if len(candidates) > 0 {
|
||||||
|
// matches with repo before those without repo
|
||||||
|
// matches with scope set sorted before those without scope
|
||||||
|
slices.SortFunc(candidates, func(a, b *alternativeConfig) int {
|
||||||
|
return cmp.Or(
|
||||||
|
strings.Compare(b.repo, a.repo),
|
||||||
|
cmp.Compare(len(b.scope), len(a.scope)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
candidates = candidates[:1]
|
||||||
|
key += "|" + candidates[0].dir
|
||||||
|
if candidates[0].configFile == nil {
|
||||||
|
if cfgDir, err := config.Load(candidates[0].dir); err == nil {
|
||||||
|
cfg = cfgDir
|
||||||
|
candidates[0].configFile = cfg
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg = candidates[0].configFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, exists := ap.authConfigCache[key]
|
||||||
|
if exists && (cacheExpireCheck == nil || !cacheExpireCheck(entry.Created, key)) {
|
||||||
|
return *entry.Auth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hostKey := host
|
||||||
|
if host == authprovider.DockerHubRegistryHost {
|
||||||
|
hostKey = authprovider.DockerHubConfigfileKey
|
||||||
|
}
|
||||||
|
|
||||||
|
ac, err := cfg.GetAuthConfig(hostKey)
|
||||||
|
if err != nil {
|
||||||
|
return types.AuthConfig{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = authConfigCacheEntry{
|
||||||
|
Created: time.Now(),
|
||||||
|
Auth: &ac,
|
||||||
|
}
|
||||||
|
|
||||||
|
ap.authConfigCache[key] = entry
|
||||||
|
|
||||||
|
return ac, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *authConfigProvider) init() error {
|
||||||
|
base := filepath.Join(ap.buildxConfig.Dir(), "config")
|
||||||
|
return filepath.WalkDir(base, func(path string, d os.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if d.Name() != config.ConfigFileName {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
rdir, err := filepath.Rel(base, dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg := parseConfigKey(rdir)
|
||||||
|
cfg.dir = dir
|
||||||
|
ap.alternativeConfigs = append(ap.alternativeConfigs, &cfg)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseConfigKey(key string) alternativeConfig {
|
||||||
|
var out alternativeConfig
|
||||||
|
|
||||||
|
var mainPart, scopePart string
|
||||||
|
if i := strings.IndexByte(key, '@'); i >= 0 {
|
||||||
|
mainPart = key[:i]
|
||||||
|
scopePart = key[i+1:]
|
||||||
|
} else {
|
||||||
|
mainPart = key
|
||||||
|
}
|
||||||
|
|
||||||
|
if scopePart != "" {
|
||||||
|
out.scope = make(map[string]struct{})
|
||||||
|
for s := range strings.SplitSeq(scopePart, ",") {
|
||||||
|
if s = strings.TrimSpace(s); s != "" {
|
||||||
|
out.scope[s] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mainPart == "" {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
slash := strings.IndexByte(mainPart, '/')
|
||||||
|
if slash < 0 {
|
||||||
|
out.host = mainPart
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
out.host = mainPart[:slash]
|
||||||
|
out.repo = mainPart[slash+1:]
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
type alternativeConfig struct {
|
||||||
|
dir string
|
||||||
|
|
||||||
|
host string
|
||||||
|
repo string
|
||||||
|
scope map[string]struct{}
|
||||||
|
|
||||||
|
configFile *configfile.ConfigFile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *alternativeConfig) matchesScopes(q scopes) bool {
|
||||||
|
if a.repo != "" {
|
||||||
|
if _, ok := q["repository:"+a.repo]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a.scope) > 0 {
|
||||||
|
if a.repo == "" {
|
||||||
|
// no repo means one query must match all scopes
|
||||||
|
for _, scopeActions := range q {
|
||||||
|
ok := true
|
||||||
|
for s := range a.scope {
|
||||||
|
if _, exists := scopeActions[s]; !exists {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for s := range a.scope {
|
||||||
|
for k, scopeActions := range q {
|
||||||
|
if k == "repository:"+a.repo {
|
||||||
|
if _, ok := scopeActions[s]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type authConfigCacheEntry struct {
|
||||||
|
Created time.Time
|
||||||
|
Auth *types.AuthConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type scopes map[string]map[string]struct{}
|
||||||
|
|
||||||
|
func parseScopes(s []string) scopes {
|
||||||
|
// https://distribution.github.io/distribution/spec/auth/scope/
|
||||||
|
m := map[string]map[string]struct{}{}
|
||||||
|
for _, scopeStr := range s {
|
||||||
|
if scopeStr == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// The scopeStr may have strings that contain multiple scopes separated by a space.
|
||||||
|
for scope := range strings.SplitSeq(scopeStr, " ") {
|
||||||
|
parts := strings.SplitN(scope, ":", 3)
|
||||||
|
names := []string{parts[0]}
|
||||||
|
if len(parts) > 1 {
|
||||||
|
names = append(names, parts[1])
|
||||||
|
}
|
||||||
|
var actions []string
|
||||||
|
if len(parts) == 3 {
|
||||||
|
actions = append(actions, strings.Split(parts[2], ",")...)
|
||||||
|
}
|
||||||
|
name := strings.Join(names, ":")
|
||||||
|
ma, ok := m[name]
|
||||||
|
if !ok {
|
||||||
|
ma = map[string]struct{}{}
|
||||||
|
m[name] = ma
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range actions {
|
||||||
|
ma[a] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
package dockerconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseConfigKey(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
in string
|
||||||
|
host string
|
||||||
|
repo string
|
||||||
|
scope map[string]struct{}
|
||||||
|
}{
|
||||||
|
"domain-only": {
|
||||||
|
in: "example.com",
|
||||||
|
host: "example.com",
|
||||||
|
},
|
||||||
|
"domain-with-path": {
|
||||||
|
in: "example.com/a/b",
|
||||||
|
host: "example.com",
|
||||||
|
repo: "a/b",
|
||||||
|
},
|
||||||
|
"with-scopes": {
|
||||||
|
in: "example.com/x@y,z",
|
||||||
|
host: "example.com",
|
||||||
|
repo: "x",
|
||||||
|
scope: map[string]struct{}{
|
||||||
|
"y": {},
|
||||||
|
"z": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"empty-scope-list": {
|
||||||
|
in: "example.com/a/b@",
|
||||||
|
host: "example.com",
|
||||||
|
repo: "a/b",
|
||||||
|
},
|
||||||
|
"path-empty-before-at": {
|
||||||
|
in: "example.com/@foo",
|
||||||
|
host: "example.com",
|
||||||
|
repo: "",
|
||||||
|
scope: map[string]struct{}{
|
||||||
|
"foo": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"scope-no-slash": {
|
||||||
|
in: "registry-1.docker.io@pull,push",
|
||||||
|
host: "registry-1.docker.io",
|
||||||
|
repo: "",
|
||||||
|
scope: map[string]struct{}{
|
||||||
|
"pull": {},
|
||||||
|
"push": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"with-port-and-scopes": {
|
||||||
|
in: "example.com:5000/x@y,z",
|
||||||
|
host: "example.com:5000",
|
||||||
|
repo: "x",
|
||||||
|
scope: map[string]struct{}{
|
||||||
|
"y": {},
|
||||||
|
"z": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
out := parseConfigKey(tc.in)
|
||||||
|
|
||||||
|
require.Equal(t, tc.host, out.host)
|
||||||
|
require.Equal(t, tc.repo, out.repo)
|
||||||
|
|
||||||
|
if tc.scope == nil {
|
||||||
|
require.Nil(t, out.scope)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, tc.scope, out.scope)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestMatchesScopes(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
cfgKey string
|
||||||
|
query []string
|
||||||
|
matches bool
|
||||||
|
}{
|
||||||
|
"invalid-scope": {
|
||||||
|
cfgKey: "example.com/foo/bar@a,b",
|
||||||
|
query: []string{"repository:foo/bar:pull"},
|
||||||
|
matches: false,
|
||||||
|
},
|
||||||
|
"sub-scope-match": {
|
||||||
|
cfgKey: "example.com/foo/bar@push",
|
||||||
|
query: []string{"repository:foo/bar:pull,push"},
|
||||||
|
matches: true,
|
||||||
|
},
|
||||||
|
"invalid-scope-single": {
|
||||||
|
cfgKey: "example.com/foo/bar@push",
|
||||||
|
query: []string{"repository:foo/bar:pull"},
|
||||||
|
matches: false,
|
||||||
|
},
|
||||||
|
"wrong-repo": {
|
||||||
|
cfgKey: "example.com/foo/bar@push",
|
||||||
|
query: []string{"repository:foo/bar2:pull,push"},
|
||||||
|
matches: false,
|
||||||
|
},
|
||||||
|
"multi-scope-match": {
|
||||||
|
cfgKey: "example.com/foo/bar@push,pull",
|
||||||
|
query: []string{"repository:foo/bar:pull,push"},
|
||||||
|
matches: true,
|
||||||
|
},
|
||||||
|
"empty-action-list-in-query": {
|
||||||
|
cfgKey: "example.com/foo/bar@pull",
|
||||||
|
query: []string{"repository:foo/bar:"}, // no actions
|
||||||
|
matches: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"repo-no-scopes-required": {
|
||||||
|
cfgKey: "example.com/foo/bar",
|
||||||
|
query: []string{"repository:foo/bar:pull"},
|
||||||
|
matches: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"repo-empty-scope-in-config": {
|
||||||
|
cfgKey: "example.com/foo/bar@", // explicit empty scope list
|
||||||
|
query: []string{"repository:foo/bar:pull"},
|
||||||
|
matches: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"multiple-repos-config-targets-only-one": {
|
||||||
|
cfgKey: "example.com/foo/bar@pull",
|
||||||
|
query: []string{
|
||||||
|
"repository:foo/bar:pull",
|
||||||
|
"repository:other:push",
|
||||||
|
},
|
||||||
|
matches: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"multiple-repos-but-target-lacks-scope": {
|
||||||
|
cfgKey: "example.com/foo/bar@push",
|
||||||
|
query: []string{
|
||||||
|
"repository:foo/bar:pull", // missing push
|
||||||
|
"repository:other:push", // irrelevant
|
||||||
|
},
|
||||||
|
matches: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"no-repo-in-config-match-any": {
|
||||||
|
cfgKey: "example.com@pull",
|
||||||
|
query: []string{
|
||||||
|
"repository:a:pull",
|
||||||
|
"repository:b:pull,push",
|
||||||
|
},
|
||||||
|
matches: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"no-repo-in-config-but-no-repo-has-scope": {
|
||||||
|
cfgKey: "example.com@push",
|
||||||
|
query: []string{
|
||||||
|
"repository:a:pull",
|
||||||
|
"repository:b:pull",
|
||||||
|
},
|
||||||
|
matches: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"single-action-in-query-with-spaces": {
|
||||||
|
cfgKey: "example.com/foo/bar@pull",
|
||||||
|
query: []string{"repository:foo/bar:pull repository:foo/bar2:pull"},
|
||||||
|
matches: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"single-action-in-query-with-spaces-one-match": {
|
||||||
|
cfgKey: "example.com@push",
|
||||||
|
query: []string{"repository:foo/bar:push repository:foo/bar2:pull"},
|
||||||
|
matches: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"single-action-in-query-with-spaces-invalid": {
|
||||||
|
cfgKey: "example.com@push",
|
||||||
|
query: []string{"repository:foo/bar:pull repository:foo/bar2:pull"},
|
||||||
|
matches: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"multi-scope-config-order-does-not-matter": {
|
||||||
|
cfgKey: "example.com/foo/bar@push,pull",
|
||||||
|
query: []string{"repository:foo/bar:pull,push"},
|
||||||
|
matches: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
cfg := parseConfigKey(tc.cfgKey)
|
||||||
|
q := parseScopes(tc.query)
|
||||||
|
got := cfg.matchesScopes(q)
|
||||||
|
require.Equal(t, tc.matches, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user