Skip to content

Commit

Permalink
feat: ability to purge a service binding
Browse files Browse the repository at this point in the history
The CSB command has a "purge" subcommand for purging a whole service
instance. This adds a "purge-binding" subcommand for purging just a
single binding.

[#185835561](https://www.pivotaltracker.com/story/show/185835561)
  • Loading branch information
blgm committed Feb 16, 2024
1 parent e09d09c commit adce16f
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 11 deletions.
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))
}

0 comments on commit adce16f

Please sign in to comment.