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

Implemented GET /v3/service_credential_bindings/:guid/details endpoint #3762

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ddraganovv
Copy link
Contributor

Is there a related GitHub Issue?

No

What is this change about?

Implementation of an endpoint according to the CF api documentation:
GET /v3/service_credential_bindings/:guid/details

Does this PR introduce a breaking change?

No

@ddraganovv ddraganovv force-pushed the add_service_binding_getDetails_handler branch 2 times, most recently from e8183f3 to dedb18a Compare January 27, 2025 22:49
ServiceBindingPath = "/v3/service_credential_bindings/{guid}"
ServiceBindingsPath = "/v3/service_credential_bindings"
ServiceBindingPath = "/v3/service_credential_bindings/{guid}"
ServiceBindingDetailsPath = ServiceBindingPath + "/details"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend not using string concatenation - plain strings make reasoning about what endpoints are implemented easier and is machine friendlier should we want to have a tool to generate a list of supported endpoints

type Credentials map[string]any
type ServiceBindingDetailsRecord struct {
Credentials Credentials
SyslogDrainURL *string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provided that the repository never populates SyslogDrainURL and VolumeMounts, I would suggest removing them from the record. We can introduce them when we properly implement them.

@@ -75,6 +77,12 @@ func (r ServiceBindingRecord) Relationships() map[string]string {
}
}

type Credentials map[string]any
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason to alias map[string]any as Credentials? I.e. is the Credentials type needed?

@@ -41,6 +41,12 @@ type ServiceBindingLinks struct {
Details Link `json:"details"`
}

type ServiceBindingDetailsResponse struct {
Credentials map[string]any `json:"credentials"`
SyslogDrainURL *string `json:"syslog_drain_url,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the ServiceBindingDetailsRecord, we do not support SyslogDrainURL and VolumeMounts, so I would recommend removing them from the response type.

@@ -272,6 +280,52 @@ func (r *ServiceBindingRepo) GetServiceBinding(ctx context.Context, authInfo aut
return serviceBindingToRecord(*serviceBinding), nil
}

func (r *ServiceBindingRepo) GetServiceBindingDetails(ctx context.Context, authInfo authorization.Info, bindingGUID string) (ServiceBindingDetailsRecord, error) {
userClient, err := r.userClientFactory.BuildClient(authInfo)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting the CFServiceBinding here is the same as getting it in GetServiceBinding. I would suggest extracting a reusable utility function to remove code duplication

credentialBytes, err := json.Marshal(credentials)
Expect(err).To(BeNil())

creds := map[string][]byte{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use ToCredentialsSecretData to create the secret data in the correct format


It("returns details", func() {
Expect(getErr).To(BeNil())
Expect(serviceBindingRecord.Credentials).To(HaveKeyWithValue("bar", "val2"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more concise way to assert two things would be

Expect(serviceBindingRecord.Credentials).To(SatisfyAll(
  HaveKeyWithValue("bar", "val2"),
  HaveKeyWithValue("foo", "val1"),
))

Or even simpler:

Expect(serviceBindingRecord.Credentials).To(Equal(map[string]any{
  "bar": "val2",
  "foo": "val1",
}))

BeforeEach(func() {
serviceInstanceGUID := prefixedGUID("instance")
cfServiceInstance = createServiceInstanceCR(ctx, k8sClient, serviceInstanceGUID, space.Name, "service-instance-name", serviceInstanceGUID)
serviceBindingGUID = prefixedGUID("binding")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally prefer uuid.NewString() instead of those prefixedXXX utilities. We are trying to slowly get rid of them alltogether, let's not use them in new code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to user prefixedGUID instead of uuid.NewString()?

ManagedType = "managed"
UserProvidedType = "user-provided"
ManagedType = "managed"
CredentialsSecretKey = "credentials"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a constant for that key, no need of a new one. But if you use the utilities from the tools/secrets.go file, you should probably not need to reference it.

@danail-branekov
Copy link
Member

As this is a new endpoint, I would also ask you to add an E2E test (in tests/e2e/service_bindings_test.go). In e2e tests we test the very-very happy path to verify that the invocation of the endpoint succeeds and maybe a field or two in the response. Thus we verify that endpoints are properly wired in the api main. No need to test error cases there, they are already tested with unit/integration tests.

@ddraganovv ddraganovv force-pushed the add_service_binding_getDetails_handler branch from dedb18a to f34a991 Compare February 4, 2025 23:55
@ddraganovv ddraganovv force-pushed the add_service_binding_getDetails_handler branch from f34a991 to fd51307 Compare February 5, 2025 22:40
Expect(rr).To(HaveHTTPHeaderWithValue("Content-Type", "application/json"))
Expect(rr).To(HaveHTTPBody(SatisfyAll(
MatchJSONPath("$.credentials.connection", "mydb://user@password:example.com"),
MatchJSONPath("$.syslog_drain_url", "http://syslog.example.com/drain"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With regards to syslog_drain_url and volume_mounts, we would better not return anything. Furnthermore these values are not stubbed in the before each, so this test would probably fail.

MatchJSONPath("$.volume_mounts[0]", "mount1"),
MatchJSONPath("$.volume_mounts[1]", "mount2"),
)))
Expect(serviceBindingRepo.GetServiceBindingDetailsCallCount()).To(Equal(1))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also add a check of the arguments passed to GetServiceBindingDetails

serviceBindingRepo.GetServiceBindingDetailsReturns(repositories.ServiceBindingDetailsRecord{}, apierrors.NewForbiddenError(nil, repositories.ServiceBindingResourceType))
})

It("returns 404 NotFound when binding not found", func() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the context of the test is already stated in the When above, so this would be more concise as:

It("returns 404 Not Found")

if err != nil {
return ServiceBindingRecord{}, apierrors.FromK8sError(err, ServiceBindingResourceType)
return ServiceBindingDetailsRecord{}, fmt.Errorf("failed to get credentials: %w", apierrors.ForbiddenAsNotFound(apierrors.FromK8sError(err, ServiceBindingResourceType))) //
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things:

  • We usually do the ForbiddenAsNotFound masking in the handler layer only
  • The trailing comment should go

func (r *ServiceBindingRepo) GetServiceBindingDetails(ctx context.Context, authInfo authorization.Info, guid string) (ServiceBindingDetailsRecord, error) {
binding, err := r.getServiceBinding(ctx, authInfo, guid)
if err != nil {
return ServiceBindingDetailsRecord{}, fmt.Errorf("get-service-binding-details failed to retrieve the service binding: %w", err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error is already wrapped in the function, so no need to wrap it again, as we do not add any significant info here.

if err != nil {
return ServiceBindingRecord{}, err
return ServiceBindingRecord{}, fmt.Errorf("get-service-binding failed to create user client: %w", err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error is already wrapped, no need to wrap again, furthermore the description is incorrect.

BeforeEach(func() {
serviceInstanceGUID := prefixedGUID("instance")
cfServiceInstance = createServiceInstanceCR(ctx, k8sClient, serviceInstanceGUID, space.Name, "service-instance-name", serviceInstanceGUID)
serviceBindingGUID = prefixedGUID("binding")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to user prefixedGUID instead of uuid.NewString()?

var result credentialsResponse

BeforeEach(func() {
brokerGUID := createBroker(serviceBrokerURL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test would be much simpler if we create a binding to a user provided service instance. The details endpoint is valid for both types of service binding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants