re-instate injecting the image pull secret into the namespace

This enables use cases like deploying to an AKS cluster with a
private third party registry such as quay.io. This is the same
behaviour as draftd used to work in previous releases.
This commit is contained in:
Matthew Fisher 2018-03-28 12:53:01 -07:00
Родитель 8b5579ecfd
Коммит 55a005912e
4 изменённых файлов: 189 добавлений и 1 удалений

Просмотреть файл

@ -38,7 +38,7 @@ We'll also need to log into the cluster to push images from our local docker dae
$ az acr login -n myregistry -g myresourcegroup
```
NOTE: Before deploying the chart to the cluster, Draft will inject a registry auth secret into the destination namespace so the image can be pulled into the cluster. You do not need to add a container registry secret into your chart; Draft will do this for you.
## Running Tiller with RBAC enabled

Просмотреть файл

@ -3,6 +3,7 @@ package builder
import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"io/ioutil"
@ -28,6 +29,9 @@ import (
"github.com/docker/docker/pkg/term"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
"k8s.io/api/core/v1"
apiErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8s "k8s.io/client-go/kubernetes"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/helm"
@ -36,6 +40,13 @@ import (
"k8s.io/helm/pkg/strvals"
)
const (
// name of the docker pull secret draft will create in the desired destination namespace
PullSecretName = "draft-pullsecret"
// name of the default service account draft will modify with the imagepullsecret
DefaultServiceAccountName = "default"
)
// Builder contains information about the build environment
type Builder struct {
DockerClient command.Cli
@ -472,6 +483,13 @@ func (b *Builder) release(ctx context.Context, app *AppContext, out chan<- *Summ
// notify that particular stage has started.
summary("started", SummaryStarted)
// inject a registry secret only if a registry was configured
if app.ctx.Env.Registry != "" {
if err := b.prepareReleaseEnvironment(ctx, app); err != nil {
return err
}
}
// If a release does not exist, install it. If another error occurs during the check,
// ignore the error and continue with the upgrade.
//
@ -523,6 +541,95 @@ func (b *Builder) release(ctx context.Context, app *AppContext, out chan<- *Summ
return nil
}
func (b *Builder) prepareReleaseEnvironment(ctx context.Context, app *AppContext) error {
// determine if the destination namespace exists, create it if not.
if _, err := b.Kube.CoreV1().Namespaces().Get(app.ctx.Env.Namespace, metav1.GetOptions{}); err != nil {
if !apiErrors.IsNotFound(err) {
return err
}
_, err = b.Kube.CoreV1().Namespaces().Create(&v1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: app.ctx.Env.Namespace},
})
if err != nil {
return fmt.Errorf("could not create namespace %q: %v", app.ctx.Env.Namespace, err)
}
}
regAuthToken, err := command.RetrieveAuthTokenFromImage(ctx, b.DockerClient, app.img)
if err != nil {
return fmt.Errorf("failed to retrieve auth token from image %s: %v", app.img, err)
}
// we need to translate the auth token Docker gives us into a Kubernetes registry auth secret token.
regAuth, err := FromAuthConfigToken(regAuthToken)
if err != nil {
return fmt.Errorf("failed to convert '%s' to a kubernetes registry auth secret token: %v", regAuthToken, err)
}
// create a new json string with the full dockerauth, including the registry URL.
js, err := json.Marshal(map[string]*DockerConfigEntryWithAuth{app.ctx.Env.Registry: regAuth})
if err != nil {
return fmt.Errorf("could not json encode docker authentication string: %v", err)
}
// determine if the registry pull secret exists in the desired namespace, create it if not.
var secret *v1.Secret
if secret, err = b.Kube.CoreV1().Secrets(app.ctx.Env.Namespace).Get(PullSecretName, metav1.GetOptions{}); err != nil {
if !apiErrors.IsNotFound(err) {
return err
}
_, err = b.Kube.CoreV1().Secrets(app.ctx.Env.Namespace).Create(
&v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: PullSecretName,
Namespace: app.ctx.Env.Namespace,
},
Type: v1.SecretTypeDockercfg,
StringData: map[string]string{
".dockercfg": string(js),
},
},
)
if err != nil {
return fmt.Errorf("could not create registry pull secret: %v", err)
}
} else {
// the registry pull secret exists, check if it needs to be updated.
if data, ok := secret.StringData[".dockercfg"]; ok && data != string(js) {
secret.StringData[".dockercfg"] = string(js)
_, err = b.Kube.CoreV1().Secrets(app.ctx.Env.Namespace).Update(secret)
if err != nil {
return fmt.Errorf("could not update registry pull secret: %v", err)
}
}
}
// determine if the default service account in the desired namespace has the correct
// imagePullSecret. If not, add it.
svcAcct, err := b.Kube.CoreV1().ServiceAccounts(app.ctx.Env.Namespace).Get(DefaultServiceAccountName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("could not load default service account: %v", err)
}
found := false
for _, ps := range svcAcct.ImagePullSecrets {
if ps.Name == PullSecretName {
found = true
break
}
}
if !found {
svcAcct.ImagePullSecrets = append(svcAcct.ImagePullSecrets, v1.LocalObjectReference{
Name: PullSecretName,
})
_, err := b.Kube.CoreV1().ServiceAccounts(app.ctx.Env.Namespace).Update(svcAcct)
if err != nil {
return fmt.Errorf("could not modify default service account with registry pull secret: %v", err)
}
}
return nil
}
func formatReleaseStatus(app *AppContext, rls *release.Release, summary func(string, SummaryStatusCode)) {
status := fmt.Sprintf("%s %v", app.ctx.Env.Name, rls.Info.Status.Code)
summary(status, SummaryLogging)

48
pkg/builder/registry.go Normal file
Просмотреть файл

@ -0,0 +1,48 @@
package builder
import (
"encoding/base64"
"encoding/json"
"github.com/docker/docker/api/types"
)
// DockerConfigEntryWithAuth is used solely for translating docker's AuthConfig token
// into a credentialprovider.dockerConfigEntry during JSON deserialization.
//
// pulled from https://github.com/kubernetes/kubernetes/blob/97892854cfa736315378cc2c206a7f4b3e190d05/pkg/credentialprovider/config.go#L232-L243
type DockerConfigEntryWithAuth struct {
// +optional
Username string `json:"username,omitempty"`
// +optional
Password string `json:"password,omitempty"`
// +optional
Email string `json:"email,omitempty"`
// +optional
Auth string `json:"auth,omitempty"`
}
// FromAuthConfigToken converts a docker auth token into type DockerConfigEntryWithAuth. This allows us to
// Marshal the object into a Kubernetes registry auth secret.
func FromAuthConfigToken(authToken string) (*DockerConfigEntryWithAuth, error) {
data, err := base64.StdEncoding.DecodeString(authToken)
if err != nil {
return nil, err
}
var regAuth types.AuthConfig
if err := json.Unmarshal(data, &regAuth); err != nil {
return nil, err
}
return FromAuthConfig(regAuth), nil
}
// FromAuthConfig converts a docker auth token into type DockerConfigEntryWithAuth. This allows us to
// Marshal the object into a Kubernetes registry auth secret.
func FromAuthConfig(ac types.AuthConfig) *DockerConfigEntryWithAuth {
return &DockerConfigEntryWithAuth{
Username: ac.Username,
Password: ac.Password,
Email: ac.Email,
Auth: ac.Auth,
}
}

Просмотреть файл

@ -0,0 +1,33 @@
package builder
import (
"reflect"
"testing"
)
func TestFromAuthConfigToken(t *testing.T) {
var authConfigTests = []struct {
input string
fail bool
expected *DockerConfigEntryWithAuth
}{
{"", true, nil},
{"badbase64input", true, nil},
{"e30K", false, &DockerConfigEntryWithAuth{}},
{"eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwicGFzc3dvcmQiOiJwYXNzd29yZCJ9Cg==", false, &DockerConfigEntryWithAuth{Username: "username", Password: "password"}},
{"eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwicGFzc3dvcmQiOiJwYXNzd29yZCIsImVtYWlsIjoiZW1haWwiLCJhdXRoIjoiYXV0aCJ9Cg==", false, &DockerConfigEntryWithAuth{Username: "username", Password: "password", Email: "email", Auth: "auth"}},
{"eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwicGFzc3dvcmQiOiJwYXNzd29yZCIsImVtYWlsIjoiZW1haWwiLCJhdXRoIjoiYXV0aCIsInNlcnZlcmFkZHJlc3MiOiJodHRwOi8vc2VydmVyYWRkcmVzcy5jb20ifQo=", false, &DockerConfigEntryWithAuth{Username: "username", Password: "password", Email: "email", Auth: "auth"}},
}
for _, tt := range authConfigTests {
actual, err := FromAuthConfigToken(tt.input)
if tt.fail && err == nil {
t.Errorf("FromAuthConfigToken(%s) was expected to fail", tt.input)
} else if !tt.fail && err != nil {
t.Errorf("FromAuthConfigToken(%s) was not expected to fail", tt.input)
}
if !reflect.DeepEqual(actual, tt.expected) {
t.Errorf("FromAuthConfigToken(%s): expected output differs from actual", tt.input)
}
}
}