diff --git a/README.md b/README.md index f54106a..0fa57a3 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ Service Labels | Label | Effect | | ------ | ------- | +| `k8ify.imagePullSecret: $env_var` | Creates an ImagePullSecret from the referenced environment variable. Can be used once per service| | `k8ify.singleton: true` | Compose service is only deployed once per environment instead of once per `$ref` per environment | | `k8ify.expose: $host` | The first port is exposed to the internet via a HTTPS ingress with the host name set to `$host` | | `k8ify.expose.$port: $host` | The port `$port` is exposed to the internet via a HTTPS ingress with the host name set to `$host` | diff --git a/pkg/converter/converter.go b/pkg/converter/converter.go index 3d3dccc..969641d 100644 --- a/pkg/converter/converter.go +++ b/pkg/converter/converter.go @@ -2,7 +2,6 @@ package converter import ( "fmt" - v1 "k8s.io/api/policy/v1" "log" "maps" "os" @@ -11,6 +10,8 @@ import ( "strconv" "strings" + v1 "k8s.io/api/policy/v1" + composeTypes "github.com/compose-spec/compose-go/types" "github.com/sirupsen/logrus" "github.com/vshn/k8ify/pkg/ir" @@ -120,6 +121,18 @@ func composeServiceToSecret(workload *ir.Service, refSlug string, labels map[str return &secret } +func composeServiceToPullSecret(authConf string, name string, labels map[string]string) *core.Secret { + secret := core.Secret{} + secret.APIVersion = "v1" + secret.Kind = "Secret" + secret.Type = core.SecretTypeDockerConfigJson + secret.Name = name + "-imagePull" + secret.Labels = labels + secret.Annotations = util.Annotations(labels, "Secret") + secret.StringData = map[string]string{".dockerconfigjson": authConf} + return &secret +} + func composeServiceToDeployment( workload *ir.Service, refSlug string, @@ -234,13 +247,30 @@ func composeServiceToPodTemplate( if secret != nil { secrets = append(secrets, *secret) } - + imagePullSecretReference := []core.LocalObjectReference{} + if util.ImagePullSecret(workload.AsCompose().Labels) != nil { + imagePullSecret := composeServiceToPullSecret( + *util.ImagePullSecret(workload.AsCompose().Labels), + workload.Name+refSlug, + labels) + secrets = append(secrets, *imagePullSecret) + imagePullSecretReference = append(imagePullSecretReference, core.LocalObjectReference{Name: imagePullSecret.Name + "-secret"}) + } for _, part := range workload.GetParts() { c, s, cvs := composeServiceToContainer(part, refSlug, projectVolumes, labels) containers = append(containers, c) if s != nil { secrets = append(secrets, *s) } + if util.ImagePullSecret(part.AsCompose().Labels) != nil { + imagePullSecret := composeServiceToPullSecret( + *util.ImagePullSecret(part.AsCompose().Labels), + part.Name+refSlug, + labels) + secrets = append(secrets, *imagePullSecret) + imagePullSecretReference = append(imagePullSecretReference, core.LocalObjectReference{Name: imagePullSecret.Name + "-secret"}) + + } maps.Copy(volumes, cvs) } @@ -259,6 +289,7 @@ func composeServiceToPodTemplate( podSpec := core.PodSpec{ EnableServiceLinks: &enableServiceLinks, + ImagePullSecrets: imagePullSecretReference, Containers: containers, RestartPolicy: core.RestartPolicyAlways, Volumes: volumesArray, diff --git a/pkg/ir/ir.go b/pkg/ir/ir.go index 3fc7816..ee349e2 100644 --- a/pkg/ir/ir.go +++ b/pkg/ir/ir.go @@ -1,11 +1,12 @@ package ir import ( + "strconv" + "strings" + composeTypes "github.com/compose-spec/compose-go/types" "github.com/vshn/k8ify/pkg/util" "k8s.io/apimachinery/pkg/api/resource" - "strconv" - "strings" ) type Inputs struct { diff --git a/pkg/util/configutils.go b/pkg/util/configutils.go index 17f1f22..a9349d3 100644 --- a/pkg/util/configutils.go +++ b/pkg/util/configutils.go @@ -2,13 +2,14 @@ package util import ( "fmt" - core "k8s.io/api/core/v1" "maps" "os" "regexp" "strconv" "strings" + core "k8s.io/api/core/v1" + "github.com/docker/go-units" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/resource" @@ -106,6 +107,10 @@ func PartOf(labels map[string]string) *string { return GetOptional(labels, "k8ify.partOf") } +func ImagePullSecret(labels map[string]string) *string { + return GetOptional(labels, "k8ify.imagePullSecret") +} + // StorageSize determines the requested storage size for a volume, or a // fallback value. func StorageSize(labels map[string]string, fallback string) resource.Quantity { diff --git a/tests/golden/imagepullsecrets.yml b/tests/golden/imagepullsecrets.yml new file mode 100644 index 0000000..ba24d5a --- /dev/null +++ b/tests/golden/imagepullsecrets.yml @@ -0,0 +1,55 @@ +--- +environments: + prod: + vars: + REGULAR_DEPLOYMENT_IMAGEPULLSECRET: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "foo" + } + } + } + REGULAR_STATEFULLSET_IMAGEPULLSECRET: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "bar" + } + } + } + PART_OF_DEPLOYMENT_IMAGEPULLSECRET: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "foo-baz" + } + } + } + PART_OF_DEPLOYMENT_SIDECAR_IMAGEPULLSECRET: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "foo-baz-part" + } + } + } + PART_OF_STATEFULLSET_IMAGEPULLSECRET: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "bar-baz" + } + } + } + PART_OF_STATEFULLSET_SIDECAR_IMMAGEPULLSECRET: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "bar-baz-part" + } + } + } + FOO : "one" + BAR : "two" + BAZ : "three" \ No newline at end of file diff --git a/tests/golden/imagepullsecrets/docker-compose-prod.yml b/tests/golden/imagepullsecrets/docker-compose-prod.yml new file mode 100644 index 0000000..480d079 --- /dev/null +++ b/tests/golden/imagepullsecrets/docker-compose-prod.yml @@ -0,0 +1,76 @@ +version: '3.4' +services: + regular-deployment: + image: nginx + labels: + k8ify.imagePullSecret: '$REGULAR_DEPLOYMENT_IMAGEPULLSECRET' + environment: + - FOO + - BAR=${BAR} + - something_else=${BAZ} + - "PASSWORD=$_ref_:mongodb-secret:password" + - "FOOREF=$_ref_:foo:fooooooo" + - "BARREF=$_ref_:bar:baaaaaar" + + regular-statefullset: + image: mongodb + labels: + k8ify.imagePullSecret: '$REGULAR_STATEFULLSET_IMAGEPULLSECRET' + k8ify.singleton: true + volumes: + - regular-statefullset:/data + environment: + - FOO + - BAR=${BAR} + - something_else=${BAZ} + - "PASSWORD=$_ref_:mongodb-secret:password" + - "FOOREF=$_ref_:foo:fooooooo" + - "BARREF=$_ref_:bar:baaaaaar" + + part-of-deployment: + image: nginx-frontend + labels: + k8ify.imagePullSecret: '$PART_OF_DEPLOYMENT_IMAGEPULLSECRET' + environment: + - FOO + - BAR=${BAR} + - something_else=${BAZ} + - "PASSWORD=$_ref_:mongodb-secret:password" + - "FOOREF=$_ref_:foo:fooooooo" + - "BARREF=$_ref_:bar:baaaaaar" + + part-of-deployment-sidecar: + image: php-backend + labels: + k8ify.imagePullSecret: '$PART_OF_DEPLOYMENT_SIDECAR_IMAGEPULLSECRET' + k8ify.partOf: 'part-of-deployment' + + part-of-statefullset: + labels: + k8ify.imagePullSecret: '$PART_OF_STATEFULLSET_IMAGEPULLSECRET' + k8ify.singleton: true + image: postgres + volumes: + - part-of-statefullset:/data + environment: + - FOO + - BAR=${BAR} + - something_else=${BAZ} + - "PASSWORD=$_ref_:mongodb-secret:password" + - "FOOREF=$_ref_:foo:fooooooo" + - "BARREF=$_ref_:bar:baaaaaar" + + part-of-statefullset-sidecar: + labels: + k8ify.imagePullSecret: '$PART_OF_STATEFULLSET_SIDECAR_IMMAGEPULLSECRET' + k8ify.partOf: 'part-of-statefullset' + k8ify.singleton: true + image: pgpool + +volumes: + regular-statefullset: + labels: + k8ify.singleton: true + part-of-statefullset: + labels: + k8ify.singleton: true diff --git a/tests/golden/imagepullsecrets/manifests/part-of-deployment-oasp-deployment.yaml b/tests/golden/imagepullsecrets/manifests/part-of-deployment-oasp-deployment.yaml new file mode 100644 index 0000000..bdd030d --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/part-of-deployment-oasp-deployment.yaml @@ -0,0 +1,66 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + k8ify.ref-slug: oasp + k8ify.service: part-of-deployment + name: part-of-deployment-oasp +spec: + selector: + matchLabels: + k8ify.ref-slug: oasp + k8ify.service: part-of-deployment + strategy: + type: Recreate + template: + metadata: + creationTimestamp: null + labels: + k8ify.ref-slug: oasp + k8ify.service: part-of-deployment + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: k8ify.service + operator: In + values: + - part-of-deployment + topologyKey: kubernetes.io/hostname + containers: + - env: + - name: BARREF + valueFrom: + secretKeyRef: + key: baaaaaar + name: bar + - name: FOOREF + valueFrom: + secretKeyRef: + key: fooooooo + name: foo + - name: PASSWORD + valueFrom: + secretKeyRef: + key: password + name: mongodb-secret + envFrom: + - secretRef: + name: part-of-deployment-oasp-env + image: nginx-frontend + imagePullPolicy: Always + name: part-of-deployment-oasp + resources: {} + - image: php-backend + imagePullPolicy: Always + name: part-of-deployment-sidecar-oasp + resources: {} + enableServiceLinks: false + imagePullSecrets: + - name: part-of-deployment-oasp-imagePull-secret + - name: part-of-deployment-sidecar-oasp-imagePull-secret + restartPolicy: Always +status: {} diff --git a/tests/golden/imagepullsecrets/manifests/part-of-deployment-oasp-env-secret.yaml b/tests/golden/imagepullsecrets/manifests/part-of-deployment-oasp-env-secret.yaml new file mode 100644 index 0000000..1bcb808 --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/part-of-deployment-oasp-env-secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + creationTimestamp: null + labels: + k8ify.ref-slug: oasp + k8ify.service: part-of-deployment + name: part-of-deployment-oasp-env +stringData: + BAR: two + FOO: one + something_else: three diff --git a/tests/golden/imagepullsecrets/manifests/part-of-deployment-oasp-imagePull-secret.yaml b/tests/golden/imagepullsecrets/manifests/part-of-deployment-oasp-imagePull-secret.yaml new file mode 100644 index 0000000..1cc4047 --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/part-of-deployment-oasp-imagePull-secret.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Secret +metadata: + creationTimestamp: null + labels: + k8ify.ref-slug: oasp + k8ify.service: part-of-deployment + name: part-of-deployment-oasp-imagePull +stringData: + .dockerconfigjson: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "foo-baz" + } + } + } +type: kubernetes.io/dockerconfigjson diff --git a/tests/golden/imagepullsecrets/manifests/part-of-deployment-sidecar-oasp-imagePull-secret.yaml b/tests/golden/imagepullsecrets/manifests/part-of-deployment-sidecar-oasp-imagePull-secret.yaml new file mode 100644 index 0000000..56b791a --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/part-of-deployment-sidecar-oasp-imagePull-secret.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Secret +metadata: + creationTimestamp: null + labels: + k8ify.ref-slug: oasp + k8ify.service: part-of-deployment + name: part-of-deployment-sidecar-oasp-imagePull +stringData: + .dockerconfigjson: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "foo-baz-part" + } + } + } +type: kubernetes.io/dockerconfigjson diff --git a/tests/golden/imagepullsecrets/manifests/part-of-statefullset-env-secret.yaml b/tests/golden/imagepullsecrets/manifests/part-of-statefullset-env-secret.yaml new file mode 100644 index 0000000..b9c4514 --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/part-of-statefullset-env-secret.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + creationTimestamp: null + labels: + k8ify.service: part-of-statefullset + name: part-of-statefullset-env +stringData: + BAR: two + FOO: one + something_else: three diff --git a/tests/golden/imagepullsecrets/manifests/part-of-statefullset-imagePull-secret.yaml b/tests/golden/imagepullsecrets/manifests/part-of-statefullset-imagePull-secret.yaml new file mode 100644 index 0000000..8c6f28b --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/part-of-statefullset-imagePull-secret.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Secret +metadata: + creationTimestamp: null + labels: + k8ify.service: part-of-statefullset + name: part-of-statefullset-imagePull +stringData: + .dockerconfigjson: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "bar-baz" + } + } + } +type: kubernetes.io/dockerconfigjson diff --git a/tests/golden/imagepullsecrets/manifests/part-of-statefullset-sidecar-imagePull-secret.yaml b/tests/golden/imagepullsecrets/manifests/part-of-statefullset-sidecar-imagePull-secret.yaml new file mode 100644 index 0000000..4909da5 --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/part-of-statefullset-sidecar-imagePull-secret.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Secret +metadata: + creationTimestamp: null + labels: + k8ify.service: part-of-statefullset + name: part-of-statefullset-sidecar-imagePull +stringData: + .dockerconfigjson: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "bar-baz-part" + } + } + } +type: kubernetes.io/dockerconfigjson diff --git a/tests/golden/imagepullsecrets/manifests/part-of-statefullset-statefulset.yaml b/tests/golden/imagepullsecrets/manifests/part-of-statefullset-statefulset.yaml new file mode 100644 index 0000000..20b29fe --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/part-of-statefullset-statefulset.yaml @@ -0,0 +1,83 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + creationTimestamp: null + labels: + k8ify.service: part-of-statefullset + name: part-of-statefullset +spec: + selector: + matchLabels: + k8ify.service: part-of-statefullset + serviceName: "" + template: + metadata: + creationTimestamp: null + labels: + k8ify.service: part-of-statefullset + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: k8ify.service + operator: In + values: + - part-of-statefullset + topologyKey: kubernetes.io/hostname + containers: + - env: + - name: BARREF + valueFrom: + secretKeyRef: + key: baaaaaar + name: bar + - name: FOOREF + valueFrom: + secretKeyRef: + key: fooooooo + name: foo + - name: PASSWORD + valueFrom: + secretKeyRef: + key: password + name: mongodb-secret + envFrom: + - secretRef: + name: part-of-statefullset-env + image: postgres + imagePullPolicy: Always + name: part-of-statefullset + resources: {} + volumeMounts: + - mountPath: /data + name: part-of-statefullset + - image: pgpool + imagePullPolicy: Always + name: part-of-statefullset-sidecar + resources: {} + enableServiceLinks: false + imagePullSecrets: + - name: part-of-statefullset-imagePull-secret + - name: part-of-statefullset-sidecar-imagePull-secret + restartPolicy: Always + updateStrategy: {} + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + creationTimestamp: null + labels: + k8ify.service: part-of-statefullset + name: part-of-statefullset + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + status: {} +status: + availableReplicas: 0 + replicas: 0 diff --git a/tests/golden/imagepullsecrets/manifests/regular-deployment-oasp-deployment.yaml b/tests/golden/imagepullsecrets/manifests/regular-deployment-oasp-deployment.yaml new file mode 100644 index 0000000..90255e1 --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/regular-deployment-oasp-deployment.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + k8ify.ref-slug: oasp + k8ify.service: regular-deployment + name: regular-deployment-oasp +spec: + selector: + matchLabels: + k8ify.ref-slug: oasp + k8ify.service: regular-deployment + strategy: + type: Recreate + template: + metadata: + creationTimestamp: null + labels: + k8ify.ref-slug: oasp + k8ify.service: regular-deployment + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: k8ify.service + operator: In + values: + - regular-deployment + topologyKey: kubernetes.io/hostname + containers: + - env: + - name: BARREF + valueFrom: + secretKeyRef: + key: baaaaaar + name: bar + - name: FOOREF + valueFrom: + secretKeyRef: + key: fooooooo + name: foo + - name: PASSWORD + valueFrom: + secretKeyRef: + key: password + name: mongodb-secret + envFrom: + - secretRef: + name: regular-deployment-oasp-env + image: nginx + imagePullPolicy: Always + name: regular-deployment-oasp + resources: {} + enableServiceLinks: false + imagePullSecrets: + - name: regular-deployment-oasp-imagePull-secret + restartPolicy: Always +status: {} diff --git a/tests/golden/imagepullsecrets/manifests/regular-deployment-oasp-env-secret.yaml b/tests/golden/imagepullsecrets/manifests/regular-deployment-oasp-env-secret.yaml new file mode 100644 index 0000000..8a3b76f --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/regular-deployment-oasp-env-secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + creationTimestamp: null + labels: + k8ify.ref-slug: oasp + k8ify.service: regular-deployment + name: regular-deployment-oasp-env +stringData: + BAR: two + FOO: one + something_else: three diff --git a/tests/golden/imagepullsecrets/manifests/regular-deployment-oasp-imagePull-secret.yaml b/tests/golden/imagepullsecrets/manifests/regular-deployment-oasp-imagePull-secret.yaml new file mode 100644 index 0000000..ba81d92 --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/regular-deployment-oasp-imagePull-secret.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Secret +metadata: + creationTimestamp: null + labels: + k8ify.ref-slug: oasp + k8ify.service: regular-deployment + name: regular-deployment-oasp-imagePull +stringData: + .dockerconfigjson: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "foo" + } + } + } +type: kubernetes.io/dockerconfigjson diff --git a/tests/golden/imagepullsecrets/manifests/regular-statefullset-env-secret.yaml b/tests/golden/imagepullsecrets/manifests/regular-statefullset-env-secret.yaml new file mode 100644 index 0000000..db904ef --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/regular-statefullset-env-secret.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + creationTimestamp: null + labels: + k8ify.service: regular-statefullset + name: regular-statefullset-env +stringData: + BAR: two + FOO: one + something_else: three diff --git a/tests/golden/imagepullsecrets/manifests/regular-statefullset-imagePull-secret.yaml b/tests/golden/imagepullsecrets/manifests/regular-statefullset-imagePull-secret.yaml new file mode 100644 index 0000000..d4d5be0 --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/regular-statefullset-imagePull-secret.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Secret +metadata: + creationTimestamp: null + labels: + k8ify.service: regular-statefullset + name: regular-statefullset-imagePull +stringData: + .dockerconfigjson: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "bar" + } + } + } +type: kubernetes.io/dockerconfigjson diff --git a/tests/golden/imagepullsecrets/manifests/regular-statefullset-statefulset.yaml b/tests/golden/imagepullsecrets/manifests/regular-statefullset-statefulset.yaml new file mode 100644 index 0000000..b763ee3 --- /dev/null +++ b/tests/golden/imagepullsecrets/manifests/regular-statefullset-statefulset.yaml @@ -0,0 +1,78 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + creationTimestamp: null + labels: + k8ify.service: regular-statefullset + name: regular-statefullset +spec: + selector: + matchLabels: + k8ify.service: regular-statefullset + serviceName: "" + template: + metadata: + creationTimestamp: null + labels: + k8ify.service: regular-statefullset + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: k8ify.service + operator: In + values: + - regular-statefullset + topologyKey: kubernetes.io/hostname + containers: + - env: + - name: BARREF + valueFrom: + secretKeyRef: + key: baaaaaar + name: bar + - name: FOOREF + valueFrom: + secretKeyRef: + key: fooooooo + name: foo + - name: PASSWORD + valueFrom: + secretKeyRef: + key: password + name: mongodb-secret + envFrom: + - secretRef: + name: regular-statefullset-env + image: mongodb + imagePullPolicy: Always + name: regular-statefullset + resources: {} + volumeMounts: + - mountPath: /data + name: regular-statefullset + enableServiceLinks: false + imagePullSecrets: + - name: regular-statefullset-imagePull-secret + restartPolicy: Always + updateStrategy: {} + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + creationTimestamp: null + labels: + k8ify.service: regular-statefullset + name: regular-statefullset + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + status: {} +status: + availableReplicas: 0 + replicas: 0