From 16a7a14df57d67383b3b1fcaa1efc2e06412c79a Mon Sep 17 00:00:00 2001 From: Dharmit Shah Date: Wed, 21 Apr 2021 18:04:35 +0530 Subject: [PATCH 1/2] Creates service on cluster upon "odo push" --- go.mod | 1 + .../adapters/kubernetes/component/adapter.go | 48 +++++-- pkg/odo/cli/component/devfile.go | 6 +- pkg/odo/cli/component/push.go | 5 +- pkg/odo/cli/service/create.go | 30 ++--- pkg/odo/cli/service/operator_backend.go | 92 +------------- pkg/service/service.go | 120 +++++++++++++++++- 7 files changed, 181 insertions(+), 121 deletions(-) diff --git a/go.mod b/go.mod index 577c31dbb73..bcdf51d8586 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,7 @@ require ( k8s.io/cli-runtime v0.17.0 k8s.io/client-go v12.0.0+incompatible k8s.io/klog v1.0.0 + k8s.io/klog/v2 v2.0.0 k8s.io/kubectl v0.0.0 sigs.k8s.io/yaml v1.2.0 ) diff --git a/pkg/devfile/adapters/kubernetes/component/adapter.go b/pkg/devfile/adapters/kubernetes/component/adapter.go index af4c3178533..3a9391c3509 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter.go @@ -8,6 +8,8 @@ import ( "strings" "time" + "github.com/openshift/odo/pkg/service" + "github.com/devfile/library/pkg/devfile/generator" componentlabels "github.com/openshift/odo/pkg/component/labels" "github.com/openshift/odo/pkg/envinfo" @@ -172,6 +174,26 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) { return errors.Wrap(err, "unable to create or update component") } + // fetch the "kubernetes inlined components" to create them on cluster + // from odo standpoint, these components contain yaml manifest of an odo service or an odo link + k8sComponents, err := a.Devfile.Data.GetComponents(parsercommon.DevfileOptions{ + ComponentOptions: parsercommon.ComponentOptions{ComponentType: devfilev1.KubernetesComponentType}, + }) + if err != nil { + return errors.Wrap(err, "error while trying to fetch service(s) from devfile") + } + // create the Kubernetes objects from the manifest + services, err := service.CreateServiceFromKubernetesInlineComponents(a.Client.GetKubeClient(), k8sComponents) + if err != nil { + return errors.Wrap(err, "failed to create service(s) associated with the component") + } + + if len(services) == 1 { + log.Infof("Created service %q on the cluster; refer %q to know how to link it to the component", services[0], "odo link -h") + } else if len(services) > 1 { + log.Infof("Created services %q on the cluster; refer %q to know how to link them to the component", strings.Join(services, ", "), "odo link -h") + } + deployment, err := a.Client.GetKubeClient().WaitForDeploymentRollout(a.ComponentName) if err != nil { return errors.Wrap(err, "error while waiting for deployment rollout") @@ -376,7 +398,7 @@ func (a Adapter) createOrUpdateComponent(componentExists bool, ei envinfo.EnvSpe } if len(containers) == 0 { - return fmt.Errorf("No valid components found in the devfile") + return fmt.Errorf("no valid components found in the devfile") } // Add the project volume before generating init containers @@ -471,7 +493,7 @@ func (a Adapter) createOrUpdateComponent(componentExists bool, ei envinfo.EnvSpe ObjectMeta: objectMeta, SelectorLabels: selectorLabels, } - service, err := generator.GetService(a.Devfile, serviceParams, parsercommon.DevfileOptions{}) + svc, err := generator.GetService(a.Devfile, serviceParams, parsercommon.DevfileOptions{}) if err != nil { return err } @@ -488,21 +510,21 @@ func (a Adapter) createOrUpdateComponent(componentExists bool, ei envinfo.EnvSpe klog.V(2).Infof("Successfully updated component %v", componentName) oldSvc, err := a.Client.GetKubeClient().KubeClient.CoreV1().Services(a.Client.Namespace).Get(componentName, metav1.GetOptions{}) ownerReference := generator.GetOwnerReference(deployment) - service.OwnerReferences = append(service.OwnerReferences, ownerReference) + svc.OwnerReferences = append(svc.OwnerReferences, ownerReference) if err != nil { // no old service was found, create a new one - if len(service.Spec.Ports) > 0 { - _, err = a.Client.GetKubeClient().CreateService(*service) + if len(svc.Spec.Ports) > 0 { + _, err = a.Client.GetKubeClient().CreateService(*svc) if err != nil { return err } klog.V(2).Infof("Successfully created Service for component %s", componentName) } } else { - if len(service.Spec.Ports) > 0 { - service.Spec.ClusterIP = oldSvc.Spec.ClusterIP - service.ResourceVersion = oldSvc.GetResourceVersion() - _, err = a.Client.GetKubeClient().UpdateService(*service) + if len(svc.Spec.Ports) > 0 { + svc.Spec.ClusterIP = oldSvc.Spec.ClusterIP + svc.ResourceVersion = oldSvc.GetResourceVersion() + _, err = a.Client.GetKubeClient().UpdateService(*svc) if err != nil { return err } @@ -521,9 +543,9 @@ func (a Adapter) createOrUpdateComponent(componentExists bool, ei envinfo.EnvSpe } klog.V(2).Infof("Successfully created component %v", componentName) ownerReference := generator.GetOwnerReference(deployment) - service.OwnerReferences = append(service.OwnerReferences, ownerReference) - if len(service.Spec.Ports) > 0 { - _, err = a.Client.GetKubeClient().CreateService(*service) + svc.OwnerReferences = append(svc.OwnerReferences, ownerReference) + if len(svc.Spec.Ports) > 0 { + _, err = a.Client.GetKubeClient().CreateService(*svc) if err != nil { return err } @@ -549,7 +571,7 @@ func getFirstContainerWithSourceVolume(containers []corev1.Container) (string, s } } - return "", "", fmt.Errorf("In order to sync files, odo requires at least one component in a devfile to set 'mountSources: true'") + return "", "", fmt.Errorf("in order to sync files, odo requires at least one component in a devfile to set 'mountSources: true'") } // Delete deletes the component diff --git a/pkg/odo/cli/component/devfile.go b/pkg/odo/cli/component/devfile.go index de958ce1b10..d80681c8308 100644 --- a/pkg/odo/cli/component/devfile.go +++ b/pkg/odo/cli/component/devfile.go @@ -83,11 +83,11 @@ func (po *PushOptions) devfilePushInner() (err error) { // Parse devfile and validate devObj, err := devfile.ParseDevfileAndValidate(parser.ParserArgs{Path: po.DevfilePath}) - if err != nil { return err } + // odo specific validations err = validate.ValidateDevfileData(devObj.Data) if err != nil { return err @@ -138,12 +138,12 @@ func (po *PushOptions) devfilePushInner() (err error) { // Start or update the component err = devfileHandler.Push(pushParams) if err != nil { - err = errors.Errorf("Failed to start component with name %s. Error: %v", + err = errors.Errorf("Failed to start component with name %q. Error: %v", componentName, err, ) } else { - log.Infof("\nPushing devfile component %s", componentName) + log.Infof("\nPushing devfile component %q", componentName) log.Success("Changes successfully pushed to component") } diff --git a/pkg/odo/cli/component/push.go b/pkg/odo/cli/component/push.go index 4bd5373616e..84ba68a7a98 100644 --- a/pkg/odo/cli/component/push.go +++ b/pkg/odo/cli/component/push.go @@ -4,14 +4,15 @@ import ( "fmt" "path/filepath" + "github.com/openshift/odo/pkg/component" + "github.com/openshift/odo/pkg/log" + "github.com/devfile/library/pkg/devfile" "github.com/openshift/odo/pkg/devfile/validate" "github.com/openshift/odo/pkg/envinfo" ktemplates "k8s.io/kubectl/pkg/util/templates" "github.com/devfile/library/pkg/devfile/parser" - "github.com/openshift/odo/pkg/component" - "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/occlient" projectCmd "github.com/openshift/odo/pkg/odo/cli/project" "github.com/openshift/odo/pkg/odo/genericclioptions" diff --git a/pkg/odo/cli/service/create.go b/pkg/odo/cli/service/create.go index 4fc0af38ce4..4cda9a4bc54 100644 --- a/pkg/odo/cli/service/create.go +++ b/pkg/odo/cli/service/create.go @@ -126,20 +126,6 @@ func (o *CreateOptions) Complete(name string, cmd *cobra.Command, args []string) return o.Backend.CompleteServiceCreate(o, cmd, args) } -// 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 "" -} - // 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 @@ -159,7 +145,7 @@ func (o *CreateOptions) Run() (err error) { // Information on what to do next; don't do this if "--dry-run" was requested as it gets appended to the file if !o.DryRun { - log.Infof("You can now link the service to a component using 'odo link'; check 'odo link -h'") + log.Info("Successfully added service to the configuration; do 'odo push' to create service on the cluster") } equivalent := o.outputNonInteractiveEquivalent() @@ -169,6 +155,20 @@ func (o *CreateOptions) Run() (err error) { 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() diff --git a/pkg/odo/cli/service/operator_backend.go b/pkg/odo/cli/service/operator_backend.go index d9b4a60118e..c56e99e2ebf 100644 --- a/pkg/odo/cli/service/operator_backend.go +++ b/pkg/odo/cli/service/operator_backend.go @@ -19,63 +19,6 @@ import ( "github.com/spf13/cobra" ) -// DynamicCRD holds the original CR obtained from the Operator (a CSV), or user -// (when they use --from-file flag), and few other attributes that are likely -// to be used to validate a CRD before creating a service from it -type DynamicCRD struct { - // contains the CR as obtained from CSV or user - OriginalCRD map[string]interface{} -} - -func NewDynamicCRD() *DynamicCRD { - return &DynamicCRD{} -} - -// validateMetadataInCRD validates if the CRD has metadata.name field and returns an error -func (d *DynamicCRD) validateMetadataInCRD() error { - metadata, ok := d.OriginalCRD["metadata"].(map[string]interface{}) - if !ok { - // this condition is satisfied if there's no metadata at all in the provided CRD - return fmt.Errorf("couldn't find \"metadata\" in the yaml; need metadata start the service") - } - - if _, ok := metadata["name"].(string); ok { - // found the metadata.name; no error - return nil - } - return fmt.Errorf("couldn't find metadata.name in the yaml; provide a name for the service") -} - -// setServiceName modifies the CRD to contain user provided name on the CLI -// instead of using the default one in almExample -func (d *DynamicCRD) setServiceName(name string) { - metaMap := d.OriginalCRD["metadata"].(map[string]interface{}) - - for k := range metaMap { - if k == "name" { - metaMap[k] = name - return - } - // if metadata doesn't have 'name' field, we set it up - metaMap["name"] = name - } -} - -// getServiceNameFromCRD fetches the service name from metadata.name field of the CRD -func (d *DynamicCRD) getServiceNameFromCRD() (string, error) { - metadata, ok := d.OriginalCRD["metadata"].(map[string]interface{}) - if !ok { - // this condition is satisfied if there's no metadata at all in the provided CRD - return "", fmt.Errorf("couldn't find \"metadata\" in the yaml; need metadata.name to start the service") - } - - if name, ok := metadata["name"].(string); ok { - // found the metadata.name; no error - return name, nil - } - return "", fmt.Errorf("couldn't find metadata.name in the yaml; provide a name for the service") -} - // This CompleteServiceCreate contains logic to complete the "odo service create" call for the case of Operator backend func (b *OperatorBackend) CompleteServiceCreate(o *CreateOptions, cmd *cobra.Command, args []string) (err error) { // since interactive mode is not supported for Operators yet, set it to false @@ -109,7 +52,7 @@ func (b *OperatorBackend) CompleteServiceCreate(o *CreateOptions, cmd *cobra.Com } func (b *OperatorBackend) ValidateServiceCreate(o *CreateOptions) (err error) { - d := NewDynamicCRD() + d := svc.NewDynamicCRD() // if the user wants to create service from a file, we check for // existence of file and validate if the requested operator and CR // exist on the cluster @@ -142,7 +85,7 @@ func (b *OperatorBackend) ValidateServiceCreate(o *CreateOptions) (err error) { return err } - err = d.validateMetadataInCRD() + err = d.ValidateMetadataInCRD() if err != nil { return err } @@ -158,9 +101,9 @@ func (b *OperatorBackend) ValidateServiceCreate(o *CreateOptions) (err error) { return fmt.Errorf("service %q already exists; please provide a different name or delete the existing service first", svcFullName) } - d.setServiceName(o.ServiceName) + d.SetServiceName(o.ServiceName) } else { - o.ServiceName, err = d.getServiceNameFromCRD() + o.ServiceName, err = d.GetServiceNameFromCRD() if err != nil { return err } @@ -202,10 +145,10 @@ func (b *OperatorBackend) ValidateServiceCreate(o *CreateOptions) (err error) { return fmt.Errorf("service %q already exists; please provide a different name or delete the existing service first", svcFullName) } - d.setServiceName(o.ServiceName) + d.SetServiceName(o.ServiceName) } - err = d.validateMetadataInCRD() + err = d.ValidateMetadataInCRD() if err != nil { return err } @@ -214,7 +157,7 @@ func (b *OperatorBackend) ValidateServiceCreate(o *CreateOptions) (err error) { b.CustomResourceDefinition = d.OriginalCRD if o.ServiceName == "" { - o.ServiceName, err = d.getServiceNameFromCRD() + o.ServiceName, err = d.GetServiceNameFromCRD() if err != nil { return err } @@ -235,18 +178,6 @@ func (b *OperatorBackend) ValidateServiceCreate(o *CreateOptions) (err error) { func (b *OperatorBackend) RunServiceCreate(o *CreateOptions) (err error) { s := &log.Status{} - // in case of an Operator backed service, name of the service is - // provided by the yaml specification in alm-examples. It might also - // happen that a user wants to spin up Service Catalog based service in - // spite of having 4.x cluster mode but we're not supporting - // interacting with both Operator Hub and Service Catalog on 4.x. So - // the user won't get to see service name in the log message - if !o.DryRun { - log.Infof("Deploying service %q of type: %q", o.ServiceName, b.CustomResource) - s = log.Spinner("Deploying service") - defer s.End(false) - } - // if cluster has resources of type CSV and o.CustomResource is not // empty, we're expected to create an Operator backed service if o.DryRun { @@ -266,15 +197,6 @@ func (b *OperatorBackend) RunServiceCreate(o *CreateOptions) (err error) { return nil } else { - err = svc.CreateOperatorService(o.KClient, b.group, b.version, b.resource, b.CustomResourceDefinition) - if err != nil { - // TODO: logic to remove CRD info from devfile because service creation failed. - return err - } else { - s.End(true) - log.Successf(`Service %q was created`, o.ServiceName) - } - crdYaml, err := yaml.Marshal(b.CustomResourceDefinition) if err != nil { return err diff --git a/pkg/service/service.go b/pkg/service/service.go index 3d17ed6a29c..e9e42cb4582 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" + "github.com/ghodss/yaml" + devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser/data/v2/common" "github.com/openshift/odo/pkg/kclient" @@ -97,7 +99,6 @@ func doesCRExist(kind string, csvs *olm.ClusterServiceVersionList) (olm.ClusterS } } return olm.ClusterServiceVersion{}, errors.New("could not find the requested cluster resource") - } // CreateOperatorService creates new service (actually a Deployment) from OperatorHub @@ -484,9 +485,9 @@ func updateStatusIfMatchingDeploymentExists(dcs []appsv1.DeploymentConfig, secre } } -// IsValidOperatorServiceName checks if the provided name follows +// IsOperatorServiceNameValid checks if the provided name follows // / format. For example: "EtcdCluster/example" is -// a valid servicename but "EtcdCluster/", "EtcdCluster", "example" aren't. +// a valid service name but "EtcdCluster/", "EtcdCluster", "example" aren't. func IsOperatorServiceNameValid(name string) (string, string, error) { checkName := strings.SplitN(name, "/", 2) @@ -738,3 +739,116 @@ func DeleteKubernetesComponentFromDevfile(name string, devfileObj parser.Devfile return devfileObj.WriteYamlDevfile() } + +// DynamicCRD holds the original CR obtained from the Operator (a CSV), or user +// (when they use --from-file flag), and few other attributes that are likely +// to be used to validate a CRD before creating a service from it +type DynamicCRD struct { + // contains the CR as obtained from CSV or user + OriginalCRD map[string]interface{} +} + +func NewDynamicCRD() *DynamicCRD { + return &DynamicCRD{} +} + +// ValidateMetadataInCRD validates if the CRD has metadata.name field and returns an error +func (d *DynamicCRD) ValidateMetadataInCRD() error { + metadata, ok := d.OriginalCRD["metadata"].(map[string]interface{}) + if !ok { + // this condition is satisfied if there's no metadata at all in the provided CRD + return fmt.Errorf("couldn't find \"metadata\" in the yaml; need metadata start the service") + } + + if _, ok := metadata["name"].(string); ok { + // found the metadata.name; no error + return nil + } + return fmt.Errorf("couldn't find metadata.name in the yaml; provide a name for the service") +} + +// SetServiceName modifies the CRD to contain user provided name on the CLI +// instead of using the default one in almExample +func (d *DynamicCRD) SetServiceName(name string) { + metaMap := d.OriginalCRD["metadata"].(map[string]interface{}) + + for k := range metaMap { + if k == "name" { + metaMap[k] = name + return + } + // if metadata doesn't have 'name' field, we set it up + metaMap["name"] = name + } +} + +// GetServiceNameFromCRD fetches the service name from metadata.name field of the CRD +func (d *DynamicCRD) GetServiceNameFromCRD() (string, error) { + metadata, ok := d.OriginalCRD["metadata"].(map[string]interface{}) + if !ok { + // this condition is satisfied if there's no metadata at all in the provided CRD + return "", fmt.Errorf("couldn't find \"metadata\" in the yaml; need metadata.name to start the service") + } + + if name, ok := metadata["name"].(string); ok { + // found the metadata.name; no error + return name, nil + } + return "", fmt.Errorf("couldn't find metadata.name in the yaml; provide a name for the service") +} + +// CreateServiceFromKubernetesInlineComponents creates service(s) from Kubernetes Inlined component in a devfile +func CreateServiceFromKubernetesInlineComponents(client *kclient.Client, k8sComponents []devfile.Component) ([]string, error) { + if len(k8sComponents) == 0 { + // if there's no Kubernetes Inlined component, there's nothing to do. + return []string{}, nil + } + + // create an object on the kubernetes cluster for all the Kubernetes Inlined components + var services []string + for _, c := range k8sComponents { + // get the string representation of the YAML definition of a CRD + strCRD := c.Kubernetes.Inlined + + // convert the YAML definition into map[string]interface{} since it's needed to create dynamic resource + d := NewDynamicCRD() + err := yaml.Unmarshal([]byte(strCRD), &d.OriginalCRD) + if err != nil { + return []string{}, err + } + + cr, csv, err := GetCSV(client, d.OriginalCRD) + if err != nil { + return []string{}, err + } + + var group, version, kind, resource string + for _, crd := range csv.Spec.CustomResourceDefinitions.Owned { + if crd.Kind == cr { + group, version, kind, resource, err = getGVKRFromCR(crd) + if err != nil { + return []string{}, err + } + break + } + } + + // create the service on cluster + err = client.CreateDynamicResource(d.OriginalCRD, group, version, resource) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + // this could be the case when "odo push" was executed after making change to code but there was no change to the service itself + // TODO: better way to handle this might be introduced by https://github.com/openshift/odo/issues/4553 + err = nil + break // this ensures that services slice is not updated + } else { + return []string{}, err + } + } + + name, _ := d.GetServiceNameFromCRD() // ignoring error because invalid yaml won't be inserted into devfile through odo + services = append(services, strings.Join([]string{kind, name}, "/")) + } + + return services, nil +} From 8e09ea562983aa6aaced5fa9a81e14dc9377e540 Mon Sep 17 00:00:00 2001 From: Dharmit Shah Date: Thu, 22 Apr 2021 13:14:01 +0530 Subject: [PATCH 2/2] Modifies integration tests to use "odo push" --- .../operatorhub/cmd_service_test.go | 44 ++++--------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/tests/integration/operatorhub/cmd_service_test.go b/tests/integration/operatorhub/cmd_service_test.go index e44a4b7bcd5..43466a70314 100644 --- a/tests/integration/operatorhub/cmd_service_test.go +++ b/tests/integration/operatorhub/cmd_service_test.go @@ -83,7 +83,7 @@ var _ = Describe("odo service command tests for OperatorHub", func() { operators := helper.CmdShouldPass("odo", "catalog", "list", "services") etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) stdOut := helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--project", commonVar.Project) - Expect(stdOut).To(ContainSubstring("Service \"example\" was created")) + Expect(stdOut).To(ContainSubstring("Successfully added service to the configuration")) // read the devfile.yaml to check if service definition was rightly inserted devfilePath := filepath.Join(commonVar.Context, "devfile.yaml") @@ -92,7 +92,8 @@ var _ = Describe("odo service command tests for OperatorHub", func() { matchInOutput := []string{"kubernetes", "inlined", "EtcdCluster", "example"} helper.MatchAllInOutput(string(content), matchInOutput) - // now verify if the pods for the operator have started + // now create the service on cluster and verify if the pods for the operator have started + helper.CmdShouldPass("odo", "push") pods := oc.GetAllPodsInNs(commonVar.Project) // Look for pod with example name because that's the name etcd will give to the pods. etcdPod := regexp.MustCompile(`example-.[a-z0-9]*`).FindString(pods) @@ -126,6 +127,7 @@ var _ = Describe("odo service command tests for OperatorHub", func() { operators := helper.CmdShouldPass("odo", "catalog", "list", "services") etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), name, "--project", commonVar.Project) + helper.CmdShouldPass("odo", "push") // now verify if the pods for the operator have started pods := oc.GetAllPodsInNs(commonVar.Project) @@ -217,6 +219,7 @@ var _ = Describe("odo service command tests for OperatorHub", func() { // now create operator backed service helper.CmdShouldPass("odo", "service", "create", "--from-file", fileName, "--project", commonVar.Project) + helper.CmdShouldPass("odo", "push") // now verify if the pods for the operator have started pods := oc.GetAllPodsInNs(commonVar.Project) @@ -250,6 +253,7 @@ var _ = Describe("odo service command tests for OperatorHub", func() { // now create operator backed service helper.CmdShouldPass("odo", "service", "create", "--from-file", fileName, name, "--project", commonVar.Project) + helper.CmdShouldPass("odo", "push") // Attempting to create service with same name should fail stdOut = helper.CmdShouldFail("odo", "service", "create", "--from-file", fileName, name, "--project", commonVar.Project) @@ -342,6 +346,7 @@ spec: operators := helper.CmdShouldPass("odo", "catalog", "list", "services") etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--project", commonVar.Project) + helper.CmdShouldPass("odo", "push") // now verify if the pods for the operator have started pods := oc.GetAllPodsInNs(commonVar.Project) @@ -426,6 +431,7 @@ spec: operators := helper.CmdShouldPass("odo", "catalog", "list", "services") etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--project", commonVar.Project) + helper.CmdShouldPass("odo", "push") // now verify if the pods for the operator have started pods := oc.GetAllPodsInNs(commonVar.Project) @@ -459,39 +465,5 @@ spec: stdOut = helper.CmdShouldFail("odo", "unlink", "EtcdCluster/example") Expect(stdOut).To(ContainSubstring("failed to unlink the service")) }) - - It("should fail if we delete a link outside of odo (using oc)", func() { - if os.Getenv("KUBERNETES") == "true" { - Skip("This is a OpenShift specific scenario, skipping") - } - - componentName := helper.RandString(6) - helper.CmdShouldPass("odo", "create", "nodejs", componentName) - helper.CmdShouldPass("odo", "push") - - // start the Operator backed service first - operators := helper.CmdShouldPass("odo", "catalog", "list", "services") - etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) - helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--project", commonVar.Project) - - // now verify if the pods for the operator have started - pods := helper.CmdShouldPass("oc", "get", "pods", "-n", commonVar.Project) - // Look for pod with example name because that's the name etcd will give to the pods. - etcdPod := regexp.MustCompile(`example-.[a-z0-9]*`).FindString(pods) - - ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", commonVar.Project} - helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { - return strings.Contains(output, "Running") - }) - - stdOut := helper.CmdShouldPass("odo", "link", "EtcdCluster/example") - Expect(stdOut).To(ContainSubstring("Successfully created link between component")) - helper.CmdShouldPass("odo", "push") - - sbrName := strings.Join([]string{componentName, "etcdcluster", "example"}, "-") - helper.CmdShouldPass("oc", "delete", fmt.Sprintf("ServiceBinding/%s", sbrName)) - stdOut = helper.CmdShouldFail("odo", "unlink", "EtcdCluster/example") - helper.MatchAllInOutput(stdOut, []string{"component's link with", "has been deleted outside odo"}) - }) }) })