Skip to content

Commit

Permalink
Create pull secrets for sidecar containers.
Browse files Browse the repository at this point in the history
  • Loading branch information
hpidcock committed Jul 8, 2021
1 parent 43b8197 commit 4cffa5d
Show file tree
Hide file tree
Showing 26 changed files with 459 additions and 90 deletions.
68 changes: 68 additions & 0 deletions caas/kubernetes/provider/application/application.go
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/juju/juju/caas/kubernetes/provider/constants"
"github.com/juju/juju/caas/kubernetes/provider/resources"
"github.com/juju/juju/caas/kubernetes/provider/storage"
"github.com/juju/juju/caas/kubernetes/provider/utils"
k8sutils "github.com/juju/juju/caas/kubernetes/provider/utils"
k8swatcher "github.com/juju/juju/caas/kubernetes/provider/watcher"
"github.com/juju/juju/cloudconfig/podcfg"
Expand Down Expand Up @@ -162,6 +163,11 @@ func (a *app) Ensure(config caas.ApplicationConfig) (err error) {
}
applier.Apply(&secret)

err = a.ensureImagePullSecrets(applier, config)
if err != nil {
return errors.Annotatef(err, "applying image pull secrets")
}

serviceAccount := resources.ServiceAccount{
ServiceAccount: corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -478,6 +484,7 @@ func (a *app) Ensure(config caas.ApplicationConfig) (err error) {

// Upgrade upgrades the app to the specified version.
func (a *app) Upgrade(ver version.Number) error {
// TODO(sidecar): Unify this with Ensure
applier := a.newApplier()

if err := a.upgradeMainResource(applier, ver); err != nil {
Expand Down Expand Up @@ -1253,6 +1260,8 @@ func (a *app) applicationPodSpec(config caas.ApplicationConfig) (*corev1.PodSpec
},
}}

imagePullSecrets := []corev1.LocalObjectReference(nil)

for _, v := range containers {
container := corev1.Container{
Name: v.Name,
Expand Down Expand Up @@ -1291,6 +1300,9 @@ func (a *app) applicationPodSpec(config caas.ApplicationConfig) (*corev1.PodSpec
},
},
}
if v.Image.Password != "" {
imagePullSecrets = append(imagePullSecrets, corev1.LocalObjectReference{Name: a.imagePullSecretName(v.Name)})
}
containerSpecs = append(containerSpecs, container)
}

Expand Down Expand Up @@ -1319,6 +1331,7 @@ func (a *app) applicationPodSpec(config caas.ApplicationConfig) (*corev1.PodSpec
AutomountServiceAccountToken: &automountToken,
ServiceAccountName: a.serviceAccountName(),
NodeSelector: nodeSelector,
ImagePullSecrets: imagePullSecrets,
InitContainers: []corev1.Container{{
Name: "charm-init",
ImagePullPolicy: corev1.PullIfNotPresent,
Expand Down Expand Up @@ -1387,6 +1400,52 @@ func (a *app) applicationPodSpec(config caas.ApplicationConfig) (*corev1.PodSpec
}, nil
}

func (a *app) ensureImagePullSecrets(applier resources.Applier, config caas.ApplicationConfig) error {
desired := []resources.Resource(nil)
for _, container := range config.Containers {
if container.Image.Password == "" {
continue
}
secretData, err := utils.CreateDockerConfigJSON(container.Image.Username, container.Image.Password, container.Image.RegistryPath)
if err != nil {
return errors.Trace(err)
}
secret := &resources.Secret{
Secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: a.imagePullSecretName(container.Name),
Namespace: a.namespace,
Labels: a.labels(),
Annotations: a.annotations(config),
},
Type: corev1.SecretTypeDockerConfigJson,
Data: map[string][]byte{
corev1.DockerConfigJsonKey: secretData,
},
},
}
desired = append(desired, secret)
}

secrets, err := resources.ListSecrets(context.Background(), a.client, a.namespace, metav1.ListOptions{
LabelSelector: a.labelSelector(),
})
if err != nil {
return errors.Trace(err)
}

existing := []resources.Resource(nil)
for _, s := range secrets {
secret := s
if a.matchImagePullSecret(secret.Name) {
existing = append(existing, &secret)
}
}

applier.ApplySet(existing, desired)
return nil
}

func (a *app) annotations(config caas.ApplicationConfig) annotations.Annotation {
return k8sutils.ResourceTagsToAnnotations(config.ResourceTags, a.legacyLabels).
Merge(k8sutils.AnnotationsForVersion(config.AgentVersion.String(), a.legacyLabels))
Expand Down Expand Up @@ -1430,6 +1489,15 @@ func (a *app) qualifiedClusterName() string {
return fmt.Sprintf("%s-%s", a.modelName, a.name)
}

func (a *app) imagePullSecretName(containerName string) string {
// A pod may have multiple containers with different images and thus different secrets
return a.name + "-" + containerName + "-secret"
}

func (a *app) matchImagePullSecret(name string) bool {
return strings.HasPrefix(name, a.name+"-") && strings.HasSuffix(name, "-secret")
}

type annotationGetter interface {
GetAnnotations() map[string]string
}
Expand Down
125 changes: 123 additions & 2 deletions caas/kubernetes/provider/application/application_test.go
Expand Up @@ -147,6 +147,22 @@ func (s *applicationSuite) assertEnsure(c *gc.C, app caas.Application, cons cons
}},
},
}
pullSecretConfig, _ := k8sutils.CreateDockerConfigJSON("username", "password", "nginx-image:latest")
nginxPullSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "gitlab-nginx-secret",
Namespace: "test",
Labels: map[string]string{
"app.kubernetes.io/name": "gitlab",
"app.kubernetes.io/managed-by": "juju",
},
Annotations: map[string]string{"juju.is/version": "1.1.1"},
},
Type: corev1.SecretTypeDockerConfigJson,
Data: map[string][]byte{
corev1.DockerConfigJsonKey: pullSecretConfig,
},
}

