зеркало из https://github.com/Azure/draft-classic.git
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:
Родитель
8b5579ecfd
Коммит
55a005912e
|
@ -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)
|
||||
|
|
|
@ -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, ®Auth); 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)
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче