Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions controllers/servicebinding_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ func TestServiceBindingReconciler(t *testing.T) {
secretName := "my-secret"
key := types.NamespacedName{Namespace: namespace, Name: name}

podSpecableMapping := `{"versions":[{"version":"*","annotations":".spec.template.metadata.annotations","containers":[{"path":".spec.template.spec.initContainers[*]","name":".name","env":".env","volumeMounts":".volumeMounts"},{"path":".spec.template.spec.containers[*]","name":".name","env":".env","volumeMounts":".volumeMounts"}],"volumes":".spec.template.spec.volumes"}]}`

scheme := runtime.NewScheme()
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(servicebindingv1beta1.AddToScheme(scheme))
Expand Down Expand Up @@ -102,6 +104,9 @@ func TestServiceBindingReconciler(t *testing.T) {
})
})
projectedWorkload := workload.
MetadataDie(func(d *diemetav1.ObjectMetaDie) {
d.AddAnnotation(fmt.Sprintf("projector.servicebinding.io/mapping-%s", uid), podSpecableMapping)
}).
SpecDie(func(d *dieappsv1.DeploymentSpecDie) {
d.TemplateDie(func(d *diecorev1.PodTemplateSpecDie) {
d.MetadataDie(func(d *diemetav1.ObjectMetaDie) {
Expand Down Expand Up @@ -143,6 +148,7 @@ func TestServiceBindingReconciler(t *testing.T) {
})
})
}).DieReleaseUnstructured()
unstructured.SetNestedMap(unprojectedWorkload.UnstructuredContent(), map[string]interface{}{}, "metadata", "annotations")
unstructured.SetNestedMap(unprojectedWorkload.UnstructuredContent(), map[string]interface{}{}, "spec", "template", "metadata", "annotations")
containers, _, _ := unstructured.NestedSlice(unprojectedWorkload.UnstructuredContent(), "spec", "template", "spec", "containers")
unstructured.SetNestedSlice(containers[0].(map[string]interface{}), []interface{}{}, "volumeMounts")
Expand Down Expand Up @@ -731,6 +737,8 @@ func TestProjectBinding(t *testing.T) {
uid := types.UID("dde10100-d7b3-4cba-9430-51d60a8612a6")
secretName := "my-secret"

podSpecableMapping := `{"versions":[{"version":"*","annotations":".spec.template.metadata.annotations","containers":[{"path":".spec.template.spec.initContainers[*]","name":".name","env":".env","volumeMounts":".volumeMounts"},{"path":".spec.template.spec.containers[*]","name":".name","env":".env","volumeMounts":".volumeMounts"}],"volumes":".spec.template.spec.volumes"}]}`

scheme := runtime.NewScheme()
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(servicebindingv1beta1.AddToScheme(scheme))
Expand Down Expand Up @@ -781,6 +789,9 @@ func TestProjectBinding(t *testing.T) {
})
})
projectedWorkload := workload.
MetadataDie(func(d *diemetav1.ObjectMetaDie) {
d.AddAnnotation(fmt.Sprintf("projector.servicebinding.io/mapping-%s", uid), podSpecableMapping)
}).
SpecDie(func(d *dieappsv1.DeploymentSpecDie) {
d.TemplateDie(func(d *diecorev1.PodTemplateSpecDie) {
d.MetadataDie(func(d *diemetav1.ObjectMetaDie) {
Expand Down Expand Up @@ -822,6 +833,7 @@ func TestProjectBinding(t *testing.T) {
})
})
}).DieReleaseUnstructured()
unstructured.SetNestedMap(unprojectedWorkload.UnstructuredContent(), map[string]interface{}{}, "metadata", "annotations")
unstructured.SetNestedMap(unprojectedWorkload.UnstructuredContent(), map[string]interface{}{}, "spec", "template", "metadata", "annotations")
containers, _, _ := unstructured.NestedSlice(unprojectedWorkload.UnstructuredContent(), "spec", "template", "spec", "containers")
unstructured.SetNestedSlice(containers[0].(map[string]interface{}), []interface{}{}, "volumeMounts")
Expand Down
19 changes: 19 additions & 0 deletions controllers/webhook_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ func TestAdmissionProjectorWebhook(t *testing.T) {
requestUID := types.UID("9deefaa1-2c90-4f40-9c7b-3f5c1fd75dde")
bindingUID := types.UID("89deaf20-7bab-4610-81db-6f8c3f7fa51d")

podSpecableMapping := `{"versions":[{"version":"*","annotations":".spec.template.metadata.annotations","containers":[{"path":".spec.template.spec.initContainers[*]","name":".name","env":".env","volumeMounts":".volumeMounts"},{"path":".spec.template.spec.containers[*]","name":".name","env":".env","volumeMounts":".volumeMounts"}],"volumes":".spec.template.spec.volumes"}]}`

workload := dieappsv1.DeploymentBlank.
APIVersion("apps/v1").
Kind("Deployment").
Expand Down Expand Up @@ -275,6 +277,9 @@ func TestAdmissionProjectorWebhook(t *testing.T) {
AdmissionRequest: request.
Object(
workload.
MetadataDie(func(d *diemetav1.ObjectMetaDie) {
d.AddAnnotation(fmt.Sprintf("projector.servicebinding.io/mapping-%s", bindingUID), podSpecableMapping)
}).
SpecDie(func(d *dieappsv1.DeploymentSpecDie) {
d.TemplateDie(func(d *diecorev1.PodTemplateSpecDie) {
d.MetadataDie(func(d *diemetav1.ObjectMetaDie) {
Expand Down Expand Up @@ -331,6 +336,13 @@ func TestAdmissionProjectorWebhook(t *testing.T) {
ExpectedResponse: admission.Response{
AdmissionResponse: response.DieRelease(),
Patches: []jsonpatch.Operation{
{
Operation: "add",
Path: "/metadata/annotations",
Value: map[string]interface{}{
fmt.Sprintf("projector.servicebinding.io/mapping-%s", bindingUID): podSpecableMapping,
},
},
{
Operation: "add",
Path: "/spec/template/metadata/annotations",
Expand Down Expand Up @@ -399,6 +411,13 @@ func TestAdmissionProjectorWebhook(t *testing.T) {
ExpectedResponse: admission.Response{
AdmissionResponse: response.DieRelease(),
Patches: []jsonpatch.Operation{
{
Operation: "add",
Path: "/metadata/annotations",
Value: map[string]interface{}{
fmt.Sprintf("projector.servicebinding.io/mapping-%s", bindingUID): podSpecableMapping,
},
},
{
Operation: "add",
Path: "/spec/template/metadata/annotations",
Expand Down
131 changes: 114 additions & 17 deletions projector/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ package projector

import (
"context"
"encoding/json"
"fmt"
"path"
"sort"
"strings"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"

Expand All @@ -37,6 +40,7 @@ const (
SecretAnnotationPrefix = Group + "/secret-"
TypeAnnotationPrefix = Group + "/type-"
ProviderAnnotationPrefix = Group + "/provider-"
MappingAnnotationPrefix = Group + "/mapping-"
)

var _ ServiceBindingProjector = (*serviceBindingProjector)(nil)
Expand All @@ -54,35 +58,95 @@ func New(mappingSource MappingSource) ServiceBindingProjector {
}

func (p *serviceBindingProjector) Project(ctx context.Context, binding *servicebindingv1beta1.ServiceBinding, workload runtime.Object) error {
mapping, err := p.mappingSource.LookupMapping(ctx, workload)
ctx, resourceMapping, version, err := p.lookupClusterMapping(ctx, workload)
if err != nil {
return err
}
mpt, err := NewMetaPodTemplate(ctx, workload, mapping)

// rather than attempt to merge an existing binding, unproject it
if err := p.Unproject(ctx, binding, workload); err != nil {
return err
}

versionMapping := MappingVersion(version, resourceMapping)
mpt, err := NewMetaPodTemplate(ctx, workload, versionMapping)
if err != nil {
return err
}
p.project(binding, mpt)
return mpt.WriteToWorkload(ctx)

if p.secretName(binding) != "" {
if err := p.stashLocalMapping(binding, mpt, resourceMapping); err != nil {
return err
}
}
if err := mpt.WriteToWorkload(ctx); err != nil {
return err
}

return nil
}

func (p *serviceBindingProjector) Unproject(ctx context.Context, binding *servicebindingv1beta1.ServiceBinding, workload runtime.Object) error {
mapping, err := p.mappingSource.LookupMapping(ctx, workload)
resourceMapping, err := p.retrieveLocalMapping(binding, workload)
if err != nil {
return err
}
ctx, m, version, err := p.lookupClusterMapping(ctx, workload)
if err != nil {
return err
}
mpt, err := NewMetaPodTemplate(ctx, workload, mapping)
if resourceMapping == nil {
// fall back to using the remote mappings, this isn't ideal as the mapping may have changed after the binding was originally projected
resourceMapping = m
}
versionMapping := MappingVersion(version, resourceMapping)
mpt, err := NewMetaPodTemplate(ctx, workload, versionMapping)
if err != nil {
return err
}
p.unproject(binding, mpt)
return mpt.WriteToWorkload(ctx)

if err := p.stashLocalMapping(binding, mpt, nil); err != nil {
return err
}
if err := mpt.WriteToWorkload(ctx); err != nil {
return err
}

return nil
}

func (p *serviceBindingProjector) project(binding *servicebindingv1beta1.ServiceBinding, mpt *metaPodTemplate) {
// rather than attempt to merge an existing binding, unproject it
p.unproject(binding, mpt)
type mappingValue struct {
WorkloadMapping *servicebindingv1beta1.ClusterWorkloadResourceMappingSpec
RESTMapping *meta.RESTMapping
}

// lookupClusterMapping resolves the mapping from the context or from the cluster. This
// avoids redundant calls to the mappingSource for the same workload call when Unproject
// is called from Project. When the lookup is from the cluster, the value is stashed into
// the context for future lookups in this turn.
func (p *serviceBindingProjector) lookupClusterMapping(ctx context.Context, workload runtime.Object) (context.Context, *servicebindingv1beta1.ClusterWorkloadResourceMappingSpec, string, error) {
raw := ctx.Value(mappingValue{})
if value, ok := raw.(mappingValue); ok {
return ctx, value.WorkloadMapping, value.RESTMapping.Resource.Version, nil
}
rm, err := p.mappingSource.LookupRESTMapping(ctx, workload)
if err != nil {
return ctx, nil, "", err
}
wm, err := p.mappingSource.LookupWorkloadMapping(ctx, rm.Resource)
if err != nil {
return ctx, nil, "", err
}
ctx = context.WithValue(ctx, mappingValue{}, mappingValue{
WorkloadMapping: wm,
RESTMapping: rm,
})
return ctx, wm, rm.Resource.Version, nil
}

func (p *serviceBindingProjector) project(binding *servicebindingv1beta1.ServiceBinding, mpt *metaPodTemplate) {
if p.secretName(binding) == "" {
// no secret to bind
return
Expand All @@ -100,9 +164,9 @@ func (p *serviceBindingProjector) unproject(binding *servicebindingv1beta1.Servi
}

// cleanup annotations
delete(mpt.Annotations, p.secretAnnotationName(binding))
delete(mpt.Annotations, p.typeAnnotationName(binding))
delete(mpt.Annotations, p.providerAnnotationName(binding))
delete(mpt.PodTemplateAnnotations, p.secretAnnotationName(binding))
delete(mpt.PodTemplateAnnotations, p.typeAnnotationName(binding))
delete(mpt.PodTemplateAnnotations, p.providerAnnotationName(binding))
}

func (p *serviceBindingProjector) projectVolume(binding *servicebindingv1beta1.ServiceBinding, mpt *metaPodTemplate) {
Expand Down Expand Up @@ -296,7 +360,7 @@ func (p *serviceBindingProjector) projectEnv(binding *servicebindingv1beta1.Serv

func (p *serviceBindingProjector) unprojectEnv(binding *servicebindingv1beta1.ServiceBinding, mpt *metaPodTemplate, mc *metaContainer) {
env := []corev1.EnvVar{}
secret := mpt.Annotations[p.secretAnnotationName(binding)]
secret := mpt.PodTemplateAnnotations[p.secretAnnotationName(binding)]
typeFieldPath := fmt.Sprintf("metadata.annotations['%s']", p.typeAnnotationName(binding))
providerFieldPath := fmt.Sprintf("metadata.annotations['%s']", p.providerAnnotationName(binding))
for _, e := range mc.Env {
Expand Down Expand Up @@ -364,7 +428,7 @@ func (p *serviceBindingProjector) isProjectedEnv(e corev1.EnvVar, secrets sets.S

func (p *serviceBindingProjector) knownProjectedSecrets(mpt *metaPodTemplate) sets.String {
secrets := sets.NewString()
for k, v := range mpt.Annotations {
for k, v := range mpt.PodTemplateAnnotations {
if strings.HasPrefix(k, SecretAnnotationPrefix) {
secrets.Insert(v)
}
Expand All @@ -385,7 +449,7 @@ func (p *serviceBindingProjector) secretAnnotation(binding *servicebindingv1beta
if secret == "" {
return ""
}
mpt.Annotations[key] = secret
mpt.PodTemplateAnnotations[key] = secret
return secret
}

Expand All @@ -399,7 +463,7 @@ func (p *serviceBindingProjector) volumeName(binding *servicebindingv1beta1.Serv

func (p *serviceBindingProjector) typeAnnotation(binding *servicebindingv1beta1.ServiceBinding, mpt *metaPodTemplate) string {
key := p.typeAnnotationName(binding)
mpt.Annotations[key] = binding.Spec.Type
mpt.PodTemplateAnnotations[key] = binding.Spec.Type
return key
}

Expand All @@ -409,10 +473,43 @@ func (p *serviceBindingProjector) typeAnnotationName(binding *servicebindingv1be

func (p *serviceBindingProjector) providerAnnotation(binding *servicebindingv1beta1.ServiceBinding, mpt *metaPodTemplate) string {
key := p.providerAnnotationName(binding)
mpt.Annotations[key] = binding.Spec.Provider
mpt.PodTemplateAnnotations[key] = binding.Spec.Provider
return key
}

func (p *serviceBindingProjector) providerAnnotationName(binding *servicebindingv1beta1.ServiceBinding) string {
return fmt.Sprintf("%s%s", ProviderAnnotationPrefix, binding.UID)
}

func (p *serviceBindingProjector) retrieveLocalMapping(binding *servicebindingv1beta1.ServiceBinding, workload runtime.Object) (*servicebindingv1beta1.ClusterWorkloadResourceMappingSpec, error) {
annoations := workload.(metav1.Object).GetAnnotations()
if annoations == nil {
return nil, nil
}
data, ok := annoations[p.mappingAnnotationName(binding)]
if !ok {
return nil, nil
}
var mapping servicebindingv1beta1.ClusterWorkloadResourceMappingSpec
if err := json.Unmarshal([]byte(data), &mapping); err != nil {
return nil, err
}
return &mapping, nil
}

func (p *serviceBindingProjector) stashLocalMapping(binding *servicebindingv1beta1.ServiceBinding, mpt *metaPodTemplate, mapping *servicebindingv1beta1.ClusterWorkloadResourceMappingSpec) error {
if mapping == nil {
delete(mpt.WorkloadAnnotations, p.mappingAnnotationName(binding))
return nil
}
data, err := json.Marshal(mapping)
if err != nil {
return err
}
mpt.WorkloadAnnotations[p.mappingAnnotationName(binding)] = string(data)
return nil
}

func (p *serviceBindingProjector) mappingAnnotationName(binding *servicebindingv1beta1.ServiceBinding) string {
return fmt.Sprintf("%s%s", MappingAnnotationPrefix, binding.UID)
}
Loading