Skip to content
This repository has been archived by the owner on May 6, 2022. It is now read-only.

Commit

Permalink
Unbind abandon
Browse files Browse the repository at this point in the history
  • Loading branch information
Tara Gu committed Mar 10, 2019
1 parent ce2050d commit 9db404a
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 9 deletions.
70 changes: 65 additions & 5 deletions cmd/svcat/binding/unbind_cmd.go
Expand Up @@ -17,16 +17,22 @@ limitations under the License.
package binding

import (
"bufio"
"fmt"
"io"
"strings"
"sync"

"github.com/pkg/errors"

"github.com/kubernetes-incubator/service-catalog/cmd/svcat/command"
"github.com/kubernetes-incubator/service-catalog/cmd/svcat/output"
"github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
)

type unbindCmd struct {
Expand All @@ -35,6 +41,9 @@ type unbindCmd struct {

instanceName string
bindingNames []string
abandon bool
stdin io.Reader
skipPrompt bool
}

// NewUnbindCmd builds a "svcat unbind" command
Expand All @@ -49,6 +58,7 @@ func NewUnbindCmd(cxt *command.Context) *cobra.Command {
Example: command.NormalizeExamples(`
svcat unbind wordpress-mysql-instance
svcat unbind --name wordpress-mysql-binding
svcat unbind --abandon wordpress-mysql-instance
`),
PreRunE: command.PreRunE(unbindCmd),
RunE: command.RunE(unbindCmd),
Expand All @@ -60,6 +70,20 @@ func NewUnbindCmd(cxt *command.Context) *cobra.Command {
[]string{},
"The name of the binding to remove",
)
cmd.Flags().BoolVar(
&unbindCmd.abandon,
"abandon",
false,
"Forcefully and immediately delete the resource from Service Catalog ONLY, potentially abandoning any broker resources that you may continue to be charged for.",
)
cmd.Flags().BoolVarP(
&unbindCmd.skipPrompt,
"yes",
"y",
false,
`Automatic yes to prompts. Assume "yes" as answer to all prompts and run non-interactively.`,
)

unbindCmd.AddWaitFlags(cmd)

return cmd
Expand All @@ -80,13 +104,49 @@ func (c *unbindCmd) Validate(args []string) error {
func (c *unbindCmd) Run() error {
// Indicates an error occurred and that a non-zero exit code should be used
var hasErrors bool
var bindings []types.NamespacedName
var bindingNames []types.NamespacedName
var err error

if c.abandon {
fmt.Fprintln(c.Output, "This action is not reversible and may cause you to be charged for the broker resources that are abandoned.")
if !c.skipPrompt {
fmt.Fprintln(c.Output, "Are you sure? [y|N]: ")
s := bufio.NewScanner(c.stdin)
s.Scan()

err = s.Err()
fmt.Fprintln(c.Output, err)

if strings.ToLower(s.Text()) != "y" {
err = fmt.Errorf("aborted abandon operation")
return err
}
}

if c.instanceName == "" {
err = fmt.Errorf("Please provide instance name; aborted abandon operation")
return err
}

si := &v1beta1.ServiceInstance{ObjectMeta: metav1.ObjectMeta{Name: c.instanceName, Namespace: c.Namespace}}
bindings, err := c.App.RetrieveBindingsByInstance(si)
if err != nil {
fmt.Fprintln(c.Output, err)
return err
}
for _, binding := range bindings {
finalizers := sets.NewString(binding.Finalizers...)
finalizers.Delete(v1beta1.FinalizerServiceCatalog)
fmt.Fprintln(c.Output, fmt.Sprintf("deleted %s\n", binding.Name))
}
fmt.Fprintln(c.Output, fmt.Sprintf("deleted %s\n", c.instanceName))
return nil
}

if c.instanceName != "" {
bindings, err = c.App.Unbind(c.Namespace, c.instanceName)
bindingNames, err = c.App.Unbind(c.Namespace, c.instanceName)
} else {
bindings, err = c.App.DeleteBindings(c.getBindingsToDelete())
bindingNames, err = c.App.DeleteBindings(c.getBindingsToDelete())
}

if err != nil {
Expand All @@ -96,9 +156,9 @@ func (c *unbindCmd) Run() error {
}

if c.Wait {
hasErrors = c.waitForBindingDeletes("waiting for the binding(s) to be deleted...", bindings...) || hasErrors
hasErrors = c.waitForBindingDeletes("waiting for the binding(s) to be deleted...", bindingNames...) || hasErrors
} else {
for _, binding := range bindings {
for _, binding := range bindingNames {
output.WriteDeletedResourceName(c.Output, binding.Name)
}
}
Expand Down
62 changes: 58 additions & 4 deletions cmd/svcat/binding/unbind_cmd_test.go
Expand Up @@ -19,20 +19,19 @@ package binding
import (
"bytes"
"errors"
"fmt"
"strings"
"testing"

"github.com/kubernetes-incubator/service-catalog/cmd/svcat/command"
"github.com/kubernetes-incubator/service-catalog/cmd/svcat/test"
svcattest "github.com/kubernetes-incubator/service-catalog/cmd/svcat/test"
"github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1"
svcatfake "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset/fake"
"github.com/kubernetes-incubator/service-catalog/pkg/svcat"
"k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
k8sfake "k8s.io/client-go/kubernetes/fake"
testing2 "k8s.io/client-go/testing"

_ "github.com/kubernetes-incubator/service-catalog/internal/test"
)

func TestUnbindCommand(t *testing.T) {
Expand All @@ -47,6 +46,9 @@ func TestUnbindCommand(t *testing.T) {
wantOutput string
wantError bool
allowDiffOrder bool // whether the order of lines in the output can be different from the one in wantOutput
abandon bool // delete all finalizers from the service instance so that it is deleted immediately
userResponse string
skipPrompt bool
}{
{
name: "delete binding",
Expand Down Expand Up @@ -154,6 +156,50 @@ func TestUnbindCommand(t *testing.T) {
wantOutput: "error:\n remove binding default/badbinding failed: sabotaged\ncould not remove all bindings",
wantError: true,
},
{
name: "delete all finalizers with user answering yes to interactive prompt",
fakeBindings: []string{"mybinding"},
instanceName: "myinstance",
abandon: true,
userResponse: "y",
wantOutput: "This action is not reversible and may cause you to be charged for the broker resources that are abandoned.\nAre you sure? [y|N]: \n<nil>\ndeleted myinstance",
},
{
name: "delete all finalizers with user skip prompt flag",
fakeBindings: []string{"mybinding"},
instanceName: "myinstance",
abandon: true,
skipPrompt: true,
userResponse: "y",
wantOutput: "This action is not reversible and may cause you to be charged for the broker resources that are abandoned.\ndeleted myinstance",
},
{
name: "delete all finalizers with user answering no to interactive prompt",
fakeBindings: []string{"mybinding"},
instanceName: "myinstance",
abandon: true,
userResponse: "n",
wantOutput: "This action is not reversible and may cause you to be charged for the broker resources that are abandoned.\nAre you sure? [y|N]: \n<nil>\naborted abandon operation",
wantError: true,
},
{
name: "delete all finalizers with user providing random response",
fakeBindings: []string{"mybinding"},
instanceName: "myinstance",
abandon: true,
userResponse: "foo",
wantOutput: "This action is not reversible and may cause you to be charged for the broker resources that are abandoned.\nAre you sure? [y|N]: \n<nil>\naborted abandon operation",
wantError: true,
},
{
name: "delete all finalizers - instance name not provided",
fakeBindings: []string{"mybinding"},
instanceName: "",
abandon: true,
userResponse: "y",
wantOutput: "This action is not reversible and may cause you to be charged for the broker resources that are abandoned.\nAre you sure? [y|N]: \n<nil>\nPlease provide instance name; aborted abandon operation",
wantError: true,
},
}

for _, tc := range testcases {
Expand Down Expand Up @@ -203,6 +249,14 @@ func TestUnbindCommand(t *testing.T) {
cmd.bindingNames = tc.bindingNames
cmd.instanceName = tc.instanceName
cmd.Wait = tc.wait
cmd.abandon = tc.abandon
cmd.skipPrompt = tc.skipPrompt

if tc.userResponse != "" {
var stdin bytes.Buffer
stdin.Write([]byte(fmt.Sprintf("%s\n", tc.userResponse)))
cmd.stdin = &stdin
}

err := cmd.Run()

Expand Down
5 changes: 5 additions & 0 deletions cmd/svcat/testdata/output/completion-bash.txt
Expand Up @@ -1068,6 +1068,8 @@ _svcat_unbind()
flags_with_completion=()
flags_completion=()

flags+=("--abandon")
local_nonpersistent_flags+=("--abandon")
flags+=("--interval=")
local_nonpersistent_flags+=("--interval=")
flags+=("--name=")
Expand All @@ -1079,6 +1081,9 @@ _svcat_unbind()
local_nonpersistent_flags+=("--timeout=")
flags+=("--wait")
local_nonpersistent_flags+=("--wait")
flags+=("--yes")
flags+=("-y")
local_nonpersistent_flags+=("--yes")
flags+=("--context=")
flags+=("--kubeconfig=")
flags+=("--logtostderr")
Expand Down
5 changes: 5 additions & 0 deletions cmd/svcat/testdata/output/completion-zsh.txt
Expand Up @@ -1202,6 +1202,8 @@ _svcat_unbind()
flags_with_completion=()
flags_completion=()
flags+=("--abandon")
local_nonpersistent_flags+=("--abandon")
flags+=("--interval=")
local_nonpersistent_flags+=("--interval=")
flags+=("--name=")
Expand All @@ -1213,6 +1215,9 @@ _svcat_unbind()
local_nonpersistent_flags+=("--timeout=")
flags+=("--wait")
local_nonpersistent_flags+=("--wait")
flags+=("--yes")
flags+=("-y")
local_nonpersistent_flags+=("--yes")
flags+=("--context=")
flags+=("--kubeconfig=")
flags+=("--logtostderr")
Expand Down
9 changes: 9 additions & 0 deletions cmd/svcat/testdata/plugin.yaml
Expand Up @@ -413,7 +413,12 @@ tree:
example: |2-
svcat unbind wordpress-mysql-instance
svcat unbind --name wordpress-mysql-binding
svcat unbind --abandon wordpress-mysql-instance
flags:
- desc: Forcefully and immediately delete the resource from Service Catalog ONLY,
potentially abandoning any broker resources that you may continue to be charged
for.
name: abandon
- desc: 'Poll interval for --wait, specified in human readable format: 30s, 1m,
1h'
name: interval
Expand All @@ -424,6 +429,10 @@ tree:
name: timeout
- desc: Wait until the operation completes.
name: wait
- desc: Automatic yes to prompts. Assume "yes" as answer to all prompts and run
non-interactively.
name: "yes"
shorthand: "y"
name: unbind
shortDesc: Unbinds an instance. When an instance name is specified, all of its bindings
are removed, otherwise use --name to remove a specific binding
Expand Down

0 comments on commit 9db404a

Please sign in to comment.