Skip to content

Commit

Permalink
Add wildcard support and ensure order matters
Browse files Browse the repository at this point in the history
  • Loading branch information
callumcrossley12 committed Nov 5, 2024
1 parent 71db93d commit 43719b0
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 57 deletions.
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,22 +306,31 @@ In some instances, like when using SSO, you may want to override the default pro
This can be achieved by creating a `registryConfig.yaml` file in the `~/.ecr` directory. To override this directory set the
`AWS_ECR_REGISTRY_CONFIG_PATH` environment variable. This file configures registries to use a specific `AWS_Profile` as
defined in your AWS config. If no entry is found for a given repository it will fallback to the default AWS credentials.
The general format for the file is:

_NOTE: The 'pattern' supports *only* prefix or suffix wildcards or exact matches. The order is important and only the first matching pattern will apply._

The general format for the file is highlighted below.

```yaml
registryConfigs:
<registry>:
profile: "<AWS_PROFILE_NAME>"
- pattern: <registry_matching_pattern>
config:
profile: "<AWS_PROFILE_NAME>"
```
For example:
```yaml
registryConfigs:
123456789000.dkr.ecr.ap-southeast-2.amazonaws.com:
profile: "Profile1"
987654321000.dkr.ecr.ap-southeast-2.amazonaws.com:
profile: "Profile2"
- pattern: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com"
config:
profile: "some_profile"
- pattern: "987654321000.us-east-1.amazonaws.com"
config:
profile: "another_profile"
- pattern: "*.us-east-1.amazonaws.com"
config:
profile: "fallback_profile"
```
## Usage
Expand Down
33 changes: 27 additions & 6 deletions ecr-login/config/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ type RegistryConfig struct {
Profile string `yaml:"profile"`
}

type RegistryConfigEntry struct {
Pattern string `yaml:"pattern"`
Config RegistryConfig `yaml:"config"`
}

type RegistryConfigs struct {
RegistryConfigs map[string]RegistryConfig `yaml:"registryConfigs"`
RegistryConfigs []RegistryConfigEntry `yaml:"registryConfigs"`
}

const ENV_AWS_ECR_REGISTRY_CONFIG_PATH = "AWS_ECR_REGISTRY_CONFIG_PATH"
Expand All @@ -26,6 +31,20 @@ var (
GetRegistryProfile = getRegistryProfile // Provide override for mocking
)

// Helper to match registry with wildard patterns
func matchesPattern(pattern, registry string) bool {
if pattern == "*" {
return true
}
if strings.HasPrefix(pattern, "*") && strings.HasSuffix(registry, pattern[1:]) {
return true
}
if strings.HasSuffix(pattern, "*") && strings.HasPrefix(registry, pattern[:len(pattern)-1]) {
return true
}
return pattern == registry // Exact match
}

