From 12a766fd4704efab30447975abb0c2091114e2fb Mon Sep 17 00:00:00 2001 From: Manuel Hutter Date: Wed, 13 Nov 2024 14:42:37 +0100 Subject: [PATCH 1/2] Add support for "fieldRef" environment variables Sometimes it's necessary to expose certain pod metadata (e.g. its IP) as environment variables. To achieve this, Kubernetes provides the so-called "downward API" [1]. This commit allows users to make use of this API in a similar way we implemented references to existing secrets. [1]: https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/ Signed-off-by: Manuel Hutter --- docs/conversion.md | 34 +++++++++++++------ main.go | 1 + pkg/converter/converter.go | 24 +++++++++++-- tests/golden/env-vars/compose.yml | 1 + .../manifests/fooBar-oasp-deployment.yaml | 4 +++ 5 files changed, 51 insertions(+), 13 deletions(-) diff --git a/docs/conversion.md b/docs/conversion.md index 50b85a7..30f4657 100644 --- a/docs/conversion.md +++ b/docs/conversion.md @@ -142,16 +142,23 @@ spec: - secretRef: # `$name(-$refSlug)-env` name: "myapp-feat-foo-env" - # To reference a value in a Secret you need to use a special syntax in `services.$name.environment`: - # If an environment value starts with a literal '$_ref_:', it is interpreted as a Secret reference. - # Example which would generate the secretRef shown below: - # `DATABASE_PASSWORD=$_ref_:database-credentials-secret:password` env: - - DATABASE_PASSWORD + # To reference a value in a Secret you need to use a special syntax in `services.$name.environment`: + # If an environment value starts with a literal '$_ref_:', it is interpreted as a Secret reference. + # Example which would generate the secretRef shown below: + # `DATABASE_PASSWORD=$_ref_:database-credentials-secret:password` + - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: database-credentials-secret key: password + # To reference a pod field, use `$_fieldRef_:` instead + # Example which would generate the fieldRef shown below: + # `MY_POD_IP=$_fieldRef_:status.podIP` + - name: MY_POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP # List of the target port values from `services.$name.ports` ports: - containerPort: 8000 @@ -265,16 +272,23 @@ spec: - secretRef: # `$name(-$refSlug)-env` name: "myapp-feat-foo-env" - # To reference a value in a Secret you need to use a special syntax in `services.$name.environment`: - # If an environment value starts with a literal '$_ref_:', it is interpreted as a Secret reference. - # Example which would generate the secretRef shown below: - # `DATABASE_PASSWORD=$_ref_:database-credentials-secret:password` env: - - DATABASE_PASSWORD + # To reference a value in a Secret you need to use a special syntax in `services.$name.environment`: + # If an environment value starts with a literal '$_ref_:', it is interpreted as a Secret reference. + # Example which would generate the secretRef shown below: + # `DATABASE_PASSWORD=$_ref_:database-credentials-secret:password` + - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: database-credentials-secret key: password + # To reference a pod field, use `$_fieldRef_:` instead + # Example which would generate the fieldRef shown below: + # `MY_POD_IP=$_fieldRef_:status.podIP` + - name: MY_POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP # List of the target port values from `services.$name.ports` ports: - containerPort: 8000 diff --git a/main.go b/main.go index a48f46d..6a81040 100644 --- a/main.go +++ b/main.go @@ -87,6 +87,7 @@ func Main(args []string) int { return 1 } env["_ref_"] = converter.SecretRefMagic + env["_fieldRef_"] = converter.FieldRefMagic configDetails := composeTypes.ConfigDetails{ ConfigFiles: composeConfigFiles, Environment: env, diff --git a/pkg/converter/converter.go b/pkg/converter/converter.go index 3d3dccc..5704d9f 100644 --- a/pkg/converter/converter.go +++ b/pkg/converter/converter.go @@ -27,7 +27,11 @@ import ( ) var ( - SecretRefMagic = "ylkBUFN0o29yr4yLCTUZqzgIT6qCIbyj" // magic string to indicate that what follows isn't a value but a reference to a secret + // magic string to indicate that what follows isn't a value but a reference to a secret + SecretRefMagic = "ylkBUFN0o29yr4yLCTUZqzgIT6qCIbyj" + + // magic string to indicate that what follows isn't a value but a fieldRef + FieldRefMagic = "VtF2ZSrJBKSEJCiUVkGCyUawAfGBCwou" ) func composeServiceVolumesToK8s( @@ -94,11 +98,17 @@ func composeServicePortsToK8sContainerPorts(workload *ir.Service) []core.Contain return containerPorts } +func isReference(value *string) bool { + return value != nil && (strings.HasPrefix(*value, SecretRefMagic+":") || strings.HasPrefix(*value, FieldRefMagic+":")) +} + func composeServiceToSecret(workload *ir.Service, refSlug string, labels map[string]string) *core.Secret { stringData := make(map[string]string) for key, value := range workload.AsCompose().Environment { - if value != nil && strings.HasPrefix(*value, SecretRefMagic+":") { - // we've encountered a reference to another secret (starting with "$_ref_:" in the compose file), ignore + if isReference(value) { + // we've encountered a reference to another secret or a field ref + // (starting with "$_ref_:" or "$_fieldRef_:" in the compose file), + // ignore continue } if value == nil { @@ -339,6 +349,14 @@ func composeServiceToContainer( continue } env = append(env, core.EnvVar{Name: key, ValueFrom: &core.EnvVarSource{SecretKeyRef: &core.SecretKeySelector{LocalObjectReference: core.LocalObjectReference{Name: refStrings[0]}, Key: refStrings[1]}}}) + } else if value != nil && strings.HasPrefix(*value, FieldRefMagic+":") { + // we've encountered a fieldRef + refValue := (*value)[len(FieldRefMagic)+1:] + if strings.Contains(refValue, ":") { + logrus.Warnf("FieldRef '$_fieldRef_:%s' has invalid format, should be '$_ref_:PATH'. Ignoring.", refValue) + continue + } + env = append(env, core.EnvVar{Name: key, ValueFrom: &core.EnvVarSource{FieldRef: &core.ObjectFieldSelector{FieldPath: refValue}}}) } } diff --git a/tests/golden/env-vars/compose.yml b/tests/golden/env-vars/compose.yml index 26fee60..7d602cd 100644 --- a/tests/golden/env-vars/compose.yml +++ b/tests/golden/env-vars/compose.yml @@ -8,3 +8,4 @@ services: - "PASSWORD=$_ref_:mongodb-secret:password" - "FOOREF=$_ref_:foo:fooooooo" - "BARREF=$_ref_:bar:baaaaaar" + - "MY_IP=$_fieldRef_:status.podIP" diff --git a/tests/golden/env-vars/manifests/fooBar-oasp-deployment.yaml b/tests/golden/env-vars/manifests/fooBar-oasp-deployment.yaml index 51ceb89..701e411 100644 --- a/tests/golden/env-vars/manifests/fooBar-oasp-deployment.yaml +++ b/tests/golden/env-vars/manifests/fooBar-oasp-deployment.yaml @@ -42,6 +42,10 @@ spec: secretKeyRef: key: fooooooo name: foo + - name: MY_IP + valueFrom: + fieldRef: + fieldPath: status.podIP - name: PASSWORD valueFrom: secretKeyRef: From 98f3ed1f908481981e5e66e2ad97d5d186a0822c Mon Sep 17 00:00:00 2001 From: Manuel Hutter Date: Thu, 14 Nov 2024 15:36:18 +0100 Subject: [PATCH 2/2] feat: deprecate `_ref_` in favor of `_secretRef_` `_ref_` will keep working as is; for now there also is no way to differentiate between usages of _ref_ and _secretRef_ Signed-off-by: Manuel Hutter --- docs/conversion.md | 8 ++++---- main.go | 9 ++++++--- pkg/converter/converter.go | 8 ++++---- tests/golden/env-vars/compose.yml | 6 +++--- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/docs/conversion.md b/docs/conversion.md index 30f4657..ad3a8ab 100644 --- a/docs/conversion.md +++ b/docs/conversion.md @@ -144,9 +144,9 @@ spec: name: "myapp-feat-foo-env" env: # To reference a value in a Secret you need to use a special syntax in `services.$name.environment`: - # If an environment value starts with a literal '$_ref_:', it is interpreted as a Secret reference. + # If an environment value starts with a literal '$_secretRef_:', it is interpreted as a Secret reference. # Example which would generate the secretRef shown below: - # `DATABASE_PASSWORD=$_ref_:database-credentials-secret:password` + # `DATABASE_PASSWORD=$_secretRef_:database-credentials-secret:password` - name: DATABASE_PASSWORD valueFrom: secretKeyRef: @@ -274,9 +274,9 @@ spec: name: "myapp-feat-foo-env" env: # To reference a value in a Secret you need to use a special syntax in `services.$name.environment`: - # If an environment value starts with a literal '$_ref_:', it is interpreted as a Secret reference. + # If an environment value starts with a literal '$_secretRef_:', it is interpreted as a Secret reference. # Example which would generate the secretRef shown below: - # `DATABASE_PASSWORD=$_ref_:database-credentials-secret:password` + # `DATABASE_PASSWORD=$_secretRef_:database-credentials-secret:password` - name: DATABASE_PASSWORD valueFrom: secretKeyRef: diff --git a/main.go b/main.go index 6a81040..5ab6602 100644 --- a/main.go +++ b/main.go @@ -82,11 +82,14 @@ func Main(args []string) int { } } env := util.GetEnv() - if _, ok := env["_ref_"]; ok { - logrus.Error("The environment variable '_ref_' must not be defined as it is needed for internal purposes") - return 1 + for _, key := range []string{"_ref_", "_secretRef_", "_fieldRef_"} { + if _, ok := env[key]; ok { + logrus.Errorf("The environment variable '%s' must not be defined as it is needed for internal purposes", key) + return 1 + } } env["_ref_"] = converter.SecretRefMagic + env["_secretRef_"] = converter.SecretRefMagic env["_fieldRef_"] = converter.FieldRefMagic configDetails := composeTypes.ConfigDetails{ ConfigFiles: composeConfigFiles, diff --git a/pkg/converter/converter.go b/pkg/converter/converter.go index 5704d9f..cfab0a1 100644 --- a/pkg/converter/converter.go +++ b/pkg/converter/converter.go @@ -107,7 +107,7 @@ func composeServiceToSecret(workload *ir.Service, refSlug string, labels map[str for key, value := range workload.AsCompose().Environment { if isReference(value) { // we've encountered a reference to another secret or a field ref - // (starting with "$_ref_:" or "$_fieldRef_:" in the compose file), + // (starting with "$_secretRef_:" or "$_fieldRef_:" in the compose file), // ignore continue } @@ -341,11 +341,11 @@ func composeServiceToContainer( for _, key := range keys { value := workload.AsCompose().Environment[key] if value != nil && strings.HasPrefix(*value, SecretRefMagic+":") { - // we've encountered a reference to another secret (starting with "$_ref_:" in the compose file) + // we've encountered a reference to another secret (starting with "$_secretRef_:" in the compose file) refValue := (*value)[len(SecretRefMagic)+1:] refStrings := strings.SplitN(refValue, ":", 2) if len(refStrings) != 2 { - logrus.Warnf("Secret reference '$_ref_:%s' has invalid format, should be '$_ref_:SECRETNAME:KEY'. Ignoring.", refValue) + logrus.Warnf("Secret reference '$_secretRef_:%s' has invalid format, should be '$_secretRef_:SECRETNAME:KEY'. Ignoring.", refValue) continue } env = append(env, core.EnvVar{Name: key, ValueFrom: &core.EnvVarSource{SecretKeyRef: &core.SecretKeySelector{LocalObjectReference: core.LocalObjectReference{Name: refStrings[0]}, Key: refStrings[1]}}}) @@ -353,7 +353,7 @@ func composeServiceToContainer( // we've encountered a fieldRef refValue := (*value)[len(FieldRefMagic)+1:] if strings.Contains(refValue, ":") { - logrus.Warnf("FieldRef '$_fieldRef_:%s' has invalid format, should be '$_ref_:PATH'. Ignoring.", refValue) + logrus.Warnf("FieldRef '$_fieldRef_:%s' has invalid format, should be '$_fieldRef_:PATH'. Ignoring.", refValue) continue } env = append(env, core.EnvVar{Name: key, ValueFrom: &core.EnvVarSource{FieldRef: &core.ObjectFieldSelector{FieldPath: refValue}}}) diff --git a/tests/golden/env-vars/compose.yml b/tests/golden/env-vars/compose.yml index 7d602cd..c08d913 100644 --- a/tests/golden/env-vars/compose.yml +++ b/tests/golden/env-vars/compose.yml @@ -5,7 +5,7 @@ services: - FOO - BAR=${BAR} - something_else=${BAZ} - - "PASSWORD=$_ref_:mongodb-secret:password" - - "FOOREF=$_ref_:foo:fooooooo" - - "BARREF=$_ref_:bar:baaaaaar" + - "PASSWORD=$_secretRef_:mongodb-secret:password" + - "FOOREF=$_secretRef_:foo:fooooooo" + - "BARREF=$_secretRef_:bar:baaaaaar" - "MY_IP=$_fieldRef_:status.podIP"