Skip to content

Commit

Permalink
add metadata to binding secrets (#151)
Browse files Browse the repository at this point in the history
* add metadata to binding secrets
  • Loading branch information
pavelmaliy authored Apr 7, 2022
1 parent a268116 commit 66fe150
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 16 deletions.
39 changes: 29 additions & 10 deletions controllers/controller_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,42 @@ import (
v1 "k8s.io/api/authentication/v1"
)

func normalizeCredentials(credentialsJSON json.RawMessage) (map[string][]byte, error) {
type SecretMetadataProperty struct {
Name string `json:"name"`
Container bool `json:"container,omitempty"`
Format string `json:"format"`
}

type format string

const (
TEXT format = "text"
JSON format = "json"
UNKNOWN format = "unknown"
)

func normalizeCredentials(credentialsJSON json.RawMessage) (map[string][]byte, []SecretMetadataProperty, error) {
var credentialsMap map[string]interface{}
err := json.Unmarshal(credentialsJSON, &credentialsMap)
if err != nil {
return nil, err
return nil, nil, err
}

normalized := make(map[string][]byte)
metadata := make([]SecretMetadataProperty, 0)
for propertyName, value := range credentialsMap {
keyString := strings.Replace(propertyName, " ", "_", -1)
normalizedValue, err := serialize(value)
normalizedValue, typpe, err := serialize(value)
if err != nil {
return nil, err
return nil, nil, err
}
metadata = append(metadata, SecretMetadataProperty{
Name: keyString,
Format: string(typpe),
})
normalized[keyString] = normalizedValue
}
return normalized, nil
return normalized, metadata, nil
}

func buildUserInfo(ctx context.Context, userInfo *v1.UserInfo) string {
Expand All @@ -43,18 +62,18 @@ func buildUserInfo(ctx context.Context, userInfo *v1.UserInfo) string {
return string(userInfoStr)
}

func serialize(value interface{}) ([]byte, error) {
func serialize(value interface{}) ([]byte, format, error) {
if byteArrayVal, ok := value.([]byte); ok {
return byteArrayVal, nil
return byteArrayVal, JSON, nil
}
if strVal, ok := value.(string); ok {
return []byte(strVal), nil
return []byte(strVal), TEXT, nil
}
data, err := json.Marshal(value)
if err != nil {
return nil, err
return nil, UNKNOWN, err
}
return data, nil
return data, JSON, nil
}

func contains(slice []string, i string) bool {
Expand Down
42 changes: 42 additions & 0 deletions controllers/controller_util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package controllers

import (
"encoding/json"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("Controller Util", func() {

Context("normalize credentials", func() {
var credentialsJSON json.RawMessage

BeforeEach(func() {
credentialsJSON = []byte(`{"keyStr":"val", "keyBool":true,"keyNum":8,"keyJSON":{"a":"b"}}`)
})

It("should normalize correctly", func() {
res, metadata, err := normalizeCredentials(credentialsJSON)
str := SecretMetadataProperty{
Name: "keyStr",
Format: string(TEXT),
}
boolean := SecretMetadataProperty{
Name: "keyBool",
Format: string(JSON),
}
num := SecretMetadataProperty{
Name: "keyNum",
Format: string(JSON),
}
json := SecretMetadataProperty{
Name: "keyJSON",
Format: string(JSON),
}
Expect(err).ToNot(HaveOccurred())
Expect(len(res)).To(Equal(4))
Expect(metadata).To(ContainElements(str, boolean, num, json))
})

})
})
61 changes: 55 additions & 6 deletions controllers/servicebinding_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,23 +467,33 @@ func (r *ServiceBindingReconciler) storeBindingSecret(ctx context.Context, k8sBi
logger := log.WithValues("bindingName", k8sBinding.Name, "secretName", k8sBinding.Spec.SecretName)

var credentialsMap map[string][]byte
var credentialProperties []SecretMetadataProperty

if len(smBinding.Credentials) == 0 {
log.Info("Binding credentials are empty")
credentialsMap = make(map[string][]byte)
} else if k8sBinding.Spec.SecretKey != nil {
credentialsMap = map[string][]byte{
*k8sBinding.Spec.SecretKey: smBinding.Credentials,
}
credentialProperties = []SecretMetadataProperty{
{
Name: *k8sBinding.Spec.SecretKey,
Format: string(JSON),
Container: true,
},
}
} else {
var err error
credentialsMap, err = normalizeCredentials(smBinding.Credentials)
credentialsMap, credentialProperties, err = normalizeCredentials(smBinding.Credentials)
if err != nil {
logger.Error(err, "Failed to store binding secret")
return fmt.Errorf("failed to create secret. Error: %v", err.Error())
}
}

if err := r.addInstanceInfo(ctx, k8sBinding, credentialsMap); err != nil {
metaDataProperties, err := r.addInstanceInfo(ctx, k8sBinding, credentialsMap)
if err != nil {
log.Error(err, "failed to enrich binding with service instance info")
}

Expand All @@ -493,6 +503,17 @@ func (r *ServiceBindingReconciler) storeBindingSecret(ctx context.Context, k8sBi
if err != nil {
return err
}
} else {
metadata := map[string][]SecretMetadataProperty{
"metaDataProperties": metaDataProperties,
"credentialProperties": credentialProperties,
}
metadataByte, err := json.Marshal(metadata)
if err != nil {
log.Error(err, "failed to enrich binding with metadata")
} else {
credentialsMap[".metadata"] = metadataByte
}
}

secret := &corev1.Secret{
Expand Down Expand Up @@ -646,24 +667,52 @@ func (r *ServiceBindingReconciler) handleSecretError(ctx context.Context, op smT
return r.markAsTransientError(ctx, op, err, binding)
}

func (r *ServiceBindingReconciler) addInstanceInfo(ctx context.Context, binding *servicesv1.ServiceBinding, credentialsMap map[string][]byte) error {
func (r *ServiceBindingReconciler) addInstanceInfo(ctx context.Context, binding *servicesv1.ServiceBinding, credentialsMap map[string][]byte) ([]SecretMetadataProperty, error) {
instance, err := r.getServiceInstanceForBinding(ctx, binding)
if err != nil {
return err
return nil, err
}

credentialsMap["instance_name"] = []byte(binding.Spec.ServiceInstanceName)
credentialsMap["instance_guid"] = []byte(instance.Status.InstanceID)
credentialsMap["plan"] = []byte(instance.Spec.ServicePlanName)
credentialsMap["label"] = []byte(instance.Spec.ServiceOfferingName)
credentialsMap["type"] = []byte(instance.Spec.ServiceOfferingName)
if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
tagsBytes, err := json.Marshal(mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags))
if err != nil {
return err
return nil, err
}
credentialsMap["tags"] = tagsBytes
}
return nil

metadata := []SecretMetadataProperty{
{
Name: "instance_name",
Format: string(TEXT),
},
{
Name: "instance_guid",
Format: string(TEXT),
},
{
Name: "plan",
Format: string(TEXT),
},
{
Name: "label",
Format: string(TEXT),
},
{
Name: "type",
Format: string(TEXT),
},
}
if _, ok := credentialsMap["tags"]; ok {
metadata = append(metadata, SecretMetadataProperty{Name: "tags", Format: string(JSON)})
}

return metadata, nil
}

func (r *ServiceBindingReconciler) singleKeyMap(credentialsMap map[string][]byte, key string) (map[string][]byte, error) {
Expand Down
32 changes: 32 additions & 0 deletions controllers/servicebinding_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,24 @@ var _ = Describe("ServiceBinding controller", func() {
validateInstanceInfo := func(bindingSecret *corev1.Secret) {
validateSecretData(bindingSecret, "plan", `a-plan-name`)
validateSecretData(bindingSecret, "label", `an-offering-name`)
validateSecretData(bindingSecret, "type", `an-offering-name`)
validateSecretData(bindingSecret, "tags", "[\"test\",\"custom-tag\"]")
validateSecretData(bindingSecret, "instance_name", instanceName)
Expect(bindingSecret.Data).To(HaveKey("instance_guid"))
}

validateSecretMetadata := func(bindingSecret *corev1.Secret, credentialProperties []SecretMetadataProperty) {
metadata := make(map[string][]SecretMetadataProperty)
Expect(json.Unmarshal(bindingSecret.Data[".metadata"], &metadata)).To(Succeed())
Expect(metadata["credentialProperties"]).To(ContainElements(credentialProperties))
Expect(metadata["metaDataProperties"]).To(ContainElement(SecretMetadataProperty{Name: "instance_name", Format: string(TEXT)}))
Expect(metadata["metaDataProperties"]).To(ContainElement(SecretMetadataProperty{Name: "instance_guid", Format: string(TEXT)}))
Expect(metadata["metaDataProperties"]).To(ContainElement(SecretMetadataProperty{Name: "plan", Format: string(TEXT)}))
Expect(metadata["metaDataProperties"]).To(ContainElement(SecretMetadataProperty{Name: "label", Format: string(TEXT)}))
Expect(metadata["metaDataProperties"]).To(ContainElement(SecretMetadataProperty{Name: "type", Format: string(TEXT)}))
Expect(metadata["metaDataProperties"]).To(ContainElement(SecretMetadataProperty{Name: "tags", Format: string(JSON)}))
}

It("Should create binding and store the binding credentials in a secret", func() {
ctx := context.Background()
createdBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, "binding-external-name")
Expand All @@ -313,6 +326,17 @@ var _ = Describe("ServiceBinding controller", func() {
validateSecretData(bindingSecret, "secret_key", "secret_value")
validateSecretData(bindingSecret, "escaped", `{"escaped_key":"escaped_val"}`)
validateInstanceInfo(bindingSecret)
credentialProperties := []SecretMetadataProperty{
{
Name: "secret_key",
Format: string(TEXT),
},
{
Name: "escaped",
Format: string(TEXT),
},
}
validateSecretMetadata(bindingSecret, credentialProperties)
})

It("should put the raw broker response into the secret if spec.secretKey is provided", func() {
Expand All @@ -336,6 +360,14 @@ var _ = Describe("ServiceBinding controller", func() {
bindingSecret := getSecret(ctx, binding.Spec.SecretName, bindingTestNamespace, true)
validateSecretData(bindingSecret, secretKey, `{"secret_key": "secret_value", "escaped": "{\"escaped_key\":\"escaped_val\"}"}`)
validateInstanceInfo(bindingSecret)
credentialProperties := []SecretMetadataProperty{
{
Name: "mycredentials",
Format: string(JSON),
Container: true,
},
}
validateSecretMetadata(bindingSecret, credentialProperties)
})

It("should put binding data in single key if spec.secretRootKey is provided", func() {
Expand Down

0 comments on commit 66fe150

Please sign in to comment.