Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set secret name and namespace from create Service Binding request #794

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
80 changes: 61 additions & 19 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,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 @@ -398,36 +400,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
Loading