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

[GenericRegistry] Round off johnmehan pull #612 #819

Open
wants to merge 6 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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*.dll
*.so
*.dylib
*.iml

# Test binary, built with `go test -c`
*.test
Expand All @@ -14,6 +15,9 @@
# Dependency directories (remove the comment below to include it)
# vendor/

# Mac
.DS_Store

.idea/
coverage.txt
k8s-image-swapper
18 changes: 13 additions & 5 deletions .k8s-image-swapper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ source:
#- jmespath: "ends_with(obj.metadata.namespace,'-dev')"

# registries:
# dockerio:
# username:
# password:
# - type: "generic"
# generic:
# repository: "repo1.azurecr.io"
# username: "user"
# password: "pass"

target:
type: aws
Expand Down Expand Up @@ -98,5 +100,11 @@ target:
}
]
}
# dockerio:
# quayio:

#target:
# type: generic
# generic:
# repository: "repo1.azurecr.io"
# username: "user"
# password: "pass"
# ignoreCert: false
6 changes: 6 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,25 @@ A mutating webhook for Kubernetes, pointing the images to a new location.`,
// Create registry clients for source registries
sourceRegistryClients := []registry.Client{}
for _, reg := range cfg.Source.Registries {
log.Trace().Msgf("Connecting to Source Registry")
sourceRegistryClient, err := registry.NewClient(reg)
if err != nil {
log.Err(err).Msgf("error connecting to source registry at %s", reg.Domain())
os.Exit(1)
}
log.Trace().Msgf("Added Source Registry: %s", sourceRegistryClient.Endpoint())
sourceRegistryClients = append(sourceRegistryClients, sourceRegistryClient)
}

// Create a registry client for private target registry

log.Trace().Msgf("Connecting to Target Registry")
targetRegistryClient, err := registry.NewClient(cfg.Target)
if err != nil {
log.Err(err).Msgf("error connecting to target registry at %s", cfg.Target.Domain())
os.Exit(1)
}
log.Trace().Msgf("Added Target Registry: %s", targetRegistryClient.Endpoint())

imageSwapPolicy, err := types.ParseImageSwapPolicy(cfg.ImageSwapPolicy)
if err != nil {
Expand All @@ -102,6 +107,7 @@ A mutating webhook for Kubernetes, pointing the images to a new location.`,
imagePullSecretProvider.SetAuthenticatedRegistries(sourceRegistryClients)

wh, err := webhook.NewImageSwapperWebhookWithOpts(
sourceRegistryClients,
targetRegistryClient,
webhook.Filters(cfg.Source.Filters),
webhook.ImagePullSecretsProvider(imagePullSecretProvider),
Expand Down
39 changes: 39 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,30 @@ Registries are described with an AWS account ID and region, mostly to construct
accountId: 234567890
region: us-east-1
```
#### Generic

By providing configuration on Generic registries you can ask `k8s-image-swapper` to handle the authentication using
username and password.

Registries are described with a repository URL, username and password.

!!! example
```yaml
source:
registries:
- type: "generic"
generic:
repository: "repo1.azurecr.io"
username: "username1"
password: "pass1"
- type: "generic"
generic:
repository: "repo2.azurecr.io"
username: "username2"
password: "pass2"
```


### Filters

Filters provide control over what pods will be processed.
Expand Down Expand Up @@ -172,6 +196,7 @@ The AWS Account ID and Region is primarily used to construct the ECR domain `[AC
region: ap-southeast-2
```


#### ECR Options

##### Tags
Expand Down Expand Up @@ -204,3 +229,17 @@ The GCP location, projectId, and repositoryId are used to constrct the GCP Artif
projectId: gcp-project-123
repositoryId: main
```

### Generic

The option `target.generic` holds details about the target registry storing the images.

!!! example
```yaml
target:
type: generic
generic:
repository: "repo2.azurecr.io"
username: "username2"
password: "pass2"
```
25 changes: 22 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ type Source struct {
}

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

type AWS struct {
Expand All @@ -78,6 +79,13 @@ type GCP struct {
RepositoryID string `yaml:"repositoryId"`
}

type Generic struct {
Repository string `yaml:"repository"`
Username string `yaml:"username"`
Password string `yaml:"password"`
IgnoreCert bool `yaml:"ignoreCert"`
}

type ECROptions struct {
AccessPolicy string `yaml:"accessPolicy"`
LifecyclePolicy string `yaml:"lifecyclePolicy"`
Expand Down Expand Up @@ -150,6 +158,17 @@ func CheckRegistryConfiguration(r Registry) error {
if r.GCP.RepositoryID == "" {
return errorWithType(`requires a field "repositoryId"`)
}
case types.RegistryGeneric:
if r.Generic.Repository == "" {
return errorWithType(`requires a field "repository"`)
}
if r.Generic.Username == "" {
return errorWithType(`requires a field "username"`)
}
if r.Generic.Password == "" {
return errorWithType(`requires a field "password"`)
}
return nil
}

return nil
Expand Down
16 changes: 15 additions & 1 deletion pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/stretchr/testify/assert"
)



// TestConfigParses validates if yaml annotation do not overlap
func TestConfigParses(t *testing.T) {
tests := []struct {
Expand Down Expand Up @@ -114,6 +116,11 @@ source:
aws:
accountId: "12345678912"
region: "us-west-1"
- type: "generic"
generic:
repository: "https://12345678912"
username: "demo"
password: "pass"
- type: "aws"
aws:
accountId: "12345678912"
Expand All @@ -139,6 +146,13 @@ source:
AccountID: "12345678912",
Region: "us-west-1",
}},
{
Type: "generic",
Generic: Generic{
Repository: "https://12345678912",
Username: "demo",
Password: "pass",
}},
{
Type: "aws",
AWS: AWS{
Expand Down Expand Up @@ -215,4 +229,4 @@ target:
}
})
}
}
}
7 changes: 4 additions & 3 deletions pkg/registry/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ import (
ctypes "github.com/containers/image/v5/types"
)

