Merge pull request #228 from keel-hq/feature/registry_cfg

Feature/registry cfg
gen-manifests-from-chart 0.9.3
Karolis Rusenas 2018-06-17 23:01:22 +01:00 committed by GitHub
commit df7ed4ae09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 157 additions and 68 deletions

View File

@ -6,7 +6,7 @@ jobs:
build:
docker:
# specify the version
- image: circleci/golang:1.9.3
- image: circleci/golang:1.10.3
#### TEMPLATE_NOTE: go expects specific checkout path representing url
#### expecting it in the form of

View File

@ -1,4 +1,4 @@
FROM golang:1.9.3
FROM golang:1.10.3
COPY . /go/src/github.com/keel-hq/keel
WORKDIR /go/src/github.com/keel-hq/keel
RUN make install

View File

@ -59,6 +59,10 @@ const (
EnvHelmProvider = "HELM_PROVIDER" // helm provider
EnvHelmTillerAddress = "TILLER_ADDRESS" // helm provider
// EnvDefaultDockerRegistryCfg - default registry configuration that can be passed into
// keel for polling trigger
EnvDefaultDockerRegistryCfg = "DOCKER_REGISTRY_CFG"
)
// kubernetes config, if empty - will default to InCluster
@ -174,7 +178,18 @@ func main() {
providers := setupProviders(implementer, sender, approvalsManager, &t.GenericResourceCache)
// registering secrets based credentials helper
secretsGetter := secrets.NewGetter(implementer)
dockerConfig := make(secrets.DockerCfg)
if os.Getenv(EnvDefaultDockerRegistryCfg) != "" {
dockerConfigStr := os.Getenv(EnvDefaultDockerRegistryCfg)
dockerConfig, err = secrets.DecodeDockerCfgJson([]byte(dockerConfigStr))
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Fatalf("failed to decode secret provided in %s env variable", EnvDefaultDockerRegistryCfg)
}
}
secretsGetter := secrets.NewGetter(implementer, dockerConfig)
ch := secretsCredentialsHelper.New(secretsGetter)
credentialshelper.RegisterCredentialsHelper("secrets", ch)

View File

@ -30,7 +30,7 @@ spec:
spec:
serviceAccountName: keel
containers:
- image: keelhq/keel:0.8.3
- image: keelhq/keel:alpha
imagePullPolicy: Always
env:
# - name: POLL
@ -42,6 +42,10 @@ spec:
# value: "1"
# - name: TILLER_ADDRESS
# value: tiller-deploy.kube-system.svc.cluster.local:44134
# Default Docker registry configuration (will override secrets if there are any)
# 'auth' parameter is base 64 encoded username:password pair. Ideally this environment variable would be a Kubernetes secret.
# - name: DOCKER_REGISTRY_CFG
# value: '{"auths":{"https://index.docker.io/v1/":{"username":"login","password":"somepass","email":"email@email.com","auth":"longbase64secret"}}}'
- name: PROJECT_ID
value: "my-project-id"
# - name: WEBHOOK_ENDPOINT

View File

@ -86,7 +86,7 @@ spec:
spec:
serviceAccountName: keel
containers:
- image: keelhq/keel:0.8.3
- image: keelhq/keel:0.9.3
imagePullPolicy: Always
env:
# - name: POLL
@ -96,6 +96,10 @@ spec:
# Enable/disable Helm provider
# - name: HELM_PROVIDER
# value: "1"
# Default Docker registry configuration (will override secrets if there are any)
# 'auth' parameter is base 64 encoded username:password pair. Ideally this environment variable would be a Kubernetes secret.
# - name: DOCKER_REGISTRY_CFG
# value: '{"auths":{"https://index.docker.io/v1/":{"username":"login","password":"somepass","email":"email@email.com","auth":"longbase64secret"}}}'
- name: PROJECT_ID
value: "my-project-id"
# - name: WEBHOOK_ENDPOINT

View File

@ -16,7 +16,7 @@ spec:
spec:
serviceAccountName: keel
containers:
- image: keelhq/keel:0.8.3
- image: keelhq/keel:0.9.3
imagePullPolicy: Always
env:
# - name: POLL

View File

