Skip to content

Commit

Permalink
feat: ImageSwapPolicy defines the mutation strategy used by the webhook.
Browse files Browse the repository at this point in the history
feat: ImageCopyPolicy defines the image copy strategy used by the webhook.�
feat: check if image exists before mutating
feat: skopeo will retry 3 times before failing
feat: debug logs will show skopeo output
  • Loading branch information
estahn committed Dec 23, 2020
1 parent f6e7363 commit e64bc6d
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 101 deletions.
18 changes: 17 additions & 1 deletion .k8s-image-swapper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ dryRun: true
logLevel: trace
logFormat: console

# imageSwapPolicy defines the mutation strategy used by the webhook.
# - always: Will always swap the image regardless of the image existence in the target registry.
# This can result in pods ending in state ImagePullBack if images fail to be copied to the target registry.
# - exists: Only swaps the image if it exits in the target registry.
# This can result in pods pulling images from the source registry, e.g. the first pod pulls
# from source registry, subsequent pods pull from target registry.
imageSwapPolicy: exists

# imageCopyPolicy defines the image copy strategy used by the webhook.
# - delayed: Submits the copy job to a process queue and moves on.
# - immediate: Submits the copy job to a process queue and waits for it to finish (deadline 8s).
# - force: Attempts to immediately copy the image (deadline 8s).
imagePullPolicy: delayed

source:
# Filters provide control over what pods will be processed.
# By default all pods will be processed. If a condition matches, the pod will NOT be processed.
Expand All @@ -28,7 +42,9 @@ target:
accountId: 123456789
region: ap-southeast-2
ecrOptions:
tags: []
tags:
- key: CreatedBy
value: k8s-image-swapper
imageTagMutability: MUTABLE
imageScanningConfiguration:
imageScanOnPush: true
Expand Down
17 changes: 14 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ import (

"os"

"github.com/estahn/k8s-image-swapper/pkg"
"github.com/estahn/k8s-image-swapper/pkg/config"
"github.com/estahn/k8s-image-swapper/pkg/registry"
"github.com/estahn/k8s-image-swapper/pkg/types"
"github.com/estahn/k8s-image-swapper/pkg/webhook"
homedir "github.com/mitchellh/go-homedir"
"github.com/prometheus/client_golang/prometheus/promhttp"
Expand All @@ -44,7 +45,7 @@ import (
)

var cfgFile string
var cfg *pkg.Config = &pkg.Config{}
var cfg *config.Config = &config.Config{}

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Expand All @@ -66,7 +67,17 @@ A mutating webhook for Kubernetes, pointing the images to a new location.`,
os.Exit(1)
}

wh, err := webhook.NewImageSwapperWebhook(rClient, cfg.Source.Filters)
imageSwapPolicy, err := types.ParseImageSwapPolicy(cfg.ImageSwapPolicy)
if err != nil {
log.Err(err)
}

imageCopyPolicy, err := types.ParseImageCopyPolicy(cfg.ImageCopyPolicy)
if err != nil {
log.Err(err)
}

wh, err := webhook.NewImageSwapperWebhook(rClient, cfg.Source.Filters, imageSwapPolicy, imageCopyPolicy)
if err != nil {
log.Err(err).Msg("error creating webhook")
os.Exit(1)
Expand Down
22 changes: 21 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@ The option `logLevel` & `logFormat` allow to adjust the verbosity and format (e.
logFormat: console
```

## ImageSwapPolicy

The option `imageSwapPolicy` (default: `exists`) defines the mutation strategy used.

* `always`: Will always swap the image regardless of the image existence in the target registry.
This can result in pods ending in state ImagePullBack if images fail to be copied to the target registry.
* `exists`: Only swaps the image if it exits in the target registry.
This can result in pods pulling images from the source registry, e.g. the first pod pulls
from source registry, subsequent pods pull from target registry.

## ImageCopyPolicy

The option `imageCopyPolicy` (default: `delayed`) defines the image copy strategy used.

* `delayed`: Submits the copy job to a process queue and moves on.
* `immediate`: Submits the copy job to a process queue and waits for it to finish (deadline 8s).
* `force`: Attempts to immediately copy the image (deadline 8s).



## Source

This section configures details about the image source.
Expand Down Expand Up @@ -95,7 +115,7 @@ Below you will find a list of common queries and/or ideas:
```yaml
source:
filters:
- jmespath: "contains(container.image, `.dkr.ecr.`) && contains(container.image, `.amazonaws.com`)"
- jmespath: "contains(container.image, '.dkr.ecr.') && contains(container.image, '.amazonaws.com')"
```

`k8s-image-swapper` will log the filter data and result in `debug` mode.
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/spf13/cobra v1.1.1
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.6.1
gopkg.in/yaml.v2 v2.3.0
k8s.io/api v0.19.3
k8s.io/apimachinery v0.20.0
)
59 changes: 0 additions & 59 deletions pkg/config.go