const dockerPrefix = "docker://"

// Client provides methods required to be implemented by the various target registry clients, e.g. ECR, Docker, Quay.
type Client interface {
CreateRepository(ctx context.Context, name string) error
RepositoryExists() bool
CopyImage(ctx context.Context, src ctypes.ImageReference, srcCreds string, dest ctypes.ImageReference, destCreds string) error
PullImage() error
PutImage() error
ImageExists(ctx context.Context, ref ctypes.ImageReference) bool

// Endpoint returns the domain of the registry
Expand Down Expand Up @@ -53,6 +52,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
84 changes: 84 additions & 0 deletions pkg/registry/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package registry

import (
"context"
"fmt"
"testing"

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

var genConfig = config.Generic{
Repository: "localhost",
Username: "user",
Password: "password",
IgnoreCert: true,
}

func TestNewClientSuccess(t *testing.T) {

genConfig = config.Generic{
Repository: "localhost",
Username: "user",
Password: "password",
IgnoreCert: true,
}
r := config.Registry{
Type: "generic",
Generic: genConfig,
}

curCommandExecutor := commandExecutor
defer func() { commandExecutor = curCommandExecutor }()

commandExecutor = func(ctx context.Context, name string, arg ...string) ShellCommand {
fmt.Printf("exec.Command() for %v called with %v and %v\n", t.Name(), name, arg)
return testCommandExecutor{
output: []byte("login successful"),
err: nil,
}
}

client, err := NewClient(r)
assert.Nil(t, err)
assert.NotNil(t, client)
}

func TestNewClientFailureNoType(t *testing.T) {

genConfig = config.Generic{
Repository: "localhost",
Username: "user",
Password: "password",
IgnoreCert: true,
}
r := config.Registry{
Type: "",
Generic: genConfig,
}

client, err := NewClient(r)
assert.NotNil(t, err)
assert.Nil(t, client)
assert.Equal(t, "a registry requires a type", err.Error())
}

func TestNewClientFailureInvalidType(t *testing.T) {

genConfig = config.Generic{
Repository: "localhost",
Username: "user",
Password: "password",
IgnoreCert: true,
}
r := config.Registry{
Type: "badType",
Generic: genConfig,
}

client, err := NewClient(r)
assert.NotNil(t, err)
assert.Nil(t, client)
assert.Equal(t, "unknown target registry string: 'badType', defaulting to unknown", err.Error())
}
22 changes: 22 additions & 0 deletions pkg/registry/cmd-executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package registry

import (
"context"
"os/exec"
)

type ShellCommand interface {
CombinedOutput() ([]byte, error)
Run() error
}

type execShellCommand struct {
*exec.Cmd
}

func newCommandExecutor(ctx context.Context, name string, arg ...string) ShellCommand {
execCmd := exec.CommandContext(ctx, name, arg...)
return execShellCommand{Cmd: execCmd}
}

var commandExecutor = newCommandExecutor
Loading