-
Notifications
You must be signed in to change notification settings - Fork 72
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
base: main
Are you sure you want to change the base?
Implemented GET /v3/service_credential_bindings/:guid/details endpoint #3762
Conversation
e8183f3
to
dedb18a
Compare
api/handlers/service_binding.go
Outdated
ServiceBindingPath = "/v3/service_credential_bindings/{guid}" | ||
ServiceBindingsPath = "/v3/service_credential_bindings" | ||
ServiceBindingPath = "/v3/service_credential_bindings/{guid}" | ||
ServiceBindingDetailsPath = ServiceBindingPath + "/details" |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
api/presenter/service_binding.go
Outdated
@@ -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"` |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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{ |
There was a problem hiding this comment.
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")) |
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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.
As this is a new endpoint, I would also ask you to add an E2E test (in |
dedb18a
to
f34a991
Compare
f34a991
to
fd51307
Compare
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"), |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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))) // |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
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