@ -36,12 +36,20 @@ type Getter interface {
// DefaultGetter - default kubernetes secret getter implementation
type DefaultGetter struct {
kubernetesImplementer kubernetes.Implementer
defaultDockerConfig DockerCfg // default configuration supplied by optional environment variable
}
// NewGetter - create new default getter
func NewGetter(implementer kubernetes.Implementer) *DefaultGetter {
func NewGetter(implementer kubernetes.Implementer, defaultDockerConfig DockerCfg) *DefaultGetter {
// initialising empty configuration
if defaultDockerConfig == nil {
defaultDockerConfig = make(DockerCfg)
}
return &DefaultGetter{
kubernetesImplementer: implementer,
defaultDockerConfig: defaultDockerConfig,
}
}
@ -51,6 +59,12 @@ func (g *DefaultGetter) Get(image *types.TrackedImage) (*types.Credentials, erro
return nil, ErrNamespaceNotSpecified
}
// checking in default creds
creds, found := g.lookupDefaultDockerConfig(image)
if found {
return creds, nil
}
switch image.Provider {
case helm.ProviderName:
// looking up secrets based on selector
@ -66,6 +80,10 @@ func (g *DefaultGetter) Get(image *types.TrackedImage) (*types.Credentials, erro
return g.getCredentialsFromSecret(image)
}
func (g *DefaultGetter) lookupDefaultDockerConfig(image *types.TrackedImage) (*types.Credentials, bool) {
return credentialsFromConfig(image, g.defaultDockerConfig)
}
func (g *DefaultGetter) lookupSecrets(image *types.TrackedImage) ([]string, error) {
secrets := []string{}
@ -170,7 +188,7 @@ func (g *DefaultGetter) getCredentialsFromSecret(image *types.TrackedImage) (*ty
continue
}
dockerCfg, err = decodeJSONSecret(secretDataBts)
dockerCfg, err = DecodeDockerCfgJson(secretDataBts)
if err != nil {
log.WithFields(log.Fields{
"image": image.Image.Repository(),
@ -192,58 +210,9 @@ func (g *DefaultGetter) getCredentialsFromSecret(image *types.TrackedImage) (*ty
continue
}
// looking for our registry
for registry, auth := range dockerCfg {
h, err := hostname(registry)
if err != nil {
log.WithFields(log.Fields{
"image": image.Image.Repository(),
"namespace": image.Namespace,
"registry": registry,
"secret_ref": secretRef,
"error": err,
}).Error("secrets.defaultGetter: failed to parse hostname")
continue
}
if h == image.Image.Registry() {
if auth.Username != "" && auth.Password != "" {
credentials.Username = auth.Username
credentials.Password = auth.Password
} else if auth.Auth != "" {
username, password, err := decodeBase64Secret(auth.Auth)
if err != nil {
log.WithFields(log.Fields{
"image": image.Image.Repository(),
"namespace": image.Namespace,
"registry": registry,
"secret_ref": secretRef,
"error": err,
}).Error("secrets.defaultGetter: failed to decode auth secret")
continue
}
credentials.Username = username
credentials.Password = password
} else {
log.WithFields(log.Fields{
"image": image.Image.Repository(),
"namespace": image.Namespace,
"registry": registry,
"secret_ref": secretRef,
"error": err,
}).Warn("secrets.defaultGetter: secret doesn't have username, password and base64 encoded auth, skipping")
continue
}
log.WithFields(log.Fields{
"namespace": image.Namespace,
"provider": image.Provider,
"registry": image.Image.Registry(),
"image": image.Image.Repository(),
}).Debug("secrets.defaultGetter: secret looked up successfully")
return credentials, nil
}
creds, found := credentialsFromConfig(image, dockerCfg)
if found {
return creds, nil
}
}
@ -260,6 +229,64 @@ func (g *DefaultGetter) getCredentialsFromSecret(image *types.TrackedImage) (*ty
return credentials, nil
}
func credentialsFromConfig(image *types.TrackedImage, cfg DockerCfg) (*types.Credentials, bool) {
credentials := &types.Credentials{}
found := false
// looking for our registry
for registry, auth := range cfg {
h, err := hostname(registry)
if err != nil {
log.WithFields(log.Fields{
"image": image.Image.Repository(),
"namespace": image.Namespace,
"registry": registry,
"error": err,
}).Error("secrets.defaultGetter: failed to parse hostname")
continue
}
if h == image.Image.Registry() {
if auth.Username != "" && auth.Password != "" {
credentials.Username = auth.Username
credentials.Password = auth.Password
} else if auth.Auth != "" {
username, password, err := decodeBase64Secret(auth.Auth)
if err != nil {
log.WithFields(log.Fields{
"image": image.Image.Repository(),
"namespace": image.Namespace,
"registry": registry,
"error": err,
}).Error("secrets.defaultGetter: failed to decode auth secret")
continue
}
credentials.Username = username
credentials.Password = password
found = true
} else {
log.WithFields(log.Fields{
"image": image.Image.Repository(),
"namespace": image.Namespace,
"registry": registry,
"error": err,
}).Warn("secrets.defaultGetter: secret doesn't have username, password and base64 encoded auth, skipping")
continue
}
log.WithFields(log.Fields{
"namespace": image.Namespace,
"provider": image.Provider,
"registry": image.Image.Registry(),
"image": image.Image.Repository(),
}).Debug("secrets.defaultGetter: secret looked up successfully")
return credentials, true
}
}
return credentials, found
}
func decodeBase64Secret(authSecret string) (username, password string, err error) {
decoded, err := base64.StdEncoding.DecodeString(authSecret)
if err != nil {
@ -296,7 +323,7 @@ func decodeSecret(data []byte) (DockerCfg, error) {
return cfg, nil
}
func decodeJSONSecret(data []byte) (DockerCfg, error) {
func DecodeDockerCfgJson(data []byte) (DockerCfg, error) {
var cfg DockerCfgJSON
err := json.Unmarshal(data, &cfg)
if err != nil {

View File

@ -39,7 +39,7 @@ func TestGetSecret(t *testing.T) {
},
}
getter := NewGetter(impl)
getter := NewGetter(impl, nil)
trackedImage := &types.TrackedImage{
Image: imgRef,
@ -73,7 +73,7 @@ func TestGetDockerConfigJSONSecret(t *testing.T) {
},
}
getter := NewGetter(impl)
getter := NewGetter(impl, nil)
trackedImage := &types.TrackedImage{
Image: imgRef,
@ -106,7 +106,7 @@ func TestGetDockerConfigJSONSecretUsernmePassword(t *testing.T) {
},
}
getter := NewGetter(impl)
getter := NewGetter(impl, nil)
trackedImage := &types.TrackedImage{
Image: imgRef,
@ -128,6 +128,45 @@ func TestGetDockerConfigJSONSecretUsernmePassword(t *testing.T) {
}
}
func TestGetFromDefaultCredentials(t *testing.T) {
imgRef, _ := image.Parse("karolisr/webhook-demo:0.0.11")
impl := &testutil.FakeK8sImplementer{
AvailableSecret: &v1.Secret{
Data: map[string][]byte{
dockerConfigJSONKey: []byte(secretDockerConfigJSONPayloadWithUsernamePassword),
},
Type: v1.SecretTypeDockerConfigJson,
},
}
getter := NewGetter(impl, DockerCfg{
"https://index.docker.io/v1/": &Auth{
Username: "aa",
Password: "bb",
},
})
trackedImage := &types.TrackedImage{
Image: imgRef,
Namespace: "default",
Secrets: []string{"myregistrysecret"},
}
creds, err := getter.Get(trackedImage)
if err != nil {
t.Errorf("failed to get creds: %s", err)
}
if creds.Username != "aa" {
t.Errorf("unexpected username: %s", creds.Username)
}
if creds.Password != "bb" {
t.Errorf("unexpected pass: %s", creds.Password)
}
}
func TestGetSecretNotFound(t *testing.T) {
imgRef, _ := image.Parse("karolisr/webhook-demo:0.0.11")
@ -135,7 +174,7 @@ func TestGetSecretNotFound(t *testing.T) {
Error: fmt.Errorf("some error"),
}
getter := NewGetter(impl)
getter := NewGetter(impl, nil)
trackedImage := &types.TrackedImage{
Image: imgRef,
@ -183,7 +222,7 @@ func TestLookupHelmSecret(t *testing.T) {
},
}
getter := NewGetter(impl)
getter := NewGetter(impl, nil)
trackedImage := &types.TrackedImage{
Image: imgRef,
@ -229,7 +268,7 @@ func TestLookupHelmEncodedSecret(t *testing.T) {
},
}
getter := NewGetter(impl)
getter := NewGetter(impl, nil)
trackedImage := &types.TrackedImage{
Image: imgRef,
@ -270,7 +309,7 @@ func TestLookupHelmNoSecretsFound(t *testing.T) {
Error: fmt.Errorf("not found"),
}
getter := NewGetter(impl)
getter := NewGetter(impl, nil)
trackedImage := &types.TrackedImage{
Image: imgRef,