diff --git a/Changelog.md b/Changelog.md index 9dbf7cd57..f35ea6bf5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,7 @@ - Added a column in odo list output to indicate components created by odo ([#4962](https://github.com/openshift/odo/pull/4962)) - Fix description for "odo link" command ([#4930](https://github.com/openshift/odo/pull/4930)) - Add deprecation warnings for s2i components([#4967](https://github.com/openshift/odo/issues/4967)) +- Dropping support for service catalog based services ([#4906](https://github.com/openshift/odo/pull/4906)) ### Bug Fixes diff --git a/Makefile b/Makefile index f15537ea5..eb2bba423 100644 --- a/Makefile +++ b/Makefile @@ -166,10 +166,6 @@ test-cmd-login-logout: ## Run odo login and logout tests test-cmd-link-unlink-4-cluster: ## Run link and unlink commnad tests against 4.x cluster $(RUN_GINKGO) $(GINKGO_FLAGS) -focus="odo link and unlink commnad tests" tests/integration/ -.PHONY: test-cmd-link-unlink-311-cluster -test-cmd-link-unlink-311-cluster: ## Run link and unlink command tests against 3.11 cluster - $(RUN_GINKGO) $(GINKGO_FLAGS) -focus="odo link and unlink command tests" tests/integration/servicecatalog/ - .PHONY: test-cmd-service test-cmd-service: ## Run odo service command tests $(RUN_GINKGO) $(GINKGO_FLAGS) -focus="odo service command tests" tests/integration/servicecatalog/ @@ -299,11 +295,6 @@ test-integration-devfile: ## Run devfile integration tests $(RUN_GINKGO) $(GINKGO_FLAGS) tests/integration/devfile/ $(RUN_GINKGO) $(GINKGO_FLAGS_SERIAL) tests/integration/devfile/debug/ -# Only service and link command tests are the part of this test run -.PHONY: test-integration-service-catalog -test-integration-service-catalog: ## Run command's integration tests which are dependent on service catalog enabled cluster. - $(RUN_GINKGO) $(GINKGO_FLAGS) tests/integration/servicecatalog/ - .PHONY: test-e2e-beta test-e2e-beta: ## Run core beta flow e2e tests $(RUN_GINKGO) $(GINKGO_FLAGS) -focus="odo core beta flow" tests/e2escenarios/ diff --git a/pkg/application/application.go b/pkg/application/application.go index 8ef23a547..4e1495732 100644 --- a/pkg/application/application.go +++ b/pkg/application/application.go @@ -8,7 +8,6 @@ import ( applabels "github.com/openshift/odo/pkg/application/labels" "github.com/openshift/odo/pkg/component" "github.com/openshift/odo/pkg/occlient" - "github.com/openshift/odo/pkg/service" "github.com/openshift/odo/pkg/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -86,21 +85,6 @@ func Delete(client *occlient.Client, name string) error { labels := applabels.GetLabels(name, false) - // first delete the services (ServiceInstance in OpenShift terminology) - // belonging to the app - svcList, err := service.List(client, name) - if err != nil { - // error is returned when there's no Service Catalog enabled in the service - klog.V(4).Infof("Service catalog is not enabled in the cluster, skipping service deletion") - } else { - for _, svc := range svcList.Items { - err = service.DeleteServiceAndUnlinkComponents(client, svc.Name, name) - if err != nil { - return errors.Wrapf(err, "unable to delete the application %s due to failure in deleting service(s) in the application", name) - } - } - } - supported, err := client.IsDeploymentConfigSupported() if err != nil { return err diff --git a/pkg/application/doc.go b/pkg/application/doc.go index 343c04a7f..6012c0ca4 100644 --- a/pkg/application/doc.go +++ b/pkg/application/doc.go @@ -1,5 +1,5 @@ // Package application provides functions to list, check existence of, delete and get machine readable description of applications. // An application is a set of components and services. // An application is materialized by the `app:` label in `deployments`, `deploymentconfigs`, -// or service instances (service instances from Service Catalog or from Operator Backed Services). +// or service instances (service instances from Operator Backed Services). package application diff --git a/pkg/odo/cli/application/application.go b/pkg/odo/cli/application/application.go index 46eb41653..1ba5e2a05 100644 --- a/pkg/odo/cli/application/application.go +++ b/pkg/odo/cli/application/application.go @@ -4,17 +4,14 @@ import ( "fmt" applabels "github.com/openshift/odo/pkg/application/labels" + "github.com/openshift/odo/pkg/component" "github.com/openshift/odo/pkg/kclient" "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/occlient" "github.com/openshift/odo/pkg/odo/genericclioptions" - "github.com/pkg/errors" - "k8s.io/klog" - - "github.com/openshift/odo/pkg/component" odoutil "github.com/openshift/odo/pkg/odo/util" "github.com/openshift/odo/pkg/odo/util/completion" - "github.com/openshift/odo/pkg/service" + "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -87,18 +84,5 @@ func printAppInfo(client *occlient.Client, kClient *kclient.Client, appName stri } } } - // List services that will be removed - serviceList, err := service.List(client, appName) - if err != nil { - log.Info("No services / could not get services") - klog.V(4).Info(err.Error()) - } - if len(serviceList.Items) != 0 { - log.Info("This application has following service(s) that will be deleted") - for _, ser := range serviceList.Items { - log.Info("service named", ser.ObjectMeta.Name, "of type", ser.Spec.Type) - } - } - return nil } diff --git a/pkg/odo/cli/catalog/describe/service.go b/pkg/odo/cli/catalog/describe/service.go index c399ecfd6..4c84e0485 100644 --- a/pkg/odo/cli/catalog/describe/service.go +++ b/pkg/odo/cli/catalog/describe/service.go @@ -5,7 +5,6 @@ import ( "github.com/openshift/odo/pkg/odo/genericclioptions" "github.com/openshift/odo/pkg/service" - svc "github.com/openshift/odo/pkg/service" "github.com/spf13/cobra" ktemplates "k8s.io/kubectl/pkg/util/templates" ) @@ -13,29 +12,18 @@ import ( const serviceRecommendedCommandName = "service" var ( - serviceExample = ktemplates.Examples(` # Describe a service catalog service - %[1]s mysql-persistent - - # Describe a Operator backed service + serviceExample = ktemplates.Examples(`# Describe a Operator backed service %[1]s `) serviceLongDesc = ktemplates.LongDesc(`Describes a service type. - This command supports both Service Catalog services and Operator backed services. + This command supports Operator backed services. A user can describe an Operator backed service by providing the full identifier for an Operand i.e. / which they can find by running "odo catalog list services". - - If the format doesn't match / then service catalog services would be searched. - `) ) // DescribeServiceOptions encapsulates the options for the odo catalog describe service command type DescribeServiceOptions struct { - // name of the service to describe, from command arguments - serviceName string - // resolved service - service svc.ServiceClass - plans []svc.ServicePlan // generic context options common to all commands *genericclioptions.Context @@ -59,7 +47,7 @@ func (o *DescribeServiceOptions) Complete(name string, cmd *cobra.Command, args if _, _, err := service.SplitServiceKindName(args[0]); err == nil { o.backend = NewOperatorBackend() } else { - o.backend = NewServiceCatalogBackend() + return fmt.Errorf("no deployable operators found") } return o.backend.CompleteDescribeService(o, args) diff --git a/pkg/odo/cli/catalog/describe/service_catalog_backend.go b/pkg/odo/cli/catalog/describe/service_catalog_backend.go deleted file mode 100644 index 10d24790d..000000000 --- a/pkg/odo/cli/catalog/describe/service_catalog_backend.go +++ /dev/null @@ -1,99 +0,0 @@ -package describe - -import ( - "fmt" - "os" - "strings" - - "github.com/olekukonko/tablewriter" - svc "github.com/openshift/odo/pkg/service" -) - -type serviceCatalogBackend struct { -} - -func NewServiceCatalogBackend() *serviceCatalogBackend { - return &serviceCatalogBackend{} -} - -func (ohb *serviceCatalogBackend) CompleteDescribeService(dso *DescribeServiceOptions, args []string) error { - dso.serviceName = args[0] - return nil -} - -func (ohb *serviceCatalogBackend) ValidateDescribeService(dso *DescribeServiceOptions) error { - var err error - dso.service, dso.plans, err = svc.GetServiceClassAndPlans(dso.Client, dso.serviceName) - return err -} - -func (ohb *serviceCatalogBackend) RunDescribeService(dso *DescribeServiceOptions) error { - table := tablewriter.NewWriter(os.Stdout) - table.SetBorder(false) - table.SetAlignment(tablewriter.ALIGN_LEFT) - - serviceData := [][]string{ - {"Name", dso.service.Name}, - {"Bindable", fmt.Sprint(dso.service.Bindable)}, - {"Operated by the broker", dso.service.ServiceBrokerName}, - {"Short Description", dso.service.ShortDescription}, - {"Long Description", dso.service.LongDescription}, - {"Versions Available", strings.Join(dso.service.VersionsAvailable, ",")}, - {"Tags", strings.Join(dso.service.Tags, ",")}, - } - - table.AppendBulk(serviceData) - - table.Append([]string{""}) - - if len(dso.plans) > 0 { - table.Append([]string{"PLANS"}) - - for _, plan := range dso.plans { - - // create the display values for required and optional parameters - requiredWithMandatoryUserInputParameterNames := []string{} - requiredWithOptionalUserInputParameterNames := []string{} - optionalParameterDisplay := []string{} - for _, parameter := range plan.Parameters { - if parameter.Required { - // until we have a better solution for displaying the plan data (like a separate table perhaps) - // this is simplest thing to do - if len(parameter.Default) > 0 { - requiredWithOptionalUserInputParameterNames = append( - requiredWithOptionalUserInputParameterNames, - fmt.Sprintf("%s (default: '%s')", parameter.Name, parameter.Default)) - } else { - requiredWithMandatoryUserInputParameterNames = append(requiredWithMandatoryUserInputParameterNames, parameter.Name) - } - - } else { - optionalParameterDisplay = append(optionalParameterDisplay, parameter.Name) - } - } - - table.Append([]string{"***********************", "*****************************************************"}) - planLineSeparator := []string{"-----------------", "-----------------"} - - planData := [][]string{ - {"Name", plan.Name}, - planLineSeparator, - {"Display Name", plan.DisplayName}, - planLineSeparator, - {"Short Description", plan.Description}, - planLineSeparator, - {"Required Params without a default value", strings.Join(requiredWithMandatoryUserInputParameterNames, ", ")}, - planLineSeparator, - {"Required Params with a default value", strings.Join(requiredWithOptionalUserInputParameterNames, ", ")}, - planLineSeparator, - {"Optional Params", strings.Join(optionalParameterDisplay, ", ")}, - {"", ""}, - } - table.AppendBulk(planData) - } - table.Render() - } else { - return fmt.Errorf("no plans found for service %s", dso.serviceName) - } - return nil -} diff --git a/pkg/odo/cli/catalog/list/services.go b/pkg/odo/cli/catalog/list/services.go index 9b0f4a893..f3f3f8ca9 100644 --- a/pkg/odo/cli/catalog/list/services.go +++ b/pkg/odo/cli/catalog/list/services.go @@ -17,13 +17,11 @@ import ( const servicesRecommendedCommandName = "services" -var servicesExample = ` # Get the supported services from service catalog +var servicesExample = ` # Get the supported services %[1]s` // ServiceOptions encapsulates the options for the odo catalog list services command type ServiceOptions struct { - // list of known services - services catalog.ServiceTypeList // list of clusterserviceversions (installed by Operators) csvs *olm.ClusterServiceVersionList // generic context options common to all commands @@ -49,11 +47,6 @@ func (o *ServiceOptions) Complete(name string, cmd *cobra.Command, args []string return err } } - - o.services, _ = catalog.ListSvcCatServices(o.Client) - - o.services = util.FilterHiddenServices(o.services) - return } @@ -65,20 +58,16 @@ func (o *ServiceOptions) Validate() (err error) { // Run contains the logic for the command associated with ListServicesOptions func (o *ServiceOptions) Run(cmd *cobra.Command) (err error) { if log.IsJSON() { - machineoutput.OutputSuccess(newCatalogListOutput(&o.services, o.csvs)) + machineoutput.OutputSuccess(newCatalogListOutput(o.csvs)) } else { - if len(o.csvs.Items) == 0 && len(o.services.Items) == 0 { - log.Info("no deployable services/operators found") + if len(o.csvs.Items) == 0 { + log.Info("no deployable operators found") return } if len(o.csvs.Items) > 0 { util.DisplayClusterServiceVersions(o.csvs) } - - if len(o.services.Items) > 0 { - util.DisplayServices(o.services) - } } return } @@ -102,18 +91,16 @@ func NewCmdCatalogListServices(name, fullName string) *cobra.Command { type catalogListOutput struct { v1.TypeMeta `json:",inline"` v1.ObjectMeta `json:"metadata,omitempty"` - Services *catalog.ServiceTypeList `json:"services,omitempty"` // list of clusterserviceversions (installed by Operators) Operators *olm.ClusterServiceVersionList `json:"operators,omitempty"` } -func newCatalogListOutput(services *catalog.ServiceTypeList, operators *olm.ClusterServiceVersionList) catalogListOutput { +func newCatalogListOutput(operators *olm.ClusterServiceVersionList) catalogListOutput { return catalogListOutput{ TypeMeta: v1.TypeMeta{ Kind: "List", APIVersion: machineoutput.APIVersion, }, - Services: services, Operators: operators, } } diff --git a/pkg/odo/cli/catalog/search/service.go b/pkg/odo/cli/catalog/search/service.go index ca11a9e96..e864fa694 100644 --- a/pkg/odo/cli/catalog/search/service.go +++ b/pkg/odo/cli/catalog/search/service.go @@ -96,7 +96,7 @@ func NewCmdCatalogSearchService(name, fullName string) *cobra.Command { Long: `Search service type in catalog. This searches for a partial match for the given search term in all the available -services from service catalog. +services from operator hub services. `, Example: fmt.Sprintf(serviceExample, fullName), Args: cobra.ExactArgs(1), diff --git a/pkg/odo/cli/catalog/util/util_test.go b/pkg/odo/cli/catalog/util/util_test.go deleted file mode 100644 index 71f2d08c6..000000000 --- a/pkg/odo/cli/catalog/util/util_test.go +++ /dev/null @@ -1,184 +0,0 @@ -package util - -import ( - "reflect" - "testing" - - "github.com/openshift/odo/pkg/catalog" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestFilterHiddenServices(t *testing.T) { - tests := []struct { - name string - input catalog.ServiceTypeList - expected catalog.ServiceTypeList - }{ - /* - This test is not needed.. Also fails using DeepEqual anyways.. - --- FAIL: TestFilterHiddenServices/Case_1:_empty_input (0.00s) - util_test.go:101: got: [], wanted: [] - { - name: "Case 1: empty input", - input: catalog.ServiceTypeList{}, - expected: catalog.ServiceTypeList{}, - }, - */ - { - name: "Case 2: non empty input", - input: catalog.ServiceTypeList{ - TypeMeta: metav1.TypeMeta{ - Kind: "List", - APIVersion: "odo.dev/v1alpha1", - }, - ListMeta: metav1.ListMeta{}, - Items: []catalog.ServiceType{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "n1", - }, - Spec: catalog.ServiceSpec{ - Hidden: true, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "n2", - }, - Spec: catalog.ServiceSpec{ - Hidden: false, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "n3", - }, - Spec: catalog.ServiceSpec{ - Hidden: true, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "n4", - }, - Spec: catalog.ServiceSpec{ - Hidden: false, - }, - }, - }, - }, - expected: catalog.ServiceTypeList{ - TypeMeta: metav1.TypeMeta{ - Kind: "List", - APIVersion: "odo.dev/v1alpha1", - }, - ListMeta: metav1.ListMeta{}, - Items: []catalog.ServiceType{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "n2", - }, - Spec: catalog.ServiceSpec{ - Hidden: false, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "n4", - }, - Spec: catalog.ServiceSpec{ - Hidden: false, - }, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - output := FilterHiddenServices(tt.input) - if !reflect.DeepEqual(tt.expected, output) { - t.Errorf("got: %+v, wanted: %+v", output.Items, tt.expected.Items) - } - }) - } -} - -func TestFilterHiddenComponents(t *testing.T) { - tests := []struct { - name string - input []catalog.ComponentType - expected []catalog.ComponentType - }{ - { - name: "Case 1: empty input", - input: []catalog.ComponentType{}, - expected: []catalog.ComponentType{}, - }, - { - name: "Case 2: non empty input", - input: []catalog.ComponentType{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "n1", - }, - Spec: catalog.ComponentSpec{ - NonHiddenTags: []string{"1", "latest"}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "n2", - }, - Spec: catalog.ComponentSpec{ - NonHiddenTags: []string{}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "n3", - }, - Spec: catalog.ComponentSpec{ - NonHiddenTags: []string{}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "n4", - }, - Spec: catalog.ComponentSpec{ - NonHiddenTags: []string{"10"}, - }, - }, - }, - expected: []catalog.ComponentType{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "n1", - }, - Spec: catalog.ComponentSpec{ - NonHiddenTags: []string{"1", "latest"}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "n4", - }, - Spec: catalog.ComponentSpec{ - NonHiddenTags: []string{"10"}, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - output := FilterHiddenComponents(tt.input) - if !reflect.DeepEqual(tt.expected, output) { - t.Errorf("got: %+v, wanted: %+v", output, tt.expected) - } - }) - } -} diff --git a/pkg/odo/cli/component/link.go b/pkg/odo/cli/component/link.go index 662f01635..d59544d0b 100644 --- a/pkg/odo/cli/component/link.go +++ b/pkg/odo/cli/component/link.go @@ -12,7 +12,6 @@ import ( projectCmd "github.com/openshift/odo/pkg/odo/cli/project" "github.com/openshift/odo/pkg/odo/genericclioptions" odoutil "github.com/openshift/odo/pkg/odo/util" - "github.com/openshift/odo/pkg/odo/util/completion" svc "github.com/openshift/odo/pkg/service" ktemplates "k8s.io/kubectl/pkg/util/templates" @@ -44,7 +43,7 @@ var ( # and make the secrets accessible as files in the '/bindings/etcd/' directory %[1]s EtcdCluster/myetcd --bind-as-files --name etcd`) - linkLongDesc = `Link component to a service (backed by an Operator or Service Catalog) or component + linkLongDesc = `Link component to a service (backed by an Operator) or component If the source component is not provided, the current active component is assumed. In both use cases, link adds the appropriate secret to the environment of the source component. @@ -72,14 +71,7 @@ odo link backend --component frontend Now the frontend has 2 ENV variables it can use: COMPONENT_BACKEND_HOST=backend-app COMPONENT_BACKEND_PORT=8080 - -If you wish to use a database, we can use the Service Catalog and link it to our backend: -odo service create dh-postgresql-apb --plan dev -p postgresql_user=luke -p postgresql_password=secret -odo link dh-postgresql-apb - -Now backend has 2 ENV variables it can use: -DB_USER=luke -DB_PASSWORD=secret` +` ) // LinkOptions encapsulates the options for the odo link command @@ -182,7 +174,5 @@ func NewCmdLink(name, fullName string) *cobra.Command { //Adding context flag genericclioptions.AddContextFlag(linkCmd, &o.componentContext) - completion.RegisterCommandHandler(linkCmd, completion.LinkCompletionHandler) - return linkCmd } diff --git a/pkg/odo/cli/component/unlink.go b/pkg/odo/cli/component/unlink.go index 25ddbc8b6..af69134e8 100644 --- a/pkg/odo/cli/component/unlink.go +++ b/pkg/odo/cli/component/unlink.go @@ -7,7 +7,6 @@ import ( appCmd "github.com/openshift/odo/pkg/odo/cli/application" projectCmd "github.com/openshift/odo/pkg/odo/cli/project" - "github.com/openshift/odo/pkg/odo/util/completion" svc "github.com/openshift/odo/pkg/service" "github.com/openshift/odo/pkg/odo/util" @@ -112,7 +111,5 @@ func NewCmdUnlink(name, fullName string) *cobra.Command { // Adding context flag genericclioptions.AddContextFlag(unlinkCmd, &o.componentContext) - completion.RegisterCommandHandler(unlinkCmd, completion.UnlinkCompletionHandler) - return unlinkCmd } diff --git a/pkg/odo/cli/service/create.go b/pkg/odo/cli/service/create.go index 245c5f83f..cd7b51962 100644 --- a/pkg/odo/cli/service/create.go +++ b/pkg/odo/cli/service/create.go @@ -1,44 +1,29 @@ package service import ( - "bytes" "errors" "fmt" - "strings" - "text/template" - "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/odo/cli/component" - "github.com/openshift/odo/pkg/odo/cli/service/ui" "github.com/openshift/odo/pkg/odo/genericclioptions" - "github.com/openshift/odo/pkg/odo/util/completion" "github.com/spf13/cobra" ktemplates "k8s.io/kubectl/pkg/util/templates" + "strings" ) const ( createRecommendedCommandName = "create" - equivalentTemplate = "{{.CmdFullName}} {{.ServiceType}}" + - "{{if .ServiceName}} {{.ServiceName}}{{end}}" + - " --app {{.Application}}" + - " --project {{.Project}}" + - "{{if .Plan}} --plan {{.Plan}}{{end}}" + - "{{range $key, $value := .ParametersMap}} -p {{$key}}={{$value}}{{end}}" ) var ( - createExample = ktemplates.Examples(` - # Create new postgresql service from service catalog using dev plan and name my-postgresql-db. - %[1]s dh-postgresql-apb my-postgresql-db --plan dev -p postgresql_user=luke -p postgresql_password=secret`) - createOperatorExample = ktemplates.Examples(` # Create new EtcdCluster service from etcdoperator.v0.9.4 operator. %[1]s etcdoperator.v0.9.4/EtcdCluster`) - createShortDesc = `Create a new service from Operator Hub or Service Catalog and deploy it on OpenShift.` + createShortDesc = `Create a new service from Operator Hub and deploy it on Kubernetes or OpenShift.` createLongDesc = ktemplates.LongDesc(` -Create a new service from Operator Hub or Service Catalog and deploy it on OpenShift. +Create a new service from Operator Hub and deploy it on Kubernetes or OpenShift. Service creation can be performed from a valid component directory (one containing a devfile.yaml) only. @@ -46,8 +31,6 @@ To create the service from outside a component directory, specify path to a vali When creating a service using Operator Hub, provide a service name along with Operator name. -When creating a service using Service Catalog, a --plan must be passed along with the service type. Parameters to configure the service are passed as key=value pairs. - For a full list of service types, use: 'odo catalog list services'`) ) @@ -55,8 +38,6 @@ For a full list of service types, use: 'odo catalog list services'`) type CreateOptions struct { // parameters hold the user-provided values for service class parameters via flags (populated by cobra) parameters []string - // Plan is the selected service plan - Plan string // ServiceType corresponds to the service class name ServiceType string // ServiceName is how the service will be named and known by odo @@ -65,8 +46,6 @@ type CreateOptions struct { ParametersMap map[string]string // interactive specifies whether the command operates in interactive mode or not interactive bool - // outputCLI specifies whether to output the non-interactive version of the command or not - outputCLI bool // CmdFullName records the command's full name CmdFullName string // whether or not to wait for the service to be ready @@ -79,7 +58,7 @@ type CreateOptions struct { DryRun bool // Location of the file in which yaml specification of CR is stored. fromFile string - // Backend is the service provider backend (Operator Hub or Service Catalog) providing the service requested by the user + // Backend is the service provider backend providing the service requested by the user Backend ServiceProviderBackend } @@ -114,36 +93,17 @@ func (o *CreateOptions) Complete(name string, cmd *cobra.Command, args []string) if err != nil { return err } - - // decide which service backend to use - if o.fromFile != "" { - // fromFile is supported only for Operator backend - o.Backend = NewOperatorBackend() - // since interactive mode is not supported for Operators yet, set it to false - o.interactive = false - - return o.Backend.CompleteServiceCreate(o, cmd, args) + //if no args are provided and if request is not from file, user wants interactive mode + if o.fromFile == "" && len(args) == 0 { + return fmt.Errorf("odo doesn't support interactive mode for creating Operator backed service") } - - // check if interactive mode is requested - if len(args) == 0 { - o.interactive = true - // only Service Catalog backend supports interactive mode for service creation - o.Backend = NewServiceCatalogBackend() - } else { - o.Backend = decideBackend(args[0]) - } - + o.Backend = NewOperatorBackend() + o.interactive = false return o.Backend.CompleteServiceCreate(o, cmd, args) } // Validate validates the CreateOptions based on completed values func (o *CreateOptions) Validate() (err error) { - // if we are in interactive mode, all values are already valid - if o.interactive { - return nil - } - return o.Backend.ValidateServiceCreate(o) } @@ -159,54 +119,30 @@ func (o *CreateOptions) Run(cmd *cobra.Command) (err error) { log.Info("Successfully added service to the configuration; do 'odo push' to create service on the cluster") } - equivalent := o.outputNonInteractiveEquivalent() - if len(equivalent) > 0 { - log.Info("Equivalent command:\n" + ui.StyledOutput(equivalent, "cyan")) - } return } -// outputNonInteractiveEquivalent outputs the populated options as the equivalent command that would be used in non-interactive mode -func (o *CreateOptions) outputNonInteractiveEquivalent() string { - if o.outputCLI { - var tpl bytes.Buffer - t := template.Must(template.New("service-create-cli").Parse(equivalentTemplate)) - e := t.Execute(&tpl, o) - if e != nil { - panic(e) // shouldn't happen - } - return strings.TrimSpace(tpl.String()) - } - return "" -} - // NewCmdServiceCreate implements the odo service create command. func NewCmdServiceCreate(name, fullName string) *cobra.Command { o := NewCreateOptions() o.CmdFullName = fullName serviceCreateCmd := &cobra.Command{ - Use: name + " --plan [service_name]", + Use: name + " / [service_name] [flags]", Short: createShortDesc, Long: createLongDesc, - Example: fmt.Sprintf(createExample, fullName), + Example: fmt.Sprintf(createOperatorExample, fullName), Args: cobra.RangeArgs(0, 2), Run: func(cmd *cobra.Command, args []string) { genericclioptions.GenericRun(o, cmd, args) }, } - serviceCreateCmd.Use += fmt.Sprintf(" [flags]\n %s / [service_name] [flags]", o.CmdFullName) - serviceCreateCmd.Example += "\n\n" + fmt.Sprintf(createOperatorExample, fullName) serviceCreateCmd.Flags().BoolVar(&o.DryRun, "dry-run", false, "Print the yaml specificiation that will be used to create the operator backed service") // remove this feature after enabling service create interactive mode for operator backed services serviceCreateCmd.Flags().StringVar(&o.fromFile, "from-file", "", "Path to the file containing yaml specification to use to start operator backed service") - serviceCreateCmd.Flags().StringVar(&o.Plan, "plan", "", "The name of the plan of the service to be created") serviceCreateCmd.Flags().StringArrayVarP(&o.parameters, "parameters", "p", []string{}, "Parameters of the plan where a parameter is expressed as == 0 && len(pageEntries) > index { - selected := pageEntries[index] - class := classes[selected] - return ansi.ColorCode("default+bu") + "Service class details" + ansi.ColorCode("reset") + ":\n" + - classInfoItem("Name", class.GetExternalName()) + - classInfoItem("Description", class.GetDescription()) + - classInfoItem("Long", getLongDescription(class)) - } - return "No matching entry" - } - defer delete(core.TemplateFuncs, displayClassInfo) - - // record original template and defer restoring it once done - original := survey.SelectQuestionTemplate - defer restoreOriginalTemplate(original) - - // add more information about the currently selected class - survey.SelectQuestionTemplate = original + ` - {{- if not .ShowAnswer}} - {{$classInfo:=(displayClassInfo .SelectedIndex .PageEntries)}} - {{- if $classInfo}} -{{$classInfo}} - {{- end}} - {{- end}}` - - prompt = &survey.Select{ - Message: "Which " + category + " service class should we use", - Options: getServiceClassNames(classes), - } - - err = survey.AskOne(prompt, &serviceType, survey.Required) - ui.HandleError(err) - - return classes[serviceType], serviceType -} - -// classInfoItem computes how a given service class information item should be displayed -func classInfoItem(name, value string) string { - // wrap value if needed accounting for size of value "header" (its name) - value = wrapIfNeeded(value, len(name)+3) - - if len(value) > 0 { - // display the name using the default color, in bold and then reset style right after - return StyledOutput(name, "default+b") + ": " + value + "\n" - } - return "" -} - -// StyledOutput returns an ANSI color code to style the specified text accordingly, issuing a reset code when done using the -// https://github.com/mgutz/ansi#style-format format -func StyledOutput(text, style string) string { - return ansi.ColorCode(style) + text + ansi.ColorCode("reset") -} - -const defaultColumnNumberBeforeWrap = 80 - -// wrapIfNeeded wraps the given string taking the given prefix size into account based on the width of the terminal (or -// defaultColumnNumberBeforeWrap if terminal size cannot be determined). -func wrapIfNeeded(value string, prefixSize int) string { - // get the width of the terminal - width, _, err := terminal2.GetSize(0) - if width == 0 || err != nil { - // if for some reason we couldn't get the size use default value - width = defaultColumnNumberBeforeWrap - } - - // if the value length is greater than the width, wrap it - // note that we need to account for the size of the name of the value being displayed before the value (i.e. its name) - valueSize := len(value) - if valueSize+prefixSize >= width { - // look at each line of the value - split := strings.Split(value, "\n") - for index, line := range split { - // for each line, trim it and split it in space-separated clusters ("words") - line = strings.TrimSpace(line) - words := strings.Split(line, " ") - newLine := "" - lineSize := 0 - - for _, word := range words { - if lineSize+len(word)+1+prefixSize < width { - // concatenate word to the new computed line only if adding it to the line won't make it larger than acceptable - newLine = newLine + " " + word - lineSize = lineSize + 1 + len(word) // accumulate the line size - } else { - // otherwise, break the line and add the word on a new "line" - newLine = newLine + "\n" + word - lineSize = len(word) // reset the line size - } - } - // replace the initial line with the new computed version - split[index] = strings.TrimSpace(newLine) - } - // compute the new value by joining all the modified lines - value = strings.Join(split, "\n") - } - return value -} - -// restoreOriginalTemplate restores the original survey template once we're done with the display -func restoreOriginalTemplate(original string) { - survey.SelectQuestionTemplate = original -} - -// Convert the provided ClusterServiceClass to its UI representation -func getLongDescription(class scv1beta1.ClusterServiceClass) (longDescription string) { - extension := class.Spec.ExternalMetadata - if extension != nil { - var meta map[string]interface{} - err := json.Unmarshal(extension.Raw, &meta) - if err != nil { - klog.V(4).Infof("Unable unmarshal Extension metadata for ClusterServiceClass '%v'", class.Spec.ExternalName) - } - if val, ok := meta["longDescription"]; ok { - longDescription = val.(string) - } - } - - return -} - -// EnterServicePropertiesInteractively lets the user enter the properties specified by the provided plan if not already -// specified by the passed values -func EnterServicePropertiesInteractively(svcPlan scv1beta1.ClusterServicePlan) (values map[string]string) { - return enterServicePropertiesInteractively(svcPlan) -} - -// enterServicePropertiesInteractively lets user enter the properties interactively using the specified Stdio instance (useful -// for testing purposes) -func enterServicePropertiesInteractively(svcPlan scv1beta1.ClusterServicePlan, stdio ...terminal.Stdio) (values map[string]string) { - planDetails, _ := service.NewServicePlan(svcPlan) - - properties := make(map[string]service.ServicePlanParameter, len(planDetails.Parameters)) - for _, v := range planDetails.Parameters { - properties[v.Name] = v - } - - values = make(map[string]string, len(properties)) - - sort.Sort(planDetails.Parameters) - - // first deal with required properties - for _, prop := range planDetails.Parameters { - if prop.Required { - addValueFor(prop, values, stdio...) - // remove property from list of properties to consider - delete(properties, prop.Name) - } - } - - // finally check if we still have plan properties that have not been considered - if len(properties) > 0 && ui.Proceed("Provide values for non-required properties", stdio...) { - for _, prop := range properties { - addValueFor(prop, values, stdio...) - } - } - - return values -} - -func addValueFor(prop service.ServicePlanParameter, values map[string]string, stdio ...terminal.Stdio) { - var result string - prompt := &survey.Input{ - Message: fmt.Sprintf("Enter a value for %s property %s:", prop.Type, propDesc(prop)), - } - - if len(stdio) == 1 { - prompt.WithStdio(stdio[0]) - } - - if len(prop.Default) > 0 { - prompt.Default = prop.Default - } - - err := survey.AskOne(prompt, &result, ui.GetValidatorFor(prop.AsValidatable())) - ui.HandleError(err) - values[prop.Name] = result -} - -// propDesc computes a human-readable description of the specified property -func propDesc(prop service.ServicePlanParameter) string { - msg := "" - if len(prop.Title) > 0 { - msg = prop.Title - } else if len(prop.Description) > 0 { - msg = prop.Description - } - - if len(msg) > 0 { - msg = " (" + strings.TrimSpace(msg) + ")" - } - - return prop.Name + msg -} diff --git a/pkg/odo/cli/service/ui/ui_test.go b/pkg/odo/cli/service/ui/ui_test.go deleted file mode 100644 index 76a3c43d1..000000000 --- a/pkg/odo/cli/service/ui/ui_test.go +++ /dev/null @@ -1,238 +0,0 @@ -package ui - -import ( - "reflect" - "testing" - - "github.com/Netflix/go-expect" - beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1" - "github.com/openshift/odo/pkg/service" - "github.com/openshift/odo/pkg/testingutil" - "github.com/stretchr/testify/require" - "gopkg.in/AlecAivazis/survey.v1/core" - "gopkg.in/AlecAivazis/survey.v1/terminal" -) - -func init() { - // disable color output for all prompts to simplify testing - core.DisableColor = true -} - -func TestGetCategories(t *testing.T) { - t.Run("getServiceClassesCategories should work", func(t *testing.T) { - foo := testingutil.FakeClusterServiceClass("foo", "footag", "footag2") - bar := testingutil.FakeClusterServiceClass("bar", "") - boo := testingutil.FakeClusterServiceClass("boo") - classes := map[string][]beta1.ClusterServiceClass{"footag": {foo}, "other": {bar, boo}} - - categories := getServiceClassesCategories(classes) - expected := []string{"footag", "other"} - if !reflect.DeepEqual(expected, categories) { - t.Errorf("test failed, expected %v, got %v", expected, categories) - } - }) -} - -func TestGetServicePlanNames(t *testing.T) { - t.Run("GetServicePlanNames should work", func(t *testing.T) { - foo := testingutil.FakeClusterServicePlan("foo", 1) - bar := testingutil.FakeClusterServicePlan("bar", 2) - boo := testingutil.FakeClusterServicePlan("boo", 3) - - plans := GetServicePlanNames(map[string]beta1.ClusterServicePlan{"foo": foo, "bar": bar, "boo": boo}) - expected := []string{"bar", "boo", "foo"} - if !reflect.DeepEqual(expected, plans) { - t.Errorf("test failed, expected %v, got %v", expected, plans) - } - }) -} - -func TestWrapIfNeeded(t *testing.T) { - tests := []struct { - name string - input string - prefixSize int - expected string - }{ - { - name: "empty string, empty prefix", - input: "", - prefixSize: 0, - expected: "", - }, - { - name: "short string, empty prefix should not be wrapped", - input: "foo bar baz", - prefixSize: 0, - expected: "foo bar baz", - }, - { - name: "short string, empty prefix should not be wrapped with default width", - input: "foo bar baz", - prefixSize: 2, - expected: "foo bar baz", - }, - { - name: "short string, long prefix should wrap", - input: "foo bar baz", - prefixSize: 78, - expected: "foo\nbar\nbaz", - }, - { - name: "long string, empty prefix should wrap", - input: "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789", - prefixSize: 0, - expected: "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789\n0123456789", - }, - { - name: "long string, short prefix should wrap", - input: "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789", - prefixSize: 2, - expected: "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789\n0123456789", - }, - { - name: "long string, longer prefix should wrap more", - input: "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789", - prefixSize: 10, - expected: "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789\n0123456789 0123456789", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - output := wrapIfNeeded(tt.input, tt.prefixSize) - if tt.expected != output { - t.Errorf("test failed, expected %s, got %s", tt.expected, output) - } - }) - } -} - -func init() { - // disable color output for all prompts to simplify testing - core.DisableColor = true -} - -func TestEnterServicePropertiesInteractively(t *testing.T) { - // TODO: this test is currently skipped because it is not currently working properly. :( - t.Skip("TODO: Skip this test until we can figure out what is wrong with it") - - tests := []struct { - name string - servicePlan beta1.ClusterServicePlan - expectedValues map[string]string - }{ - { - name: "test 1 : with correct values", - servicePlan: testingutil.FakeClusterServicePlan("dev", 1), - expectedValues: map[string]string{ - "PLAN_DATABASE_URI": "someuri", - "PLAN_DATABASE_USERNAME": "default", - "PLAN_DATABASE_PASSWORD": "foo", - }, - }, - } - - for _, tt := range tests { - plan := tt.servicePlan - - valuesPtr := new(map[string]string) - testingutil.RunTest(t, func(c *expect.Console) { - _, _ = c.ExpectString("Enter a value for string property PLAN_DATABASE_PASSWORD:") - _, _ = c.SendLine("foo") - _, _ = c.ExpectString("Enter a value for string property PLAN_DATABASE_URI:") - _, _ = c.SendLine("") - _, _ = c.ExpectString("Enter a value for string property PLAN_DATABASE_USERNAME:") - _, _ = c.SendLine("") - _, _ = c.ExpectString("Provide values for non-required properties") - _, _ = c.SendLine("") - _, _ = c.ExpectEOF() - }, func(stdio terminal.Stdio) error { - values := enterServicePropertiesInteractively(plan, stdio) - valuesPtr = &values - return nil - }) - - require.Equal(t, tt.expectedValues, *valuesPtr) - } -} - -func TestGetLongDescription(t *testing.T) { - desc := testingutil.FakeClusterServiceClass("foo") - desc.Spec.ExternalMetadata = testingutil.SingleValuedRawExtension("longDescription", "description") - empty := testingutil.FakeClusterServiceClass("foo") - empty.Spec.ExternalMetadata = testingutil.SingleValuedRawExtension("longDescription", "") - tests := []struct { - name string - input beta1.ClusterServiceClass - expected string - }{ - { - name: "no metadata", - input: testingutil.FakeClusterServiceClass("foo"), - expected: "", - }, - { - name: "description", - input: desc, - expected: "description", - }, - { - name: "empty description", - input: empty, - expected: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - output := getLongDescription(tt.input) - if tt.expected != output { - t.Errorf("test failed, expected %s, got %s", tt.expected, output) - } - }) - } -} - -func TestPropDesc(t *testing.T) { - tests := []struct { - name string - prop service.ServicePlanParameter - expected string - }{ - { - name: "empty", - prop: service.ServicePlanParameter{}, - expected: "", - }, - { - name: "name only", - prop: service.ServicePlanParameter{Name: "foo"}, - expected: "foo", - }, - { - name: "with title", - prop: service.ServicePlanParameter{Name: "foo", Title: "title"}, - expected: "foo (title)", - }, - { - name: "with description", - prop: service.ServicePlanParameter{Name: "foo", Description: "desc"}, - expected: "foo (desc)", - }, - { - name: "with title and description", - prop: service.ServicePlanParameter{Name: "foo", Description: "desc", Title: "title"}, - expected: "foo (title)", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - output := propDesc(tt.prop) - if tt.expected != output { - t.Errorf("test failed, expected %v, got %v", tt.expected, output) - } - }) - } -} diff --git a/pkg/odo/cli/service/utils.go b/pkg/odo/cli/service/utils.go index 3aa6f573d..0d210f217 100644 --- a/pkg/odo/cli/service/utils.go +++ b/pkg/odo/cli/service/utils.go @@ -4,8 +4,6 @@ import ( "fmt" "path/filepath" - svc "github.com/openshift/odo/pkg/service" - "github.com/openshift/odo/pkg/odo/cli/component" "github.com/openshift/odo/pkg/util" ) @@ -22,15 +20,3 @@ func validDevfileDirectory(componentContext string) error { } return nil } - -// decideBackend returns the type of service provider backend to be used -func decideBackend(arg string) ServiceProviderBackend { - _, _, err := svc.SplitServiceKindName(arg) - if err != nil { - // failure to split provided name into two; hence ServiceCatalogBackend - return NewServiceCatalogBackend() - } else { - // provided name adheres to the format /; hence OperatorBackend - return NewOperatorBackend() - } -} diff --git a/pkg/odo/util/completion/completionhandlers.go b/pkg/odo/util/completion/completionhandlers.go index 7962cea71..25cae9b30 100644 --- a/pkg/odo/util/completion/completionhandlers.go +++ b/pkg/odo/util/completion/completionhandlers.go @@ -1,9 +1,6 @@ package completion import ( - "fmt" - "strings" - applabels "github.com/openshift/odo/pkg/application/labels" "github.com/openshift/odo/pkg/application" @@ -11,7 +8,6 @@ import ( "github.com/openshift/odo/pkg/component" "github.com/openshift/odo/pkg/config" "github.com/openshift/odo/pkg/odo/genericclioptions" - "github.com/openshift/odo/pkg/service" "github.com/openshift/odo/pkg/storage" "github.com/openshift/odo/pkg/url" "github.com/openshift/odo/pkg/util" @@ -19,128 +15,6 @@ import ( "github.com/spf13/cobra" ) -// ServiceCompletionHandler provides service name completion for the current project and application -var ServiceCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) { - completions = make([]string, 0) - - services, err := service.List(context.Client, context.Application) - if err != nil { - return completions - } - - for _, class := range services.Items { - if args.commands[class.ObjectMeta.Name] { - return nil - } - completions = append(completions, class.ObjectMeta.Name) - } - - return -} - -// ServiceClassCompletionHandler provides catalog service class name completion -var ServiceClassCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) { - completions = make([]string, 0) - services, err := context.Client.GetKubeClient().ListClusterServiceClasses() - if err != nil { - complete.Log("error retrieving services") - return completions - } - - complete.Log(fmt.Sprintf("found %d services", len(services))) - for _, class := range services { - if args.commands[class.Spec.ExternalName] { - return nil - } - completions = append(completions, class.Spec.ExternalName) - } - - return -} - -// ServicePlanCompletionHandler provides completion for the the plan of a selected service -var ServicePlanCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) { - completions = make([]string, 0) - // if we have less than two arguments, it means the user didn't specify the name of the service - // meaning that there is no point in providing suggestions - if len(args.original.Completed) < 2 { - complete.Log("Couldn't extract the service name") - return completions - } - - inputServiceName := args.original.Completed[1] - - complete.Log(fmt.Sprintf("Using input: serviceName = %s", inputServiceName)) - - clusterServiceClass, err := context.Client.GetKubeClient().GetClusterServiceClass(inputServiceName) - if err != nil { - complete.Log("Error retrieving details of service") - return completions - } - - servicePlans, err := context.Client.GetKubeClient().ListClusterServicePlansByServiceName(clusterServiceClass.Name) - if err != nil { - complete.Log("Error retrieving details of plans of service") - return completions - } - - for _, servicePlan := range servicePlans { - completions = append(completions, servicePlan.Spec.ExternalName) - } - - return completions -} - -// ServiceParameterCompletionHandler provides completion for the parameter names of a selected service and plan -var ServiceParameterCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) { - completions = make([]string, 0) - if len(args.original.Completed) < 2 { - complete.Log("Couldn't extract the service name") - return completions - } - - inputServiceName := args.original.Completed[1] - inputPlanName := args.flagValues["plan"] - - complete.Log(fmt.Sprintf("Using input: serviceName = %s, servicePlan = %s", inputServiceName, inputPlanName)) - - _, servicePlans, err := service.GetServiceClassAndPlans(context.Client, inputServiceName) - if err != nil { - complete.Log("Error retrieving details of service") - return completions - } - - var matchingServicePlan *service.ServicePlan = nil - if len(servicePlans) == 0 { - complete.Log("Service has no plans so no parameters can be found") - return completions - } else if len(servicePlans) == 1 && inputPlanName == "" { - matchingServicePlan = &servicePlans[0] - } else { - for _, sp := range servicePlans { - servicePlan := sp - if servicePlan.Name == inputPlanName { - matchingServicePlan = &servicePlan - break - } - } - if matchingServicePlan == nil { - complete.Log("No service plan for the service matched the supplied plan name") - return completions - } - } - - alreadyAddedParameters := args.flagValues["parameters"] - for _, servicePlanParameter := range matchingServicePlan.Parameters { - // don't add the parameter if it's already on the command line - if !strings.Contains(alreadyAddedParameters, servicePlanParameter.Name) { - completions = append(completions, servicePlanParameter.Name) - } - } - - return completions -} - // AppCompletionHandler provides completion for the app commands var AppCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) { completions = make([]string, 0) @@ -304,99 +178,6 @@ var CreateCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context return completions } -// LinkCompletionHandler provides completion for the odo link command -// The function returns both components and services -var LinkCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) { - components, err := component.GetComponentNames(context.Client, context.Application) - if err != nil { - return completions - } - - services, err := service.List(context.Client, context.Application) - if err != nil { - return completions - } - - completions = make([]string, 0, len(components)+len(services.Items)) - for _, component := range components { - // we found the name in the list which means - // that the name has been already selected by the user so no need to suggest more - if val, ok := args.commands[component]; ok && val { - return nil - } - // we don't want to show the selected component as a target for linking, so we remove it from the suggestions - if component != context.Component() { - completions = append(completions, component) - } - } - - for _, service := range services.Items { - // we found the name in the list which means - // that the name has been already selected by the user so no need to suggest more - if val, ok := args.commands[service.ObjectMeta.Name]; ok && val { - return nil - } - completions = append(completions, service.ObjectMeta.Name) - } - - return completions -} - -// LinkCompletionHandler provides completion for the odo unlink command -// The function returns both components and services -var UnlinkCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) { - // first we need to retrieve the current component - comp, err := component.GetPushedComponent(context.Client, context.Component(), context.Application) - if err != nil { - return completions - } - - components, err := component.GetComponentNames(context.Client, context.Application) - if err != nil { - return completions - } - - services, err := service.List(context.Client, context.Application) - if err != nil { - return completions - } - - completions = make([]string, 0, len(components)+len(services.Items)) - secretMounts := comp.GetLinkedSecrets() - for _, component := range components { - // we found the name in the list which means - // that the name has been already selected by the user so no need to suggest more - if val, ok := args.commands[component]; ok && val { - return nil - } - // we don't want to show the selected component as a target for linking, so we remove it from the suggestions - if component != context.Component() { - // we also need to make sure that this component has been linked to the current component - for _, secret := range secretMounts { - if strings.Contains(secret.SecretName, component) { - completions = append(completions, component) - } - } - } - } - - for _, service := range services.Items { - // we found the name in the list which means - // that the name has been already selected by the user so no need to suggest more - if val, ok := args.commands[service.Name]; ok && val { - return nil - } - // we also need to make sure that this component has been linked to the current component - for _, secret := range secretMounts { - if strings.Contains(secret.SecretName, service.Name) { - completions = append(completions, service.Name) - } - } - } - - return completions -} - // ComponentNameCompletionHandler provides component name completion var ComponentNameCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) { completions = make([]string, 0) diff --git a/pkg/odo/util/completion/completionhandlers_test.go b/pkg/odo/util/completion/completionhandlers_test.go deleted file mode 100644 index fc6b21172..000000000 --- a/pkg/odo/util/completion/completionhandlers_test.go +++ /dev/null @@ -1,748 +0,0 @@ -package completion - -import ( - "reflect" - "sort" - "testing" - - "github.com/openshift/odo/pkg/component" - "github.com/openshift/odo/pkg/testingutil" - "github.com/posener/complete" - - scv1beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1" - appsv1 "github.com/openshift/api/apps/v1" - applabels "github.com/openshift/odo/pkg/application/labels" - componentlabels "github.com/openshift/odo/pkg/component/labels" - "github.com/openshift/odo/pkg/occlient" - "github.com/openshift/odo/pkg/odo/genericclioptions" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - ktesting "k8s.io/client-go/testing" -) - -func TestServicePlanCompletionHandler(t *testing.T) { - serviceClassList := &scv1beta1.ClusterServiceClassList{ - Items: []scv1beta1.ClusterServiceClass{testingutil.FakeClusterServiceClass("class name", "dummy")}, - } - tests := []struct { - name string - returnedServiceClass *scv1beta1.ClusterServiceClassList - returnedServicePlan []scv1beta1.ClusterServicePlan - output []string - parsedArgs parsedArgs - }{ - { - name: "Case 0: no service name supplied", - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create"}, - }, - }, - output: []string{}, - }, - { - name: "Case 1: single plan exists", - returnedServiceClass: serviceClassList, - returnedServicePlan: []scv1beta1.ClusterServicePlan{testingutil.FakeClusterServicePlan("default", 1)}, - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create", "class name"}, - }, - }, - output: []string{"default"}, - }, - { - name: "Case 2: multiple plans exist", - returnedServiceClass: serviceClassList, - returnedServicePlan: []scv1beta1.ClusterServicePlan{ - testingutil.FakeClusterServicePlan("plan1", 1), - testingutil.FakeClusterServicePlan("plan2", 2), - }, - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create", "class name"}, - }, - }, - output: []string{"plan1", "plan2"}, - }, - } - - for _, tt := range tests { - client, fakeClientSet := occlient.FakeNew() - context := genericclioptions.NewFakeContext("project", "app", "component", client, nil) - - fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceclasses", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - return true, tt.returnedServiceClass, nil - }) - - fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceplans", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - return true, &scv1beta1.ClusterServicePlanList{Items: tt.returnedServicePlan}, nil - }) - - completions := ServicePlanCompletionHandler(nil, tt.parsedArgs, context) - - // Sort the output and expected output in order to avoid false negatives (since ordering of the results is not important) - sort.Strings(completions) - sort.Strings(tt.output) - - if !reflect.DeepEqual(tt.output, completions) { - t.Errorf("expected output: %#v,got: %#v", tt.output, completions) - } - } -} - -func TestServiceParameterCompletionHandler(t *testing.T) { - serviceClassList := &scv1beta1.ClusterServiceClassList{ - Items: []scv1beta1.ClusterServiceClass{testingutil.FakeClusterServiceClass("class name", "dummy")}, - } - tests := []struct { - name string - returnedServiceClass *scv1beta1.ClusterServiceClassList - returnedServicePlan []scv1beta1.ClusterServicePlan - output []string - parsedArgs parsedArgs - }{ - { - name: "Case 0: no service name supplied", - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create"}, - }, - }, - output: []string{}, - }, - { - name: "Case 1: no plan supplied and single plan exists", - returnedServiceClass: serviceClassList, - returnedServicePlan: []scv1beta1.ClusterServicePlan{testingutil.FakeClusterServicePlan("default", 1)}, - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create", "class name"}, - }, - }, - output: []string{"PLAN_DATABASE_URI", "PLAN_DATABASE_USERNAME", "PLAN_DATABASE_PASSWORD", "SOME_OTHER"}, - }, - { - name: "Case 2: no plan supplied and multiple plans exists", - returnedServiceClass: serviceClassList, - returnedServicePlan: []scv1beta1.ClusterServicePlan{ - testingutil.FakeClusterServicePlan("plan1", 1), - testingutil.FakeClusterServicePlan("plan2", 2), - }, - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create", "class name"}, - }, - }, - output: []string{}, - }, - { - name: "Case 3: plan supplied but doesn't match", - returnedServiceClass: serviceClassList, - returnedServicePlan: []scv1beta1.ClusterServicePlan{testingutil.FakeClusterServicePlan("default", 1)}, - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create", "class name"}, - }, - flagValues: map[string]string{"plan": "other"}, - }, - output: []string{}, - }, - { - name: "Case 4: matching plan supplied and no other parameters supplied", - returnedServiceClass: serviceClassList, - returnedServicePlan: []scv1beta1.ClusterServicePlan{ - testingutil.FakeClusterServicePlan("plan2", 2), - testingutil.FakeClusterServicePlan("plan1", 1), - }, - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create", "class name"}, - }, - flagValues: map[string]string{"plan": "plan1"}, - }, - output: []string{"PLAN_DATABASE_URI", "PLAN_DATABASE_USERNAME", "PLAN_DATABASE_PASSWORD", "SOME_OTHER"}, - }, - { - name: "Case 5: no plan supplied but some other parameters supplied", - returnedServiceClass: serviceClassList, - returnedServicePlan: []scv1beta1.ClusterServicePlan{testingutil.FakeClusterServicePlan("default", 1)}, - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create", "class name"}, - }, - flagValues: map[string]string{"parameters": "[PLAN_DATABASE_USERNAME, SOME_OTHER]"}, - }, - output: []string{"PLAN_DATABASE_URI", "PLAN_DATABASE_PASSWORD"}, - }, - { - name: "Case 6: matching plan supplied but some other parameters supplied", - returnedServiceClass: serviceClassList, - returnedServicePlan: []scv1beta1.ClusterServicePlan{testingutil.FakeClusterServicePlan("default", 1)}, - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create", "class name"}, - }, - flagValues: map[string]string{"plan": "default", "parameters": "[PLAN_DATABASE_USERNAME]"}, - }, - output: []string{"PLAN_DATABASE_URI", "PLAN_DATABASE_PASSWORD", "SOME_OTHER"}, - }, - } - - for _, tt := range tests { - client, fakeClientSet := occlient.FakeNew() - context := genericclioptions.NewFakeContext("project", "app", "component", client, nil) - - fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceclasses", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - return true, tt.returnedServiceClass, nil - }) - - fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceplans", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - return true, &scv1beta1.ClusterServicePlanList{Items: tt.returnedServicePlan}, nil - }) - - completions := ServiceParameterCompletionHandler(nil, tt.parsedArgs, context) - - // Sort the output and expected output in order to avoid false negatives (since ordering of the results is not important) - sort.Strings(completions) - sort.Strings(tt.output) - - if !reflect.DeepEqual(tt.output, completions) { - t.Errorf("expected output: %#v,got: %#v", tt.output, completions) - } - } -} - -func TestLinkCompletionHandler(t *testing.T) { - - tests := []struct { - name string - component string - dcList appsv1.DeploymentConfigList - serviceList scv1beta1.ServiceInstanceList - output []string - }{ - { - name: "Case 1: both components and services are present", - component: "frontend", - serviceList: scv1beta1.ServiceInstanceList{ - Items: []scv1beta1.ServiceInstance{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "mysql-persistent", - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "mysql-persistent", - componentlabels.ComponentTypeLabel: "mysql-persistent", - }, - Annotations: map[string]string{ - component.ComponentSourceTypeAnnotation: "local", - }, - }, - Spec: scv1beta1.ServiceInstanceSpec{ - PlanReference: scv1beta1.PlanReference{ - ClusterServiceClassExternalName: "mysql-persistent", - ClusterServicePlanExternalName: "default", - }, - }, - Status: scv1beta1.ServiceInstanceStatus{ - Conditions: []scv1beta1.ServiceInstanceCondition{ - { - Reason: "ProvisionedSuccessfully", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "postgresql-ephemeral", - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "postgresql-ephemeral", - componentlabels.ComponentTypeLabel: "postgresql-ephemeral", - }, - Annotations: map[string]string{ - component.ComponentSourceTypeAnnotation: "local", - }, - }, - Spec: scv1beta1.ServiceInstanceSpec{ - PlanReference: scv1beta1.PlanReference{ - ClusterServiceClassExternalName: "postgresql-ephemeral", - ClusterServicePlanExternalName: "default", - }, - }, - Status: scv1beta1.ServiceInstanceStatus{ - Conditions: []scv1beta1.ServiceInstanceCondition{ - { - Reason: "Provisioning", - }, - }, - }, - }, - }, - }, - dcList: appsv1.DeploymentConfigList{ - Items: []appsv1.DeploymentConfig{ - { - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "backend", - componentlabels.ComponentTypeLabel: "java", - }, - Annotations: map[string]string{ - component.ComponentSourceTypeAnnotation: "local", - }, - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "dummyContainer", - }, - }, - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "frontend", - componentlabels.ComponentTypeLabel: "nodejs", - }, - Annotations: map[string]string{ - component.ComponentSourceTypeAnnotation: "local", - }, - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "dummyContainer", - }, - }, - }, - }, - }, - }, - }, - }, - // make sure that the 'component' is not part of the suggestions - output: []string{"backend", "mysql-persistent", "postgresql-ephemeral"}, - }, - } - - for _, tt := range tests { - client, fakeClientSet := occlient.FakeNew() - parsedArgs := parsedArgs{ - commands: make(map[string]bool), - } - context := genericclioptions.NewFakeContext("project", "app", tt.component, client, nil) - - fakeClientSet.ProjClientset.PrependReactor("get", "projects", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - return true, &testingutil.FakeOnlyOneExistingProjects().Items[0], nil - }) - - //fake the services - fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "serviceinstances", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.serviceList, nil - }) - - //fake the dcs - fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.dcList, nil - }) - - for i := range tt.dcList.Items { - fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.dcList.Items[i], nil - }) - } - - completions := LinkCompletionHandler(nil, parsedArgs, context) - sort.Strings(completions) - - if !reflect.DeepEqual(tt.output, completions) { - t.Errorf("expected output: %#v,got: %#v", tt.output, completions) - } - } -} - -func TestUnlinkCompletionHandler(t *testing.T) { - - tests := []struct { - name string - component string - dcList appsv1.DeploymentConfigList - serviceList scv1beta1.ServiceInstanceList - output []string - }{ - { - name: "Case 1: both components and services are present", - component: "frontend", - serviceList: scv1beta1.ServiceInstanceList{ - Items: []scv1beta1.ServiceInstance{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "mysql-persistent", - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "mysql-persistent", - componentlabels.ComponentTypeLabel: "mysql-persistent", - }, - }, - Spec: scv1beta1.ServiceInstanceSpec{ - PlanReference: scv1beta1.PlanReference{ - ClusterServiceClassExternalName: "mysql-persistent", - ClusterServicePlanExternalName: "default", - }, - }, - Status: scv1beta1.ServiceInstanceStatus{ - Conditions: []scv1beta1.ServiceInstanceCondition{ - { - Reason: "ProvisionedSuccessfully", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "postgresql-ephemeral", - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "postgresql-ephemeral", - componentlabels.ComponentTypeLabel: "postgresql-ephemeral", - }, - }, - Spec: scv1beta1.ServiceInstanceSpec{ - PlanReference: scv1beta1.PlanReference{ - ClusterServiceClassExternalName: "postgresql-ephemeral", - ClusterServicePlanExternalName: "default", - }, - }, - Status: scv1beta1.ServiceInstanceStatus{ - Conditions: []scv1beta1.ServiceInstanceCondition{ - { - Reason: "ProvisionedSuccessfully", - }, - }, - }, - }, - }, - }, - dcList: appsv1.DeploymentConfigList{ - Items: []appsv1.DeploymentConfig{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "backend-app", - Namespace: "project", - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "backend", - componentlabels.ComponentTypeLabel: "java", - }, - Annotations: map[string]string{ - component.ComponentSourceTypeAnnotation: "local", - }, - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "dummyContainer", - }, - }, - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "backend2-app", - Namespace: "project", - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "backend2", - componentlabels.ComponentTypeLabel: "java", - }, - Annotations: map[string]string{ - component.ComponentSourceTypeAnnotation: "local", - }, - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "dummyContainer", - }, - }, - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "frontend-app", - Namespace: "project", - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "frontend", - componentlabels.ComponentTypeLabel: "nodejs", - }, - Annotations: map[string]string{ - component.ComponentSourceTypeAnnotation: "local", - }, - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "dummyContainer", - EnvFrom: []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "postgresql-ephemeral"}, - }, - }, - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: "backend-8080"}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - // make sure that the 'component' is not part of the suggestions and that only actually linked components/services show up - output: []string{"backend", "postgresql-ephemeral"}, - }, - } - - p := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "postgresql-ephemeral", - }, - } - - for _, tt := range tests { - client, fakeClientSet := occlient.FakeNew() - parsedArgs := parsedArgs{ - commands: make(map[string]bool), - } - context := genericclioptions.NewFakeContext("project", "app", tt.component, client, nil) - - //fake the services - fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "serviceinstances", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.serviceList, nil - }) - - fakeClientSet.ProjClientset.PrependReactor("get", "projects", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - return true, &testingutil.FakeOnlyOneExistingProjects().Items[0], nil - }) - - //fake the dcs - fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.dcList, nil - }) - - for i := range tt.dcList.Items { - fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.dcList.Items[i], nil - }) - } - - fakeClientSet.Kubernetes.PrependReactor("get", "secrets", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &p, nil - }) - - completions := UnlinkCompletionHandler(nil, parsedArgs, context) - sort.Strings(completions) - - if !reflect.DeepEqual(tt.output, completions) { - t.Errorf("expected output: %#v,got: %#v", tt.output, completions) - } - } -} - -func TestServiceCompletionHandler(t *testing.T) { - tests := []struct { - name string - returnedServiceClassInstances *scv1beta1.ServiceInstanceList - output []string - parsedArgs parsedArgs - }{ - { - name: "test case 1: no service instance exists and name not typed", - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"delete"}, - }, - }, - returnedServiceClassInstances: &scv1beta1.ServiceInstanceList{}, - output: []string{}, - }, - { - name: "test case 2: one service class instance exists and name not typed", - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"delete"}, - }, - }, - returnedServiceClassInstances: &scv1beta1.ServiceInstanceList{ - Items: []scv1beta1.ServiceInstance{ - testingutil.FakeServiceClassInstance("service-1", "mariadb-apb", "default", "ProvisionedSuccessfully"), - }, - }, - output: []string{"service-1"}, - }, - { - name: "test case 3: multiple service class instance exists and name not typed", - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"delete"}, - }, - }, - returnedServiceClassInstances: &scv1beta1.ServiceInstanceList{ - Items: []scv1beta1.ServiceInstance{ - testingutil.FakeServiceClassInstance("service-1", "mariadb-apb", "default", "ProvisionedSuccessfully"), - testingutil.FakeServiceClassInstance("service-2", "mariadb-apb", "prod", "ProvisionedSuccessfully"), - }, - }, - output: []string{"service-1", "service-2"}, - }, - { - name: "test case 4: multiple service class instance exists and name fully typed", - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"delete"}, - }, - commands: map[string]bool{"service-1": true}, - }, - returnedServiceClassInstances: &scv1beta1.ServiceInstanceList{ - Items: []scv1beta1.ServiceInstance{ - testingutil.FakeServiceClassInstance("service-1", "mariadb-apb", "default", "ProvisionedSuccessfully"), - testingutil.FakeServiceClassInstance("service-2", "mariadb-apb", "prod", "ProvisionedSuccessfully"), - }, - }, - output: nil, - }, - } - - for _, tt := range tests { - client, fakeClientSet := occlient.FakeNew() - context := genericclioptions.NewFakeContext("project", "app", "component", client, nil) - - //fake the services - fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "serviceinstances", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, tt.returnedServiceClassInstances, nil - }) - - completions := ServiceCompletionHandler(nil, tt.parsedArgs, context) - - // Sort the output and expected output in order to avoid false negatives (since ordering of the results is not important) - sort.Strings(completions) - sort.Strings(tt.output) - - if !reflect.DeepEqual(tt.output, completions) { - t.Errorf("expected output: %#v,got: %#v", tt.output, completions) - } - } -} - -func TestServiceClassCompletionHandler(t *testing.T) { - tests := []struct { - name string - returnedServiceClasses *scv1beta1.ClusterServiceClassList - output []string - parsedArgs parsedArgs - }{ - { - name: "test case 1: no service class exists and name not typed", - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create"}, - }, - }, - returnedServiceClasses: &scv1beta1.ClusterServiceClassList{}, - output: []string{}, - }, - { - name: "test case 2: one service class exists and name not typed", - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create"}, - }, - }, - returnedServiceClasses: &scv1beta1.ClusterServiceClassList{ - Items: []scv1beta1.ClusterServiceClass{ - testingutil.FakeClusterServiceClass("foo"), - }, - }, - output: []string{"foo"}, - }, - { - name: "test case 3: multiple service classes exist and name not typed", - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"create"}, - }, - }, - returnedServiceClasses: &scv1beta1.ClusterServiceClassList{ - Items: []scv1beta1.ClusterServiceClass{ - testingutil.FakeClusterServiceClass("foo"), - testingutil.FakeClusterServiceClass("bar"), - }, - }, - output: []string{"foo", "bar"}, - }, - { - name: "test case 4: multiple service classes exist and name fully typed", - parsedArgs: parsedArgs{ - original: complete.Args{ - Completed: []string{"delete"}, - }, - commands: map[string]bool{"foo": true}, - }, - returnedServiceClasses: &scv1beta1.ClusterServiceClassList{ - Items: []scv1beta1.ClusterServiceClass{ - testingutil.FakeClusterServiceClass("foo"), - testingutil.FakeClusterServiceClass("bar"), - }, - }, - output: nil, - }, - } - - for _, tt := range tests { - client, fakeClientSet := occlient.FakeNew() - context := genericclioptions.NewFakeContext("project", "app", "component", client, nil) - - //fake the services - fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceclasses", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, tt.returnedServiceClasses, nil - }) - - completions := ServiceClassCompletionHandler(nil, tt.parsedArgs, context) - - // Sort the output and expected output in order to avoid false negatives (since ordering of the results is not important) - sort.Strings(completions) - sort.Strings(tt.output) - - if !reflect.DeepEqual(tt.output, completions) { - t.Errorf("expected output: %#v,got: %#v", tt.output, completions) - } - } -} diff --git a/pkg/service/service.go b/pkg/service/service.go index 6c77688ea..678a21d98 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -10,12 +10,10 @@ import ( parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common" "github.com/openshift/odo/pkg/kclient" "github.com/openshift/odo/pkg/log" - "github.com/openshift/odo/pkg/odo/util/validation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/klog" - scv1beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1" appsv1 "github.com/openshift/api/apps/v1" olm "github.com/operator-framework/api/pkg/operators/v1alpha1" @@ -44,45 +42,8 @@ const ServiceLabel = "app.kubernetes.io/service-name" // ServiceKind is the kind of the service in the service binding object const ServiceKind = "app.kubernetes.io/service-kind" -// NewServicePlanParameter creates a new ServicePlanParameter instance with the specified state -func NewServicePlanParameter(name, typeName, defaultValue string, required bool) ServicePlanParameter { - return ServicePlanParameter{ - Name: name, - Default: defaultValue, - Validatable: validation.Validatable{ - Type: typeName, - Required: required, - }, - } -} - type servicePlanParameters []ServicePlanParameter -func (params servicePlanParameters) Len() int { - return len(params) -} - -func (params servicePlanParameters) Less(i, j int) bool { - return params[i].Name < params[j].Name -} - -func (params servicePlanParameters) Swap(i, j int) { - params[i], params[j] = params[j], params[i] -} - -// CreateService creates new service from serviceCatalog -// It returns string representation of service instance created on the cluster and error (if any). -func CreateService(client *occlient.Client, serviceName, serviceType, servicePlan string, parameters map[string]string, applicationName string) (string, error) { - labels := componentlabels.GetLabels(serviceName, applicationName, true) - // save service type as label - labels[componentlabels.ComponentTypeLabel] = serviceType - serviceInstance, err := client.GetKubeClient().CreateServiceInstance(serviceName, serviceType, servicePlan, parameters, labels) - if err != nil { - return "", errors.Wrap(err, "unable to create service instance") - } - return serviceInstance, nil -} - // GetCSV checks if the CR provided by the user in the YAML file exists in the namesapce // It returns a CR (string representation) and CSV (Operator) upon successfully // able to find them, an error otherwise. @@ -583,115 +544,11 @@ func SplitServiceKindName(serviceName string) (string, string, error) { return kind, name, nil } -// GetServiceClassAndPlans returns the service class details with the associated plans -// serviceName is the name of the service class -// the first parameter returned is the ServiceClass object -// the second parameter returned is the array of ServicePlan associated with the service class -func GetServiceClassAndPlans(client *occlient.Client, serviceName string) (ServiceClass, []ServicePlan, error) { - result, err := client.GetKubeClient().GetClusterServiceClass(serviceName) - if err != nil { - return ServiceClass{}, nil, errors.Wrap(err, "unable to get the given service") - } - - var meta map[string]interface{} - err = json.Unmarshal(result.Spec.ExternalMetadata.Raw, &meta) - if err != nil { - return ServiceClass{}, nil, errors.Wrap(err, "unable to unmarshal data the given service") - } - - service := ServiceClass{ - Name: result.Spec.ExternalName, - Bindable: result.Spec.Bindable, - ShortDescription: result.Spec.Description, - Tags: result.Spec.Tags, - ServiceBrokerName: result.Spec.ClusterServiceBrokerName, - } - - if val, ok := meta["longDescription"]; ok { - service.LongDescription = val.(string) - } - - if val, ok := meta["dependencies"]; ok { - versions := fmt.Sprint(val) - versions = strings.Replace(versions, "[", "", -1) - versions = strings.Replace(versions, "]", "", -1) - service.VersionsAvailable = strings.Split(versions, " ") - } - - // get the plans according to the service name - planResults, err := client.GetKubeClient().ListClusterServicePlansByServiceName(result.Name) - if err != nil { - return ServiceClass{}, nil, errors.Wrap(err, "unable to get plans for the given service") - } - - var plans []ServicePlan - for _, result := range planResults { - plan, err := NewServicePlan(result) - if err != nil { - return ServiceClass{}, nil, err - } - - plans = append(plans, plan) - } - - return service, plans, nil -} - type InstanceCreateParameterSchema struct { Required []string Properties map[string]ServicePlanParameter } -// NewServicePlan creates a new ServicePlan based on the specified ClusterServicePlan -func NewServicePlan(result scv1beta1.ClusterServicePlan) (plan ServicePlan, err error) { - plan = ServicePlan{ - Name: result.Spec.ExternalName, - Description: result.Spec.Description, - } - - // get the display name from the external meta data - var externalMetaData map[string]interface{} - err = json.Unmarshal(result.Spec.ExternalMetadata.Raw, &externalMetaData) - if err != nil { - return plan, errors.Wrap(err, "unable to unmarshal data the given service") - } - - if val, ok := externalMetaData["displayName"]; ok { - plan.DisplayName = val.(string) - } - - // get the create parameters - schema := InstanceCreateParameterSchema{} - paramBytes := result.Spec.InstanceCreateParameterSchema.Raw - err = json.Unmarshal(paramBytes, &schema) - if err != nil { - return plan, errors.Wrapf(err, "unable to unmarshal data the given service: %s", string(paramBytes[:])) - } - - plan.Parameters = make([]ServicePlanParameter, 0, len(schema.Properties)) - for k, v := range schema.Properties { - v.Name = k - // we set the Required flag if the name of parameter - // is one of the parameters indicated as required - // these parameters are not strictly required since they might have default values - v.Required = isRequired(schema.Required, k) - - plan.Parameters = append(plan.Parameters, v) - } - - return -} - -// isRequired checks whether the parameter with the specified name is among the given list of required ones -func isRequired(required []string, name string) bool { - for _, n := range required { - if n == name { - return true - } - } - return false -} - // IsCSVSupported checks if the cluster supports resources of type ClusterServiceVersion func IsCSVSupported() (bool, error) { client, err := occlient.New() diff --git a/pkg/service/service_test.go b/pkg/service/service_test.go index 6444ee8d0..fac565045 100644 --- a/pkg/service/service_test.go +++ b/pkg/service/service_test.go @@ -1,9 +1,6 @@ package service import ( - "encoding/json" - "fmt" - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser/data/v2/common" @@ -12,767 +9,10 @@ import ( devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" "github.com/devfile/library/pkg/devfile/parser/data" devfileFileSystem "github.com/devfile/library/pkg/testingutil/filesystem" - "github.com/kylelemons/godebug/pretty" - "github.com/onsi/gomega/matchers" - "github.com/openshift/odo/pkg/testingutil" - "reflect" - "sort" "testing" - - scv1beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1" - appsv1 "github.com/openshift/api/apps/v1" - applabels "github.com/openshift/odo/pkg/application/labels" - componentlabels "github.com/openshift/odo/pkg/component/labels" - "github.com/openshift/odo/pkg/occlient" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - ktesting "k8s.io/client-go/testing" ) -func TestGetServiceClassAndPlans(t *testing.T) { - - classExternalMetaData := make(map[string]interface{}) - classExternalMetaData["longDescription"] = "example long description" - classExternalMetaData["dependencies"] = []string{"docker.io/centos/7", "docker.io/centos/8"} - - classExternalMetaDataRaw, err := json.Marshal(classExternalMetaData) - if err != nil { - fmt.Printf("error occured %v during marshalling", err) - return - } - - type args struct { - ServiceName string - } - plan1 := testingutil.FakeClusterServicePlan("dev", 1) - plan2 := testingutil.FakeClusterServicePlan("prod", 2) - tests := []struct { - name string - args args - returnedClassID string - returnedServiceClass *scv1beta1.ClusterServiceClassList - returnedServicePlan []scv1beta1.ClusterServicePlan - wantedServiceClass ServiceClass - wantedServicePlans []ServicePlan - wantErr bool - }{ - { - name: "test 1 : with correct values", - args: args{ - ServiceName: "class name", - }, - returnedClassID: "1dda1477cace09730bd8ed7a6505607e", - returnedServiceClass: &scv1beta1.ClusterServiceClassList{ - Items: []scv1beta1.ClusterServiceClass{ - { - ObjectMeta: metav1.ObjectMeta{Name: "1dda1477cace09730bd8ed7a6505607e"}, - Spec: scv1beta1.ClusterServiceClassSpec{ - CommonServiceClassSpec: scv1beta1.CommonServiceClassSpec{ - ExternalName: "class name", - Bindable: false, - Description: "example description", - Tags: []string{"php", "java"}, - ExternalMetadata: &runtime.RawExtension{Raw: classExternalMetaDataRaw}, - }, - ClusterServiceBrokerName: "broker name", - }, - }, - }, - }, - returnedServicePlan: []scv1beta1.ClusterServicePlan{plan1, plan2}, - wantedServiceClass: ServiceClass{ - Name: "class name", - ShortDescription: "example description", - LongDescription: "example long description", - Tags: []string{"php", "java"}, - Bindable: false, - ServiceBrokerName: "broker name", - VersionsAvailable: []string{"docker.io/centos/7", "docker.io/centos/8"}, - }, - wantedServicePlans: []ServicePlan{ - { - Name: "dev", - Description: "this is a example description 1", - DisplayName: "plan-name-1", - Parameters: []ServicePlanParameter{ - NewServicePlanParameter("PLAN_DATABASE_URI", "string", "someuri", true), - NewServicePlanParameter("PLAN_DATABASE_USERNAME", "string", "name", true), - NewServicePlanParameter("PLAN_DATABASE_PASSWORD", "string", "", true), - NewServicePlanParameter("SOME_OTHER", "string", "other", false), - }, - }, - { - Name: "prod", - Description: "this is a example description 2", - DisplayName: "plan-name-2", - Parameters: []ServicePlanParameter{ - NewServicePlanParameter("PLAN_DATABASE_USERNAME_2", "string", "user2", true), - NewServicePlanParameter("PLAN_DATABASE_PASSWORD", "string", "", true), - }, - }, - }, - wantErr: false, - }, - } - - for _, tt := range tests { - client, fakeClientSet := occlient.FakeNew() - - fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceclasses", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - if action.(ktesting.ListAction).GetListRestrictions().Fields.String() != fmt.Sprintf("spec.externalName=%v", tt.args.ServiceName) { - t.Errorf("got a different service name got: %v , expected: %v", action.(ktesting.ListAction).GetListRestrictions().Fields.String(), fmt.Sprintf("spec.externalName=%v", tt.args.ServiceName)) - } - return true, tt.returnedServiceClass, nil - }) - - fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceplans", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) { - if action.(ktesting.ListAction).GetListRestrictions().Fields.String() != fmt.Sprintf("spec.clusterServiceClassRef.name=%v", tt.returnedClassID) { - t.Errorf("got a different service name got: %v , expected: %v", action.(ktesting.ListAction).GetListRestrictions().Fields.String(), fmt.Sprintf("spec.clusterServiceClassRef.name=%v", tt.returnedClassID)) - } - return true, &scv1beta1.ClusterServicePlanList{Items: tt.returnedServicePlan}, nil - }) - - serviceClass, servicePlans, err := GetServiceClassAndPlans(client, tt.args.ServiceName) - - if err == nil && !tt.wantErr { - if len(fakeClientSet.ServiceCatalogClientSet.Actions()) != 2 { - t.Errorf("expected 2 actions in GetServiceClassAndPlans got: %v", fakeClientSet.ServiceCatalogClientSet.Actions()) - } - - if !reflect.DeepEqual(tt.wantedServiceClass.Name, serviceClass.Name) { - t.Errorf("different service class name expected got: %v , expected: %v", serviceClass.Name, tt.wantedServiceClass.Name) - } - - if !reflect.DeepEqual(tt.wantedServiceClass.Bindable, serviceClass.Bindable) { - t.Errorf("different service class bindable value expected got: %v , expected: %v", serviceClass.Bindable, tt.wantedServiceClass.Bindable) - } - - if !reflect.DeepEqual(tt.wantedServiceClass.ShortDescription, serviceClass.ShortDescription) { - t.Errorf("different short description value expected got: %v , expected: %v", serviceClass.ShortDescription, tt.wantedServiceClass.ShortDescription) - } - - if !reflect.DeepEqual(tt.wantedServiceClass.LongDescription, serviceClass.LongDescription) { - t.Errorf("different long description value expected got: %v , expected: %v", serviceClass.LongDescription, tt.wantedServiceClass.LongDescription) - } - - if !reflect.DeepEqual(tt.wantedServiceClass.ServiceBrokerName, serviceClass.ServiceBrokerName) { - t.Errorf("different service broker name value expected got: %v , expected: %v", serviceClass.ServiceBrokerName, tt.wantedServiceClass.ServiceBrokerName) - } - - if !reflect.DeepEqual(tt.wantedServiceClass.Tags, serviceClass.Tags) { - t.Errorf("different service class tags value expected got: %v , expected: %v", serviceClass.Tags, tt.wantedServiceClass.Tags) - } - - for _, wantedServicePlan := range tt.wantedServicePlans { - - // make sure that the plans are sorted so we can compare them later - sort.Slice(wantedServicePlan.Parameters, func(i, j int) bool { - return wantedServicePlan.Parameters[i].Name < wantedServicePlan.Parameters[j].Name - }) - - found := false - for _, gotServicePlan := range servicePlans { - if reflect.DeepEqual(wantedServicePlan.Name, gotServicePlan.Name) { - found = true - } else { - continue - } - - // make sure that the plans are sorted so we can compare them - sort.Slice(gotServicePlan.Parameters, func(i, j int) bool { - return gotServicePlan.Parameters[i].Name < gotServicePlan.Parameters[j].Name - }) - - if !reflect.DeepEqual(wantedServicePlan.Parameters, gotServicePlan.Parameters) { - t.Errorf("Different plan parameters value. Expected: %v , got: %v", wantedServicePlan.Parameters, gotServicePlan.Parameters) - } - - if !reflect.DeepEqual(wantedServicePlan.DisplayName, gotServicePlan.DisplayName) { - t.Errorf("Different plan display name value. Expected: %v , got: %v", wantedServicePlan.DisplayName, gotServicePlan.DisplayName) - } - - if !reflect.DeepEqual(wantedServicePlan.Description, gotServicePlan.Description) { - t.Errorf("Different plan description value. Expected: %v , got: %v", wantedServicePlan.Description, gotServicePlan.Description) - } - } - - if !found { - t.Errorf("service plan %v not found", wantedServicePlan.Name) - } - } - } else if err == nil && tt.wantErr { - t.Error("test failed, expected: false, got true") - } else if err != nil && !tt.wantErr { - t.Errorf("test failed, expected: no error, got error: %s", err.Error()) - } - } -} - -func TestListWithDetailedStatus(t *testing.T) { - - type args struct { - Project string - Selector string - } - - tests := []struct { - name string - args args - serviceList scv1beta1.ServiceInstanceList - secretList corev1.SecretList - dcList appsv1.DeploymentConfigList - output []Service - }{ - { - name: "Case 1: services with various statuses, some bound and some linked", - args: args{ - Project: "myproject", - Selector: "app.kubernetes.io/instance=mysql-persistent,app.kubernetes.io/part-of=app", - }, - serviceList: scv1beta1.ServiceInstanceList{ - Items: []scv1beta1.ServiceInstance{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "mysql-persistent", - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "mysql-persistent", - componentlabels.ComponentTypeLabel: "mysql-persistent", - }, - Namespace: "myproject", - }, - Spec: scv1beta1.ServiceInstanceSpec{ - PlanReference: scv1beta1.PlanReference{ - ClusterServiceClassExternalName: "mysql-persistent", - ClusterServicePlanExternalName: "default", - }, - }, - Status: scv1beta1.ServiceInstanceStatus{ - Conditions: []scv1beta1.ServiceInstanceCondition{ - { - Reason: "ProvisionedSuccessfully", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "postgresql-ephemeral", - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "postgresql-ephemeral", - componentlabels.ComponentTypeLabel: "postgresql-ephemeral", - }, - Namespace: "myproject", - }, - Spec: scv1beta1.ServiceInstanceSpec{ - PlanReference: scv1beta1.PlanReference{ - ClusterServiceClassExternalName: "postgresql-ephemeral", - ClusterServicePlanExternalName: "default", - }, - }, - Status: scv1beta1.ServiceInstanceStatus{ - Conditions: []scv1beta1.ServiceInstanceCondition{ - { - Reason: "ProvisionedSuccessfully", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "mongodb", - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "mongodb", - componentlabels.ComponentTypeLabel: "mongodb", - }, - Namespace: "myproject", - }, - Spec: scv1beta1.ServiceInstanceSpec{ - PlanReference: scv1beta1.PlanReference{ - ClusterServiceClassExternalName: "mongodb", - ClusterServicePlanExternalName: "default", - }, - }, - Status: scv1beta1.ServiceInstanceStatus{ - Conditions: []scv1beta1.ServiceInstanceCondition{ - { - Reason: "ProvisionedSuccessfully", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "jenkins-persistent", - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - componentlabels.ComponentLabel: "jenkins-persistent", - componentlabels.ComponentTypeLabel: "jenkins-persistent", - }, - Namespace: "myproject", - }, - Spec: scv1beta1.ServiceInstanceSpec{ - PlanReference: scv1beta1.PlanReference{ - ClusterServiceClassExternalName: "jenkins-persistent", - ClusterServicePlanExternalName: "default", - }, - }, - Status: scv1beta1.ServiceInstanceStatus{ - Conditions: []scv1beta1.ServiceInstanceCondition{ - { - Reason: "Provisioning", - }, - }, - }, - }, - }, - }, - secretList: corev1.SecretList{ - Items: []corev1.Secret{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dummySecret", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "postgresql-ephemeral", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "mysql-persistent", - }, - }, - }, - }, - dcList: appsv1.DeploymentConfigList{ - Items: []appsv1.DeploymentConfig{ - { - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - }, - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "dummyContainer", - }, - }, - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - applabels.ApplicationLabel: "app", - }, - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - EnvFrom: []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "mysql-persistent", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - output: []Service{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - APIVersion: "odo.dev/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "mysql-persistent", - }, - Spec: ServiceSpec{ - Type: "mysql-persistent", - Plan: "default", - }, - Status: ServiceStatus{ - Status: "ProvisionedAndLinked", - }, - }, - { - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - APIVersion: "odo.dev/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "postgresql-ephemeral", - }, - Spec: ServiceSpec{ - Type: "postgresql-ephemeral", - Plan: "default", - }, - Status: ServiceStatus{ - Status: "ProvisionedAndBound", - }, - }, - { - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - APIVersion: "odo.dev/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "mongodb", - }, - Spec: ServiceSpec{ - Type: "mongodb", - Plan: "default", - }, - Status: ServiceStatus{ - Status: "ProvisionedSuccessfully", - }, - }, - { - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - APIVersion: "odo.dev/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "jenkins-persistent", - }, - Spec: ServiceSpec{ - Type: "jenkins-persistent", - Plan: "default", - }, - Status: ServiceStatus{ - Status: "Provisioning", - }, - }, - }, - }, - } - - for _, tt := range tests { - client, fakeClientSet := occlient.FakeNew() - - //fake the services - fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "serviceinstances", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.serviceList, nil - }) - - //fake the secrets - fakeClientSet.Kubernetes.PrependReactor("list", "secrets", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.secretList, nil - }) - - //fake the dcs - fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.dcList, nil - }) - - svcInstanceList, _ := ListWithDetailedStatus(client, "app") - - if !reflect.DeepEqual(tt.output, svcInstanceList.Items) { - t.Error(fmt.Sprintf("Expected output: %v", pretty.Compare(tt.serviceList, svcInstanceList.Items))) - } - } -} - -func TestDeleteServiceAndUnlinkComponents(t *testing.T) { - const appName = "app" - type args struct { - ServiceName string - } - tests := []struct { - name string - args args - serviceList scv1beta1.ServiceInstanceList - dcList appsv1.DeploymentConfigList - expectedDCNamesToBeUpdated []string - wantErr bool - }{ - { - name: "Case 1: Delete service that has linked component", - args: args{ - ServiceName: "mysql", - }, - wantErr: false, - expectedDCNamesToBeUpdated: []string{"component-with-matching-link"}, - serviceList: scv1beta1.ServiceInstanceList{ - Items: []scv1beta1.ServiceInstance{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "mysql", - Labels: map[string]string{ - applabels.ApplicationLabel: appName, - componentlabels.ComponentLabel: "mysql", - componentlabels.ComponentTypeLabel: "mysql-persistent", - }, - }, - }, - }, - }, - dcList: appsv1.DeploymentConfigList{ - Items: []appsv1.DeploymentConfig{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "component-with-no-links" + "-" + appName, - Labels: map[string]string{ - applabels.ApplicationLabel: appName, - componentlabels.ComponentLabel: "component-with-no-links", - }, - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "dummyContainer", - }, - }, - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "component-with-matching-link" + "-" + appName, - Labels: map[string]string{ - applabels.ApplicationLabel: appName, - componentlabels.ComponentLabel: "component-with-matching-link", - }, - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - EnvFrom: []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "mysql", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "component-with-non-matching-link" + "-" + appName, - Labels: map[string]string{ - applabels.ApplicationLabel: appName, - componentlabels.ComponentLabel: "component-with-non-matching-link", - }, - }, - Spec: appsv1.DeploymentConfigSpec{ - Template: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - EnvFrom: []corev1.EnvFromSource{ - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "other", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - - for _, tt := range tests { - client, fakeClientSet := occlient.FakeNew() - - //fake the services listing - fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "serviceinstances", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.serviceList, nil - }) - - // Fake the servicebinding delete - fakeClientSet.ServiceCatalogClientSet.PrependReactor("delete", "servicebindings", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - - // Fake the serviceinstance delete - fakeClientSet.ServiceCatalogClientSet.PrependReactor("delete", "serviceinstances", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - - //fake the dc listing - fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - return true, &tt.dcList, nil - }) - - //fake the dc get - fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) { - dcNameToFind := action.(ktesting.GetAction).GetName() - var matchingDC appsv1.DeploymentConfig - found := false - for _, dc := range tt.dcList.Items { - if dc.Name == dcNameToFind { - matchingDC = dc - found = true - break - } - } - - if !found { - t.Errorf("Expected to find DeploymentConfig named %s in the dcList", dcNameToFind) - } - - return true, &matchingDC, nil - }) - - err := DeleteServiceAndUnlinkComponents(client, tt.args.ServiceName, "app") - - if !tt.wantErr == (err != nil) { - t.Errorf("service.DeleteServiceAndUnlinkComponents(...) unexpected error %v, wantErr %v", err, tt.wantErr) - } - - // ensure we deleted the service - if len(fakeClientSet.ServiceCatalogClientSet.Actions()) != 3 && !tt.wantErr { - t.Errorf("service was deleted properly, got actions: %v", fakeClientSet.ServiceCatalogClientSet.Actions()) - } - - // ensure we updated the correct number of deployments - // there should always be a list action - // then each update to a dc is 2 actions, a get and an update - expectedNumberOfDCActions := 1 + (2 * len(tt.expectedDCNamesToBeUpdated)) - if len(fakeClientSet.AppsClientset.Actions()) != 3 && !tt.wantErr { - t.Errorf("expected to see %d actions, got: %v", expectedNumberOfDCActions, fakeClientSet.AppsClientset.Actions()) - } - } -} - -func TestServicePlanParameterUnmarshalling(t *testing.T) { - parameter := NewServicePlanParameter("name", "string", "default", true) - parameter.Title = "title" - parameter.Description = "description" - - tests := []struct { - name string - json string - expected ServicePlanParameter - }{ - { - name: "full", - json: `{ -"name": "name", -"title": "title", -"description": "description", -"default": "default", -"required": true, -"type": "string" -}`, - expected: parameter, - }, - { - name: "not required", - json: `{ -"name": "name", -"default": "default", -"type": "integer" -}`, - expected: NewServicePlanParameter("name", "integer", "default", false), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - spp := &ServicePlanParameter{} - err := json.Unmarshal([]byte(tt.json), &spp) - if err != nil { - t.Errorf("unmarshalling failed: %v", err) - } - if !reflect.DeepEqual(tt.expected, *spp) { - t.Errorf("param: %v, got: %v", tt.expected, *spp) - } - }) - } -} - -func TestServicePlanParameterMarshalling(t *testing.T) { - parameter := NewServicePlanParameter("name", "string", "default", true) - parameter.Title = "title" - parameter.Description = "description" - - tests := []struct { - name string - json string - param ServicePlanParameter - }{ - { - name: "full", - json: `{ -"name": "name", -"title": "title", -"description": "description", -"default": "default", -"required": true, -"type": "string" -}`, - param: parameter, - }, - { - name: "not required", - json: `{ -"name": "name", -"default": "default", -"type": "integer" -}`, - param: NewServicePlanParameter("name", "integer", "default", false), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - actual, err := json.Marshal(tt.param) - if err != nil { - t.Errorf("marshalling failed: %v", err) - } - s := string(actual) - matcher := matchers.MatchJSONMatcher{JSONToMatch: tt.json} - success, err := matcher.Match(s) - if err != nil { - t.Errorf("couldn't match json: %v", err) - } - if !success { - t.Errorf("param: %v, got: %v", tt.json, s) - } - }) - } -} - func TestAddKubernetesComponentToDevfile(t *testing.T) { fs := devfileFileSystem.NewFakeFs() diff --git a/tests/integration/cmd_link_unlink_test.go b/tests/integration/cmd_link_unlink_test.go index 1fb9199c4..34dd28ac1 100644 --- a/tests/integration/cmd_link_unlink_test.go +++ b/tests/integration/cmd_link_unlink_test.go @@ -33,7 +33,7 @@ var _ = Describe("odo link and unlink command tests", func() { It("should display the help", func() { By("for the link command", func() { appHelp := helper.Cmd("odo", "link", "-h").ShouldPass().Out() - helper.MatchAllInOutput(appHelp, []string{"Link component to a service ", "backed by an Operator or Service Catalog", "or component"}) + helper.MatchAllInOutput(appHelp, []string{"Link component to a service ", "backed by an Operator", "or component"}) }) By("for the unlink command", func() { appHelp := helper.Cmd("odo", "unlink", "-h").ShouldPass().Out() diff --git a/tests/integration/servicecatalog/cmd_link_unlink_test.go b/tests/integration/servicecatalog/cmd_link_unlink_test.go deleted file mode 100644 index 66ee9c112..000000000 --- a/tests/integration/servicecatalog/cmd_link_unlink_test.go +++ /dev/null @@ -1,180 +0,0 @@ -package integration - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - "github.com/openshift/odo/tests/helper" -) - -var _ = Describe("odo link and unlink command tests", func() { - //new clean context for each test - /* - Uncomment when we uncomment the test specs - var context1, context2 string - var oc helper.OcRunner - */ - var commonVar helper.CommonVar - - // This is run before every Spec (It) - var _ = BeforeEach(func() { - // oc = helper.NewOcRunner("oc") - commonVar = helper.CommonBeforeEach() - //context1 = helper.CreateNewContext() - //context2 = helper.CreateNewContext() - }) - - // Clean up after the test - // This is run after every Spec (It) - var _ = AfterEach(func() { - //helper.DeleteDir(context1) - //helper.DeleteDir(context2) - helper.CommonAfterEach(commonVar) - }) - - Context("when running help for link and unlink command", func() { - It("should display the help", func() { - appHelp := helper.Cmd("odo", "link", "-h").ShouldPass().Out() - Expect(appHelp).To(ContainSubstring("Link component to a service")) - appHelp = helper.Cmd("odo", "unlink", "-h").ShouldPass().Out() - Expect(appHelp).To(ContainSubstring("Unlink component or service from a component")) - }) - }) - - /* - Context("When link between components using wrong port", func() { - It("should fail", func() { - helper.CopyExample(filepath.Join("source", "nodejs"), context1) - helper.CmdShouldPass("odo", "create", "nodejs", "frontend", "--context", context1, "--project", commonVar.Project) - helper.CmdShouldPass("odo", "push", "--context", context1) - helper.CopyExample(filepath.Join("source", "python"), context2) - helper.CmdShouldPass("odo", "create", "python", "backend", "--context", context2, "--project", commonVar.Project) - helper.CmdShouldPass("odo", "push", "--context", context2) - stdErr := helper.CmdShouldFail("odo", "link", "backend", "--context", context1, "--port", "1234") - Expect(stdErr).To(ContainSubstring("Unable to properly link to component backend using port 1234")) - }) - }) - - Context("When handling link/unlink between components", func() { - It("should link the frontend application to the backend and then unlink successfully", func() { - helper.CopyExample(filepath.Join("source", "nodejs"), context1) - helper.CmdShouldPass("odo", "create", "nodejs", "frontend", "--context", context1, "--project", commonVar.Project) - helper.CmdShouldPass("odo", "url", "create", "--port", "8080", "--context", context1) - helper.CmdShouldPass("odo", "push", "--context", context1) - frontendURL := helper.DetermineRouteURL(context1) - helper.CopyExample(filepath.Join("source", "python"), context2) - helper.CmdShouldPass("odo", "create", "python", "backend", "--context", context2, "--project", commonVar.Project) - helper.CmdShouldPass("odo", "url", "create", "--context", context2) - helper.CmdShouldPass("odo", "push", "--context", context2) - - helper.CmdShouldPass("odo", "link", "backend", "--context", context1) - - // ensure that the proper envFrom entry was created - envFromOutput := oc.GetEnvFromEntry("frontend", "app", commonVar.Project) - Expect(envFromOutput).To(ContainSubstring("backend")) - - dcName := oc.GetDcName("frontend", commonVar.Project) - // wait for DeploymentConfig rollout to finish, so we can check if application is successfully running - oc.WaitForDCRollout(dcName, commonVar.Project, 20*time.Second) - helper.HttpWaitFor(frontendURL, "Hello world from node.js!", 20, 1) - - outputErr := helper.CmdShouldFail("odo", "link", "backend", "--context", context1) - Expect(outputErr).To(ContainSubstring("been linked")) - helper.CmdShouldPass("odo", "unlink", "backend", "--context", context1) - }) - }) - - Context("When link backend between component and service", func() { - It("should link backend to service successfully", func() { - helper.CopyExample(filepath.Join("source", "nodejs"), context1) - helper.CmdShouldPass("odo", "create", "nodejs", "frontend", "--context", context1, "--project", commonVar.Project) - helper.CmdShouldPass("odo", "push", "--context", context1) - helper.CopyExample(filepath.Join("source", "python"), context2) - helper.CmdShouldPass("odo", "create", "python", "backend", "--context", context2, "--project", commonVar.Project) - helper.CmdShouldPass("odo", "push", "--context", context2) - helper.CmdShouldPass("odo", "link", "backend", "--context", context1) // context1 is the frontend - // Switching to context2 dir because --context flag is not supported with service command - helper.Chdir(context2) - helper.CmdShouldPass("odo", "service", "create", "mysql-persistent") - - ocArgs := []string{"get", "serviceinstance", "-n", commonVar.Project, "-o", "name"} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "mysql-persistent") - }) - helper.CmdShouldPass("odo", "link", "mysql-persistent", "--wait-for-target", "--component", "backend", "--project", commonVar.Project) - // ensure that the proper envFrom entry was created - envFromOutput := oc.GetEnvFromEntry("backend", "app", commonVar.Project) - Expect(envFromOutput).To(ContainSubstring("mysql-persistent")) - outputErr := helper.CmdShouldFail("odo", "link", "mysql-persistent", "--context", context2) - Expect(outputErr).To(ContainSubstring("been linked")) - }) - }) - - Context("When deleting service and unlink the backend from the frontend", func() { - It("should pass", func() { - helper.CopyExample(filepath.Join("source", "nodejs"), context1) - helper.CmdShouldPass("odo", "create", "nodejs", "frontend", "--context", context1, "--project", commonVar.Project) - helper.CmdShouldPass("odo", "push", "--context", context1) - helper.CopyExample(filepath.Join("source", "python"), context2) - helper.CmdShouldPass("odo", "create", "python", "backend", "--context", context2, "--project", commonVar.Project) - helper.CmdShouldPass("odo", "push", "--context", context2) - helper.CmdShouldPass("odo", "link", "backend", "--context", context1) - helper.Chdir(context2) - helper.CmdShouldPass("odo", "service", "create", "mysql-persistent") - - ocArgs := []string{"get", "serviceinstance", "-n", commonVar.Project, "-o", "name"} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "mysql-persistent") - }) - helper.CmdShouldPass("odo", "service", "delete", "mysql-persistent", "-f") - // ensure that the backend no longer has an envFrom value - backendEnvFromOutput := oc.GetEnvFromEntry("backend", "app", commonVar.Project) - Expect(backendEnvFromOutput).To(Equal("''")) - // ensure that the frontend envFrom was not changed - frontEndEnvFromOutput := oc.GetEnvFromEntry("frontend", "app", commonVar.Project) - Expect(frontEndEnvFromOutput).To(ContainSubstring("backend")) - helper.CmdShouldPass("odo", "unlink", "backend", "--component", "frontend", "--project", commonVar.Project) - // ensure that the proper envFrom entry was created - envFromOutput := oc.GetEnvFromEntry("frontend", "app", commonVar.Project) - Expect(envFromOutput).To(Equal("''")) - }) - }) - - Context("When linking or unlinking a service or component", func() { - It("should print the environment variables being linked/unlinked", func() { - helper.CopyExample(filepath.Join("source", "python"), context1) - helper.CmdShouldPass("odo", "create", "python", "component1", "--context", context1, "--project", commonVar.Project) - helper.CmdShouldPass("odo", "push", "--context", context1) - helper.CopyExample(filepath.Join("source", "nodejs"), context2) - helper.CmdShouldPass("odo", "create", "nodejs", "component2", "--context", context2, "--project", commonVar.Project) - helper.CmdShouldPass("odo", "push", "--context", context2) - - // tests for linking a component to a component - stdOut := helper.CmdShouldPass("odo", "link", "component2", "--context", context1) - helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were added", "COMPONENT_COMPONENT2_HOST", "COMPONENT_COMPONENT2_PORT"}) - - // tests for unlinking a component from a component - stdOut = helper.CmdShouldPass("odo", "unlink", "component2", "--context", context1) - helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were removed", "COMPONENT_COMPONENT2_HOST", "COMPONENT_COMPONENT2_PORT"}) - - // first create a service - helper.CmdShouldPass("odo", "service", "create", "-w", "dh-postgresql-apb", "--project", commonVar.Project, "--plan", "dev", - "-p", "postgresql_user=luke", "-p", "postgresql_password=secret", - "-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6") - ocArgs := []string{"get", "serviceinstance", "-o", "name", "-n", commonVar.Project} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "dh-postgresql-apb") - }) - - // tests for linking a service to a component - stdOut = helper.CmdShouldPass("odo", "link", "dh-postgresql-apb", "--context", context1) - helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were added", "DB_PORT", "DB_HOST"}) - - // tests for unlinking a service to a component - stdOut = helper.CmdShouldPass("odo", "unlink", "dh-postgresql-apb", "--context", context1) - helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were removed", "DB_PORT", "DB_HOST"}) - }) - }) - - */ -}) diff --git a/tests/integration/servicecatalog/cmd_service_test.go b/tests/integration/servicecatalog/cmd_service_test.go deleted file mode 100644 index 5f53cc80a..000000000 --- a/tests/integration/servicecatalog/cmd_service_test.go +++ /dev/null @@ -1,421 +0,0 @@ -package integration - -import ( - "path/filepath" - "strings" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - "github.com/openshift/odo/tests/helper" -) - -var _ = Describe("odo service command tests", func() { - var app, serviceName string - /* - Uncomment when we uncomment the test specs - var context1, context2 string - var oc helper.OcRunner - */ - var commonVar helper.CommonVar - - // This is run before every Spec (It) - var _ = BeforeEach(func() { - //oc = helper.NewOcRunner("oc") - commonVar = helper.CommonBeforeEach() - // context1 = helper.CreateNewContext() - // context2 = helper.CreateNewContext() - }) - - // Clean up after the test - // This is run after every Spec (It) - var _ = AfterEach(func() { - helper.CommonAfterEach(commonVar) - // helper.DeleteDir(context1) - // helper.DeleteDir(context2) - }) - - Context("when running help for service command", func() { - It("should display the help", func() { - appHelp := helper.Cmd("odo", "service", "-h").ShouldPass().Out() - Expect(appHelp).To(ContainSubstring("Perform service catalog operations")) - }) - }) - - Context("check catalog service search functionality", func() { - It("check that a service does not exist", func() { - serviceRandomName := helper.RandString(7) - output := helper.Cmd("odo", "catalog", "search", "service", serviceRandomName).ShouldFail().Err() - Expect(output).To(ContainSubstring("no service matched the query: " + serviceRandomName)) - }) - }) - - Context("checking machine readable output for service catalog", func() { - It("should succeed listing catalog components", func() { - // Since service catalog is constantly changing, we simply check to see if this command passes.. rather than checking the JSON each time. - output := helper.Cmd("odo", "catalog", "list", "services", "-o", "json").ShouldPass().Out() - Expect(output).To(ContainSubstring("List")) - }) - }) - - Context("checking machine readable output for service catalog", func() { - It("should succeed listing catalog components", func() { - // Since service catalog is constantly changing, we simply check to see if this command passes.. rather than checking the JSON each time. - helper.Cmd("odo", "catalog", "list", "services", "-o", "json").ShouldPass() - }) - }) - - Context("check search functionality", func() { - - It("should pass with searching for part of a service name", func() { - - // We just use "sql" as some catalogs only have postgresql-persistent and - // others dh-postgresql-db. So let's just see if there's "any" postgresql to begin - // with - output := helper.Cmd("odo", "catalog", "search", "service", "sql").ShouldPass().Out() - Expect(output).To(ContainSubstring("postgresql")) - }) - - }) - - Context("create service with Env non-interactively", func() { - JustBeforeEach(func() { - app = helper.RandString(7) - }) - - It("should be able to create postgresql with env", func() { - helper.Cmd("odo", "service", "create", "dh-postgresql-apb", "--project", commonVar.Project, "--app", app, - "--plan", "dev", "-p", "postgresql_user=lukecage", "-p", "postgresql_password=secret", - "-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6", "-w").ShouldPass() - // there is only a single pod in the project - ocArgs := []string{"describe", "pod", "-n", commonVar.Project} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "lukecage") - }) - - // Delete the service - helper.Cmd("odo", "service", "delete", "dh-postgresql-apb", "-f", "--app", app, "--project", commonVar.Project).ShouldPass() - }) - - It("should be able to create postgresql with env multiple times", func() { - helper.Cmd("odo", "service", "create", "dh-postgresql-apb", "--project", commonVar.Project, "--app", app, - "--plan", "dev", "-p", "postgresql_user=lukecage", "-p", "postgresql_user=testworker", "-p", "postgresql_password=secret", - "-p", "postgresql_password=universe", "-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6", "-w").ShouldPass() - // there is only a single pod in the project - ocArgs := []string{"describe", "pod", "-n", commonVar.Project} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "testworker") - }) - - // Delete the service - helper.Cmd("odo", "service", "delete", "dh-postgresql-apb", "-f", "--app", app, "--project", commonVar.Project).ShouldPass() - }) - }) - - /* - Context("When creating with a spring boot application", func() { - JustBeforeEach(func() { - context = helper.CreateNewContext() - os.Setenv("GLOBALODOCONFIG", filepath.Join(context, "config.yaml")) - project = helper.CreateRandProject() - originalDir = helper.Getwd() - helper.Chdir(context) - }) - JustAfterEach(func() { - helper.DeleteProject(project) - helper.Chdir(originalDir) - helper.DeleteDir(context) - os.Unsetenv("GLOBALODOCONFIG") - }) - It("should be able to create postgresql and link it with springboot", func() { - oc.ImportJavaIS(project) - helper.CopyExample(filepath.Join("source", "openjdk-sb-postgresql"), context) - - // Local config needs to be present in order to create service https://github.com/openshift/odo/issues/1602 - helper.CmdShouldPass("odo", "create", "--s2i", "java:8", "sb-app", "--project", project) - - // Create a URL - helper.CmdShouldPass("odo", "url", "create", "--port", "8080") - - // push - helper.CmdShouldPass("odo", "push") - - // create the postgres service - helper.CmdShouldPass("odo", "service", "create", "dh-postgresql-apb", "--project", project, "--plan", "dev", - "-p", "postgresql_user=luke", "-p", "postgresql_password=secret", - "-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6", "-w") - - // link the service - helper.CmdShouldPass("odo", "link", "dh-postgresql-apb", "--project", project, "-w", "--wait-for-target") - odoArgs := []string{"service", "list"} - helper.WaitForCmdOut("odo", odoArgs, 1, true, func(output string) bool { - return strings.Contains(output, "dh-postgresql-apb") && - strings.Contains(output, "ProvisionedAndLinked") - }) - - routeURL := helper.DetermineRouteURL("") - - // Ping said URL - helper.HttpWaitFor(routeURL, "Spring Boot", 90, 1) - - // Delete the service - helper.CmdShouldPass("odo", "service", "delete", "dh-postgresql-apb", "-f") - - // Delete the component and the config - helper.CmdShouldPass("odo", "delete", "sb-app", "-f", "--all") - }) - }) - */ - - // TODO: auth issue, we need to find a proper way how to test it without requiring cluster admin privileges - - // Context("odo hides a hidden service in service catalog", func() { - // It("not show a hidden service in the catalog", func() { - // runCmdShouldPass("oc apply -f https://github.com/openshift/library/raw/master/official/sso/templates/sso72-https.json -n openshift") - // outputErr := runCmdShouldFail("odo catalog search service sso72-https") - // Expect(outputErr).To(ContainSubstring("No service matched the query: sso72-https")) - // }) - // }) - - Context("When working from outside a component dir", func() { - JustBeforeEach(func() { - app = helper.RandString(7) - serviceName = "odo-postgres-service" - helper.Chdir(commonVar.Context) - }) - - It("should be able to create, list and delete a service using a given value for --context", func() { - // create a component by copying the example - helper.CopyExample(filepath.Join("source", "python"), commonVar.Context) - helper.Cmd("odo", "create", "--s2i", "python", "--app", app, "--project", commonVar.Project).ShouldPass() - - // cd to the originalDir to create service using --context - helper.Chdir(commonVar.OriginalWorkingDirectory) - helper.Cmd("odo", "service", "create", "dh-postgresql-apb", "--plan", "dev", - "-p", "postgresql_user=luke", "-p", "postgresql_password=secret", - "-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6", serviceName, - "--context", commonVar.Context, - ).ShouldPass() - - // now check if listing the service using --context works - ocArgs := []string{"get", "serviceinstance", "-o", "name", "-n", commonVar.Project} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, serviceName) - }) - stdOut := helper.Cmd("odo", "service", "list", "--context", commonVar.Context).ShouldPass().Out() - Expect(stdOut).To(ContainSubstring(serviceName)) - - // now check if deleting the service using --context works - stdOut = helper.Cmd("odo", "service", "delete", "-f", serviceName, "--context", commonVar.Context).ShouldPass().Out() - Expect(stdOut).To(ContainSubstring(serviceName)) - }) - - It("should be able to list services, as well as json list in a given app and project combination", func() { - // create a component by copying the example - helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context) - helper.Cmd("odo", "create", "--s2i", "nodejs", "--app", app, "--project", commonVar.Project).ShouldPass() - - // create a service from within a component directory - helper.Cmd("odo", "service", "create", "dh-prometheus-apb", "--plan", "ephemeral", - "--app", app, "--project", commonVar.Project, - ).ShouldPass() - ocArgs := []string{"get", "serviceinstance", "-o", "name", "-n", commonVar.Project} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "dh-prometheus-apb") - }) - - // Listing the services should work as expected from within the component directory. - // This means, it should not require --app or --project flags - stdOut := helper.Cmd("odo", "service", "list").ShouldPass().Out() - Expect(stdOut).To(ContainSubstring("dh-prometheus-apb")) - - // Check json output - stdOut = helper.Cmd("odo", "service", "list", "-o", "json").ShouldPass().Out() - helper.MatchAllInOutput(stdOut, []string{"dh-prometheus-apb", "List"}) - - // cd to a non-component directory and list services - helper.Chdir(commonVar.OriginalWorkingDirectory) - stdOut = helper.Cmd("odo", "service", "list", "--app", app, "--project", commonVar.Project).ShouldPass().Out() - Expect(stdOut).To(ContainSubstring("dh-prometheus-apb")) - - // Check json output - helper.Chdir(commonVar.OriginalWorkingDirectory) - stdOut = helper.Cmd("odo", "service", "list", "--app", app, "--project", commonVar.Project, "-o", "json").ShouldPass().Out() - helper.MatchAllInOutput(stdOut, []string{"dh-prometheus-apb", "List"}) - }) - }) - - Context("When working from outside a component dir", func() { - It("should be able to create, list and delete services without a context and using --app and --project flags instaed", func() { - app = helper.RandString(7) - // create the service - helper.Cmd("odo", "service", "create", "dh-postgresql-apb", "--plan", "dev", - "-p", "postgresql_user=luke", "-p", "postgresql_password=secret", - "-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6", - "--app", app, "--project", commonVar.Project).ShouldPass() - - ocArgs := []string{"get", "serviceinstance", "-o", "name", "-n", commonVar.Project} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "dh-postgresql-apb") - }) - - // list the service using app and project flags - stdOut := helper.Cmd("odo", "service", "list", "--app", app, "--project", commonVar.Project).ShouldPass().Out() - Expect(stdOut).To(ContainSubstring("dh-postgresql-apb")) - - // delete the service using app and project flags - helper.Cmd("odo", "service", "delete", "-f", "dh-postgresql-apb", "--app", app, "--project", commonVar.Project).ShouldPass() - }) - }) - - /* - Context("When link backend between component and service", func() { - JustBeforeEach(func() { - preSetup() - }) - JustAfterEach(func() { - cleanPreSetup() - }) - It("should link backend to service successfully", func() { - helper.CopyExample(filepath.Join("source", "nodejs"), context1) - helper.CmdShouldPass("odo", "create", "--s2i", "nodejs", "frontend", "--context", context1, "--project", project) - helper.CmdShouldPass("odo", "push", "--context", context1) - helper.CopyExample(filepath.Join("source", "python"), context2) - helper.CmdShouldPass("odo", "create", "--s2i", "python", "backend", "--context", context2, "--project", project) - helper.CmdShouldPass("odo", "push", "--context", context2) - helper.CmdShouldPass("odo", "link", "backend", "--context", context1) - // Switching to context2 dir because --context flag is not supported with service command - helper.Chdir(context2) - helper.CmdShouldPass("odo", "service", "create", "mysql-persistent") - - ocArgs := []string{"get", "serviceinstance", "-n", project, "-o", "name"} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "mysql-persistent") - }) - helper.CmdShouldPass("odo", "link", "mysql-persistent", "--wait-for-target", "--component", "backend", "--project", project) - // ensure that the proper envFrom entry was created - envFromOutput := oc.GetEnvFromEntry("backend", "app", project) - Expect(envFromOutput).To(ContainSubstring("mysql-persistent")) - outputErr := helper.CmdShouldFail("odo", "link", "mysql-persistent", "--context", context2) - Expect(outputErr).To(ContainSubstring("been linked")) - }) - }) - */ - - /* - Context("When deleting service and unlink the backend from the frontend", func() { - JustBeforeEach(func() { - preSetup() - }) - JustAfterEach(func() { - cleanPreSetup() - }) - It("should pass", func() { - helper.CopyExample(filepath.Join("source", "nodejs"), context1) - helper.CmdShouldPass("odo", "create", "--s2i", "nodejs", "frontend", "--context", context1, "--project", project) - helper.CmdShouldPass("odo", "push", "--context", context1) - helper.CopyExample(filepath.Join("source", "python"), context2) - helper.CmdShouldPass("odo", "create", "--s2i", "python", "backend", "--context", context2, "--project", project) - helper.CmdShouldPass("odo", "push", "--context", context2) - helper.CmdShouldPass("odo", "link", "backend", "--context", context1) - helper.Chdir(context2) - helper.CmdShouldPass("odo", "service", "create", "mysql-persistent") - - ocArgs := []string{"get", "serviceinstance", "-n", project, "-o", "name"} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "mysql-persistent") - }) - helper.CmdShouldPass("odo", "service", "delete", "mysql-persistent", "-f") - // ensure that the backend no longer has an envFrom value - backendEnvFromOutput := oc.GetEnvFromEntry("backend", "app", project) - Expect(backendEnvFromOutput).To(Equal("''")) - // ensure that the frontend envFrom was not changed - frontEndEnvFromOutput := oc.GetEnvFromEntry("frontend", "app", project) - Expect(frontEndEnvFromOutput).To(ContainSubstring("backend")) - helper.CmdShouldPass("odo", "unlink", "backend", "--component", "frontend", "--project", project) - // ensure that the proper envFrom entry was created - envFromOutput := oc.GetEnvFromEntry("frontend", "app", project) - Expect(envFromOutput).To(Equal("''")) - }) - }) - */ - - /* - Context("When linking or unlinking a service or component", func() { - JustBeforeEach(func() { - preSetup() - }) - JustAfterEach(func() { - cleanPreSetup() - }) - - It("should print the environment variables being linked/unlinked", func() { - helper.CopyExample(filepath.Join("source", "python"), context1) - helper.CmdShouldPass("odo", "create", "--s2i", "python", "component1", "--context", context1, "--project", project) - helper.CmdShouldPass("odo", "push", "--context", context1) - helper.CopyExample(filepath.Join("source", "nodejs"), context2) - helper.CmdShouldPass("odo", "create", "--s2i", "nodejs", "component2", "--context", context2, "--project", project) - helper.CmdShouldPass("odo", "push", "--context", context2) - - // tests for linking a component to a component - stdOut := helper.CmdShouldPass("odo", "link", "component2", "--context", context1) - helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were added", "COMPONENT_COMPONENT2_HOST", "COMPONENT_COMPONENT2_PORT"}) - - // tests for unlinking a component from a component - stdOut = helper.CmdShouldPass("odo", "unlink", "component2", "--context", context1) - helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were removed", "COMPONENT_COMPONENT2_HOST", "COMPONENT_COMPONENT2_PORT"}) - - // first create a service - helper.CmdShouldPass("odo", "service", "create", "-w", "dh-postgresql-apb", "--project", project, "--plan", "dev", - "-p", "postgresql_user=luke", "-p", "postgresql_password=secret", - "-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6") - ocArgs := []string{"get", "serviceinstance", "-o", "name", "-n", project} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "dh-postgresql-apb") - }) - - // tests for linking a service to a component - stdOut = helper.CmdShouldPass("odo", "link", "dh-postgresql-apb", "--context", context1) - helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were added", "DB_PORT", "DB_HOST"}) - - // tests for unlinking a service to a component - stdOut = helper.CmdShouldPass("odo", "unlink", "dh-postgresql-apb", "--context", context1) - helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were removed", "DB_PORT", "DB_HOST"}) - }) - }) - */ - - Context("When describing services", func() { - It("should succeed when we're describing service that could have integer value for default field", func() { - // https://github.com/openshift/odo/issues/2488 - helper.CopyExample(filepath.Join("source", "python"), commonVar.Context) - helper.Cmd("odo", "create", "--s2i", "python", "component1", "--context", commonVar.Context, "--project", commonVar.Project).ShouldPass() - helper.Chdir(commonVar.Context) - - helper.Cmd("odo", "catalog", "describe", "service", "dh-es-apb").ShouldPass() - helper.Cmd("odo", "catalog", "describe", "service", "dh-import-vm-apb").ShouldPass() - }) - }) - - Context("When the application is deleted", func() { - JustBeforeEach(func() { - app = helper.RandString(6) - }) - It("should delete the service(s) in the application as well", func() { - helper.Cmd("odo", "service", "create", "--app", app, "-w", "dh-postgresql-apb", "--project", commonVar.Project, "--plan", "dev", - "-p", "postgresql_user=luke", "-p", "postgresql_password=secret", - "-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6").ShouldPass() - ocArgs := []string{"get", "serviceinstance", "-o", "name", "-n", commonVar.Project} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "dh-postgresql-apb") - }) - - helper.Cmd("odo", "app", "delete", app, "--project", commonVar.Project, "-f").ShouldPass() - - ocArgs = []string{"get", "serviceinstances", "-n", commonVar.Project} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "No resources found") - }, true) - }) - }) -}) diff --git a/tests/integration/servicecatalog/servicecatalog_suite_test.go b/tests/integration/servicecatalog/servicecatalog_suite_test.go deleted file mode 100644 index cef14e658..000000000 --- a/tests/integration/servicecatalog/servicecatalog_suite_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package integration - -import ( - "testing" - - "github.com/openshift/odo/tests/helper" -) - -func TestServicecatalog(t *testing.T) { - helper.RunTestSpecs(t, "Servicecatalog Suite") -}