From e04e4cf3a98940daf83b9124b2906777947ef7a2 Mon Sep 17 00:00:00 2001 From: Parthvi Vala Date: Thu, 2 Jun 2022 21:21:49 +0530 Subject: [PATCH] odo remove binding Signed-off-by: Parthvi Vala --- pkg/binding/binding.go | 44 ++++++ pkg/binding/interface.go | 5 + pkg/binding/mock.go | 130 +++++++++++------- pkg/odo/cli/add/add.go | 4 +- pkg/odo/cli/cli.go | 2 + pkg/odo/cli/remove/binding/binding.go | 97 +++++++++++++ pkg/odo/cli/remove/remove.go | 26 ++++ .../devfile/cmd_remove_binding_test.go | 59 ++++++++ 8 files changed, 314 insertions(+), 53 deletions(-) create mode 100644 pkg/odo/cli/remove/binding/binding.go create mode 100644 pkg/odo/cli/remove/remove.go create mode 100644 tests/integration/devfile/cmd_remove_binding_test.go diff --git a/pkg/binding/binding.go b/pkg/binding/binding.go index 5e24c60aab5..81939823e9e 100644 --- a/pkg/binding/binding.go +++ b/pkg/binding/binding.go @@ -2,8 +2,13 @@ package binding import ( "fmt" + "path/filepath" + "strings" + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser" + "github.com/devfile/library/pkg/devfile/parser/data/v2/common" + devfilefs "github.com/devfile/library/pkg/testingutil/filesystem" sboApi "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1" "gopkg.in/yaml.v2" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -146,3 +151,42 @@ func (o *BindingClient) GetServiceInstances() (map[string]unstructured.Unstructu return bindableObjectMap, nil } + +// ValidateRemoveBinding validates if the command has adequate arguments/flags +func (o *BindingClient) ValidateRemoveBinding(flags map[string]string) error { + if flags[backendpkg.FLAG_NAME] == "" { + return fmt.Errorf("you must specify the service binding name with --%s flag", backendpkg.FLAG_NAME) + } + return nil +} + +// RemoveBinding removes the binding from devfile +func (o *BindingClient) RemoveBinding(servicebindingName string, obj parser.DevfileObj) (parser.DevfileObj, error) { + var componentName string + var options []string + components, err := obj.Data.GetComponents(common.DevfileOptions{ + ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.KubernetesComponentType}, + }) + if err != nil { + return obj, err + } + for _, component := range components { + unstructuredObj, err := libdevfile.GetK8sComponentAsUnstructured(obj, component.Name, filepath.Dir(obj.Ctx.GetAbsPath()), devfilefs.DefaultFs{}) + if err != nil { + continue + } + if unstructuredObj.GetKind() == kclient.ServiceBindingKind { + options = append(options, unstructuredObj.GetName()) + if unstructuredObj.GetName() == servicebindingName { + componentName = component.Name + break + } + } + } + + err = obj.Data.DeleteComponent(componentName) + if err != nil { + err = fmt.Errorf("Failed to remove Service Binding %q from the the devfile. Available Service Bindings: %s", servicebindingName, strings.Join(options, ", ")) + } + return obj, err +} diff --git a/pkg/binding/interface.go b/pkg/binding/interface.go index a7c099ea866..631179d70c9 100644 --- a/pkg/binding/interface.go +++ b/pkg/binding/interface.go @@ -21,4 +21,9 @@ type Client interface { AddBinding(bindingName string, bindAsFiles bool, unstructuredService unstructured.Unstructured, obj parser.DevfileObj, componentContext string) (parser.DevfileObj, error) // GetServiceInstances returns a map of bindable instance name with its unstructured.Unstructured object, and an error GetServiceInstances() (map[string]unstructured.Unstructured, error) + + // ValidateRemoveBinding validates if the command has adequate arguments/flags + ValidateRemoveBinding(flags map[string]string) error + // RemoveBinding removes the binding from devfile + RemoveBinding(bindingName string, obj parser.DevfileObj) (parser.DevfileObj, error) } diff --git a/pkg/binding/mock.go b/pkg/binding/mock.go index c89d0cba24d..98530e28489 100644 --- a/pkg/binding/mock.go +++ b/pkg/binding/mock.go @@ -5,67 +5,79 @@ package binding import ( - reflect "reflect" - parser "github.com/devfile/library/pkg/devfile/parser" gomock "github.com/golang/mock/gomock" unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + reflect "reflect" ) -// MockClient is a mock of Client interface. +// MockClient is a mock of Client interface type MockClient struct { ctrl *gomock.Controller recorder *MockClientMockRecorder } -// MockClientMockRecorder is the mock recorder for MockClient. +// MockClientMockRecorder is the mock recorder for MockClient type MockClientMockRecorder struct { mock *MockClient } -// NewMockClient creates a new mock instance. +// NewMockClient creates a new mock instance func NewMockClient(ctrl *gomock.Controller) *MockClient { mock := &MockClient{ctrl: ctrl} mock.recorder = &MockClientMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use. +// EXPECT returns an object that allows the caller to indicate expected use func (m *MockClient) EXPECT() *MockClientMockRecorder { return m.recorder } -// AddBinding mocks base method. -func (m *MockClient) AddBinding(bindingName string, bindAsFiles bool, unstructuredService unstructured.Unstructured, obj parser.DevfileObj, componentContext string) (parser.DevfileObj, error) { +// GetFlags mocks base method +func (m *MockClient) GetFlags(flags map[string]string) map[string]string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddBinding", bindingName, bindAsFiles, unstructuredService, obj, componentContext) - ret0, _ := ret[0].(parser.DevfileObj) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret := m.ctrl.Call(m, "GetFlags", flags) + ret0, _ := ret[0].(map[string]string) + return ret0 } -// AddBinding indicates an expected call of AddBinding. -func (mr *MockClientMockRecorder) AddBinding(bindingName, bindAsFiles, unstructuredService, obj, componentContext interface{}) *gomock.Call { +// GetFlags indicates an expected call of GetFlags +func (mr *MockClientMockRecorder) GetFlags(flags interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBinding", reflect.TypeOf((*MockClient)(nil).AddBinding), bindingName, bindAsFiles, unstructuredService, obj, componentContext) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFlags", reflect.TypeOf((*MockClient)(nil).GetFlags), flags) } -// AskBindAsFiles mocks base method. -func (m *MockClient) AskBindAsFiles(flags map[string]string) (bool, error) { +// Validate mocks base method +func (m *MockClient) Validate(flags map[string]string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AskBindAsFiles", flags) - ret0, _ := ret[0].(bool) + ret := m.ctrl.Call(m, "Validate", flags) + ret0, _ := ret[0].(error) + return ret0 +} + +// Validate indicates an expected call of Validate +func (mr *MockClientMockRecorder) Validate(flags interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockClient)(nil).Validate), flags) +} + +// SelectServiceInstance mocks base method +func (m *MockClient) SelectServiceInstance(flags map[string]string, serviceMap map[string]unstructured.Unstructured) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SelectServiceInstance", flags, serviceMap) + ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } -// AskBindAsFiles indicates an expected call of AskBindAsFiles. -func (mr *MockClientMockRecorder) AskBindAsFiles(flags interface{}) *gomock.Call { +// SelectServiceInstance indicates an expected call of SelectServiceInstance +func (mr *MockClientMockRecorder) SelectServiceInstance(flags, serviceMap interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskBindAsFiles", reflect.TypeOf((*MockClient)(nil).AskBindAsFiles), flags) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectServiceInstance", reflect.TypeOf((*MockClient)(nil).SelectServiceInstance), flags, serviceMap) } -// AskBindingName mocks base method. +// AskBindingName mocks base method func (m *MockClient) AskBindingName(serviceName, componentName string, flags map[string]string) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AskBindingName", serviceName, componentName, flags) @@ -74,27 +86,43 @@ func (m *MockClient) AskBindingName(serviceName, componentName string, flags map return ret0, ret1 } -// AskBindingName indicates an expected call of AskBindingName. +// AskBindingName indicates an expected call of AskBindingName func (mr *MockClientMockRecorder) AskBindingName(serviceName, componentName, flags interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskBindingName", reflect.TypeOf((*MockClient)(nil).AskBindingName), serviceName, componentName, flags) } -// GetFlags mocks base method. -func (m *MockClient) GetFlags(flags map[string]string) map[string]string { +// AskBindAsFiles mocks base method +func (m *MockClient) AskBindAsFiles(flags map[string]string) (bool, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetFlags", flags) - ret0, _ := ret[0].(map[string]string) - return ret0 + ret := m.ctrl.Call(m, "AskBindAsFiles", flags) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 } -// GetFlags indicates an expected call of GetFlags. -func (mr *MockClientMockRecorder) GetFlags(flags interface{}) *gomock.Call { +// AskBindAsFiles indicates an expected call of AskBindAsFiles +func (mr *MockClientMockRecorder) AskBindAsFiles(flags interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFlags", reflect.TypeOf((*MockClient)(nil).GetFlags), flags) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskBindAsFiles", reflect.TypeOf((*MockClient)(nil).AskBindAsFiles), flags) } -// GetServiceInstances mocks base method. +// AddBinding mocks base method +func (m *MockClient) AddBinding(bindingName string, bindAsFiles bool, unstructuredService unstructured.Unstructured, obj parser.DevfileObj, componentContext string) (parser.DevfileObj, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddBinding", bindingName, bindAsFiles, unstructuredService, obj, componentContext) + ret0, _ := ret[0].(parser.DevfileObj) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddBinding indicates an expected call of AddBinding +func (mr *MockClientMockRecorder) AddBinding(bindingName, bindAsFiles, unstructuredService, obj, componentContext interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBinding", reflect.TypeOf((*MockClient)(nil).AddBinding), bindingName, bindAsFiles, unstructuredService, obj, componentContext) +} + +// GetServiceInstances mocks base method func (m *MockClient) GetServiceInstances() (map[string]unstructured.Unstructured, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetServiceInstances") @@ -103,37 +131,37 @@ func (m *MockClient) GetServiceInstances() (map[string]unstructured.Unstructured return ret0, ret1 } -// GetServiceInstances indicates an expected call of GetServiceInstances. +// GetServiceInstances indicates an expected call of GetServiceInstances func (mr *MockClientMockRecorder) GetServiceInstances() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceInstances", reflect.TypeOf((*MockClient)(nil).GetServiceInstances)) } -// SelectServiceInstance mocks base method. -func (m *MockClient) SelectServiceInstance(flags map[string]string, serviceMap map[string]unstructured.Unstructured) (string, error) { +// ValidateRemoveBinding mocks base method +func (m *MockClient) ValidateRemoveBinding(flags map[string]string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SelectServiceInstance", flags, serviceMap) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret := m.ctrl.Call(m, "ValidateRemoveBinding", flags) + ret0, _ := ret[0].(error) + return ret0 } -// SelectServiceInstance indicates an expected call of SelectServiceInstance. -func (mr *MockClientMockRecorder) SelectServiceInstance(flags, serviceMap interface{}) *gomock.Call { +// ValidateRemoveBinding indicates an expected call of ValidateRemoveBinding +func (mr *MockClientMockRecorder) ValidateRemoveBinding(flags interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectServiceInstance", reflect.TypeOf((*MockClient)(nil).SelectServiceInstance), flags, serviceMap) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRemoveBinding", reflect.TypeOf((*MockClient)(nil).ValidateRemoveBinding), flags) } -// Validate mocks base method. -func (m *MockClient) Validate(flags map[string]string) error { +// RemoveBinding mocks base method +func (m *MockClient) RemoveBinding(bindingName string, obj parser.DevfileObj) (parser.DevfileObj, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Validate", flags) - ret0, _ := ret[0].(error) - return ret0 + ret := m.ctrl.Call(m, "RemoveBinding", bindingName, obj) + ret0, _ := ret[0].(parser.DevfileObj) + ret1, _ := ret[1].(error) + return ret0, ret1 } -// Validate indicates an expected call of Validate. -func (mr *MockClientMockRecorder) Validate(flags interface{}) *gomock.Call { +// RemoveBinding indicates an expected call of RemoveBinding +func (mr *MockClientMockRecorder) RemoveBinding(bindingName, obj interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockClient)(nil).Validate), flags) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveBinding", reflect.TypeOf((*MockClient)(nil).RemoveBinding), bindingName, obj) } diff --git a/pkg/odo/cli/add/add.go b/pkg/odo/cli/add/add.go index 14c041d5333..5c227abcec6 100644 --- a/pkg/odo/cli/add/add.go +++ b/pkg/odo/cli/add/add.go @@ -7,10 +7,10 @@ import ( "github.com/redhat-developer/odo/pkg/odo/util" ) -// RecommendedCommandName is the recommended create command name +// RecommendedCommandName is the recommended add command name const RecommendedCommandName = "add" -// NewCmdDelete implements the delete odo command +// NewCmdAdd implements the odo add command func NewCmdAdd(name, fullName string) *cobra.Command { var createCmd = &cobra.Command{ Use: name, diff --git a/pkg/odo/cli/cli.go b/pkg/odo/cli/cli.go index 3aa8345d189..5e5e5d1bfe0 100644 --- a/pkg/odo/cli/cli.go +++ b/pkg/odo/cli/cli.go @@ -24,6 +24,7 @@ import ( "github.com/redhat-developer/odo/pkg/odo/cli/preference" "github.com/redhat-developer/odo/pkg/odo/cli/project" "github.com/redhat-developer/odo/pkg/odo/cli/registry" + "github.com/redhat-developer/odo/pkg/odo/cli/remove" "github.com/redhat-developer/odo/pkg/odo/cli/set" "github.com/redhat-developer/odo/pkg/odo/cli/telemetry" "github.com/redhat-developer/odo/pkg/odo/cli/utils" @@ -177,6 +178,7 @@ func odoRootCmd(name, fullName string) *cobra.Command { _init.NewCmdInit(_init.RecommendedCommandName, util.GetFullName(fullName, _init.RecommendedCommandName)), _delete.NewCmdDelete(_delete.RecommendedCommandName, util.GetFullName(fullName, _delete.RecommendedCommandName)), add.NewCmdAdd(add.RecommendedCommandName, util.GetFullName(fullName, add.RecommendedCommandName)), + remove.NewCmdRemove(remove.RecommendedCommandName, util.GetFullName(fullName, remove.RecommendedCommandName)), dev.NewCmdDev(dev.RecommendedCommandName, util.GetFullName(fullName, dev.RecommendedCommandName)), alizer.NewCmdAlizer(alizer.RecommendedCommandName, util.GetFullName(fullName, alizer.RecommendedCommandName)), describe.NewCmdDescribe(describe.RecommendedCommandName, util.GetFullName(fullName, describe.RecommendedCommandName)), diff --git a/pkg/odo/cli/remove/binding/binding.go b/pkg/odo/cli/remove/binding/binding.go new file mode 100644 index 00000000000..e57df20d3e5 --- /dev/null +++ b/pkg/odo/cli/remove/binding/binding.go @@ -0,0 +1,97 @@ +package binding + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + ktemplates "k8s.io/kubectl/pkg/util/templates" + + "github.com/redhat-developer/odo/pkg/binding/backend" + "github.com/redhat-developer/odo/pkg/log" + "github.com/redhat-developer/odo/pkg/odo/cmdline" + "github.com/redhat-developer/odo/pkg/odo/genericclioptions" + "github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset" +) + +// BindingRecommendedCommandName is the recommended binding sub-command name +const BindingRecommendedCommandName = "binding" + +var removeBindingExample = ktemplates.Examples(` +# Remove binding between service named 'myservice' and the component present in the working directory +%[1]s --name myservice + +`) + +type RemoveBindingOptions struct { + // Flags passed to the command + flags map[string]string + + // Context + *genericclioptions.Context + + // Clients + clientset *clientset.Clientset +} + +// NewRemoveBindingOptions returns new instance of ComponentOptions +func NewRemoveBindingOptions() *RemoveBindingOptions { + return &RemoveBindingOptions{} +} + +func (o *RemoveBindingOptions) SetClientset(clientset *clientset.Clientset) { + o.clientset = clientset +} + +func (o *RemoveBindingOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) { + o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile("")) + if err != nil { + return err + } + + // this ensures that the namespace as set in env.yaml is used + o.clientset.KubernetesClient.SetNamespace(o.GetProject()) + + o.flags = o.clientset.BindingClient.GetFlags(cmdline.GetFlags()) + + return nil +} + +func (o *RemoveBindingOptions) Validate() (err error) { + return o.clientset.BindingClient.ValidateRemoveBinding(o.flags) +} + +func (o *RemoveBindingOptions) Run(_ context.Context) error { + + devfileobj, err := o.clientset.BindingClient.RemoveBinding(o.flags[backend.FLAG_NAME], o.EnvSpecificInfo.GetDevfileObj()) + if err != nil { + return err + } + + err = devfileobj.WriteYamlDevfile() + if err != nil { + return err + } + log.Success("Successfully removed the binding from the devfile.") + return nil +} + +// NewCmdBinding implements the component odo sub-command +func NewCmdBinding(name, fullName string) *cobra.Command { + o := NewRemoveBindingOptions() + + var bindingCmd = &cobra.Command{ + Use: name, + Short: "Remove Binding", + Long: "Remove a binding between a service and the component from the devfile", + Args: cobra.NoArgs, + Example: fmt.Sprintf(removeBindingExample, fullName), + Run: func(cmd *cobra.Command, args []string) { + genericclioptions.GenericRun(o, cmd, args) + }, + } + bindingCmd.Flags().String(backend.FLAG_NAME, "", "Name of the Binding to create") + clientset.Add(bindingCmd, clientset.BINDING) + + return bindingCmd +} diff --git a/pkg/odo/cli/remove/remove.go b/pkg/odo/cli/remove/remove.go new file mode 100644 index 00000000000..81ee3f2a44c --- /dev/null +++ b/pkg/odo/cli/remove/remove.go @@ -0,0 +1,26 @@ +package remove + +import ( + "github.com/spf13/cobra" + + "github.com/redhat-developer/odo/pkg/odo/cli/remove/binding" + "github.com/redhat-developer/odo/pkg/odo/util" +) + +// RecommendedCommandName is the recommended remove command name +const RecommendedCommandName = "remove" + +// NewCmdDelete implements the odo remove command +func NewCmdRemove(name, fullName string) *cobra.Command { + var removeCmd = &cobra.Command{ + Use: name, + Short: "Remove resources from devfile", + } + + bindingCmd := binding.NewCmdBinding(binding.BindingRecommendedCommandName, util.GetFullName(fullName, binding.BindingRecommendedCommandName)) + removeCmd.AddCommand(bindingCmd) + removeCmd.Annotations = map[string]string{"command": "main"} + removeCmd.SetUsageTemplate(util.CmdUsageTemplate) + + return removeCmd +} diff --git a/tests/integration/devfile/cmd_remove_binding_test.go b/tests/integration/devfile/cmd_remove_binding_test.go new file mode 100644 index 00000000000..2bdb2033291 --- /dev/null +++ b/tests/integration/devfile/cmd_remove_binding_test.go @@ -0,0 +1,59 @@ +package devfile + +import ( + "fmt" + "path/filepath" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/redhat-developer/odo/tests/helper" +) + +var _ = Describe("odo remove binding command tests", func() { + var commonVar helper.CommonVar + + var _ = BeforeEach(func() { + if helper.IsKubernetesCluster() { + Skip("Operators have not been setup on Kubernetes cluster yet. Remove this once the issue has been fixed.") + } + commonVar = helper.CommonBeforeEach() + helper.Chdir(commonVar.Context) + // Ensure that the operators are installed + commonVar.CliRunner.EnsureOperatorIsInstalled("service-binding-operator") + commonVar.CliRunner.EnsureOperatorIsInstalled("cloud-native-postgresql") + Eventually(func() string { + out, _ := commonVar.CliRunner.GetBindableKinds() + return out + }, 120, 3).Should(ContainSubstring("Cluster")) + addBindableKind := commonVar.CliRunner.Run("apply", "-f", helper.GetExamplePath("manifests", "bindablekind-instance.yaml")) + Expect(addBindableKind.ExitCode()).To(BeEquivalentTo(0)) + }) + + // This is run after every Spec (It) + var _ = AfterEach(func() { + helper.CommonAfterEach(commonVar) + }) + When("the component with binding is bootstrapped", func() { + var bindingName string + BeforeEach(func() { + helper.Cmd("odo", "init", "--name", "mynode", "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile.yaml"), "--starter", "nodejs-starter").ShouldPass() + bindingName = fmt.Sprintf("binding-%s", helper.RandString(4)) + helper.Cmd("odo", "add", "binding", "--name", bindingName, "--service", "cluster-sample").ShouldPass() + }) + + When("removing the binding", func() { + BeforeEach(func() { + helper.Cmd("odo", "remove", "binding", "--name", bindingName) + }) + It("should successfully remove binding between component and service in the devfile", func() { + components := helper.GetDevfileComponents(filepath.Join(commonVar.Context, "devfile.yaml"), bindingName) + Expect(components).To(BeNil()) + }) + It("should fail to remove binding that does not exist", func() { + helper.Cmd("odo", "remove", "binding", "--name", "my-binding").ShouldFail() + }) + }) + + }) +})