Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ func initConfig() {
log.Err(err).Msg("failed to unmarshal the config file")
}

if err := config.CheckTargetRegistryConfiguration(cfg.Target); err != nil {
log.Err(err).Msg("invalid target configuration")
os.Exit(1)
}

//validate := validator.New()
//if err := validate.Struct(cfg); err != nil {
// validationErrors := err.(validator.ValidationErrors)
Expand Down
35 changes: 32 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,21 @@ type Source struct {
}

type Registry struct {
Type string `yaml:"type"`
AWS AWS `yaml:"aws"`
GCP GCP `yaml:"gcp"`
Type string `yaml:"type"`
AWS AWS `yaml:"aws"`
GCP GCP `yaml:"gcp"`
Generic Generic `yaml:"generic"`
}

type Generic struct {
Name string `yaml:"name"`
GenericOptions GenericOptions `yaml:"genericOptions"`
}

type GenericOptions struct {
Domain string `yaml:"domain"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}

type AWS struct {
Expand Down Expand Up @@ -109,13 +121,19 @@ func (g *GCP) GarDomain() string {
return fmt.Sprintf("%s-docker.pkg.dev/%s/%s", g.Location, g.ProjectID, g.RepositoryID)
}

func (g *Generic) GenericDomain() string {
return g.GenericOptions.Domain
}

func (r Registry) Domain() string {
registry, _ := types.ParseRegistry(r.Type)
switch registry {
case types.RegistryAWS:
return r.AWS.EcrDomain()
case types.RegistryGCP:
return r.GCP.GarDomain()
case types.RegistryGeneric:
return r.Generic.GenericDomain()
default:
return ""
}
Expand Down Expand Up @@ -155,6 +173,17 @@ func CheckRegistryConfiguration(r Registry) error {
return nil
}

// provides detailed information about wrongly provided configuration (target specific)
func CheckTargetRegistryConfiguration(r Registry) error {
registryType, err := types.ParseRegistry(r.Type)
if err != nil {
return fmt.Errorf("couldn't parse target registry type")
} else if registryType == types.RegistryGeneric {
return fmt.Errorf("generic registry not allowed as target: %s", r.Generic.Name)
}
return nil
}

// SetViperDefaults configures default values for config items that are not set.
func SetViperDefaults(v *viper.Viper) {
v.SetDefault("Target.Type", "aws")
Expand Down
42 changes: 42 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,48 @@ source:
},
},
},
{
name: "should render generic source registry",
cfg: `
source:
registries:
- type: "generic"
generic:
name: "dockerio"
genericOptions:
domain: "docker.io"
username: "testuser"
password: "testpass"
`,
expCfg: Config{
Target: Registry{
Type: "aws",
AWS: AWS{
ECROptions: ECROptions{
ImageTagMutability: "MUTABLE",
ImageScanningConfiguration: ImageScanningConfiguration{
ImageScanOnPush: true,
},
},
},
},
Source: Source{
Registries: []Registry{
{
Type: "generic",
Generic: Generic{
Name: "dockerio",
GenericOptions: GenericOptions{
Domain: "docker.io",
Username: "testuser",
Password: "testpass",
},
},
},
},
},
},
},
{
name: "should use previous defaults",
cfg: `
Expand Down
2 changes: 2 additions & 0 deletions pkg/registry/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ func NewClient(r config.Registry) (Client, error) {
return NewECRClient(r.AWS)
case types.RegistryGCP:
return NewGARClient(r.GCP)
case types.RegistryGeneric:
return NewGenericClient(r.Generic)
default:
return nil, fmt.Errorf(`registry of type "%s" is not supported`, r.Type)
}
Expand Down
66 changes: 66 additions & 0 deletions pkg/registry/generic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package registry

import (
"context"
"fmt"

ctypes "github.com/containers/image/v5/types"
"github.com/estahn/k8s-image-swapper/pkg/config"
)

type GenericClient struct {
options config.GenericOptions
}

func NewGenericClient(clientConfig config.Generic) (*GenericClient, error) {
client := GenericClient{}

client.options = clientConfig.GenericOptions

return &client, nil
}

func (g *GenericClient) CreateRepository(ctx context.Context, name string) error {
return nil
}

func (g *GenericClient) RepositoryExists() bool {
return true
}

func (g *GenericClient) CopyImage(ctx context.Context, src ctypes.ImageReference, srcCreds string, dest ctypes.ImageReference, destCreds string) error {
panic("implement me")
}

func (g *GenericClient) PullImage() error {
panic("implement me")
}

func (g *GenericClient) PutImage() error {
panic("implement me")
}

func (g *GenericClient) ImageExists(ctx context.Context, ref ctypes.ImageReference) bool {
return true
}

// Endpoint returns the domain of the registry
func (g *GenericClient) Endpoint() string {
return g.options.Domain
}

func (g *GenericClient) Credentials() string {
return fmt.Sprintf("%s:%s", g.options.Username, g.options.Password)
}

// IsOrigin returns true if the imageRef originates from this registry
func (g *GenericClient) IsOrigin(imageRef ctypes.ImageReference) bool {
return true
}

// For testing purposes
func NewDummyGenericClient(domain string, options config.GenericOptions) *GenericClient {
return &GenericClient{
options: options,
}
}
26 changes: 26 additions & 0 deletions pkg/registry/generic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package registry

import (
"encoding/base64"
"testing"

"github.com/estahn/k8s-image-swapper/pkg/config"
"github.com/stretchr/testify/assert"
)

func TestGenericDockerConfig(t *testing.T) {
fakeToken := []byte("username:password")
fakeBase64Token := base64.StdEncoding.EncodeToString(fakeToken)

expected := []byte("{\"auths\":{\"docker.io\":{\"auth\":\"" + fakeBase64Token + "\"}}}")

fakeRegistry := NewDummyGenericClient("docker.io", config.GenericOptions{
Domain: "docker.io",
Username: "username",
Password: "password",
})

r, _ := GenerateDockerConfig(fakeRegistry)

assert.Equal(t, r, expected)
}
2 changes: 1 addition & 1 deletion pkg/secrets/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func NewImagePullSecretsResultWithDefaults(defaultImagePullSecrets []registry.Cl
if err != nil {
log.Err(err)
} else {
imagePullSecretsResult.Add(fmt.Sprintf("source-ecr-%d", index), dockerConfig)
imagePullSecretsResult.Add(fmt.Sprintf("source-registry-%d", index), dockerConfig)
}
}
return imagePullSecretsResult
Expand Down
39 changes: 37 additions & 2 deletions pkg/secrets/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ func TestImagePullSecretsResult_WithDefault(t *testing.T) {

expected := &ImagePullSecretsResult{
Secrets: map[string][]byte{
"source-ecr-0": []byte("{\"auths\":{\"" + fakeEcrDomains[0] + "\":{\"auth\":\"" + fakeBase64Token + "\"}}}"),
"source-ecr-1": []byte("{\"auths\":{\"" + fakeEcrDomains[1] + "\":{\"auth\":\"" + fakeBase64Token + "\"}}}"),
"source-registry-0": []byte("{\"auths\":{\"" + fakeEcrDomains[0] + "\":{\"auth\":\"" + fakeBase64Token + "\"}}}"),
"source-registry-1": []byte("{\"auths\":{\"" + fakeEcrDomains[1] + "\":{\"auth\":\"" + fakeBase64Token + "\"}}}"),
},
Aggregate: []byte("{\"auths\":{\"" + fakeEcrDomains[0] + "\":{\"auth\":\"" + fakeBase64Token + "\"},\"" + fakeEcrDomains[1] + "\":{\"auth\":\"" + fakeBase64Token + "\"}}}"),
}
Expand All @@ -130,6 +130,41 @@ func TestImagePullSecretsResult_WithDefault(t *testing.T) {
assert.Equal(t, r, expected)
}

// TestImagePullSecretsResult_WithDefault tests if authenticated private registries work
func TestImagePullSecretsResult_WithDefault_MixedRegistries(t *testing.T) {
// Fake ECR Source Registry
fakeToken := []byte("token")
fakeBase64Token := base64.StdEncoding.EncodeToString(fakeToken)
fakeAccountId := "12345678912"
fakeRegion := "us-east-1"
fakeEcrDomain := fmt.Sprintf("%s.dkr.ecr.%s.amazonaws.com", fakeAccountId, fakeRegion)

// Fake Generic Source Registry
fakeGenericToken := []byte("username:password")
fakeGenericBase64Token := base64.StdEncoding.EncodeToString(fakeGenericToken)
fakeGenericDomain := "https://index.docker.io/v1/"

expected := &ImagePullSecretsResult{
Secrets: map[string][]byte{
"source-registry-0": []byte("{\"auths\":{\"" + fakeEcrDomain + "\":{\"auth\":\"" + fakeBase64Token + "\"}}}"),
"source-registry-1": []byte("{\"auths\":{\"" + fakeGenericDomain + "\":{\"auth\":\"" + fakeGenericBase64Token + "\"}}}"),
},
Aggregate: []byte("{\"auths\":{\"" + fakeEcrDomain + "\":{\"auth\":\"" + fakeBase64Token + "\"},\"" + fakeGenericDomain + "\":{\"auth\":\"" + fakeGenericBase64Token + "\"}}}"),
}

fakeRegistry1 := registry.NewDummyECRClient(fakeRegion, fakeAccountId, "", config.ECROptions{}, fakeToken)
fakeRegistry2 := registry.NewDummyGenericClient("docker.io", config.GenericOptions{
Domain: "https://index.docker.io/v1/",
Username: "username",
Password: "password",
})
fakeRegistries := []registry.Client{fakeRegistry1, fakeRegistry2}

r := NewImagePullSecretsResultWithDefaults(fakeRegistries)

assert.Equal(t, r, expected)
}

// TestImagePullSecretsResult_Add tests if aggregation works
func TestImagePullSecretsResult_Add(t *testing.T) {
expected := &ImagePullSecretsResult{
Expand Down
5 changes: 4 additions & 1 deletion pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ const (
RegistryUnknown = iota
RegistryAWS
RegistryGCP
RegistryGeneric
)

func (p Registry) String() string {
return [...]string{"unknown", "aws", "gcp"}[p]
return [...]string{"unknown", "aws", "gcp", "generic"}[p]
}

func ParseRegistry(p string) (Registry, error) {
Expand All @@ -20,6 +21,8 @@ func ParseRegistry(p string) (Registry, error) {
return RegistryAWS, nil
case Registry(RegistryGCP).String():
return RegistryGCP, nil
case Registry(RegistryGeneric).String():
return RegistryGeneric, nil
}
return RegistryUnknown, fmt.Errorf("unknown target registry string: '%s', defaulting to unknown", p)
}
Expand Down