Skip to content

feat(245): external-name ADR compliance for ServiceCredentialBinding#306

Open
1122Louis wants to merge 8 commits into
SAP:mainfrom
1122Louis:feature/245-servicecredentialbinding-external-name-adr
Open

feat(245): external-name ADR compliance for ServiceCredentialBinding#306
1122Louis wants to merge 8 commits into
SAP:mainfrom
1122Louis:feature/245-servicecredentialbinding-external-name-adr

Conversation

@1122Louis

@1122Louis 1122Louis commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Changes

  • Observe(): Adopt existing resources by updating external-name to real GUID when found by spec search
  • Update(): Skip CF update if external-name is not a valid UUID (allows import scenarios)
  • Delete(): Use external-name GUID directly + ignore NotFound errors gracefully
  • Types: Add External-Name Configuration doc comment

Testing

  • Unit tests: adoption flow, invalid UUID format handling, not-found on delete
  • E2E import test using ImportTester pattern
  • Upgrade test verifying external-name survives provider upgrade

Closes #245

Copilot AI review requested due to automatic review settings June 4, 2026 13:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds coverage and controller behavior to ensure ServiceCredentialBinding external-names are UUID/GUID-based and remain stable across upgrade/import flows.

Changes:

  • Introduces upgrade + e2e tests and CR testdata for ServiceCredentialBinding external-name/import scenarios
  • Adds adoption behavior to rewrite non-GUID external-names to the discovered binding GUID during Observe
  • Tightens Update/Delete logic to act only when external-name is a valid UUID, plus new unit tests for these paths

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
test/upgrade/testdata/customCrs/externalNames/serviceCredentialBinding/service_instance.yaml Adds ServiceInstance CR used by upgrade external-name test scenario
test/upgrade/testdata/customCrs/externalNames/serviceCredentialBinding/service_credential_binding.yaml Adds SCB CR used by upgrade external-name test scenario
test/upgrade/service_credential_binding_external_name_upgrade_test.go New upgrade test asserting external-name UUID format and stability across upgrade
test/e2e/crs/externalNamesImport/serviceCredentialBinding/serviceinstance.yaml Adds dependent ServiceInstance CR for e2e import flow
test/e2e/crs/externalNamesImport/serviceCredentialBinding/import.yaml Adds observed Org/Space CRs used by import test resources
test/e2e/cloudfoundry_servicecredentialbinding_import_test.go New e2e test exercising SCB import flow
internal/controller/servicecredentialbinding/controller_test.go Adds unit tests for invalid external-name UUID handling and adoption behavior
internal/controller/servicecredentialbinding/controller.go Adds UUID validation + adoption update of external-name; changes Update/Delete behavior
apis/resources/v1alpha1/servicecredentialbinding_types.go Documents external-name expectations and how to discover GUID

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +251 to +258
externalName := meta.GetExternalName(cr)
if uuid.Validate(externalName) != nil {
// External name is not a valid UUID; nothing to delete in CF
return managed.ExternalDelete{}, nil
}