// Function to determine the RegistryConfigPath
func getRegistryConfigPath() string {
// Get the path from the environment variable
Expand All @@ -45,8 +64,8 @@ func getRegistryConfig(registry string) (*RegistryConfig, error) {
fileData, err := os.ReadFile(RegistryConfigFilePath)
if err != nil {
if os.IsNotExist(err) {
// The default scenario
logrus.WithField(ENV_AWS_ECR_REGISTRY_CONFIG_PATH, RegistryConfigFilePath).Debug("No custom registry config file found. Using default credentials.")
logrus.WithField(ENV_AWS_ECR_REGISTRY_CONFIG_PATH, RegistryConfigFilePath).
Debug("No custom registry config file found. Using default credentials.")
return nil, nil
}
return nil, fmt.Errorf("failed to read config file: %w", err)
Expand All @@ -59,9 +78,11 @@ func getRegistryConfig(registry string) (*RegistryConfig, error) {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}

// Look for the registry configuration
if config, exists := configs.RegistryConfigs[registry]; exists {
return &config, nil // Return pointer to found config
// Look for the registry configuration with wildcards support in file order
for _, entry := range configs.RegistryConfigs {
if matchesPattern(entry.Pattern, registry) {
return &entry.Config, nil
}
}

return nil, nil // Return nil if registry is not found
Expand Down
10 changes: 7 additions & 3 deletions ecr-login/config/registry/registry_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@ type RegistryConfigBuilder struct {
func NewRegistryConfigBuilder() *RegistryConfigBuilder {
return &RegistryConfigBuilder{
config: &RegistryConfigs{
RegistryConfigs: make(map[string]RegistryConfig),
RegistryConfigs: []RegistryConfigEntry{}, // Initialize the slice
},
}
}

// Adds a new registry configuration with the given name and credential.
func (b *RegistryConfigBuilder) AddRegistryWithProfile(registry string, profile string) *RegistryConfigBuilder {
b.config.RegistryConfigs[registry] = RegistryConfig{Profile: profile}
func (b *RegistryConfigBuilder) AddRegistryConfigWithProfile(pattern string, profile string) *RegistryConfigBuilder {
entry := RegistryConfigEntry{
Pattern: pattern,
Config: RegistryConfig{Profile: profile},
}
b.config.RegistryConfigs = append(b.config.RegistryConfigs, entry) // Append to the slice
return b
}

Expand Down
197 changes: 156 additions & 41 deletions ecr-login/config/registry/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package config

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -56,10 +55,67 @@ func setupEnvVar(t *testing.T, key, value string) {
// Restore value after test finishes
t.Cleanup(func() { os.Setenv(key, original) })
}
func TestMatchesPattern(t *testing.T) {
testCases := []struct {
name string
registry string
pattern string
shouldMatch bool
}{
{
name: "Exact match",
registry: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
pattern: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
shouldMatch: true,
},
{
name: "No match",
registry: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
pattern: "987654321000.dkr.ecr.ap-southeast-2.amazonaws.com",
shouldMatch: false,
},
{
name: "Suffix wildcard match",
registry: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
pattern: "123456789000.*",
shouldMatch: true,
},
{
name: "Prefix wildcard match",
registry: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
pattern: "*.dkr.ecr.ap-southeast-2.amazonaws.com",
shouldMatch: true,
},
{
name: "Wildcard",
registry: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
pattern: "*",
shouldMatch: true,
},
{
name: "Wildcard no match",
registry: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
pattern: "*.dkr.ecr.us-east-1.amazonaws.com",
shouldMatch: false,
},
{
name: "Not supported double wildcard match",
registry: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
pattern: "*.dkr.ecr.*.amazonaws.com",
shouldMatch: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
match := matchesPattern(tc.pattern, tc.registry)
assert.Equal(t, tc.shouldMatch, match)
})
}
}

func TestGetRegistryConfigPath_NoEnvVar(t *testing.T) {
homedir, _ := os.UserHomeDir()
expectedPath := filepath.Join(homedir, ".ecr")
expectedPath := "~/.ecr"

setupEnvVar(t, "AWS_ECR_REGISTRY_CONFIG_PATH", "")

Expand All @@ -77,12 +133,12 @@ func TestGetRegistryConfigPath_WithEnvVar(t *testing.T) {
assert.Equal(t, expectedPath, path, "Expected path to match environment variable")
}

func TestGetRegistryConfig_ValidRegistryAndConfig(t *testing.T) {
func TestGetRegistryConfig_ExactRegistryAndConfig(t *testing.T) {
registry := "some-registry"
profile := "another-profile"

testRegistryConfigs := NewRegistryConfigBuilder().
AddRegistryWithProfile(registry, profile).
AddRegistryConfigWithProfile(registry, profile).
Build()

setupTestConfig(t, testRegistryConfigs)
Expand Down Expand Up @@ -118,43 +174,102 @@ func TestGetRegistryConfig_ConfigFileNotFound(t *testing.T) {
assert.NoError(t, err, "Expected no error when the config file cannot be found, got error instead")
}

func TestGetRegistryProfile_NoRegistryConfiguration(t *testing.T) {
profile, err := GetRegistryProfile("some_registry")

assert.Equal(t, profile, "", "Expected no profile to be returned when no configuration is set, found profile instead")
assert.NoError(t, err, "Expected no error when no configuration is set, got error instead")
}

func TestGetRegistryProfile_ValidRegistry(t *testing.T) {
registry := "some-registry"
profile := "another-profile"

testRegistryConfigs := NewRegistryConfigBuilder().
AddRegistryWithProfile(registry, profile).
Build()

setupTestConfig(t, testRegistryConfigs)

resultProfile, err := GetRegistryProfile(registry)

assert.Equal(t, profile, resultProfile, "Expect returned profile to match YAML configuration, got mismatch instead")
assert.NoError(t, err, "Expected not error for a valid registry profile configuration, got error")
}

func TestGetRegistryProfile_NoProfileForRepo(t *testing.T) {
registry := "some-registry"
profile := "another-profile"

testRegistryConfigs := NewRegistryConfigBuilder().
AddRegistryWithProfile(registry, profile).
Build()

setupTestConfig(t, testRegistryConfigs)

profile, err := GetRegistryProfile("some-other-registry")
func TestGetRegistryProfileWildcards(t *testing.T) {
testCases := []struct {
name string
registryPattern string
registryConfigs *RegistryConfigs
expectedProfile string
expectedError bool
}{
{
name: "No config",
registryPattern: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
registryConfigs: nil,
expectedProfile: "",
expectedError: false,
},
{
name: "Exact match",
registryPattern: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
registryConfigs: NewRegistryConfigBuilder().
AddRegistryConfigWithProfile("123456789000.dkr.ecr.ap-southeast-2.amazonaws.com", "production").
Build(),
expectedProfile: "production",
expectedError: false,
},
{
name: "No match",
registryPattern: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
registryConfigs: NewRegistryConfigBuilder().
AddRegistryConfigWithProfile("987654321000.dkr.ecr.ap-southeast-2.amazonaws.com", "production").
Build(),
expectedProfile: "",
expectedError: false,
},
{
name: "Wildcard prefix single match",
registryPattern: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
registryConfigs: NewRegistryConfigBuilder().
AddRegistryConfigWithProfile("*.dkr.ecr.ap-southeast-2.amazonaws.com", "production").
Build(),
expectedProfile: "production",
expectedError: false,
},
{
name: "Wildcard prefix first match",
registryPattern: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
registryConfigs: NewRegistryConfigBuilder().
AddRegistryConfigWithProfile("*.dkr.ecr.ap-southeast-2.amazonaws.com", "production").
AddRegistryConfigWithProfile("123456789000.dkr.ecr.ap-southeast-2.amazonaws.com", "some_other_profile").
Build(),
expectedProfile: "production",
expectedError: false,
},
{
name: "Wildcard suffix single match",
registryPattern: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
registryConfigs: NewRegistryConfigBuilder().
AddRegistryConfigWithProfile("123456789000.*", "production").
Build(),
expectedProfile: "production",
expectedError: false,
},
{
name: "Wildcard suffix first match",
registryPattern: "123456789000.dkr.ecr.ap-southeast-2.amazonaws.com",
registryConfigs: NewRegistryConfigBuilder().
AddRegistryConfigWithProfile("123456789000.*", "production").
AddRegistryConfigWithProfile("123456789000.dkr.ecr.ap-southeast-2.amazonaws.com", "some_other_profile").
AddRegistryConfigWithProfile("*.dkr.ecr.ap-southeast-2.amazonaws.com", "yet_another_profile").
Build(),
expectedProfile: "production",
expectedError: false,
},
{
name: "Wildcard fallback match",
registryPattern: "123456789000.dkr.ecr.us-east-1.amazonaws.com",
registryConfigs: NewRegistryConfigBuilder().
AddRegistryConfigWithProfile("987654321000.us-east-1.amazonaws.com", "production").
AddRegistryConfigWithProfile("*.us-east-1.amazonaws.com", "fallback_profile").
Build(),
expectedProfile: "fallback_profile",
expectedError: false,
},
}

assert.Equal(t, profile, "", "Expected no explicit profile (i.e. \"\") for registry, got a profile")
assert.NoError(t, err, "Expected no error for registry without configuration, got error")
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if (tc.registryConfigs != nil && len(tc.registryConfigs.RegistryConfigs) > 0) {
setupTestConfig(t, tc.registryConfigs)
}

resultProfile, err := GetRegistryProfile(tc.registryPattern)

assert.Equal(t, tc.expectedProfile, resultProfile)
assert.NoError(t, err, "expected no error")
})
}
}

func TestGetRegistryProfile_InvalidYaml(t *testing.T) {
Expand Down

0 comments on commit 43719b0

Please sign in to comment.