Skip to content

Commit

Permalink
Set secret name and namespace from create Service Binding request (#794)
Browse files Browse the repository at this point in the history
* Add secret's name and namespace fields into CreateServiceBinding request

* Set secret's name and namespace based on the request

* Add unit test for POST Service Binding with JSON object in credentials
  • Loading branch information
szwedm authored Aug 8, 2024
1 parent 5aabb76 commit c8c5803
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 26 deletions.
80 changes: 61 additions & 19 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,15 +233,17 @@ func (a *API) CreateServiceBinding(writer http.ResponseWriter, request *http.Req
a.handleError(writer, err)
return
}
secret, err := generateSecretFromSISBData(si, createdServiceBinding)
secret, err := generateSecretFromSISBData(si, createdServiceBinding, &serviceBindingRequest)
if err != nil {
a.handleError(writer, err)
return
}
err = a.secretManager.Create(context.Background(), secret)
if err != nil {
a.handleError(writer, err)
return
if secret.Name != "" && secret.Namespace != "" {
err = a.secretManager.Create(context.Background(), secret)
if err != nil {
a.handleError(writer, err)
return
}
}
sbVM, err := responses.ToServiceBindingVM(createdServiceBinding)
if err != nil {
Expand Down Expand Up @@ -400,36 +402,76 @@ func (a *API) handleError(writer http.ResponseWriter, errToHandle error, fallbac
return
}

func generateSecretFromSISBData(si *types.ServiceInstance, sb *types.ServiceBinding) (*corev1.Secret, error) {
slicedUUID := strings.Split(uuid.NewString(), "-")
suffix := strings.Join(slicedUUID[:2], "-")
secretName := fmt.Sprintf("%s-%s", sb.Name, suffix)

namespace, err := sb.ContextValueByFieldName(types.ContextNamespace)
if err != nil {
return nil, fmt.Errorf("failed to get namespace from service binding context: %w", err)
func generateSecretFromSISBData(si *types.ServiceInstance, sb *types.ServiceBinding, createSBRequest *requests.CreateServiceBinding) (*corev1.Secret, error) {
var secretName, secretNamespace string
var err error
if createSBRequest.SecretName != "" {
secretName = createSBRequest.SecretName
} else {
slicedUUID := strings.Split(uuid.NewString(), "-")
suffix := strings.Join(slicedUUID[:2], "-")
secretName = fmt.Sprintf("%s-%s", sb.Name, suffix)
}
if createSBRequest.SecretNamespace != "" {
secretNamespace = createSBRequest.SecretNamespace
} else {
secretNamespace, err = sb.ContextValueByFieldName(types.ContextNamespace)
if err != nil {
return nil, fmt.Errorf("failed to get namespace from service binding context: %w", err)
}
}

labels := map[string]string{
clusterobject.ManagedByLabelKey: clusterobject.OperatorName,
clusterobject.ServiceBindingIDLabel: sb.ID,
clusterobject.ServiceInstanceIDLabel: si.ID,
clusterobject.ServiceInstanceNameLabel: si.Name,
}

data := map[string]string{}
if err := json.Unmarshal(sb.Credentials, &data); err != nil {
return nil, fmt.Errorf("failed to unmarshal credentials from service binding: %w", err)
creds, err := normalizeCredentials(sb.Credentials)
if err != nil {
return nil, fmt.Errorf("failed to normalize credentials for secret's data: %w", err)
}

secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
Namespace: secretNamespace,
Labels: labels,
},
StringData: data,
Data: creds,
}

return secret, nil
}

func normalizeCredentials(sbCredentials json.RawMessage) (map[string][]byte, error) {
data := make(map[string]interface{})
if err := json.Unmarshal(sbCredentials, &data); err != nil {
return nil, fmt.Errorf("failed to unmarshal credentials from service binding: %w", err)
}
normalized := make(map[string][]byte)
for k, v := range data {
keyString := strings.Replace(k, " ", "_", -1)
normalizedValue, err := serialize(v)
if err != nil {
return nil, fmt.Errorf("failed to serialize value for key %s: %w", k, err)
}
normalized[keyString] = normalizedValue
}

return normalized, nil
}

func serialize(value interface{}) ([]byte, error) {
if byteArrayVal, ok := value.([]byte); ok {
return byteArrayVal, nil
}
if strVal, ok := value.(string); ok {
return []byte(strVal), nil
}
data, err := json.Marshal(value)
if err != nil {
return nil, err
}
return data, nil
}
45 changes: 45 additions & 0 deletions internal/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ func TestAPI(t *testing.T) {
Name: "sb-test-01",
ServiceInstanceID: "a7e240d6-e348-4fc0-a54c-7b7bfe9b9da6",
Parameters: []byte(`{"param1": "value1", "param2": "value2"}`),
SecretName: "binding-secret-01",
SecretNamespace: "default",
}
sbCreateRequestJSON, err := json.Marshal(sbCreateRequest)
require.NoError(t, err)
Expand Down Expand Up @@ -391,6 +393,49 @@ func TestAPI(t *testing.T) {
// then
assert.Len(t, secrets.Items, 1)

secretMgr.Clean()
err = fakeSM.RestoreDefaults()
require.NoError(t, err)
})

t.Run("POST Service Binding with JSON object in credentials", func(t *testing.T) {
// given
sbCreateRequest := requests.CreateServiceBinding{
Name: "sb-test-02",
ServiceInstanceID: servicemanager.FakeJSONObjectCredentialsServiceInstanceID,
Parameters: []byte(`{"param1": "value1", "param2": "value2"}`),
SecretName: "binding-secret-02",
SecretNamespace: "default",
}
sbCreateRequestJSON, err := json.Marshal(sbCreateRequest)
require.NoError(t, err)

// when
req, err := http.NewRequest(http.MethodPost, apiAddr+"/api/service-bindings", bytes.NewBuffer(sbCreateRequestJSON))
resp, err := apiClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusCreated, resp.StatusCode)
defer resp.Body.Close()

var sb responses.ServiceBinding
err = json.NewDecoder(resp.Body).Decode(&sb)
require.NoError(t, err)

// then
assert.NotEmpty(t, sb.ID)
assert.Equal(t, sbCreateRequest.Name, sb.Name)

// when
secrets, err := secretMgr.GetAllByLabels(context.TODO(), map[string]string{
clusterobject.ServiceBindingIDLabel: sb.ID,
clusterobject.ServiceInstanceIDLabel: sbCreateRequest.ServiceInstanceID,
})
require.NoError(t, err)

// then
assert.Len(t, secrets.Items, 1)

secretMgr.Clean()
err = fakeSM.RestoreDefaults()
require.NoError(t, err)
})
Expand Down
2 changes: 2 additions & 0 deletions internal/api/requests/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type CreateServiceBinding struct {
Name string `json:"name"`
ServiceInstanceID string `json:"service_instance_id"`
Parameters json.RawMessage `json:"parameters"`
SecretName string `json:"secret_name"`
SecretNamespace string `json:"secret_namespace"`
}

type CreateServiceInstance struct {
Expand Down
23 changes: 16 additions & 7 deletions internal/service-manager/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import (
)

const (
defaultDir = "service-manager"
rootDir = "btp-manager"
serviceOfferingsJSONPath = "testdata/service_offerings.json"
servicePlansJSONPath = "testdata/service_plans.json"
serviceInstancesJSONPath = "testdata/service_instances.json"
serviceBindingsJSONPath = "testdata/service_bindings.json"
FakeJSONObjectCredentialsServiceInstanceID = "json-object-in-credentials"
testDataServiceInstanceID = "a7e240d6-e348-4fc0-a54c-7b7bfe9b9da6"
defaultDir = "service-manager"
rootDir = "btp-manager"
serviceOfferingsJSONPath = "testdata/service_offerings.json"
servicePlansJSONPath = "testdata/service_plans.json"
serviceInstancesJSONPath = "testdata/service_instances.json"
serviceBindingsJSONPath = "testdata/service_bindings.json"
)

type FakeServiceManager struct {
Expand Down Expand Up @@ -349,6 +351,9 @@ func (h *fakeSMHandler) getServiceInstance(w http.ResponseWriter, r *http.Reques
w.WriteHeader(http.StatusBadRequest)
return
}
if siID == FakeJSONObjectCredentialsServiceInstanceID {
siID = testDataServiceInstanceID
}

var err error
data := make([]byte, 0)
Expand Down Expand Up @@ -667,7 +672,11 @@ func (h *fakeSMHandler) createServiceBinding(w http.ResponseWriter, r *http.Requ
}

sbCreateRequest.ID = uuid.New().String()
sbCreateRequest.Credentials = []byte(`{"username": "user", "password": "pass"}`)
if sbCreateRequest.ServiceInstanceID == FakeJSONObjectCredentialsServiceInstanceID {
sbCreateRequest.Credentials = []byte(`{"object":{"username":"user","password":"pass"}}`)
} else {
sbCreateRequest.Credentials = []byte(`{"username": "user", "password": "pass"}`)
}
h.serviceBindings.Items = append(h.serviceBindings.Items, sbCreateRequest)

data, err := json.Marshal(sbCreateRequest)
Expand Down

0 comments on commit c8c5803

Please sign in to comment.