Skip to content

Commit 247f86d

Browse files
authored
feat: ability to purge a service binding (#956)
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)
1 parent ed3fc42 commit 247f86d

File tree

7 files changed

+205
-11
lines changed

7 files changed

+205
-11
lines changed

cmd/purge.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ If using Cloud Foundry, the steps are:
3232
case 0:
3333
log.Fatal("missing service instance GUID")
3434
case 1:
35-
purge(args[0])
35+
purgeServiceInstance(args[0])
3636
default:
3737
log.Fatal("too many arguments")
3838
}
@@ -42,8 +42,8 @@ If using Cloud Foundry, the steps are:
4242
rootCmd.AddCommand(purgeCmd)
4343
}
4444

45-
func purge(serviceInstanceGUID string) {
46-
logger := utils.NewLogger("purge")
45+
func purgeServiceInstance(serviceInstanceGUID string) {
46+
logger := utils.NewLogger("purge-service-instance")
4747
db := dbservice.New(logger)
4848
encryptor := setupDBEncryption(db, logger)
4949
store := storage.New(db, encryptor)
@@ -53,14 +53,8 @@ func purge(serviceInstanceGUID string) {
5353
log.Fatalf("error listing bindings: %s", err)
5454
}
5555
for _, bindingGUID := range bindings {
56-
if err := store.DeleteServiceBindingCredentials(bindingGUID, serviceInstanceGUID); err != nil {
57-
log.Fatalf("error deleting binding credentials for %q: %s", bindingGUID, err)
58-
}
59-
if err := store.DeleteBindRequestDetails(bindingGUID, serviceInstanceGUID); err != nil {
60-
log.Fatalf("error deleting binding request details for %q: %s", bindingGUID, err)
61-
}
62-
if err := store.DeleteTerraformDeployment(fmt.Sprintf("tf:%s:%s", serviceInstanceGUID, bindingGUID)); err != nil {
63-
log.Fatalf("error deleting binding terraform deployment for %q: %s", bindingGUID, err)
56+
if err := deleteServiceBindingFromStore(store, serviceInstanceGUID, bindingGUID); err != nil {
57+
log.Fatalf("error deleting binding %q for service instance %q: %s", bindingGUID, serviceInstanceGUID, err)
6458
}
6559
}
6660
if err := store.DeleteProvisionRequestDetails(serviceInstanceGUID); err != nil {

cmd/purge_binding.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/spf13/cobra"
8+
9+
"github.com/cloudfoundry/cloud-service-broker/dbservice"
10+
"github.com/cloudfoundry/cloud-service-broker/internal/storage"
11+
"github.com/cloudfoundry/cloud-service-broker/utils"
12+
)
13+
14+
func init() {
15+
purgeCmd := &cobra.Command{
16+
Use: "purge-binding",
17+
Short: "purge a service binding from the database",
18+
Long: `Lets you remove a service binding (or service key) from the Cloud Service Broker database.
19+
20+
It does not actually delete the service binding, it just removes all references from the database.
21+
This can be used to remove references to a service binding that has been manually removed,
22+
or to clean up a service binding that fails to delete.
23+
24+
If using Cloud Foundry, identify the GUID of the service instance:
25+
26+
cf service <name> --guid # Prints the service instance guid
27+
28+
Then identify the GUID of the service binding, or service key that you want to remove.
29+
You can see the service keys and bindings for a service instance by running:
30+
31+
cf curl /v3/service_credential_bindings?service_instance_guids=<service-instance-guid>
32+
33+
Remove the binding from Cloud Service broker:
34+
35+
cloud-service-broker purge <service-instance-guid> <service-binding-guid>
36+
37+
Then you can delete the binding from Cloud Foundry. Cloud Service Broker will confirm
38+
to Cloud Foundry that the service binding or key no longer exists, and it will be removed
39+
from the Cloud Foundry database
40+
41+
cf unbind-service <app-name> <service-instance-name>
42+
43+
Or
44+
45+
cf delete-service-key <service-instance-name> <service-key-name>
46+
`,
47+
Run: func(cmd *cobra.Command, args []string) {
48+
switch len(args) {
49+
case 0:
50+
log.Fatal("missing service instance GUID and service binding GUID")
51+
case 1:
52+
log.Fatal("missing service binding GUID")
53+
case 2:
54+
purgeServiceBinding(args[0], args[1])
55+
default:
56+
log.Fatal("too many arguments")
57+
}
58+
},
59+
}
60+
61+
rootCmd.AddCommand(purgeCmd)
62+
}
63+
64+
func purgeServiceBinding(serviceInstanceGUID, serviceBindingGUID string) {
65+
logger := utils.NewLogger("purge-service-binding")
66+
db := dbservice.New(logger)
67+
encryptor := setupDBEncryption(db, logger)
68+
store := storage.New(db, encryptor)
69+
70+
bindings, err := store.GetServiceBindingIDsForServiceInstance(serviceInstanceGUID)
71+
if err != nil {
72+
log.Fatalf("error listing bindings: %s", err)
73+
}
74+
for _, bindingGUID := range bindings {
75+
if bindingGUID == serviceBindingGUID {
76+
if err := deleteServiceBindingFromStore(store, serviceInstanceGUID, serviceBindingGUID); err != nil {
77+
log.Fatalf("error deleting binding %q for service instance %q: %s", serviceBindingGUID, serviceInstanceGUID, err)
78+
}
79+
log.Printf("deleted binding %q for service instance %q from the Cloud Service Broker database", serviceBindingGUID, serviceInstanceGUID)
80+
return
81+
}
82+
}
83+
84+
log.Fatalf("could not find service binding %q for service instance %q", serviceBindingGUID, serviceInstanceGUID)
85+
}
86+
87+
func deleteServiceBindingFromStore(store *storage.Storage, serviceInstanceGUID, serviceBindingGUID string) error {
88+
if err := store.DeleteServiceBindingCredentials(serviceBindingGUID, serviceInstanceGUID); err != nil {
89+
return fmt.Errorf("error deleting binding credentials for %q: %w", serviceBindingGUID, err)
90+
}
91+
if err := store.DeleteBindRequestDetails(serviceBindingGUID, serviceInstanceGUID); err != nil {
92+
return fmt.Errorf("error deleting binding request details for %q: %w", serviceBindingGUID, err)
93+
}
94+
if err := store.DeleteTerraformDeployment(fmt.Sprintf("tf:%s:%s", serviceInstanceGUID, serviceBindingGUID)); err != nil {
95+
return fmt.Errorf("error deleting binding terraform deployment for %q: %s", serviceBindingGUID, err)
96+
}
97+
98+
return nil
99+
}

integrationtest/fixtures/purge-service-binding/fake-bind.tf

Whitespace-only changes.

integrationtest/fixtures/purge-service-binding/fake-provision.tf

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
version: 1
2+
name: fake-service
3+
id: 2f36d5c6-ccc3-11ee-a3be-cb7c74dcfe9a
4+
description: description
5+
display_name: Fake
6+
image_url: https://example.com/icon.jpg
7+
documentation_url: https://example.com
8+
support_url: https://example.com/support.html
9+
plans:
10+
- name: standard
11+
id: 21a3e6c4-ccc3-11ee-a9dd-d74726b3c0d2
12+
description: Standard plan
13+
display_name: Standard
14+
provision:
15+
template_ref: fake-provision.tf
16+
bind:
17+
template_refs:
18+
main: fake-bind.tf
19+
user_inputs:
20+
- field_name: foo
21+
type: string
22+
details: needed so that BindRequestDetails gets stored
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
packversion: 1
2+
name: fake-brokerpak
3+
version: 0.1.0
4+
metadata:
5+
6+
platforms:
7+
- os: linux
8+
arch: amd64
9+
- os: darwin
10+
arch: amd64
11+
terraform_binaries:
12+
- name: terraform
13+
version: 1.5.7
14+
source: https://github.com/hashicorp/terraform/archive/v1.5.7.zip
15+
service_definitions:
16+
- fake-service.yml
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package integrationtest_test
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"time"
8+
9+
"github.com/cloudfoundry/cloud-service-broker/integrationtest/packer"
10+
"github.com/cloudfoundry/cloud-service-broker/internal/testdrive"
11+
. "github.com/onsi/ginkgo/v2"
12+
. "github.com/onsi/gomega"
13+
. "github.com/onsi/gomega/gexec"
14+
)
15+
16+
var _ = Describe("Purge Service Binding", func() {
17+
const (
18+
serviceOfferingGUID = "2f36d5c6-ccc3-11ee-a3be-cb7c74dcfe9a"
19+
servicePlanGUID = "21a3e6c4-ccc3-11ee-a9dd-d74726b3c0d2"
20+
bindParams = `{"foo":"bar"}`
21+
)
22+
23+
It("purges the correct service binding and no others", func() {
24+
By("creating a broker with brokerpak")
25+
brokerpak := must(packer.BuildBrokerpak(csb, fixtures("purge-service-binding")))
26+
broker := must(testdrive.StartBroker(csb, brokerpak, database))
27+
DeferCleanup(func() {
28+
broker.Stop()
29+
cleanup(brokerpak)
30+
})
31+
32+
By("creating a service with bindings to purge")
33+
instance := must(broker.Provision(serviceOfferingGUID, servicePlanGUID))
34+
keepBinding1 := must(broker.CreateBinding(instance, testdrive.WithBindingParams(bindParams)))
35+
purgeBinding := must(broker.CreateBinding(instance, testdrive.WithBindingParams(bindParams)))
36+
keepBinding2 := must(broker.CreateBinding(instance, testdrive.WithBindingParams(bindParams)))
37+
38+
By("stopping the broker")
39+
broker.Stop()
40+
41+
By("purging the binding")
42+
purgeServiceBinding(database, instance.GUID, purgeBinding.GUID)
43+
44+
By("checking that we purged the service binding")
45+
expectServiceBindingStatus(instance.GUID, purgeBinding.GUID, BeFalse())
46+
47+
By("checking that the other service bindings still exists")
48+
expectServiceBindingStatus(instance.GUID, keepBinding1.GUID, BeTrue())
49+
expectServiceBindingStatus(instance.GUID, keepBinding2.GUID, BeTrue())
50+
})
51+
})
52+
53+
func purgeServiceBinding(database, serviceInstanceGUID, serviceBindingGUID string) {
54+
cmd := exec.Command(csb, "purge-binding", serviceInstanceGUID, serviceBindingGUID)
55+
cmd.Env = append(
56+
os.Environ(),
57+
"DB_TYPE=sqlite3",
58+
fmt.Sprintf("DB_PATH=%s", database),
59+
)
60+
purgeSession, err := Start(cmd, GinkgoWriter, GinkgoWriter)
61+
Expect(err).WithOffset(1).NotTo(HaveOccurred())
62+
Eventually(purgeSession).WithTimeout(time.Minute).WithOffset(1).Should(Exit(0))
63+
}

0 commit comments

Comments
 (0)