c.Assert(app.Ensure(
caas.ApplicationConfig{
Expand Down Expand Up @@ -191,6 +207,14 @@ func (s *applicationSuite) assertEnsure(c *gc.C, app caas.Application, cons cons
},
},
},
"nginx": {
Name: "nginx",
Image: coreresources.DockerImageDetails{
RegistryPath: "nginx-image:latest",
Username: "username",
Password: "password",
},
},
},
Constraints: cons,
},
Expand All @@ -200,6 +224,10 @@ func (s *applicationSuite) assertEnsure(c *gc.C, app caas.Application, cons cons
c.Assert(err, jc.ErrorIsNil)
c.Assert(secret, gc.DeepEquals, &appSecret)

secret, err = s.client.CoreV1().Secrets("test").Get(context.TODO(), "gitlab-nginx-secret", metav1.GetOptions{})
c.Assert(err, jc.ErrorIsNil)
c.Assert(secret, gc.DeepEquals, &nginxPullSecret)

svc, err := s.client.CoreV1().Services("test").Get(context.TODO(), "gitlab", metav1.GetOptions{})
c.Assert(err, jc.ErrorIsNil)
c.Assert(svc, gc.DeepEquals, &appSvc)
Expand All @@ -212,6 +240,7 @@ func getPodSpec(c *gc.C) corev1.PodSpec {
return corev1.PodSpec{
ServiceAccountName: "gitlab",
AutomountServiceAccountToken: pointer.BoolPtr(true),
ImagePullSecrets: []corev1.LocalObjectReference{{Name: "gitlab-nginx-secret"}},
InitContainers: []corev1.Container{{
Name: "charm-init",
ImagePullPolicy: corev1.PullIfNotPresent,
Expand All @@ -222,7 +251,7 @@ func getPodSpec(c *gc.C) corev1.PodSpec {
Env: []corev1.EnvVar{
{
Name: "JUJU_CONTAINER_NAMES",
Value: "gitlab",
Value: "gitlab,nginx",
},
{
Name: "JUJU_K8S_POD_NAME",
Expand Down Expand Up @@ -278,7 +307,7 @@ func getPodSpec(c *gc.C) corev1.PodSpec {
Env: []corev1.EnvVar{
{
Name: "JUJU_CONTAINER_NAMES",
Value: "gitlab",
Value: "gitlab,nginx",
},
{
Name: constants.EnvAgentHTTPProbePort,
Expand Down Expand Up @@ -384,6 +413,39 @@ func getPodSpec(c *gc.C) corev1.PodSpec {
MountPath: "path/to/here",
},
},
}, {
Name: "nginx",
ImagePullPolicy: corev1.PullIfNotPresent,
Image: "nginx-image:latest",
Command: []string{"/charm/bin/pebble"},
Args: []string{"run", "--create-dirs", "--hold", "--verbose"},
Env: []corev1.EnvVar{
{
Name: "JUJU_CONTAINER_NAME",
Value: "nginx",
},
{
Name: "PEBBLE_SOCKET",
Value: "/charm/container/pebble.socket",
},
},
SecurityContext: &corev1.SecurityContext{
RunAsUser: int64Ptr(0),
RunAsGroup: int64Ptr(0),
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "charm-data",
MountPath: "/charm/bin/pebble",
SubPath: "charm/bin/pebble",
ReadOnly: true,
},
{
Name: "charm-data",
MountPath: "/charm/container",
SubPath: "charm/containers/nginx",
},
},
}},
Volumes: []corev1.Volume{
{
Expand Down Expand Up @@ -1911,6 +1973,65 @@ func (s *applicationSuite) TestEnsureConstraints(c *gc.C) {
)
}

func (s *applicationSuite) TestPullSecretUpdate(c *gc.C) {
app, _ := s.getApp(c, caas.DeploymentStateful, false)

unusedPullSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "gitlab-oldcontainer-secret",
Namespace: "test",
Labels: map[string]string{
"app.kubernetes.io/name": "gitlab",
"app.kubernetes.io/managed-by": "juju",
},
Annotations: map[string]string{"juju.is/version": "1.1.1"},
},
Type: corev1.SecretTypeDockerConfigJson,
Data: map[string][]byte{
corev1.DockerConfigJsonKey: []byte("wow"),
},
}

_, err := s.client.CoreV1().Secrets(s.namespace).Create(context.TODO(), &unusedPullSecret,
metav1.CreateOptions{})
c.Assert(err, jc.ErrorIsNil)

pullSecretConfig, _ := k8sutils.CreateDockerConfigJSON("username-old", "password-old", "nginx-image:latest")
nginxPullSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "gitlab-nginx-secret",
Namespace: "test",
Labels: map[string]string{
"app.kubernetes.io/name": "gitlab",
"app.kubernetes.io/managed-by": "juju",
},
Annotations: map[string]string{"juju.is/version": "1.1.1"},
},
Type: corev1.SecretTypeDockerConfigJson,
Data: map[string][]byte{
corev1.DockerConfigJsonKey: pullSecretConfig,
},
}
_, err = s.client.CoreV1().Secrets(s.namespace).Create(context.TODO(), &nginxPullSecret,
metav1.CreateOptions{})
c.Assert(err, jc.ErrorIsNil)

s.assertEnsure(c, app, constraints.Value{}, func() {})

_, err = s.client.CoreV1().Secrets(s.namespace).Get(context.TODO(), "gitlab-oldcontainer-secret", metav1.GetOptions{})
c.Assert(err, gc.ErrorMatches, `secrets "gitlab-oldcontainer-secret" not found`)

secret, err := s.client.CoreV1().Secrets(s.namespace).Get(context.TODO(), "gitlab-nginx-secret", metav1.GetOptions{})
c.Assert(err, jc.ErrorIsNil)
c.Assert(secret, gc.NotNil)
newPullSecretConfig, _ := k8sutils.CreateDockerConfigJSON("username", "password", "nginx-image:latest")
newNginxPullSecret := nginxPullSecret
newNginxPullSecret.Data = map[string][]byte{
corev1.DockerConfigJsonKey: newPullSecretConfig,
}
c.Assert(*secret, jc.DeepEquals, newNginxPullSecret)
}

func int64Ptr(a int64) *int64 {
return &a
}
2 changes: 0 additions & 2 deletions caas/kubernetes/provider/export_test.go
Expand Up @@ -28,8 +28,6 @@ import (
var (
PrepareWorkloadSpec = prepareWorkloadSpec
OperatorPod = operatorPod
ExtractRegistryURL = extractRegistryURL
CreateDockerConfigJSON = createDockerConfigJSON
FindControllerNamespace = findControllerNamespace
GetLocalMicroK8sConfig = getLocalMicroK8sConfig
AttemptMicroK8sCloud = attemptMicroK8sCloud
Expand Down

0 comments on commit 4cffa5d

Please sign in to comment.