From 9849df2e6f706ebd48c629b3bb1bf6ddb91faf32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20BUISSON?= Date: Fri, 16 Dec 2022 10:42:36 +0100 Subject: [PATCH] feat: add custom tags to created ECR repositories (#191) This PR adds the ability to configure custom tags for created repositories. Co-authored-by: Enrico Stahn --- cmd/root.go | 7 +++--- docs/configuration.md | 18 ++++++++++++++ pkg/config/config.go | 32 +++++++++++++++++++++---- pkg/config/config_test.go | 38 +++++++++++++++++++++++++++++ pkg/registry/ecr.go | 25 ++++++++++++++----- pkg/webhook/image_swapper_test.go | 40 ++++++++++++++++++------------- 6 files changed, 130 insertions(+), 30 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 7147b53e..c14507cb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,12 +25,11 @@ import ( "context" "fmt" "net/http" + "os" "os/signal" "syscall" "time" - "os" - "github.com/estahn/k8s-image-swapper/pkg/config" "github.com/estahn/k8s-image-swapper/pkg/registry" "github.com/estahn/k8s-image-swapper/pkg/secrets" @@ -64,12 +63,14 @@ A mutating webhook for Kubernetes, pointing the images to a new location.`, //metricsRec := metrics.NewPrometheus(promReg) log.Trace().Interface("config", cfg).Msg("config") - rClient, err := registry.NewECRClient(cfg.Target.AWS.Region, cfg.Target.AWS.EcrDomain(), cfg.Target.AWS.AccountID, cfg.Target.AWS.Role, cfg.Target.AWS.AccessPolicy, cfg.Target.AWS.LifecyclePolicy) + rClient, err := registry.NewECRClient(cfg.Target.AWS.Region, cfg.Target.AWS.EcrDomain(), cfg.Target.AWS.AccountID, cfg.Target.AWS.Role, cfg.Target.AWS.ECROptions.AccessPolicy, cfg.Target.AWS.ECROptions.LifecyclePolicy) if err != nil { log.Err(err).Msg("error connecting to registry client") os.Exit(1) } + rClient.SetRepositoryTags(cfg.Target.AWS.ECROptions.Tags) + imageSwapPolicy, err := types.ParseImageSwapPolicy(cfg.ImageSwapPolicy) if err != nil { log.Err(err) diff --git a/docs/configuration.md b/docs/configuration.md index 68a113ad..4ea544ce 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -138,3 +138,21 @@ The AWS Account ID and Region is primarily used to construct the ECR domain `[AC accountId: 123456789 region: ap-southeast-2 ``` + +### ECR Options + +#### Tags + +This provides a way to add custom tags to newly created repositories. This may be useful while looking at AWS costs. +It's a slice of `Key` and `Value`. + +!!! example + ```yaml + target: + type: aws + aws: + ecrOptions: + tags: + - name: cluster + value: myCluster + ``` diff --git a/pkg/config/config.go b/pkg/config/config.go index eb2e0bc5..e74dde05 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -54,11 +54,33 @@ type Target struct { } type AWS struct { - AccountID string `yaml:"accountId"` - Region string `yaml:"region"` - Role string `yaml:"role"` - AccessPolicy string `yaml:"accessPolicy"` - LifecyclePolicy string `yaml:"lifecyclePolicy"` + AccountID string `yaml:"accountId"` + Region string `yaml:"region"` + Role string `yaml:"role"` + ECROptions ECROptions `yaml:"ecrOptions"` +} + +type ECROptions struct { + AccessPolicy string `yaml:"accessPolicy"` + LifecyclePolicy string `yaml:"lifecyclePolicy"` + Tags []Tag `yaml:"tags"` + ImageTagMutability string `yaml:"imageTagMutability"` + ImageScanningConfiguration ImageScanningConfiguration `yaml:"imageScanningConfiguration"` + EncryptionConfiguration EncryptionConfiguration `yaml:"encryptionConfiguration"` +} + +type Tag struct { + Key string `yaml:"key"` + Value string `yaml:"value"` +} + +type ImageScanningConfiguration struct { + ImageScanOnPush bool `yaml:"imageScanOnPush"` +} + +type EncryptionConfiguration struct { + EncryptionType string `yaml:"encryptionType"` + KmsKey string `yaml:"kmsKey"` } func (a *AWS) EcrDomain() string { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index ea9ee33c..a252c9c4 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -37,6 +37,44 @@ source: }, }, }, + { + name: "should render tags config", + cfg: ` +target: + type: aws + aws: + accountId: 123456789 + region: ap-southeast-2 + role: arn:aws:iam::123456789012:role/roleName + ecrOptions: + tags: + - key: CreatedBy + value: k8s-image-swapper + - key: A + value: B +`, + expCfg: Config{ + Target: Target{ + AWS: AWS{ + AccountID: "123456789", + Region: "ap-southeast-2", + Role: "arn:aws:iam::123456789012:role/roleName", + ECROptions: ECROptions{ + Tags: []Tag{ + { + Key: "CreatedBy", + Value: "k8s-image-swapper", + }, + { + Key: "A", + Value: "B", + }, + }, + }, + }, + }, + }, + }, } for _, test := range tests { diff --git a/pkg/registry/ecr.go b/pkg/registry/ecr.go index dee3310a..bf802e81 100644 --- a/pkg/registry/ecr.go +++ b/pkg/registry/ecr.go @@ -13,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go/service/ecr" "github.com/aws/aws-sdk-go/service/ecr/ecriface" "github.com/dgraph-io/ristretto" + "github.com/estahn/k8s-image-swapper/pkg/config" "github.com/go-co-op/gocron" "github.com/rs/zerolog/log" ) @@ -28,6 +29,7 @@ type ECRClient struct { targetAccount string accessPolicy string lifecyclePolicy string + tags []config.Tag } func (e *ECRClient) Credentials() string { @@ -46,12 +48,7 @@ func (e *ECRClient) CreateRepository(name string) error { }, ImageTagMutability: aws.String(ecr.ImageTagMutabilityMutable), RegistryId: &e.targetAccount, - Tags: []*ecr.Tag{ - { - Key: aws.String("CreatedBy"), - Value: aws.String("k8s-image-swapper"), - }, - }, + Tags: e.buildEcrTags(), }) if err != nil { @@ -102,6 +99,21 @@ func (e *ECRClient) CreateRepository(name string) error { return nil } +func (e *ECRClient) SetRepositoryTags(tags []config.Tag) { + e.tags = tags +} + +func (e *ECRClient) buildEcrTags() []*ecr.Tag { + ecrTags := []*ecr.Tag{} + + for _, t := range e.tags { + tag := ecr.Tag{Key: &t.Key, Value: &t.Value} + ecrTags = append(ecrTags, &tag) + } + + return ecrTags +} + func (e *ECRClient) RepositoryExists() bool { panic("implement me") } @@ -249,6 +261,7 @@ func NewMockECRClient(ecrClient ecriface.ECRAPI, region string, ecrDomain string scheduler: nil, targetAccount: targetAccount, authToken: []byte("mock-ecr-client-fake-auth-token"), + tags: []config.Tag{{Key: "CreatedBy", Value: "k8s-image-swapper"}}, } return client, nil diff --git a/pkg/webhook/image_swapper_test.go b/pkg/webhook/image_swapper_test.go index 88b800c3..2aedd3a5 100644 --- a/pkg/webhook/image_swapper_test.go +++ b/pkg/webhook/image_swapper_test.go @@ -246,10 +246,12 @@ func TestImageSwapper_Mutate(t *testing.T) { ImageTagMutability: aws.String("MUTABLE"), RepositoryName: aws.String("docker.io/library/init-container"), RegistryId: aws.String("123456789"), - Tags: []*ecr.Tag{{ - Key: aws.String("CreatedBy"), - Value: aws.String("k8s-image-swapper"), - }}, + Tags: []*ecr.Tag{ + { + Key: aws.String("CreatedBy"), + Value: aws.String("k8s-image-swapper"), + }, + }, }).Return(mock.Anything) ecrClient.On( "CreateRepository", @@ -260,10 +262,12 @@ func TestImageSwapper_Mutate(t *testing.T) { ImageTagMutability: aws.String("MUTABLE"), RepositoryName: aws.String("docker.io/library/nginx"), RegistryId: aws.String("123456789"), - Tags: []*ecr.Tag{{ - Key: aws.String("CreatedBy"), - Value: aws.String("k8s-image-swapper"), - }}, + Tags: []*ecr.Tag{ + { + Key: aws.String("CreatedBy"), + Value: aws.String("k8s-image-swapper"), + }, + }, }).Return(mock.Anything) ecrClient.On( "CreateRepository", @@ -274,10 +278,12 @@ func TestImageSwapper_Mutate(t *testing.T) { ImageTagMutability: aws.String("MUTABLE"), RepositoryName: aws.String("k8s.gcr.io/ingress-nginx/controller"), RegistryId: aws.String("123456789"), - Tags: []*ecr.Tag{{ - Key: aws.String("CreatedBy"), - Value: aws.String("k8s-image-swapper"), - }}, + Tags: []*ecr.Tag{ + { + Key: aws.String("CreatedBy"), + Value: aws.String("k8s-image-swapper"), + }, + }, }).Return(mock.Anything) registryClient, _ := registry.NewMockECRClient(ecrClient, "ap-southeast-2", "123456789.dkr.ecr.ap-southeast-2.amazonaws.com", "123456789", "arn:aws:iam::123456789:role/fakerole") @@ -328,10 +334,12 @@ func TestImageSwapper_MutateWithImagePullSecrets(t *testing.T) { ImageTagMutability: aws.String("MUTABLE"), RegistryId: aws.String("123456789"), RepositoryName: aws.String("docker.io/library/nginx"), - Tags: []*ecr.Tag{{ - Key: aws.String("CreatedBy"), - Value: aws.String("k8s-image-swapper"), - }}, + Tags: []*ecr.Tag{ + { + Key: aws.String("CreatedBy"), + Value: aws.String("k8s-image-swapper"), + }, + }, }).Return(mock.Anything) registryClient, _ := registry.NewMockECRClient(ecrClient, "ap-southeast-2", "123456789.dkr.ecr.ap-southeast-2.amazonaws.com", "123456789", "arn:aws:iam::123456789:role/fakerole")