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

feat: ability to purge a service binding #956

Merged
merged 1 commit into from
Feb 19, 2024
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
16 changes: 5 additions & 11 deletions cmd/purge.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ If using Cloud Foundry, the steps are:
case 0:
log.Fatal("missing service instance GUID")
case 1:
purge(args[0])
purgeServiceInstance(args[0])
default:
log.Fatal("too many arguments")
}
Expand All @@ -42,8 +42,8 @@ If using Cloud Foundry, the steps are:
rootCmd.AddCommand(purgeCmd)
}

func purge(serviceInstanceGUID string) {
logger := utils.NewLogger("purge")
func purgeServiceInstance(serviceInstanceGUID string) {
logger := utils.NewLogger("purge-service-instance")
db := dbservice.New(logger)
encryptor := setupDBEncryption(db, logger)
store := storage.New(db, encryptor)
Expand All @@ -53,14 +53,8 @@ func purge(serviceInstanceGUID string) {
log.Fatalf("error listing bindings: %s", err)
}
for _, bindingGUID := range bindings {
if err := store.DeleteServiceBindingCredentials(bindingGUID, serviceInstanceGUID); err != nil {
log.Fatalf("error deleting binding credentials for %q: %s", bindingGUID, err)
}
if err := store.DeleteBindRequestDetails(bindingGUID, serviceInstanceGUID); err != nil {
log.Fatalf("error deleting binding request details for %q: %s", bindingGUID, err)
}
if err := store.DeleteTerraformDeployment(fmt.Sprintf("tf:%s:%s", serviceInstanceGUID, bindingGUID)); err != nil {
log.Fatalf("error deleting binding terraform deployment for %q: %s", bindingGUID, err)
if err := deleteServiceBindingFromStore(store, serviceInstanceGUID, bindingGUID); err != nil {
log.Fatalf("error deleting binding %q for service instance %q: %s", bindingGUID, serviceInstanceGUID, err)
}
}
if err := store.DeleteProvisionRequestDetails(serviceInstanceGUID); err != nil {
Expand Down
99 changes: 99 additions & 0 deletions cmd/purge_binding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package cmd

import (
"fmt"
"log"

"github.com/spf13/cobra"

"github.com/cloudfoundry/cloud-service-broker/dbservice"
"github.com/cloudfoundry/cloud-service-broker/internal/storage"
"github.com/cloudfoundry/cloud-service-broker/utils"
)

func init() {
purgeCmd := &cobra.Command{
Use: "purge-binding",
Short: "purge a service binding from the database",
Long: `Lets you remove a service binding (or service key) from the Cloud Service Broker database.

It does not actually delete the service binding, it just removes all references from the database.
This can be used to remove references to a service binding that has been manually removed,
or to clean up a service binding that fails to delete.

If using Cloud Foundry, identify the GUID of the service instance:

cf service <name> --guid # Prints the service instance guid

Then identify the GUID of the service binding, or service key that you want to remove.
You can see the service keys and bindings for a service instance by running:

cf curl /v3/service_credential_bindings?service_instance_guids=<service-instance-guid>

Remove the binding from Cloud Service broker:

cloud-service-broker purge <service-instance-guid> <service-binding-guid>

Then you can delete the binding from Cloud Foundry. Cloud Service Broker will confirm
to Cloud Foundry that the service binding or key no longer exists, and it will be removed
from the Cloud Foundry database

cf unbind-service <app-name> <service-instance-name>

Or

cf delete-service-key <service-instance-name> <service-key-name>
`,
Run: func(cmd *cobra.Command, args []string) {
switch len(args) {
case 0:
log.Fatal("missing service instance GUID and service binding GUID")
case 1:
log.Fatal("missing service binding GUID")
case 2:
purgeServiceBinding(args[0], args[1])
default:
log.Fatal("too many arguments")
}
},
}

rootCmd.AddCommand(purgeCmd)
}

func purgeServiceBinding(serviceInstanceGUID, serviceBindingGUID string) {
logger := utils.NewLogger("purge-service-binding")
db := dbservice.New(logger)
encryptor := setupDBEncryption(db, logger)
store := storage.New(db, encryptor)

bindings, err := store.GetServiceBindingIDsForServiceInstance(serviceInstanceGUID)
if err != nil {
log.Fatalf("error listing bindings: %s", err)
}
for _, bindingGUID := range bindings {
if bindingGUID == serviceBindingGUID {
if err := deleteServiceBindingFromStore(store, serviceInstanceGUID, serviceBindingGUID); err != nil {
log.Fatalf("error deleting binding %q for service instance %q: %s", serviceBindingGUID, serviceInstanceGUID, err)
}
log.Printf("deleted binding %q for service instance %q from the Cloud Service Broker database", serviceBindingGUID, serviceInstanceGUID)
return
}
}

log.Fatalf("could not find service binding %q for service instance %q", serviceBindingGUID, serviceInstanceGUID)
}

func deleteServiceBindingFromStore(store *storage.Storage, serviceInstanceGUID, serviceBindingGUID string) error {
if err := store.DeleteServiceBindingCredentials(serviceBindingGUID, serviceInstanceGUID); err != nil {
return fmt.Errorf("error deleting binding credentials for %q: %w", serviceBindingGUID, err)
}
if err := store.DeleteBindRequestDetails(serviceBindingGUID, serviceInstanceGUID); err != nil {
return fmt.Errorf("error deleting binding request details for %q: %w", serviceBindingGUID, err)
}
if err := store.DeleteTerraformDeployment(fmt.Sprintf("tf:%s:%s", serviceInstanceGUID, serviceBindingGUID)); err != nil {
return fmt.Errorf("error deleting binding terraform deployment for %q: %s", serviceBindingGUID, err)
}

return nil
}
Empty file.
Empty file.
22 changes: 22 additions & 0 deletions integrationtest/fixtures/purge-service-binding/fake-service.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: 1
name: fake-service
id: 2f36d5c6-ccc3-11ee-a3be-cb7c74dcfe9a
description: description
display_name: Fake
image_url: https://example.com/icon.jpg
documentation_url: https://example.com
support_url: https://example.com/support.html
plans:
- name: standard
id: 21a3e6c4-ccc3-11ee-a9dd-d74726b3c0d2
description: Standard plan
display_name: Standard
provision:
template_ref: fake-provision.tf
bind:
template_refs:
main: fake-bind.tf
user_inputs:
- field_name: foo
type: string
details: needed so that BindRequestDetails gets stored
16 changes: 16 additions & 0 deletions integrationtest/fixtures/purge-service-binding/manifest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
packversion: 1
name: fake-brokerpak
version: 0.1.0
metadata:
author: [email protected]
platforms:
- os: linux
arch: amd64
- os: darwin
arch: amd64
terraform_binaries:
- name: terraform
version: 1.5.7
source: https://github.com/hashicorp/terraform/archive/v1.5.7.zip
service_definitions:
- fake-service.yml
63 changes: 63 additions & 0 deletions integrationtest/purge_service_binding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package integrationtest_test

import (
"fmt"
"os"
"os/exec"
"time"

"github.com/cloudfoundry/cloud-service-broker/integrationtest/packer"
"github.com/cloudfoundry/cloud-service-broker/internal/testdrive"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
)

var _ = Describe("Purge Service Binding", func() {
const (
serviceOfferingGUID = "2f36d5c6-ccc3-11ee-a3be-cb7c74dcfe9a"
servicePlanGUID = "21a3e6c4-ccc3-11ee-a9dd-d74726b3c0d2"
bindParams = `{"foo":"bar"}`
)

It("purges the correct service binding and no others", func() {
By("creating a broker with brokerpak")
brokerpak := must(packer.BuildBrokerpak(csb, fixtures("purge-service-binding")))
broker := must(testdrive.StartBroker(csb, brokerpak, database))
DeferCleanup(func() {
broker.Stop()
cleanup(brokerpak)
})

By("creating a service with bindings to purge")
instance := must(broker.Provision(serviceOfferingGUID, servicePlanGUID))
keepBinding1 := must(broker.CreateBinding(instance, testdrive.WithBindingParams(bindParams)))
purgeBinding := must(broker.CreateBinding(instance, testdrive.WithBindingParams(bindParams)))
keepBinding2 := must(broker.CreateBinding(instance, testdrive.WithBindingParams(bindParams)))

By("stopping the broker")
broker.Stop()

By("purging the binding")
purgeServiceBinding(database, instance.GUID, purgeBinding.GUID)

By("checking that we purged the service binding")
expectServiceBindingStatus(instance.GUID, purgeBinding.GUID, BeFalse())

By("checking that the other service bindings still exists")
expectServiceBindingStatus(instance.GUID, keepBinding1.GUID, BeTrue())
expectServiceBindingStatus(instance.GUID, keepBinding2.GUID, BeTrue())
})
})

func purgeServiceBinding(database, serviceInstanceGUID, serviceBindingGUID string) {
cmd := exec.Command(csb, "purge-binding", serviceInstanceGUID, serviceBindingGUID)
cmd.Env = append(
os.Environ(),
"DB_TYPE=sqlite3",
fmt.Sprintf("DB_PATH=%s", database),
)
purgeSession, err := Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).WithOffset(1).NotTo(HaveOccurred())
Eventually(purgeSession).WithTimeout(time.Minute).WithOffset(1).Should(Exit(0))
}
Loading