diff --git a/config/300-servicebinding.yaml b/config/300-servicebinding.yaml index f1a4311f..e39db5b9 100644 --- a/config/300-servicebinding.yaml +++ b/config/300-servicebinding.yaml @@ -142,26 +142,6 @@ spec: - name type: object type: array - mappings: - description: Mappings is the collection of mappings from existing - Secret entries to new Secret entries - items: - description: ServiceBindingMapping defines a mapping from the existing - collection of Secret values to a new Secret entry. - properties: - name: - description: Name is the name of the mapped Secret entry - type: string - value: - description: Value is the value of the new Secret entry. Contents - may be a Go template and refer to the other secret entries - by name. - type: string - required: - - name - - value - type: object - type: array name: description: Name is the name of the service as projected into the application container. Defaults to .metadata.name. diff --git a/config/300-servicebindingprojection.yaml b/config/300-servicebindingprojection.yaml index 623ec853..eacfce3d 100644 --- a/config/300-servicebindingprojection.yaml +++ b/config/300-servicebindingprojection.yaml @@ -107,6 +107,10 @@ spec: type: array name: type: string + provider: + type: string + type: + type: string required: - application - binding diff --git a/pkg/apis/labsinternal/v1alpha1/servicebindingprojection_lifecycle.go b/pkg/apis/labsinternal/v1alpha1/servicebindingprojection_lifecycle.go index 54da5608..b8fc3058 100644 --- a/pkg/apis/labsinternal/v1alpha1/servicebindingprojection_lifecycle.go +++ b/pkg/apis/labsinternal/v1alpha1/servicebindingprojection_lifecycle.go @@ -9,6 +9,7 @@ import ( "context" "crypto/sha1" "fmt" + "regexp" "sort" "strings" @@ -57,19 +58,64 @@ func (b *ServiceBindingProjection) Do(ctx context.Context, ps *duckv1.WithPod) { b.Undo(ctx, ps) injectedSecrets, injectedVolumes := b.injectedValues(ps) + key := b.annotationKey() sb := b.Spec.Binding volume := corev1.Volume{ Name: fmt.Sprintf("%s%x", bindingVolumePrefix, sha1.Sum([]byte(sb.Name))), VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: sb.Name, + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: sb.Name, + }, + }, + }, + }, }, }, } + if b.Spec.Type != "" { + typeAnnotation := fmt.Sprintf("%s-type", key) + ps.Spec.Template.Annotations[typeAnnotation] = b.Spec.Type + volume.VolumeSource.Projected.Sources = append(volume.VolumeSource.Projected.Sources, + corev1.VolumeProjection{ + DownwardAPI: &corev1.DownwardAPIProjection{ + Items: []corev1.DownwardAPIVolumeFile{ + { + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: fmt.Sprintf("metadata.annotations['%s']", typeAnnotation), + }, + Path: "type", + }, + }, + }, + }, + ) + } + if b.Spec.Provider != "" { + providerAnnotation := fmt.Sprintf("%s-provider", key) + ps.Spec.Template.Annotations[providerAnnotation] = b.Spec.Provider + volume.VolumeSource.Projected.Sources = append(volume.VolumeSource.Projected.Sources, + corev1.VolumeProjection{ + DownwardAPI: &corev1.DownwardAPIProjection{ + Items: []corev1.DownwardAPIVolumeFile{ + { + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: fmt.Sprintf("metadata.annotations['%s']", providerAnnotation), + }, + Path: "provider", + }, + }, + }, + }, + ) + } ps.Spec.Template.Spec.Volumes = append(ps.Spec.Template.Spec.Volumes, volume) - injectedSecrets.Insert(volume.Secret.SecretName) + injectedSecrets.Insert(sb.Name) injectedVolumes.Insert(volume.Name) sort.SliceStable(ps.Spec.Template.Spec.Volumes, func(i, j int) bool { iname := ps.Spec.Template.Spec.Volumes[i].Name @@ -81,7 +127,7 @@ func (b *ServiceBindingProjection) Do(ctx context.Context, ps *duckv1.WithPod) { return iname < jname }) // track which secret is injected, so it can be removed when no longer used - ps.Annotations[b.annotationKey()] = volume.Secret.SecretName + ps.Annotations[key] = sb.Name for i := range ps.Spec.Template.Spec.InitContainers { c := &ps.Spec.Template.Spec.InitContainers[i] @@ -98,6 +144,7 @@ func (b *ServiceBindingProjection) Do(ctx context.Context, ps *duckv1.WithPod) { } func (b *ServiceBindingProjection) doContainer(ctx context.Context, ps *duckv1.WithPod, c *corev1.Container, bindingVolume, secretName string, allInjectedVolumes, allInjectedSecrets sets.String) { + key := b.annotationKey() mountPath := "" // lookup predefined mount path for _, e := range c.Env { @@ -133,6 +180,30 @@ func (b *ServiceBindingProjection) doContainer(ctx context.Context, ps *duckv1.W if len(b.Spec.Env) != 0 { for _, e := range b.Spec.Env { + if e.Key == "type" && b.Spec.Type != "" { + typeAnnotation := fmt.Sprintf("%s-type", key) + c.Env = append(c.Env, corev1.EnvVar{ + Name: e.Name, + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: fmt.Sprintf("metadata.annotations['%s']", typeAnnotation), + }, + }, + }) + continue + } + if e.Key == "provider" && b.Spec.Provider != "" { + providerAnnotation := fmt.Sprintf("%s-provider", key) + c.Env = append(c.Env, corev1.EnvVar{ + Name: e.Name, + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: fmt.Sprintf("metadata.annotations['%s']", providerAnnotation), + }, + }, + }) + continue + } c.Env = append(c.Env, corev1.EnvVar{ Name: e.Name, ValueFrom: &corev1.EnvVarSource{ @@ -149,9 +220,7 @@ func (b *ServiceBindingProjection) doContainer(ctx context.Context, ps *duckv1.W iv := c.Env[i] jv := c.Env[j] // only sort injected env - if iv.ValueFrom == nil || iv.ValueFrom.SecretKeyRef == nil || - jv.ValueFrom == nil || jv.ValueFrom.SecretKeyRef == nil || - !allInjectedSecrets.HasAll(iv.ValueFrom.SecretKeyRef.Name, jv.ValueFrom.SecretKeyRef.Name) { + if !b.isInjectedEnv(iv, allInjectedSecrets) || !b.isInjectedEnv(jv, allInjectedSecrets) { return false } return iv.Name < jv.Name @@ -183,15 +252,22 @@ func (b *ServiceBindingProjection) Undo(ctx context.Context, ps *duckv1.WithPod) if ps.Annotations == nil { ps.Annotations = map[string]string{} } + if ps.Spec.Template.Annotations == nil { + ps.Spec.Template.Annotations = map[string]string{} + } key := b.annotationKey() removeSecrets := sets.NewString(ps.Annotations[key], b.Spec.Binding.Name) removeVolumes := sets.NewString() delete(ps.Annotations, key) + delete(ps.Spec.Template.Annotations, fmt.Sprintf("%s-type", key)) + delete(ps.Spec.Template.Annotations, fmt.Sprintf("%s-provider", key)) preservedVolumes := []corev1.Volume{} for _, v := range ps.Spec.Template.Spec.Volumes { - if v.Secret != nil && removeSecrets.Has(v.Secret.SecretName) { + if v.Projected != nil && len(v.Projected.Sources) > 0 && + v.Projected.Sources[0].Secret != nil && + removeSecrets.Has(v.Projected.Sources[0].Secret.Name) { removeVolumes.Insert(v.Name) continue } @@ -218,7 +294,7 @@ func (b *ServiceBindingProjection) undoContainer(ctx context.Context, ps *duckv1 preservedEnv := []corev1.EnvVar{} for _, e := range c.Env { - if e.ValueFrom == nil || e.ValueFrom.SecretKeyRef == nil || !removeSecrets.Has(e.ValueFrom.SecretKeyRef.Name) { + if !b.isInjectedEnv(e, removeSecrets) { preservedEnv = append(preservedEnv, e) } } @@ -238,13 +314,27 @@ func (b *ServiceBindingProjection) injectedValues(ps *duckv1.WithPod) (sets.Stri } } for _, v := range ps.Spec.Template.Spec.Volumes { - if v.Secret != nil && secrets.Has(v.Secret.SecretName) { + if v.Projected != nil && len(v.Projected.Sources) > 0 && + v.Projected.Sources[0].Secret != nil && + secrets.Has(v.Projected.Sources[0].Secret.Name) { volumes.Insert(v.Name) } } return secrets, volumes } +var fieldPathAnnotationRe = regexp.MustCompile(fmt.Sprintf(`^%s[0-9a-f]+%s(type|provider)%s$`, regexp.QuoteMeta(fmt.Sprintf("metadata.annotations['%s-", ServiceBindingProjectionAnnotationKey)), "-", "']")) + +func (b *ServiceBindingProjection) isInjectedEnv(e corev1.EnvVar, allInjectedSecrets sets.String) bool { + if e.ValueFrom != nil && e.ValueFrom.SecretKeyRef != nil && allInjectedSecrets.Has(e.ValueFrom.SecretKeyRef.Name) { + return true + } + if e.ValueFrom != nil && e.ValueFrom.FieldRef != nil && fieldPathAnnotationRe.MatchString(e.ValueFrom.FieldRef.FieldPath) { + return true + } + return false +} + func (bs *ServiceBindingProjectionStatus) InitializeConditions() { sbpCondSet.Manage(bs).InitializeConditions() } diff --git a/pkg/apis/labsinternal/v1alpha1/servicebindingprojection_test.go b/pkg/apis/labsinternal/v1alpha1/servicebindingprojection_test.go index 50c126b3..8c3c1c0a 100644 --- a/pkg/apis/labsinternal/v1alpha1/servicebindingprojection_test.go +++ b/pkg/apis/labsinternal/v1alpha1/servicebindingprojection_test.go @@ -504,7 +504,7 @@ func TestServiceBindingProjection_Undo(t *testing.T) { Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ {Name: "preserve"}, - {Name: "injected", VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "injected-secret"}}}, + {Name: "injected", VolumeSource: corev1.VolumeSource{Projected: &corev1.ProjectedVolumeSource{Sources: []corev1.VolumeProjection{{Secret: &corev1.SecretProjection{LocalObjectReference: corev1.LocalObjectReference{Name: "injected-secret"}}}}}}}, }, }, }, @@ -555,7 +555,75 @@ func TestServiceBindingProjection_Undo(t *testing.T) { }, }, Volumes: []corev1.Volume{ - {Name: "injected", VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "injected-secret"}}}, + {Name: "injected", VolumeSource: corev1.VolumeSource{Projected: &corev1.ProjectedVolumeSource{Sources: []corev1.VolumeProjection{{Secret: &corev1.SecretProjection{LocalObjectReference: corev1.LocalObjectReference{Name: "injected-secret"}}}}}}}, + }, + }, + }, + }, + }, + expected: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + VolumeMounts: []corev1.VolumeMount{ + {Name: "preserve"}, + }, + }, + }, + Containers: []corev1.Container{ + { + VolumeMounts: []corev1.VolumeMount{ + {Name: "preserve"}, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "remove injected container volumemounts, type and provider annotations", + binding: &ServiceBindingProjection{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-binding", + }, + }, + seed: &duckv1.WithPod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17": "injected-secret", + }, + }, + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-type": "my-type", + "internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-provider": "my-provider", + }, + }, + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + VolumeMounts: []corev1.VolumeMount{ + {Name: "preserve"}, + {Name: "injected"}, + }, + }, + }, + Containers: []corev1.Container{ + { + VolumeMounts: []corev1.VolumeMount{ + {Name: "preserve"}, + {Name: "injected"}, + }, + }, + }, + Volumes: []corev1.Volume{ + {Name: "injected", VolumeSource: corev1.VolumeSource{Projected: &corev1.ProjectedVolumeSource{Sources: []corev1.VolumeProjection{{Secret: &corev1.SecretProjection{LocalObjectReference: corev1.LocalObjectReference{Name: "injected-secret"}}}}}}}, }, }, }, @@ -639,7 +707,109 @@ func TestServiceBindingProjection_Undo(t *testing.T) { }, }, Volumes: []corev1.Volume{ - {Name: "injected", VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "injected-secret"}}}, + {Name: "injected", VolumeSource: corev1.VolumeSource{Projected: &corev1.ProjectedVolumeSource{Sources: []corev1.VolumeProjection{{Secret: &corev1.SecretProjection{LocalObjectReference: corev1.LocalObjectReference{Name: "injected-secret"}}}}}}}, + }, + }, + }, + }, + }, + expected: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + {Name: "PRESERVE"}, + }, + }, + }, + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + {Name: "PRESERVE"}, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "remove injected environment variables, type and provider mapping", + binding: &ServiceBindingProjection{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-binding", + }, + }, + seed: &duckv1.WithPod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17": "injected-secret", + }, + }, + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-type": "injected-type", + "internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-provider": "injected-provider", + }, + }, + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: "PRESERVE", + }, + { + Name: "INJECTED_TYPE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-type']", + }, + }, + }, + { + Name: "INJECTED_PROVIDER", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-provider']", + }, + }, + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: "PRESERVE", + }, + { + Name: "INJECTED_TYPE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-type']", + }, + }, + }, + { + Name: "INJECTED_PROVIDER", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-provider']", + }, + }, + }, + }, + }, + }, + Volumes: []corev1.Volume{ + {Name: "injected", VolumeSource: corev1.VolumeSource{Projected: &corev1.ProjectedVolumeSource{Sources: []corev1.VolumeProjection{{Secret: &corev1.SecretProjection{LocalObjectReference: corev1.LocalObjectReference{Name: "injected-secret"}}}}}}}, }, }, }, @@ -731,7 +901,7 @@ func TestServiceBindingProjection_Undo(t *testing.T) { }, }, Volumes: []corev1.Volume{ - {Name: "injected", VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "injected-secret"}}}, + {Name: "injected", VolumeSource: corev1.VolumeSource{Projected: &corev1.ProjectedVolumeSource{Sources: []corev1.VolumeProjection{{Secret: &corev1.SecretProjection{LocalObjectReference: corev1.LocalObjectReference{Name: "injected-secret"}}}}}}}, }, }, }, @@ -766,6 +936,94 @@ func TestServiceBindingProjection_Undo(t *testing.T) { }, }, }, + { + name: "remove injected environment variables, type and provider mapping, even if annotation is missing", + binding: &ServiceBindingProjection{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-binding", + }, + }, + seed: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: "PRESERVE", + }, + { + Name: "INJECTED_TYPE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-type']", + }, + }, + }, + { + Name: "INJECTED_PROVIDER", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-provider']", + }, + }, + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: "PRESERVE", + }, + { + Name: "INJECTED_TYPE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-type']", + }, + }, + }, + { + Name: "INJECTED_PROVIDER", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-provider']", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expected: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + {Name: "PRESERVE"}, + }, + }, + }, + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + {Name: "PRESERVE"}, + }, + }, + }, + }, + }, + }, + }, + }, } for _, c := range tests { t.Run(c.name, func(t *testing.T) { @@ -863,8 +1121,141 @@ func TestServiceBindingProjection_Do(t *testing.T) { { Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "my-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "inject volume into each container with overridden type and provider", + binding: &ServiceBindingProjection{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-binding", + }, + Spec: ServiceBindingProjectionSpec{ + Name: "my-binding-name", + Type: "my-type", + Provider: "my-provider", + Binding: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + seed: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{ + {}, + }, + Containers: []corev1.Container{ + {}, + }, + }, + }, + }, + }, + expected: &duckv1.WithPod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17": "my-secret", + }, + }, + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-type": "my-type", + "internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-provider": "my-provider", + }, + }, + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: "SERVICE_BINDING_ROOT", + Value: "/bindings", + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", + MountPath: "/bindings/my-binding-name", + ReadOnly: true, + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: "SERVICE_BINDING_ROOT", + Value: "/bindings", + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", + MountPath: "/bindings/my-binding-name", + ReadOnly: true, + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + { + DownwardAPI: &corev1.DownwardAPIProjection{ + Items: []corev1.DownwardAPIVolumeFile{ + { + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-type']", + }, + Path: "type", + }, + }, + }, + }, + { + DownwardAPI: &corev1.DownwardAPIProjection{ + Items: []corev1.DownwardAPIVolumeFile{ + { + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-provider']", + }, + Path: "provider", + }, + }, + }, + }, + }, }, }, }, @@ -959,8 +1350,16 @@ func TestServiceBindingProjection_Do(t *testing.T) { { Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "my-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + }, }, }, }, @@ -1039,8 +1438,16 @@ func TestServiceBindingProjection_Do(t *testing.T) { { Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "my-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + }, }, }, }, @@ -1113,8 +1520,16 @@ func TestServiceBindingProjection_Do(t *testing.T) { { Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "my-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + }, }, }, }, @@ -1185,8 +1600,16 @@ func TestServiceBindingProjection_Do(t *testing.T) { { Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "my-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + }, }, }, }, @@ -1197,7 +1620,7 @@ func TestServiceBindingProjection_Do(t *testing.T) { }, }, { - name: "inject custom envvars", + name: "inject envvars", binding: &ServiceBindingProjection{ ObjectMeta: metav1.ObjectMeta{ Name: "my-binding", @@ -1276,8 +1699,171 @@ func TestServiceBindingProjection_Do(t *testing.T) { { Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "my-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "inject envvars, with overridden type and provider", + binding: &ServiceBindingProjection{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-binding", + }, + Spec: ServiceBindingProjectionSpec{ + Name: "my-binding-name", + Type: "my-type", + Provider: "my-provider", + Binding: corev1.LocalObjectReference{ + Name: "my-secret", + }, + Env: []EnvVar{ + { + Name: "MY_VAR", + Key: "my-key", + }, + { + Name: "MY_TYPE", + Key: "type", + }, + { + Name: "MY_PROVIDER", + Key: "provider", + }, + }, + }, + }, + seed: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: "PRESERVE", + }, + }, + }, + }, + }, + }, + }, + }, + expected: &duckv1.WithPod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17": "my-secret", + }, + }, + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-type": "my-type", + "internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-provider": "my-provider", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: "PRESERVE", + }, + { + Name: "SERVICE_BINDING_ROOT", + Value: "/bindings", + }, + { + Name: "MY_PROVIDER", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-provider']", + }, + }, + }, + { + Name: "MY_TYPE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-type']", + }, + }, + }, + { + Name: "MY_VAR", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + Key: "my-key", + }, + }, + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", + MountPath: "/bindings/my-binding-name", + ReadOnly: true, + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + { + DownwardAPI: &corev1.DownwardAPIProjection{ + Items: []corev1.DownwardAPIVolumeFile{ + { + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-type']", + }, + Path: "type", + }, + }, + }, + }, + { + DownwardAPI: &corev1.DownwardAPIProjection{ + Items: []corev1.DownwardAPIVolumeFile{ + { + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['internal.bindings.labs.vmware.com/projection-16384e6a11df69776193b6a877bfbe80bab09a17-provider']", + }, + Path: "provider", + }, + }, + }, + }, + }, }, }, }, @@ -1311,7 +1897,7 @@ func TestServiceBindingProjection_Do(t *testing.T) { Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ {Name: "preserve"}, - {Name: "injected", VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "my-secret"}}}, + {Name: "injected", VolumeSource: corev1.VolumeSource{Projected: &corev1.ProjectedVolumeSource{Sources: []corev1.VolumeProjection{{Secret: &corev1.SecretProjection{LocalObjectReference: corev1.LocalObjectReference{Name: "my-secret"}}}}}}}, }, }, }, @@ -1331,8 +1917,16 @@ func TestServiceBindingProjection_Do(t *testing.T) { { Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "my-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + }, }, }, }, @@ -1402,8 +1996,16 @@ func TestServiceBindingProjection_Do(t *testing.T) { { Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "my-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + }, }, }, }, @@ -1459,8 +2061,16 @@ func TestServiceBindingProjection_Do(t *testing.T) { { Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "my-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + }, }, }, }, @@ -1533,8 +2143,16 @@ func TestServiceBindingProjection_Do(t *testing.T) { { Name: "binding-a9a23274b0590d5057aae1ae621be723716c4dd5", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "other-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "other-secret", + }, + }, + }, + }, }, }, }, @@ -1604,16 +2222,32 @@ func TestServiceBindingProjection_Do(t *testing.T) { { Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "my-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + }, }, }, }, { Name: "binding-a9a23274b0590d5057aae1ae621be723716c4dd5", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "other-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "other-secret", + }, + }, + }, + }, }, }, }, diff --git a/pkg/apis/labsinternal/v1alpha1/servicebindingprojection_types.go b/pkg/apis/labsinternal/v1alpha1/servicebindingprojection_types.go index 32b361f3..d86dafeb 100644 --- a/pkg/apis/labsinternal/v1alpha1/servicebindingprojection_types.go +++ b/pkg/apis/labsinternal/v1alpha1/servicebindingprojection_types.go @@ -51,6 +51,14 @@ var ( type ServiceBindingProjectionSpec struct { // Name of the service binding on disk, defaults to this resource's name Name string `json:"name"` + // Type of the provisioned service. The value is exposed directly as the + // `type` in the mounted binding + // +optional + Type string `json:"type,omitempty"` + // Provider of the provisioned service. The value is exposed directly as the + // `provider` in the mounted binding + // +optional + Provider string `json:"provider,omitempty"` // Binding reference to the service binding's projected secret Binding corev1.LocalObjectReference `json:"binding"` diff --git a/pkg/apis/servicebinding/v1alpha2/servicebinding_test.go b/pkg/apis/servicebinding/v1alpha2/servicebinding_test.go index bf42fa75..e55ef324 100644 --- a/pkg/apis/servicebinding/v1alpha2/servicebinding_test.go +++ b/pkg/apis/servicebinding/v1alpha2/servicebinding_test.go @@ -181,32 +181,6 @@ func TestServiceBinding_Validate(t *testing.T) { }, expected: nil, }, - { - name: "valid, mappings", - seed: &ServiceBinding{ - Spec: ServiceBindingSpec{ - Application: &ApplicationReference{ - Reference: tracker.Reference{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "my-app", - }, - }, - Service: &tracker.Reference{ - APIVersion: "bindings.labs.vmware.com/v1alpha1", - Kind: "ProvisionedService", - Name: "my-service", - }, - Mappings: []Mapping{ - { - Name: "my-key", - Value: "my value", - }, - }, - }, - }, - expected: nil, - }, { name: "disallow namespaces", seed: &ServiceBinding{ @@ -287,60 +261,6 @@ func TestServiceBinding_Validate(t *testing.T) { ), ), }, - { - name: "empty mapping", - seed: &ServiceBinding{ - Spec: ServiceBindingSpec{ - Application: &ApplicationReference{ - Reference: tracker.Reference{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "my-app", - }, - }, - Service: &tracker.Reference{ - APIVersion: "bindings.labs.vmware.com/v1alpha1", - Kind: "ProvisionedService", - Name: "my-service", - }, - Mappings: []Mapping{ - {}, - }, - }, - }, - expected: (&apis.FieldError{}).Also( - apis.ErrMissingField("spec.mappings[0].name"), - ), - }, - { - name: "duplicate mappings", - seed: &ServiceBinding{ - Spec: ServiceBindingSpec{ - Application: &ApplicationReference{ - Reference: tracker.Reference{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: "my-app", - }, - }, - Service: &tracker.Reference{ - APIVersion: "bindings.labs.vmware.com/v1alpha1", - Kind: "ProvisionedService", - Name: "my-service", - }, - Mappings: []Mapping{ - {Name: "my-mapping", Value: "value-1"}, - {Name: "my-mapping", Value: "value-2"}, - }, - }, - }, - expected: (&apis.FieldError{}).Also( - apis.ErrMultipleOneOf( - "spec.mappings[0].name", - "spec.mappings[1].name", - ), - ), - }, { name: "disallow status annotations", seed: &ServiceBinding{ diff --git a/pkg/apis/servicebinding/v1alpha2/servicebinding_types.go b/pkg/apis/servicebinding/v1alpha2/servicebinding_types.go index 8c5f107e..77c12584 100644 --- a/pkg/apis/servicebinding/v1alpha2/servicebinding_types.go +++ b/pkg/apis/servicebinding/v1alpha2/servicebinding_types.go @@ -63,21 +63,12 @@ type ServiceBindingSpec struct { // Env projects keys from the binding secret into the application as // environment variables Env []EnvVar `json:"env,omitempty"` - - // Mappings create new binding secret keys from literal values or templated - // from existing keys - Mappings []Mapping `json:"mappings,omitempty"` } type ApplicationReference = labsinternalv1alpha1.ApplicationReference type EnvVar = labsinternalv1alpha1.EnvVar -type Mapping struct { - Name string `json:"name"` - Value string `json:"value"` -} - type ServiceBindingStatus struct { duckv1.Status `json:",inline"` Binding *corev1.LocalObjectReference `json:"binding,omitempty"` @@ -156,29 +147,6 @@ func (b *ServiceBinding) Validate(ctx context.Context) (errs *apis.FieldError) { } } - mappingSet := map[string][]int{} - for i, m := range b.Spec.Mappings { - errs = errs.Also( - m.Validate(ctx).ViaFieldIndex("mappings", i).ViaField("spec"), - ) - if _, ok := mappingSet[m.Name]; !ok { - mappingSet[m.Name] = []int{} - } - mappingSet[m.Name] = append(mappingSet[m.Name], i) - } - // look for conflicting names - for _, v := range mappingSet { - if len(v) != 1 { - paths := make([]string, len(v)) - for pi, i := range v { - paths[i] = fmt.Sprintf("spec.mappings[%d].name", pi) - } - errs = errs.Also( - apis.ErrMultipleOneOf(paths...), - ) - } - } - if b.Status.Annotations != nil { errs = errs.Also( apis.ErrDisallowedFields("status.annotations"), @@ -188,16 +156,6 @@ func (b *ServiceBinding) Validate(ctx context.Context) (errs *apis.FieldError) { return errs } -func (m Mapping) Validate(ctx context.Context) (errs *apis.FieldError) { - if m.Name == "" { - errs = errs.Also( - apis.ErrMissingField("name"), - ) - } - - return errs -} - func (b *ServiceBinding) SetDefaults(context.Context) { if b.Spec.Name == "" { b.Spec.Name = b.Name diff --git a/pkg/apis/servicebinding/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/servicebinding/v1alpha2/zz_generated.deepcopy.go index 1b6b4060..17e326ac 100644 --- a/pkg/apis/servicebinding/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/servicebinding/v1alpha2/zz_generated.deepcopy.go @@ -16,22 +16,6 @@ import ( tracker "knative.dev/pkg/tracker" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Mapping) DeepCopyInto(out *Mapping) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mapping. -func (in *Mapping) DeepCopy() *Mapping { - if in == nil { - return nil - } - out := new(Mapping) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceBinding) DeepCopyInto(out *ServiceBinding) { *out = *in @@ -111,11 +95,6 @@ func (in *ServiceBindingSpec) DeepCopyInto(out *ServiceBindingSpec) { *out = make([]v1alpha1.EnvVar, len(*in)) copy(*out, *in) } - if in.Mappings != nil { - in, out := &in.Mappings, &out.Mappings - *out = make([]Mapping, len(*in)) - copy(*out, *in) - } return } diff --git a/pkg/reconciler/servicebinding/controller.go b/pkg/reconciler/servicebinding/controller.go index 7ede46fe..81408326 100644 --- a/pkg/reconciler/servicebinding/controller.go +++ b/pkg/reconciler/servicebinding/controller.go @@ -14,10 +14,7 @@ import ( servicebindinginformer "github.com/vmware-labs/service-bindings/pkg/client/injection/informers/servicebinding/v1alpha2/servicebinding" servicebindingreconciler "github.com/vmware-labs/service-bindings/pkg/client/injection/reconciler/servicebinding/v1alpha2/servicebinding" "github.com/vmware-labs/service-bindings/pkg/resolver" - corev1 "k8s.io/api/core/v1" "k8s.io/client-go/tools/cache" - kubeclient "knative.dev/pkg/client/injection/kube/client" - secretinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/secret" "knative.dev/pkg/configmap" "knative.dev/pkg/controller" "knative.dev/pkg/logging" @@ -31,14 +28,11 @@ func NewController( ) *controller.Impl { logger := logging.FromContext(ctx) - secretInformer := secretinformer.Get(ctx) serviceBindingProjectionInformer := servicebindingprojectioninformer.Get(ctx) serviceBindingInformer := servicebindinginformer.Get(ctx) r := &Reconciler{ - kubeclient: kubeclient.Get(ctx), bindingclient: bindingclient.Get(ctx), - secretLister: secretInformer.Lister(), serviceBindingProjectionLister: serviceBindingProjectionInformer.Lister(), } impl := servicebindingreconciler.NewImpl(ctx, r) @@ -52,16 +46,9 @@ func NewController( FilterFunc: controller.FilterControllerGK(servicebindingv1alpha2.Kind("ServiceBinding")), Handler: controller.HandleAll(impl.EnqueueControllerOf), } - secretInformer.Informer().AddEventHandler(handleMatchingControllers) serviceBindingProjectionInformer.Informer().AddEventHandler(handleMatchingControllers) r.tracker = tracker.New(impl.EnqueueKey, controller.GetTrackerLease(ctx)) - secretInformer.Informer().AddEventHandler(controller.HandleAll( - controller.EnsureTypeMeta( - r.tracker.OnChanged, - corev1.SchemeGroupVersion.WithKind("Secret"), - ), - )) return impl } diff --git a/pkg/reconciler/servicebinding/resources/names/secret.go b/pkg/reconciler/servicebinding/resources/names/secret.go deleted file mode 100644 index bc4d4f03..00000000 --- a/pkg/reconciler/servicebinding/resources/names/secret.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2020 VMware, Inc. -SPDX-License-Identifier: Apache-2.0 -*/ - -package resources - -import ( - "fmt" - - servicebindingv1alpha2 "github.com/vmware-labs/service-bindings/pkg/apis/servicebinding/v1alpha2" -) - -func ProjectedSecret(binding *servicebindingv1alpha2.ServiceBinding) string { - name := binding.Name - // limit the returned value to at most 63 characters - if len(name) > 52 { - name = name[:52] - } - return fmt.Sprintf("%s-projection", name) -} diff --git a/pkg/reconciler/servicebinding/resources/secret.go b/pkg/reconciler/servicebinding/resources/secret.go deleted file mode 100644 index 94300c4d..00000000 --- a/pkg/reconciler/servicebinding/resources/secret.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright 2020 VMware, Inc. -SPDX-License-Identifier: Apache-2.0 -*/ - -package resources - -import ( - "bytes" - "fmt" - "text/template" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "knative.dev/pkg/kmeta" - - servicebindingv1alpha2 "github.com/vmware-labs/service-bindings/pkg/apis/servicebinding/v1alpha2" - resourcenames "github.com/vmware-labs/service-bindings/pkg/reconciler/servicebinding/resources/names" -) - -func MakeProjectedSecret(binding *servicebindingv1alpha2.ServiceBinding, reference *corev1.Secret) (*corev1.Secret, error) { - projection := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourcenames.ProjectedSecret(binding), - Namespace: binding.Namespace, - Labels: kmeta.UnionMaps(binding.GetLabels(), map[string]string{ - servicebindingv1alpha2.ServiceBindingLabelKey: binding.Name, - }), - OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(binding)}, - }, - } - - projection.Data = reference.DeepCopy().Data - if binding.Spec.Type != "" { - projection.Data["type"] = []byte(binding.Spec.Type) - } - if binding.Spec.Provider != "" { - projection.Data["provider"] = []byte(binding.Spec.Provider) - } - for _, m := range binding.Spec.Mappings { - t, err := template.New("").Parse(m.Value) - if err != nil { - return nil, fmt.Errorf("Invalid template for mapping %s: %w", m.Name, err) - } - buf := bytes.NewBuffer([]byte{}) - // convert map[string][]byte to map[string]string - values := make(map[string]string, len(projection.Data)) - for k, v := range projection.Data { - values[k] = string(v) - } - err = t.Execute(buf, values) - if err != nil { - return nil, fmt.Errorf("Error executing template for mapping %s: %w", m.Name, err) - } - projection.Data[m.Name] = buf.Bytes() - } - - if _, ok := projection.Data["type"]; ok { - projection.Type = corev1.SecretType(fmt.Sprintf("service.binding/%s", projection.Data["type"])) - } else { - projection.Type = corev1.SecretTypeOpaque - } - - return projection, nil -} diff --git a/pkg/reconciler/servicebinding/resources/secret_test.go b/pkg/reconciler/servicebinding/resources/secret_test.go deleted file mode 100644 index 7db03fdc..00000000 --- a/pkg/reconciler/servicebinding/resources/secret_test.go +++ /dev/null @@ -1,258 +0,0 @@ -/* -Copyright 2020 VMware, Inc. -SPDX-License-Identifier: Apache-2.0 -*/ - -package resources - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - servicebindingv1alpha2 "github.com/vmware-labs/service-bindings/pkg/apis/servicebinding/v1alpha2" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "knative.dev/pkg/ptr" -) - -func TestMakeProjectedSecret(t *testing.T) { - tests := []struct { - name string - binding *servicebindingv1alpha2.ServiceBinding - reference *corev1.Secret - expected *corev1.Secret - expectedErr bool - }{ - { - name: "empty", - binding: &servicebindingv1alpha2.ServiceBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "my-namespace", - Name: "my-binding", - }, - }, - reference: &corev1.Secret{}, - expected: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "my-namespace", - Name: "my-binding-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: "my-binding", - Controller: ptr.Bool(true), - BlockOwnerDeletion: ptr.Bool(true), - }, - }, - }, - Type: corev1.SecretTypeOpaque, - }, - }, - { - name: "preserve existing keys in referenced secret", - binding: &servicebindingv1alpha2.ServiceBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "my-namespace", - Name: "my-binding", - }, - }, - reference: &corev1.Secret{ - Data: map[string][]byte{ - "username": []byte("root"), - "password": []byte("password1"), - }, - }, - expected: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "my-namespace", - Name: "my-binding-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: "my-binding", - Controller: ptr.Bool(true), - BlockOwnerDeletion: ptr.Bool(true), - }, - }, - }, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{ - "username": []byte("root"), - "password": []byte("password1"), - }, - }, - }, - { - name: "add type and provider to the projected secret", - binding: &servicebindingv1alpha2.ServiceBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "my-namespace", - Name: "my-binding", - }, - Spec: servicebindingv1alpha2.ServiceBindingSpec{ - Type: "mysql", - Provider: "bitnami", - }, - }, - reference: &corev1.Secret{ - Data: map[string][]byte{ - "username": []byte("root"), - "password": []byte("password1"), - }, - }, - expected: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "my-namespace", - Name: "my-binding-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: "my-binding", - Controller: ptr.Bool(true), - BlockOwnerDeletion: ptr.Bool(true), - }, - }, - }, - Type: corev1.SecretType("service.binding/mysql"), - Data: map[string][]byte{ - "username": []byte("root"), - "password": []byte("password1"), - "type": []byte("mysql"), - "provider": []byte("bitnami"), - }, - }, - }, - { - name: "add mappings to the projected secret", - binding: &servicebindingv1alpha2.ServiceBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "my-namespace", - Name: "my-binding", - }, - Spec: servicebindingv1alpha2.ServiceBindingSpec{ - Mappings: []servicebindingv1alpha2.Mapping{ - { - Name: "literal", - Value: "value", - }, - { - Name: "templated", - Value: "{{ .username }}:{{ .password }}", - }, - }, - }, - }, - reference: &corev1.Secret{ - Data: map[string][]byte{ - "username": []byte("root"), - "password": []byte("password1"), - }, - }, - expected: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "my-namespace", - Name: "my-binding-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: "my-binding", - Controller: ptr.Bool(true), - BlockOwnerDeletion: ptr.Bool(true), - }, - }, - }, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{ - "username": []byte("root"), - "password": []byte("password1"), - "literal": []byte("value"), - "templated": []byte("root:password1"), - }, - }, - }, - { - name: "invalid mapping template", - binding: &servicebindingv1alpha2.ServiceBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "my-namespace", - Name: "my-binding", - }, - Spec: servicebindingv1alpha2.ServiceBindingSpec{ - Mappings: []servicebindingv1alpha2.Mapping{ - { - Name: "bad-template", - Value: "{{ }", - }, - }, - }, - }, - reference: &corev1.Secret{ - Data: map[string][]byte{ - "username": []byte("root"), - "password": []byte("password1"), - }, - }, - expectedErr: true, - }, - { - name: "error applying mapping template", - binding: &servicebindingv1alpha2.ServiceBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "my-namespace", - Name: "my-binding", - }, - Spec: servicebindingv1alpha2.ServiceBindingSpec{ - Mappings: []servicebindingv1alpha2.Mapping{ - { - Name: "bad-template", - Value: "{{ call .invalid }}", - }, - }, - }, - }, - reference: &corev1.Secret{ - Data: map[string][]byte{}, - }, - expectedErr: true, - }, - } - for _, c := range tests { - t.Run(c.name, func(t *testing.T) { - binding := c.binding.DeepCopy() - reference := c.reference.DeepCopy() - actual, err := MakeProjectedSecret(c.binding, c.reference) - if actualErr := err == nil; actualErr == c.expectedErr { - if c.expectedErr { - t.Errorf("%s: MakeProjectedSecret() expected error", c.name) - } else { - t.Errorf("%s: MakeProjectedSecret() expected no error, got %v", c.name, err) - } - } - if diff := cmp.Diff(c.expected, actual); diff != "" { - t.Errorf("%s: MakeProjectedSecret() (-expected, +actual): %s", c.name, diff) - } - if diff := cmp.Diff(c.binding, binding); diff != "" { - t.Errorf("%s: MakeProjectedSecret() unexpected binding mutation (-expected, +actual): %s", c.name, diff) - } - if diff := cmp.Diff(c.reference, reference); diff != "" { - t.Errorf("%s: MakeProjectedSecret() unexpected reference mutation (-expected, +actual): %s", c.name, diff) - } - }) - } -} diff --git a/pkg/reconciler/servicebinding/resources/servicebindingprojection.go b/pkg/reconciler/servicebinding/resources/servicebindingprojection.go index 29dcd9b8..bc36d835 100644 --- a/pkg/reconciler/servicebinding/resources/servicebindingprojection.go +++ b/pkg/reconciler/servicebinding/resources/servicebindingprojection.go @@ -29,6 +29,8 @@ func MakeServiceBindingProjection(binding *servicebindingv1alpha2.ServiceBinding }, Spec: labsinternalv1alpha1.ServiceBindingProjectionSpec{ Name: binding.Spec.Name, + Type: binding.Spec.Type, + Provider: binding.Spec.Provider, Binding: *binding.Status.Binding, Application: *binding.Spec.Application, Env: binding.Spec.Env, diff --git a/pkg/reconciler/servicebinding/resources/servicebindingprojection_test.go b/pkg/reconciler/servicebinding/resources/servicebindingprojection_test.go index 9694d7da..1668be39 100644 --- a/pkg/reconciler/servicebinding/resources/servicebindingprojection_test.go +++ b/pkg/reconciler/servicebinding/resources/servicebindingprojection_test.go @@ -36,7 +36,9 @@ func TestMakeServiceBindingProjection(t *testing.T) { }, }, Spec: servicebindingv1alpha2.ServiceBindingSpec{ - Name: "my-binding", + Name: "my-binding", + Type: "my-type", + Provider: "my-provider", Application: &servicebindingv1alpha2.ApplicationReference{ Reference: tracker.Reference{ APIVersion: "apps/v1", @@ -78,7 +80,9 @@ func TestMakeServiceBindingProjection(t *testing.T) { }, }, Spec: labsinternalv1alpha1.ServiceBindingProjectionSpec{ - Name: "my-binding", + Name: "my-binding", + Type: "my-type", + Provider: "my-provider", Application: labsinternalv1alpha1.ApplicationReference{ Reference: tracker.Reference{ APIVersion: "apps/v1", diff --git a/pkg/reconciler/servicebinding/servicebinding.go b/pkg/reconciler/servicebinding/servicebinding.go index e6535168..07ffd77f 100644 --- a/pkg/reconciler/servicebinding/servicebinding.go +++ b/pkg/reconciler/servicebinding/servicebinding.go @@ -22,8 +22,6 @@ import ( "k8s.io/apimachinery/pkg/api/equality" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - corev1listers "k8s.io/client-go/listers/core/v1" "knative.dev/pkg/controller" "knative.dev/pkg/logging" "knative.dev/pkg/reconciler" @@ -39,9 +37,7 @@ func newReconciledNormal(namespace, name string) reconciler.Event { // Reconciler implements servicebindingreconciler.Interface for // ServiceBinding resources. type Reconciler struct { - kubeclient kubernetes.Interface bindingclient bindingclientset.Interface - secretLister corev1listers.SecretLister serviceBindingProjectionLister labsinternalv1alpha1listers.ServiceBindingProjectionLister resolver *resolver.ServiceableResolver @@ -63,14 +59,14 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, binding *servicebindingv binding.Status.InitializeConditions() - secret, err := r.projectedSecret(ctx, logger, binding) + secretRef, err := r.provisionedSecret(ctx, logger, binding) if err != nil { return err } binding.Status.Binding = nil - if secret != nil { + if secretRef != nil { binding.Status.Binding = &corev1.LocalObjectReference{ - Name: secret.Name, + Name: secretRef.Name, } binding.Status.MarkServiceAvailable() } @@ -88,82 +84,10 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, binding *servicebindingv return newReconciledNormal(binding.Namespace, binding.Name) } -func (r *Reconciler) projectedSecret(ctx context.Context, logger *zap.SugaredLogger, binding *servicebindingv1alpha2.ServiceBinding) (*corev1.Secret, error) { - recorder := controller.GetEventRecorder(ctx) - +func (r *Reconciler) provisionedSecret(ctx context.Context, logger *zap.SugaredLogger, binding *servicebindingv1alpha2.ServiceBinding) (*corev1.LocalObjectReference, error) { serviceRef := binding.Spec.Service.DeepCopy() serviceRef.Namespace = binding.Namespace - providerRef, err := r.resolver.ServiceableFromObjectReference(ctx, serviceRef, binding) - if err != nil { - return nil, err - } - r.tracker.TrackReference(tracker.Reference{ - APIVersion: "v1", - Kind: "Secret", - Namespace: binding.Namespace, - Name: providerRef.Name, - }, binding) - reference, err := r.secretLister.Secrets(binding.Namespace).Get(providerRef.Name) - if apierrs.IsNotFound(err) { - return nil, nil - } - - projectionName := resourcenames.ProjectedSecret(binding) - projection, err := r.secretLister.Secrets(binding.Namespace).Get(projectionName) - if apierrs.IsNotFound(err) { - projection, err = r.createProjectedSecret(ctx, binding, reference) - if err != nil { - recorder.Eventf(binding, corev1.EventTypeWarning, "CreationFailed", "Failed to create projected Secret %q: %v", projectionName, err) - return nil, fmt.Errorf("failed to create projected Secret: %w", err) - } - recorder.Eventf(binding, corev1.EventTypeNormal, "Created", "Created projected Secret %q", projectionName) - } else if err != nil { - return nil, fmt.Errorf("failed to get projected Secret: %w", err) - } else if !metav1.IsControlledBy(projection, binding) { - return nil, fmt.Errorf("ServiceBinding %q does not own projected Secret: %q", binding.Name, projectionName) - } else if projection, err = r.reconcileProtectedSecret(ctx, binding, reference, projection); err != nil { - return nil, fmt.Errorf("failed to reconcile projected Secret: %w", err) - } - return projection, nil -} - -func (c *Reconciler) createProjectedSecret(ctx context.Context, binding *servicebindingv1alpha2.ServiceBinding, reference *corev1.Secret) (*corev1.Secret, error) { - projection, err := resources.MakeProjectedSecret(binding, reference) - if err != nil { - return nil, err - } - return c.kubeclient.CoreV1().Secrets(binding.Namespace).Create(ctx, projection, metav1.CreateOptions{}) -} - -func projectedSecretSemanticEquals(ctx context.Context, desiredProjection, projection *corev1.Secret) (bool, error) { - return equality.Semantic.DeepEqual(desiredProjection.Type, projection.Type) && - equality.Semantic.DeepEqual(desiredProjection.Data, projection.Data) && - equality.Semantic.DeepEqual(desiredProjection.ObjectMeta.Labels, projection.ObjectMeta.Labels) && - equality.Semantic.DeepEqual(desiredProjection.ObjectMeta.Annotations, projection.ObjectMeta.Annotations), nil -} - -func (c *Reconciler) reconcileProtectedSecret(ctx context.Context, binding *servicebindingv1alpha2.ServiceBinding, reference, projection *corev1.Secret) (*corev1.Secret, error) { - existing := projection.DeepCopy() - // In the case of an upgrade, there can be default values set that don't exist pre-upgrade. - // We are setting the up-to-date default values here so an update won't be triggered if the only - // diff is the new default values. - desiredProjection, err := resources.MakeProjectedSecret(binding, reference) - if err != nil { - return nil, err - } - - if equals, err := projectedSecretSemanticEquals(ctx, desiredProjection, existing); err != nil { - return nil, err - } else if equals { - return projection, nil - } - - // Preserve the rest of the object (e.g. ObjectMeta except for labels and annotations). - existing.Data = desiredProjection.Data - existing.Type = desiredProjection.Type - existing.ObjectMeta.Annotations = desiredProjection.ObjectMeta.Annotations - existing.ObjectMeta.Labels = desiredProjection.ObjectMeta.Labels - return c.kubeclient.CoreV1().Secrets(binding.Namespace).Update(ctx, existing, metav1.UpdateOptions{}) + return r.resolver.ServiceableFromObjectReference(ctx, serviceRef, binding) } func (r *Reconciler) serviceBindingProjection(ctx context.Context, logger *zap.SugaredLogger, binding *servicebindingv1alpha2.ServiceBinding) (*labsinternalv1alpha1.ServiceBindingProjection, error) { diff --git a/pkg/reconciler/servicebinding/servicebinding_test.go b/pkg/reconciler/servicebinding/servicebinding_test.go index 8124788d..26c615dc 100644 --- a/pkg/reconciler/servicebinding/servicebinding_test.go +++ b/pkg/reconciler/servicebinding/servicebinding_test.go @@ -24,7 +24,6 @@ import ( "k8s.io/apimachinery/pkg/types" clientgotesting "k8s.io/client-go/testing" duckv1 "knative.dev/pkg/apis/duck/v1" - kubeclient "knative.dev/pkg/client/injection/kube/client" "knative.dev/pkg/configmap" "knative.dev/pkg/controller" "knative.dev/pkg/logging" @@ -35,7 +34,6 @@ import ( _ "github.com/vmware-labs/service-bindings/pkg/client/injection/ducks/duck/v1alpha2/serviceable/fake" _ "github.com/vmware-labs/service-bindings/pkg/client/injection/informers/labsinternal/v1alpha1/servicebindingprojection/fake" _ "github.com/vmware-labs/service-bindings/pkg/client/injection/informers/servicebinding/v1alpha2/servicebinding/fake" - _ "knative.dev/pkg/client/injection/kube/informers/core/v1/secret/fake" _ "knative.dev/pkg/injection/clients/dynamicclient/fake" . "github.com/vmware-labs/service-bindings/pkg/reconciler/testing" @@ -57,16 +55,7 @@ func TestReconcile(t *testing.T) { name := "my-binding" key := fmt.Sprintf("%s/%s", namespace, name) now := metav1.NewTime(time.Now()) - serviceSecret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: "my-secret", - }, - Data: map[string][]byte{ - "username": []byte("root"), - "password": []byte("password!"), - }, - } + secretName := "my-secret" provisionedService := &labsv1alpha1.ProvisionedService{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, @@ -74,7 +63,7 @@ func TestReconcile(t *testing.T) { }, Status: labsv1alpha1.ProvisionedServiceStatus{ Binding: corev1.LocalObjectReference{ - Name: serviceSecret.Name, + Name: secretName, }, }, } @@ -115,7 +104,6 @@ func TestReconcile(t *testing.T) { Key: key, Objects: []runtime.Object{ provisionedService.DeepCopy(), - serviceSecret.DeepCopy(), &servicebindingv1alpha2.ServiceBinding{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, @@ -129,7 +117,7 @@ func TestReconcile(t *testing.T) { }, Status: servicebindingv1alpha2.ServiceBindingStatus{ Binding: &corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, Status: duckv1.Status{ ObservedGeneration: 1, @@ -150,26 +138,6 @@ func TestReconcile(t *testing.T) { }, }, }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name + "-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, - }, - }, - Type: corev1.SecretTypeOpaque, - Data: serviceSecret.Data, - }, &labsinternalv1alpha1.ServiceBindingProjection{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, @@ -191,7 +159,7 @@ func TestReconcile(t *testing.T) { Name: name, Application: applicationRef, Binding: corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, }, Status: labsinternalv1alpha1.ServiceBindingProjectionStatus{ @@ -210,11 +178,10 @@ func TestReconcile(t *testing.T) { Eventf(corev1.EventTypeNormal, "Reconciled", "ServiceBinding reconciled: %q", key), }, }, { - Name: "creates projected secret and servicebindingprojection", + Name: "creates servicebindingprojection", Key: key, Objects: []runtime.Object{ provisionedService.DeepCopy(), - serviceSecret.DeepCopy(), &servicebindingv1alpha2.ServiceBinding{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, @@ -228,7 +195,7 @@ func TestReconcile(t *testing.T) { }, Status: servicebindingv1alpha2.ServiceBindingStatus{ Binding: &corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, }, }, @@ -255,29 +222,9 @@ func TestReconcile(t *testing.T) { Name: name, Application: applicationRef, Binding: corev1.LocalObjectReference{ - Name: name + "-projection", - }, - }, - }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name + "-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, + Name: secretName, }, }, - Type: corev1.SecretTypeOpaque, - Data: serviceSecret.Data, }, }, WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ @@ -294,7 +241,7 @@ func TestReconcile(t *testing.T) { }, Status: servicebindingv1alpha2.ServiceBindingStatus{ Binding: &corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, Status: duckv1.Status{ ObservedGeneration: 1, @@ -317,16 +264,14 @@ func TestReconcile(t *testing.T) { }, }}, WantEvents: []string{ - Eventf(corev1.EventTypeNormal, "Created", "Created projected Secret %q", name+"-projection"), Eventf(corev1.EventTypeNormal, "Created", "Created ServiceBindingProjection %q", name), Eventf(corev1.EventTypeNormal, "Reconciled", "ServiceBinding reconciled: %q", key), }, }, { - Name: "updates projected secret and servicebindingprojection", + Name: "updates servicebindingprojection", Key: key, Objects: []runtime.Object{ provisionedService.DeepCopy(), - serviceSecret.DeepCopy(), &servicebindingv1alpha2.ServiceBinding{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, @@ -340,7 +285,7 @@ func TestReconcile(t *testing.T) { }, Status: servicebindingv1alpha2.ServiceBindingStatus{ Binding: &corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, }, }, @@ -362,26 +307,9 @@ func TestReconcile(t *testing.T) { Name: name, Application: applicationRef, Binding: corev1.LocalObjectReference{ - Name: name + "-projection", - }, - }, - }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name + "-projection", - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, + Name: secretName, }, }, - Type: corev1.SecretTypeOpaque, - Data: serviceSecret.Data, }, }, WantUpdates: []clientgotesting.UpdateActionImpl{ @@ -407,31 +335,9 @@ func TestReconcile(t *testing.T) { Name: name, Application: applicationRef, Binding: corev1.LocalObjectReference{ - Name: name + "-projection", - }, - }, - }, - }, - { - Object: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name + "-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, + Name: secretName, }, }, - Type: corev1.SecretTypeOpaque, - Data: serviceSecret.Data, }, }, }, @@ -449,7 +355,7 @@ func TestReconcile(t *testing.T) { }, Status: servicebindingv1alpha2.ServiceBindingStatus{ Binding: &corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, Status: duckv1.Status{ ObservedGeneration: 1, @@ -491,7 +397,7 @@ func TestReconcile(t *testing.T) { }, Status: servicebindingv1alpha2.ServiceBindingStatus{ Binding: &corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, Status: duckv1.Status{ ObservedGeneration: 1, @@ -512,26 +418,6 @@ func TestReconcile(t *testing.T) { }, }, }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name + "-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, - }, - }, - Type: corev1.SecretTypeOpaque, - Data: serviceSecret.Data, - }, &labsinternalv1alpha1.ServiceBindingProjection{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, @@ -553,7 +439,7 @@ func TestReconcile(t *testing.T) { Name: name, Application: applicationRef, Binding: corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, }, Status: labsinternalv1alpha1.ServiceBindingProjectionStatus{ @@ -572,118 +458,11 @@ func TestReconcile(t *testing.T) { WantEvents: []string{ Eventf(corev1.EventTypeWarning, "InternalError", "failed to get resource for bindings.labs.vmware.com/v1alpha1, Resource=provisionedservices: provisionedservices.bindings.labs.vmware.com %q not found", serviceRef.Name), }, - }, { - Name: "error creating projected secret", - Key: key, - Objects: []runtime.Object{ - provisionedService.DeepCopy(), - serviceSecret.DeepCopy(), - &servicebindingv1alpha2.ServiceBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Generation: 1, - }, - Spec: servicebindingv1alpha2.ServiceBindingSpec{ - Name: name, - Application: &applicationRef, - Service: &serviceRef, - }, - Status: servicebindingv1alpha2.ServiceBindingStatus{ - Binding: &corev1.LocalObjectReference{ - Name: name + "-projection", - }, - Status: duckv1.Status{ - ObservedGeneration: 1, - Conditions: duckv1.Conditions{ - { - Type: servicebindingv1alpha2.ServiceBindingConditionProjectionReady, - Status: corev1.ConditionTrue, - }, - { - Type: servicebindingv1alpha2.ServiceBindingConditionReady, - Status: corev1.ConditionTrue, - }, - { - Type: servicebindingv1alpha2.ServiceBindingConditionServiceAvailable, - Status: corev1.ConditionTrue, - }, - }, - }, - }, - }, - &labsinternalv1alpha1.ServiceBindingProjection{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, - }, - }, - Spec: labsinternalv1alpha1.ServiceBindingProjectionSpec{ - Name: name, - Application: applicationRef, - Binding: corev1.LocalObjectReference{ - Name: name + "-projection", - }, - }, - Status: labsinternalv1alpha1.ServiceBindingProjectionStatus{ - Status: duckv1.Status{ - Conditions: duckv1.Conditions{ - { - Type: labsinternalv1alpha1.ServiceBindingProjectionConditionReady, - Status: corev1.ConditionTrue, - }, - }, - }, - }, - }, - }, - WithReactors: []clientgotesting.ReactionFunc{ - InduceFailure("create", "secrets"), - }, - WantErr: true, - WantCreates: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name + "-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, - }, - }, - Type: corev1.SecretTypeOpaque, - Data: serviceSecret.Data, - }, - }, - WantEvents: []string{ - Eventf(corev1.EventTypeWarning, "CreationFailed", "Failed to create projected Secret %q: inducing failure for create secrets", name+"-projection"), - Eventf(corev1.EventTypeWarning, "InternalError", "failed to create projected Secret: inducing failure for create secrets"), - }, }, { Name: "error creating servicebindingprojection", Key: key, Objects: []runtime.Object{ provisionedService.DeepCopy(), - serviceSecret.DeepCopy(), &servicebindingv1alpha2.ServiceBinding{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, @@ -697,7 +476,7 @@ func TestReconcile(t *testing.T) { }, Status: servicebindingv1alpha2.ServiceBindingStatus{ Binding: &corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, Status: duckv1.Status{ ObservedGeneration: 1, @@ -718,26 +497,6 @@ func TestReconcile(t *testing.T) { }, }, }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name + "-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, - }, - }, - Type: corev1.SecretTypeOpaque, - Data: serviceSecret.Data, - }, }, WithReactors: []clientgotesting.ReactionFunc{ InduceFailure("create", "servicebindingprojections"), @@ -765,7 +524,7 @@ func TestReconcile(t *testing.T) { Name: name, Application: applicationRef, Binding: corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, }, }, @@ -775,135 +534,10 @@ func TestReconcile(t *testing.T) { Eventf(corev1.EventTypeWarning, "InternalError", "failed to create ServiceBindingProjection: inducing failure for create servicebindingprojections"), }, }, { - Name: "error updating projected secret", - Key: key, - Objects: []runtime.Object{ - provisionedService.DeepCopy(), - serviceSecret.DeepCopy(), - &servicebindingv1alpha2.ServiceBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Generation: 1, - }, - Spec: servicebindingv1alpha2.ServiceBindingSpec{ - Name: name, - Application: &applicationRef, - Service: &serviceRef, - }, - Status: servicebindingv1alpha2.ServiceBindingStatus{ - Binding: &corev1.LocalObjectReference{ - Name: name + "-projection", - }, - Status: duckv1.Status{ - ObservedGeneration: 1, - Conditions: duckv1.Conditions{ - { - Type: servicebindingv1alpha2.ServiceBindingConditionProjectionReady, - Status: corev1.ConditionTrue, - }, - { - Type: servicebindingv1alpha2.ServiceBindingConditionReady, - Status: corev1.ConditionTrue, - }, - { - Type: servicebindingv1alpha2.ServiceBindingConditionServiceAvailable, - Status: corev1.ConditionTrue, - }, - }, - }, - }, - }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name + "-projection", - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, - }, - }, - Type: corev1.SecretTypeOpaque, - Data: serviceSecret.Data, - }, - &labsinternalv1alpha1.ServiceBindingProjection{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, - }, - }, - Spec: labsinternalv1alpha1.ServiceBindingProjectionSpec{ - Name: name, - Application: applicationRef, - Binding: corev1.LocalObjectReference{ - Name: name + "-projection", - }, - }, - Status: labsinternalv1alpha1.ServiceBindingProjectionStatus{ - Status: duckv1.Status{ - Conditions: duckv1.Conditions{ - { - Type: labsinternalv1alpha1.ServiceBindingProjectionConditionReady, - Status: corev1.ConditionTrue, - }, - }, - }, - }, - }, - }, - WithReactors: []clientgotesting.ReactionFunc{ - InduceFailure("update", "secrets"), - }, - WantErr: true, - WantUpdates: []clientgotesting.UpdateActionImpl{ - { - Object: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name + "-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, - }, - }, - Type: corev1.SecretTypeOpaque, - Data: serviceSecret.Data, - }, - }, - }, - WantEvents: []string{ - Eventf(corev1.EventTypeWarning, "InternalError", "failed to reconcile projected Secret: inducing failure for update secrets"), - }, - }, { - Name: "error updating servicebidningprojection", + Name: "error updating servicebindingprojection", Key: key, Objects: []runtime.Object{ provisionedService.DeepCopy(), - serviceSecret.DeepCopy(), &servicebindingv1alpha2.ServiceBinding{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, @@ -917,7 +551,7 @@ func TestReconcile(t *testing.T) { }, Status: servicebindingv1alpha2.ServiceBindingStatus{ Binding: &corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, Status: duckv1.Status{ ObservedGeneration: 1, @@ -938,26 +572,6 @@ func TestReconcile(t *testing.T) { }, }, }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name + "-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, - }, - }, - Type: corev1.SecretTypeOpaque, - Data: serviceSecret.Data, - }, &labsinternalv1alpha1.ServiceBindingProjection{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, @@ -976,7 +590,7 @@ func TestReconcile(t *testing.T) { Name: name, Application: applicationRef, Binding: corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, }, Status: labsinternalv1alpha1.ServiceBindingProjectionStatus{ @@ -1018,7 +632,7 @@ func TestReconcile(t *testing.T) { Name: name, Application: applicationRef, Binding: corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, }, Status: labsinternalv1alpha1.ServiceBindingProjectionStatus{ @@ -1037,99 +651,11 @@ func TestReconcile(t *testing.T) { WantEvents: []string{ Eventf(corev1.EventTypeWarning, "InternalError", "failed to reconcile ServiceBindingProjection: inducing failure for update servicebindingprojections"), }, - }, { - Name: "error projected secret is not owned by us", - Key: key, - Objects: []runtime.Object{ - provisionedService.DeepCopy(), - serviceSecret.DeepCopy(), - &servicebindingv1alpha2.ServiceBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Generation: 1, - }, - Spec: servicebindingv1alpha2.ServiceBindingSpec{ - Name: name, - Application: &applicationRef, - Service: &serviceRef, - }, - Status: servicebindingv1alpha2.ServiceBindingStatus{ - Binding: &corev1.LocalObjectReference{ - Name: name + "-projection", - }, - Status: duckv1.Status{ - ObservedGeneration: 1, - Conditions: duckv1.Conditions{ - { - Type: servicebindingv1alpha2.ServiceBindingConditionProjectionReady, - Status: corev1.ConditionTrue, - }, - { - Type: servicebindingv1alpha2.ServiceBindingConditionReady, - Status: corev1.ConditionTrue, - }, - { - Type: servicebindingv1alpha2.ServiceBindingConditionServiceAvailable, - Status: corev1.ConditionTrue, - }, - }, - }, - }, - }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name + "-projection", - }, - Data: serviceSecret.Data, - }, - &labsinternalv1alpha1.ServiceBindingProjection{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, - }, - }, - Spec: labsinternalv1alpha1.ServiceBindingProjectionSpec{ - Name: name, - Application: applicationRef, - Binding: corev1.LocalObjectReference{ - Name: name + "-projection", - }, - }, - Status: labsinternalv1alpha1.ServiceBindingProjectionStatus{ - Status: duckv1.Status{ - Conditions: duckv1.Conditions{ - { - Type: labsinternalv1alpha1.ServiceBindingProjectionConditionReady, - Status: corev1.ConditionTrue, - }, - }, - }, - }, - }, - }, - WantErr: true, - WantEvents: []string{ - Eventf(corev1.EventTypeWarning, "InternalError", "ServiceBinding %q does not own projected Secret: %q", name, name+"-projection"), - }, }, { Name: "error servicebindingprojection is not owned by us", Key: key, Objects: []runtime.Object{ provisionedService.DeepCopy(), - serviceSecret.DeepCopy(), &servicebindingv1alpha2.ServiceBinding{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, @@ -1143,7 +669,7 @@ func TestReconcile(t *testing.T) { }, Status: servicebindingv1alpha2.ServiceBindingStatus{ Binding: &corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, Status: duckv1.Status{ ObservedGeneration: 1, @@ -1164,26 +690,6 @@ func TestReconcile(t *testing.T) { }, }, }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name + "-projection", - Labels: map[string]string{ - "service.binding/servicebinding": "my-binding", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "service.binding/v1alpha2", - Kind: "ServiceBinding", - Name: name, - BlockOwnerDeletion: ptr.Bool(true), - Controller: ptr.Bool(true), - }, - }, - }, - Type: corev1.SecretTypeOpaque, - Data: serviceSecret.Data, - }, &labsinternalv1alpha1.ServiceBindingProjection{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, @@ -1193,7 +699,7 @@ func TestReconcile(t *testing.T) { Name: name, Application: applicationRef, Binding: corev1.LocalObjectReference{ - Name: name + "-projection", + Name: secretName, }, }, Status: labsinternalv1alpha1.ServiceBindingProjectionStatus{ @@ -1218,10 +724,8 @@ func TestReconcile(t *testing.T) { ctx = serviceable.WithDuck(ctx) r := &Reconciler{ - kubeclient: kubeclient.Get(ctx), bindingclient: servicebindingsclient.Get(ctx), resolver: resolver.NewServiceableResolver(ctx, func(types.NamespacedName) {}), - secretLister: listers.GetSecretLister(), serviceBindingProjectionLister: listers.GetServiceBindingProjectionLister(), tracker: GetTracker(ctx), } diff --git a/pkg/reconciler/servicebindingprojection/servicebindingprojection_test.go b/pkg/reconciler/servicebindingprojection/servicebindingprojection_test.go index 08b610f5..d62fd0a5 100644 --- a/pkg/reconciler/servicebindingprojection/servicebindingprojection_test.go +++ b/pkg/reconciler/servicebindingprojection/servicebindingprojection_test.go @@ -121,8 +121,16 @@ func TestReconcile(t *testing.T) { { Name: "binding-5c5a15a8b0b3e154d77746945e563ba40100681b", VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "my-secret", + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + }, + }, + }, }, }, }, diff --git a/samples/controlled-resource/README.md b/samples/controlled-resource/README.md index ad2a531a..c76aab4f 100644 --- a/samples/controlled-resource/README.md +++ b/samples/controlled-resource/README.md @@ -44,7 +44,7 @@ It will contain an environment variable `TARGET` provided by the binding. Value From: Secret Key Ref: Key: target - Name: controlled-resource-projection + Name: controlled-resource ... ``` diff --git a/samples/environment-variable-mappings/README.md b/samples/environment-variable-mappings/README.md deleted file mode 100644 index 44bba398..00000000 --- a/samples/environment-variable-mappings/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# Environment Variables and Mappings - -While life would be easier if provisioned services exposed all of the information necessary to consume the service and if all application understood how to read binding credentials from the volume, there is a lot of existing resources that don't. -The specification defines two concepts to make it easier to support existing services and applications with mappings and environment variables. - -Mappings allow for specifying new keys in the projected secret, or composing existing values via a template. -An environment variable maps a specific key in the projected secret into the application's environment. -Together these concepts make it easier to support existing applications. - -In this sample, we'll use a [Kubernetes Job][kubernetes-jobs] to dump the environment to the logs and exit. - -## Setup - -If not already installed, [install the ServiceBinding CRD and controller][install]. - -## Deploy - -Like Pods, Kubernetes Jobs are immutable after they are created. -We need to make sure the `ServiceBinding` is fully configured before the application is created. - -Apply the `ProvisionedService` and `ServiceBinding`: - -```sh -kubectl apply -f ./samples/environment-variable-mappings/service.yaml -f ./samples/environment-variable-mappings/service-binding.yaml -``` - -Check on the status of the `ServiceBinding`: - -```sh -kubectl get servicebinding mappings -oyaml -``` - -The `ServiceAvailable` condition should be `True` and the `ProjectionReady` condition `False`. - -``` -... - conditions: - - lastTransitionTime: "2020-08-03T15:25:45Z" - message: jobs.batch "mappings" not found - reason: ApplicationMissing - status: "False" - type: ProjectionReady - - lastTransitionTime: "2020-08-03T15:25:45Z" - message: jobs.batch "mappings" not found - reason: ApplicationMissing - status: "False" - type: Ready - - lastTransitionTime: "2020-08-03T15:25:45Z" - status: "True" - type: ServiceAvailable -``` - -Create the application `Job`: - -```sh -kubectl apply -f ./samples/environment-variable-mappings/application.yaml -``` - -## Understand - -The `ServiceBinding` resources defines both a mapping the combines values from the secret into a new value, and a environment variable that is projected into the application in addition to the binding volume mount. - -```yaml - mappings: - - name: uri - value: "https://{{ urlquery .username }}:{{ urlquery .password }}@{{ .host }}" - env: - - name: GAME_SERVER - key: uri -``` - -Mappings use [Go templates][go-template] to define new keys in the projected secret. -Keys in the secret are exposed to the template as arguments. - -The application job dumps the environment to the log and then exits. -We should see our injected environment variable as well as other variable commonly found in Kubernetes containers. - -Inspect the logs from the job: - -```sh -kubectl logs -l job-name=mappings -``` - -``` -... -GAME_SERVER=https://sfalken:JOSHUA@wopr.norad.mil -... -``` - -## Play - -Try removing and then applying all of the sample resources at once. - -```sh -kubectl delete -f ./samples/environment-variable-mappings/ -``` - -```sh -kubectl apply -f ./samples/environment-variable-mappings/ -``` - -The application logs will no longer contain the `GAME_SERVER` variable: - -```sh -kubectl logs -l job-name=mappings -``` - -While technically this is a race condition, it's very unlikely to ever successfully bind to the `Job`, unless the job is created after the `ServiceBinding` is configured. - - -[install]: ../../README.md#try-it-out -[kubernetes-jobs]: https://kubernetes.io/docs/concepts/workloads/controllers/job/ -[go-template]: https://golang.org/pkg/text/template/ diff --git a/samples/multi-binding/README.md b/samples/multi-binding/README.md index 68f7db11..b11bf1f1 100644 --- a/samples/multi-binding/README.md +++ b/samples/multi-binding/README.md @@ -64,8 +64,8 @@ kubectl describe job multi-binding ... Environment: SERVICE_BINDING_ROOT: /bindings - MULTI_BINDING_1: Optional: false - MULTI_BINDING_2: Optional: false + MULTI_BINDING_1: Optional: false + MULTI_BINDING_2: Optional: false ... ``` diff --git a/samples/overridden-type-provider/README.md b/samples/overridden-type-provider/README.md new file mode 100644 index 00000000..cfb4e093 --- /dev/null +++ b/samples/overridden-type-provider/README.md @@ -0,0 +1,98 @@ +# Overridden Type and Provider + +When projected into the application workload, the binding must contain a `type` entry and should contain a `provider` entry. +If the Secret doesn't contain a type or provider, or contains the wrong values, they can be overridden for the binding. + +In this sample, we'll use a [Kubernetes Job][kubernetes-jobs] to dump the environment to the logs and exit. + +## Setup + +If not already installed, [install the ServiceBinding CRD and controller][install]. + +## Deploy + +Like Pods, Kubernetes Jobs are immutable after they are created. +We need to make sure the `ServiceBinding`s are fully configured before the application is created. + +Apply the `ProvisionedService` and `ServiceBinding`: + +```sh +kubectl apply -f ./samples/overridden-type-provider/service.yaml -f ./samples/overridden-type-provider/service-binding.yaml +``` + +Check on the status of the `ServiceBinding`: + +```sh +kubectl get servicebinding -l sample=overridden-type-provider -oyaml +``` + +For each service binding, the `ServiceAvailable` condition should be `True` and the `ProjectionReady` condition `False`. + +``` +... + conditions: + - lastTransitionTime: "2020-08-03T15:25:45Z" + message: jobs.batch "overridden-type-provider" not found + reason: ApplicationMissing + status: "False" + type: ProjectionReady + - lastTransitionTime: "2020-08-03T15:25:45Z" + message: jobs.batch "overridden-type-provider" not found + reason: ApplicationMissing + status: "False" + type: Ready + - lastTransitionTime: "2020-08-03T15:25:45Z" + status: "True" + type: ServiceAvailable +``` + +Create the application `Job`: + +```sh +kubectl apply -f ./samples/overridden-type-provider/application.yaml +``` + +## Understand + +Each `ServiceBinding` resource defines an environment variable that is projected into the application in addition to the binding volume mount. + +```sh +kubectl describe job overridden-type-provider +``` + +``` +... +Environment: + SERVICE_BINDING_ROOT: /bindings + BOUND_PROVIDER: (v1:metadata.annotations['internal.bindings.labs.vmware.com/projection-717760b0e7853de4a23bffadf9d02d6109c6ad2e-provider']) + BOUND_TYPE: (v1:metadata.annotations['internal.bindings.labs.vmware.com/projection-717760b0e7853de4a23bffadf9d02d6109c6ad2e-type']) +... +``` + +The application job dumps the environment to the log and then exits. +We should see our injected environment variable as well as other variable commonly found in Kubernetes containers. + +Inspect the logs from the job: + +```sh +kubectl logs -l job-name=overridden-type-provider +``` + +``` +... +SERVICE_BINDING_ROOT=/bindings +BOUND_PROVIDER=overridden-provider +BOUND_TYPE=overridden-type +... +``` + +## Play + +Try changing the `.spec.type` or `.spec.provider` fields on the ServiceBinding, or return them to the original values (empty string). +Remember that Jobs are immutable after they are created, so you'll need to delete and recreate the Job to see the additional binding. + +Alternatively, define a `Deployment` and update each binding to target the new Deployment. +Since Deployments are mutable, each service binding that is added or removed will be reflected on the Deployment and trigger the rollout of a new `ReplicaSet`. + +[install]: ../../README.md#try-it-out +[kubernetes-jobs]: https://kubernetes.io/docs/concepts/workloads/controllers/job/ diff --git a/samples/environment-variable-mappings/application.yaml b/samples/overridden-type-provider/application.yaml similarity index 90% rename from samples/environment-variable-mappings/application.yaml rename to samples/overridden-type-provider/application.yaml index bab76a75..ced6972d 100644 --- a/samples/environment-variable-mappings/application.yaml +++ b/samples/overridden-type-provider/application.yaml @@ -5,7 +5,7 @@ apiVersion: batch/v1 kind: Job metadata: - name: mappings + name: overridden-type-provider spec: template: spec: diff --git a/samples/environment-variable-mappings/service-binding.yaml b/samples/overridden-type-provider/service-binding.yaml similarity index 56% rename from samples/environment-variable-mappings/service-binding.yaml rename to samples/overridden-type-provider/service-binding.yaml index fbb50fc2..32aee797 100644 --- a/samples/environment-variable-mappings/service-binding.yaml +++ b/samples/overridden-type-provider/service-binding.yaml @@ -5,20 +5,23 @@ apiVersion: service.binding/v1alpha2 kind: ServiceBinding metadata: - name: mappings + name: overridden-type-provider + labels: + sample: overridden-type-provider spec: + type: overridden-type + provider: overridden-provider application: apiVersion: batch/v1 kind: Job - name: mappings + name: overridden-type-provider # direct Secret reference is used for compatibility, but not recommended for dynamically provisioned services service: apiVersion: v1 kind: Secret - name: mappings - mappings: - - name: uri - value: "https://{{ urlquery .username }}:{{ urlquery .password }}@{{ .host }}" + name: overridden-type-provider env: - - name: GAME_SERVER - key: uri + - name: BOUND_TYPE + key: type + - name: BOUND_PROVIDER + key: provider diff --git a/samples/environment-variable-mappings/service.yaml b/samples/overridden-type-provider/service.yaml similarity index 61% rename from samples/environment-variable-mappings/service.yaml rename to samples/overridden-type-provider/service.yaml index 00ee4f71..cf6f8e3c 100644 --- a/samples/environment-variable-mappings/service.yaml +++ b/samples/overridden-type-provider/service.yaml @@ -5,9 +5,8 @@ apiVersion: v1 kind: Secret metadata: - name: mappings + name: overridden-type-provider type: Opaque stringData: - username: sfalken - password: JOSHUA - host: wopr.norad.mil + type: original-type + provider: original-provider diff --git a/samples/spring-petclinic/README.md b/samples/spring-petclinic/README.md index 4f1bc818..397031cc 100644 --- a/samples/spring-petclinic/README.md +++ b/samples/spring-petclinic/README.md @@ -46,7 +46,7 @@ The describe output will contain: Volumes: binding-49a23274b0590d5057aae1ae621be723716c4dd5: Type: Secret (a volume populated by a Secret) - SecretName: spring-petclinic-db-projection + SecretName: spring-petclinic-db Optional: false ... ```