This file was deleted.

2 changes: 1 addition & 1 deletion pkg/registry/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type Client interface {
CopyImage() error
PullImage() error
PutImage() error
ImageExists() bool
ImageExists(ref string) bool

// Endpoint returns the domain of the registry
Endpoint() string
Expand Down
36 changes: 34 additions & 2 deletions pkg/registry/ecr.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package registry

import (
"encoding/base64"
"os/exec"
"time"

"github.com/aws/aws-sdk-go/aws"
Expand Down Expand Up @@ -32,6 +33,16 @@ func (e *ECRClient) CreateRepository(name string) error {

_, err := e.client.CreateRepository(&ecr.CreateRepositoryInput{
RepositoryName: aws.String(name),
ImageScanningConfiguration: &ecr.ImageScanningConfiguration{
ScanOnPush: aws.Bool(true),
},
ImageTagMutability: aws.String(ecr.ImageTagMutabilityMutable),
Tags: []*ecr.Tag{
{
Key: aws.String("CreatedBy"),
Value: aws.String("k8s-image-swapper"),
},
},
})
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
Expand Down Expand Up @@ -69,8 +80,29 @@ func (e *ECRClient) PutImage() error {
panic("implement me")
}

func (e *ECRClient) ImageExists() bool {
panic("implement me")
func (e *ECRClient) ImageExists(ref string) bool {
if _, found := e.cache.Get(ref); found {
return true
}

app := "skopeo"
args := []string{
"inspect",
"--retry-times", "3",
"docker://" + ref,
"--creds", e.Credentials(),
}

log.Trace().Str("app", app).Strs("args", args).Msg("executing command to inspect image")
cmd := exec.Command(app, args...)

if _, err := cmd.Output(); err != nil {
return false
}

e.cache.Set(ref, "", 1)

return true
}

func (e *ECRClient) Endpoint() string {
Expand Down
48 changes: 48 additions & 0 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package types

import "fmt"

type ImageSwapPolicy int

const (
ImageSwapPolicyAlways = iota
ImageSwapPolicyExists
)

func (p ImageSwapPolicy) String() string {
return [...]string{"always", "exists"}[p]
}

func ParseImageSwapPolicy(p string) (ImageSwapPolicy, error) {
switch p {
case ImageSwapPolicy(ImageSwapPolicyAlways).String():
return ImageSwapPolicyAlways, nil
case ImageSwapPolicy(ImageSwapPolicyExists).String():
return ImageSwapPolicyExists, nil
}
return ImageSwapPolicyExists, fmt.Errorf("unknown image swap policy string: '%s', defaulting to exists", p)
}

type ImageCopyPolicy int

const (
ImageCopyPolicyDelayed = iota
ImageCopyPolicyImmediate
ImageCopyPolicyForce
)

func (p ImageCopyPolicy) String() string {
return [...]string{"delayed", "immediate", "force"}[p]
}

func ParseImageCopyPolicy(p string) (ImageCopyPolicy, error) {
switch p {
case ImageCopyPolicy(ImageCopyPolicyDelayed).String():
return ImageCopyPolicyDelayed, nil
case ImageCopyPolicy(ImageCopyPolicyImmediate).String():
return ImageCopyPolicyImmediate, nil
case ImageCopyPolicy(ImageCopyPolicyForce).String():
return ImageCopyPolicyForce, nil
}
return ImageCopyPolicyDelayed, fmt.Errorf("unknown image copy policy string: '%s', defaulting to delayed", p)
}
Loading

0 comments on commit e64bc6d

Please sign in to comment.