err := scb.Delete(ctx, c.scbClient, externalName)
if err := clients.IgnoreNotFoundErr(err); err != nil {
Comment thread internal/controller/servicecredentialbinding/controller.go
errUpdateStatus = "cannot update status after retiring binding: %w"
errExtractParams = "cannot extract specified parameters: %w"
errUnknownState = "unknown last operation state for " + resourceType + " in " + externalSystem
errUpdateCR = "cannot update managed resource"
if guid != serviceBinding.GUID {
meta.SetExternalName(cr, serviceBinding.GUID)
if err := c.kube.Update(ctx, cr); err != nil {
return managed.ExternalObservation{}, fmt.Errorf("%s: %w", errUpdateCR, err)
Comment on lines +75 to +79
func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
serviceCredentialBinding := &v1alpha1.ServiceCredentialBinding{}
r := cfg.Client().Resources()

err := r.Get(ctx, serviceCredentialBindingName, cfg.Namespace(), serviceCredentialBinding)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We could have both register, or extract a helper. The post-upgrade r.Get may silently fail or use the wrong kind if the scheme isn't registered.

t.Fatalf("External name '%s' does not match expected UUID format", externalName)
}

return context.WithValue(ctx, "preUpgradeExternalName", externalName)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should be Must be an unexported struct type:

type contextKey struct{}
type preUpgradeExternalNameKey struct{}
// then
context.WithValue(ctx, preUpgradeExternalNameKey{}, externalName)
ctx.Value(preUpgradeExternalNameKey{}).(string)

t.Fatalf("External name '%s' does not match expected UUID format after upgrade", externalName)
}

preUpgradeExternalName, ok := ctx.Value("preUpgradeExternalName").(string)
Comment on lines +116 to +121
// External-Name Configuration:
// - Follows Standard: yes
// - Format: Service Credential Binding GUID (UUID format)
// - How to find:
// - UI: Not available in the BTP Cockpit
// - CLI: Use CF CLI: `cf service-keys <SERVICE_INSTANCE>` and look up the key GUID via `cf curl /v3/service_credential_bindings?names=<KEY_NAME>`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Copilot is valid here

- Observe(): adopt existing resources by updating external-name to real GUID
- Update(): skip CF update if external-name is not a valid UUID
- Delete(): use external-name GUID directly + ignore NotFound errors
- Add External-Name Configuration doc comment to types file
- Add unit tests: adoption, invalid UUID format, not-found on delete
- Add E2E import test using ImportTester pattern
- Add upgrade test verifying external-name survives provider upgrade

Closes SAP#245
@1122Louis 1122Louis force-pushed the feature/245-servicecredentialbinding-external-name-adr branch from e0073e0 to 82ad561 Compare June 4, 2026 14:07
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 4, 2026 14:07 — with GitHub Actions Inactive
- Extract isBindingNotFoundError() to reduce Observe() cyclomatic complexity
- Extract isValidUUID() helper to avoid nilerr lint false positive
- Regenerate CRDs

@SatabdiG SatabdiG left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Really good work! some comments from my side but overall solid

Comment on lines +116 to +121
// External-Name Configuration:
// - Follows Standard: yes
// - Format: Service Credential Binding GUID (UUID format)
// - How to find:
// - UI: Not available in the BTP Cockpit
// - CLI: Use CF CLI: `cf service-keys <SERVICE_INSTANCE>` and look up the key GUID via `cf curl /v3/service_credential_bindings?names=<KEY_NAME>`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Copilot is valid here

Comment thread internal/controller/servicecredentialbinding/controller.go
t.Fatalf("External name '%s' does not match expected UUID format", externalName)
}

return context.WithValue(ctx, "preUpgradeExternalName", externalName)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should be Must be an unexported struct type:

type contextKey struct{}
type preUpgradeExternalNameKey struct{}
// then
context.WithValue(ctx, preUpgradeExternalNameKey{}, externalName)
ctx.Value(preUpgradeExternalNameKey{}).(string)

}

annotations := serviceCredentialBinding.GetAnnotations()
externalName, exists := annotations["crossplane.io/external-name"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we should use meta.GetExternalName(serviceCredentialBinding)

Comment on lines +75 to +79
func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
serviceCredentialBinding := &v1alpha1.ServiceCredentialBinding{}
r := cfg.Client().Resources()

err := r.Get(ctx, serviceCredentialBindingName, cfg.Namespace(), serviceCredentialBinding)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We could have both register, or extract a helper. The post-upgrade r.Get may silently fail or use the wrong kind if the scheme isn't registered.

@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 9, 2026 10:17 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-no-approval June 9, 2026 10:57 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:11 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:11 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:11 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:11 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:11 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:11 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:11 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:11 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:11 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:11 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:11 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:11 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-no-approval June 10, 2026 09:26 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 10, 2026 09:35 — with GitHub Actions Inactive
@1122Louis 1122Louis temporarily deployed to pr-e2e-no-approval June 11, 2026 09:26 — with GitHub Actions Inactive
  - Fix doc comment formatting (proper indentation)
  - Use unexported struct for context key (avoid collisions)
  - Use meta.GetExternalName() instead of direct annotation access
  - Register scheme in post-upgrade assessment
  - Improve Delete logic to fallback to status GUID (prevent orphaning)
@1122Louis 1122Louis temporarily deployed to pr-e2e-approval June 12, 2026 12:39 — with GitHub Actions Inactive
1122Louis and others added 4 commits June 12, 2026 14:40
- Remove extra blank line in apis/generate.go
- Add missing blank line after comment in orgquota_types.go
- Fix comment indentation in serviceinstance_types.go
- Update InvalidUUID_SkipsDelete test to expect Delete call with status GUID
- Add InvalidUUID_BothInvalid_SkipsDelete test for true skip case
- Reflects new Delete logic that falls back to status GUID when external-name is not a valid UUID
Use standard indentation for doc comment (matching other types in codebase)
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.

[TASK] External Name (ServiceCredentialBinding): Ensure ADR Compliance

3 participants