Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(registry): Add generic registry for source, not target #737

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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 @@
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)

Check warning on line 256 in cmd/root.go

View check run for this annotation

Codecov / codecov/patch

cmd/root.go#L254-L256

Added lines #L254 - L256 were not covered by tests
}

//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 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 @@
return fmt.Sprintf("%s-docker.pkg.dev/%s/%s", g.Location, g.ProjectID, g.RepositoryID)
}

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

Check warning on line 125 in pkg/config/config.go

View check run for this annotation

Codecov / codecov/patch

pkg/config/config.go#L124-L125

Added lines #L124 - L125 were not covered by tests
}

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()

Check warning on line 136 in pkg/config/config.go

View check run for this annotation

Codecov / codecov/patch

pkg/config/config.go#L135-L136

Added lines #L135 - L136 were not covered by tests
default:
return ""
}
Expand Down Expand Up @@ -155,6 +173,17 @@
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)

Check warning on line 182 in pkg/config/config.go

View check run for this annotation

Codecov / codecov/patch

pkg/config/config.go#L177-L182

Added lines #L177 - L182 were not covered by tests
}
return nil

Check warning on line 184 in pkg/config/config.go

View check run for this annotation

Codecov / codecov/patch

pkg/config/config.go#L184

Added line #L184 was not covered by tests
}

// 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 @@
return NewECRClient(r.AWS)
case types.RegistryGCP:
return NewGARClient(r.GCP)
case types.RegistryGeneric:
return NewGenericClient(r.Generic)

Check warning on line 57 in pkg/registry/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/registry/client.go#L56-L57

Added lines #L56 - L57 were not covered by tests
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{}

Check warning on line 16 in pkg/registry/generic.go

View check run for this annotation

Codecov / codecov/patch

pkg/registry/generic.go#L15-L16

Added lines #L15 - L16 were not covered by tests

client.options = clientConfig.GenericOptions

Check warning on line 18 in pkg/registry/generic.go

View check run for this annotation

Codecov / codecov/patch

pkg/registry/generic.go#L18

Added line #L18 was not covered by tests

return &client, nil

Check warning on line 20 in pkg/registry/generic.go

View check run for this annotation

Codecov / codecov/patch

pkg/registry/generic.go#L20

Added line #L20 was not covered by tests
}

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

Check warning on line 24 in pkg/registry/generic.go

View check run for this annotation

Codecov / codecov/patch

pkg/registry/generic.go#L23-L24

Added lines #L23 - L24 were not covered by tests
}

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

Check warning on line 28 in pkg/registry/generic.go

View check run for this annotation

Codecov / codecov/patch

pkg/registry/generic.go#L27-L28

Added lines #L27 - L28 were not covered by tests
}

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

Check warning on line 32 in pkg/registry/generic.go

View check run for this annotation

Codecov / codecov/patch

pkg/registry/generic.go#L31-L32

Added lines #L31 - L32 were not covered by tests
}

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

Check warning on line 36 in pkg/registry/generic.go

View check run for this annotation

Codecov / codecov/patch

pkg/registry/generic.go#L35-L36

Added lines #L35 - L36 were not covered by tests
}

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

Check warning on line 40 in pkg/registry/generic.go

View check run for this annotation

Codecov / codecov/patch

pkg/registry/generic.go#L39-L40

Added lines #L39 - L40 were not covered by tests
}

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

Check warning on line 44 in pkg/registry/generic.go

View check run for this annotation

Codecov / codecov/patch

pkg/registry/generic.go#L43-L44

Added lines #L43 - L44 were not covered by tests
}

// 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

Check warning on line 58 in pkg/registry/generic.go

View check run for this annotation

Codecov / codecov/patch

pkg/registry/generic.go#L57-L58

Added lines #L57 - L58 were not covered by tests
}

// 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 @@
RegistryUnknown = iota
RegistryAWS
RegistryGCP
RegistryGeneric
)

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

Check warning on line 15 in pkg/types/types.go

View check run for this annotation

Codecov / codecov/patch

pkg/types/types.go#L15

Added line #L15 was not covered by tests
}

func ParseRegistry(p string) (Registry, error) {
Expand All @@ -20,6 +21,8 @@
return RegistryAWS, nil
case Registry(RegistryGCP).String():
return RegistryGCP, nil
case Registry(RegistryGeneric).String():
return RegistryGeneric, nil

Check warning on line 25 in pkg/types/types.go

View check run for this annotation

Codecov / codecov/patch

pkg/types/types.go#L24-L25

Added lines #L24 - L25 were not covered by tests
}
return RegistryUnknown, fmt.Errorf("unknown target registry string: '%s', defaulting to unknown", p)
}
Expand Down
Loading