diff --git a/pkg/component/component_full_description.go b/pkg/component/component_full_description.go index 80b049a4293..5263ef692ff 100644 --- a/pkg/component/component_full_description.go +++ b/pkg/component/component_full_description.go @@ -109,7 +109,7 @@ func (cfd *ComponentFullDescription) fillEmptyFields(componentDesc Component, co } // NewComponentFullDescriptionFromClientAndLocalConfig gets the complete description of the component from both localconfig and cluster -func NewComponentFullDescriptionFromClientAndLocalConfig(client *occlient.Client, localConfigInfo *config.LocalConfigInfo, envInfo *envinfo.EnvSpecificInfo, componentName string, applicationName string, projectName string) (*ComponentFullDescription, error) { +func NewComponentFullDescriptionFromClientAndLocalConfig(client *occlient.Client, localConfigInfo *config.LocalConfigInfo, envInfo *envinfo.EnvSpecificInfo, componentName string, applicationName string, projectName string, context string) (*ComponentFullDescription, error) { cfd := &ComponentFullDescription{} var state State if client == nil { @@ -126,7 +126,7 @@ func NewComponentFullDescriptionFromClientAndLocalConfig(client *occlient.Client if err != nil { return cfd, err } - configLinks, err = service.ListDevfileLinks(devfile) + configLinks, err = service.ListDevfileLinks(devfile, context) } else { componentDesc, err = GetComponentFromConfig(localConfigInfo) } diff --git a/pkg/devfile/adapters/kubernetes/component/adapter.go b/pkg/devfile/adapters/kubernetes/component/adapter.go index 9beef7e07f7..9a4b0943947 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter.go @@ -189,7 +189,7 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) { log.Infof("\nCreating Services for component %s", a.ComponentName) // create the Kubernetes objects from the manifest and delete the ones not in the devfile - err = service.PushServices(a.Client.GetKubeClient(), k8sComponents, labels) + err = service.PushServices(a.Client.GetKubeClient(), k8sComponents, labels, a.Context) if err != nil { return errors.Wrap(err, "failed to create service(s) associated with the component") } @@ -230,13 +230,13 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) { } } - err = service.UpdateServicesWithOwnerReferences(a.Client.GetKubeClient(), k8sComponents, ownerReference) + err = service.UpdateServicesWithOwnerReferences(a.Client.GetKubeClient(), k8sComponents, ownerReference, a.Context) if err != nil { return err } // create the Kubernetes objects from the manifest and delete the ones not in the devfile - needRestart, err := service.PushLinks(a.Client.GetKubeClient(), k8sComponents, labels, a.deployment) + needRestart, err := service.PushLinks(a.Client.GetKubeClient(), k8sComponents, labels, a.deployment, a.Context) if err != nil { return errors.Wrap(err, "failed to create service(s) associated with the component") } diff --git a/pkg/odo/cli/component/common_link.go b/pkg/odo/cli/component/common_link.go index 8996dceabf2..024b36a9fd8 100644 --- a/pkg/odo/cli/component/common_link.go +++ b/pkg/odo/cli/component/common_link.go @@ -46,6 +46,8 @@ type commonLinkOptions struct { *genericclioptions.Context // choose between Operator Hub and Service Catalog. If true, Operator Hub csvSupport bool + + inlined bool } func newCommonLinkOptions() *commonLinkOptions { @@ -371,7 +373,7 @@ func (o *commonLinkOptions) validateForOperator() (err error) { } if o.operationName == unlink { - _, found, err := svc.FindDevfileServiceBinding(o.EnvSpecificInfo.GetDevfileObj(), o.serviceType, o.serviceName) + _, found, err := svc.FindDevfileServiceBinding(o.EnvSpecificInfo.GetDevfileObj(), o.serviceType, o.serviceName, o.ComponentContext) if err != nil { return err } @@ -446,16 +448,24 @@ func (o *commonLinkOptions) linkOperator() (err error) { return err } - _, found, err := svc.FindDevfileServiceBinding(o.EnvSpecificInfo.GetDevfileObj(), o.serviceType, o.serviceName) + _, found, err := svc.FindDevfileServiceBinding(o.EnvSpecificInfo.GetDevfileObj(), o.serviceType, o.serviceName, o.ComponentContext) if err != nil { return err } if found { return fmt.Errorf("component %q is already linked with the %s %q", o.Context.EnvSpecificInfo.GetName(), o.getLinkType(), o.suppliedName) } - err = svc.AddKubernetesComponentToDevfile(string(yamlDesc), o.serviceBinding.Name, o.EnvSpecificInfo.GetDevfileObj()) - if err != nil { - return err + + if o.inlined { + err = svc.AddKubernetesComponentToDevfile(string(yamlDesc), o.serviceBinding.Name, o.EnvSpecificInfo.GetDevfileObj()) + if err != nil { + return err + } + } else { + err = svc.AddKubernetesComponent(string(yamlDesc), o.serviceBinding.Name, o.ComponentContext, o.EnvSpecificInfo.GetDevfileObj()) + if err != nil { + return err + } } log.Successf("Successfully created link between component %q and %s %q\n", o.Context.EnvSpecificInfo.GetName(), o.getLinkType(), o.suppliedName) @@ -467,12 +477,12 @@ func (o *commonLinkOptions) linkOperator() (err error) { func (o *commonLinkOptions) unlinkOperator() (err error) { // We already tested `found` in `validateForOperator` - name, _, err := svc.FindDevfileServiceBinding(o.EnvSpecificInfo.GetDevfileObj(), o.serviceType, o.serviceName) + name, _, err := svc.FindDevfileServiceBinding(o.EnvSpecificInfo.GetDevfileObj(), o.serviceType, o.serviceName, o.ComponentContext) if err != nil { return err } - err = svc.DeleteKubernetesComponentFromDevfile(name, o.EnvSpecificInfo.GetDevfileObj()) + err = svc.DeleteKubernetesComponentFromDevfile(name, o.EnvSpecificInfo.GetDevfileObj(), o.ComponentContext) if err != nil { return err } diff --git a/pkg/odo/cli/component/create_helpers.go b/pkg/odo/cli/component/create_helpers.go index 51828c52ff3..2e45875feea 100644 --- a/pkg/odo/cli/component/create_helpers.go +++ b/pkg/odo/cli/component/create_helpers.go @@ -84,7 +84,7 @@ func (co *CreateOptions) DevfileJSON() error { return err } - cfd, err := component.NewComponentFullDescriptionFromClientAndLocalConfig(co.Client, co.LocalConfigInfo, envInfo, envInfo.GetName(), envInfo.GetApplication(), co.Project) + cfd, err := component.NewComponentFullDescriptionFromClientAndLocalConfig(co.Client, co.LocalConfigInfo, envInfo, envInfo.GetName(), envInfo.GetApplication(), co.Project, co.ComponentContext) if err != nil { return err } diff --git a/pkg/odo/cli/component/describe.go b/pkg/odo/cli/component/describe.go index a63ec4bac9b..219063b2637 100644 --- a/pkg/odo/cli/component/describe.go +++ b/pkg/odo/cli/component/describe.go @@ -2,10 +2,10 @@ package component import ( "fmt" - "github.com/openshift/odo/pkg/log" "github.com/openshift/odo/pkg/machineoutput" "github.com/openshift/odo/pkg/odo/genericclioptions" + "os" "github.com/openshift/odo/pkg/component" appCmd "github.com/openshift/odo/pkg/odo/cli/application" @@ -38,6 +38,12 @@ func NewDescribeOptions() *DescribeOptions { // Complete completes describe args func (do *DescribeOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { + if do.componentContext == "" { + do.componentContext, err = os.Getwd() + if err != nil { + return err + } + } err = do.ComponentOptions.Complete(name, cmd, args) if err != nil { return err @@ -58,7 +64,7 @@ func (do *DescribeOptions) Validate() (err error) { // Run has the logic to perform the required actions as part of command func (do *DescribeOptions) Run(cmd *cobra.Command) (err error) { - cfd, err := component.NewComponentFullDescriptionFromClientAndLocalConfig(do.Context.Client, do.LocalConfigInfo, do.EnvSpecificInfo, do.componentName, do.Context.Application, do.Context.Project) + cfd, err := component.NewComponentFullDescriptionFromClientAndLocalConfig(do.Context.Client, do.LocalConfigInfo, do.EnvSpecificInfo, do.componentName, do.Context.Application, do.Context.Project, do.componentContext) if err != nil { return err } diff --git a/pkg/odo/cli/component/link.go b/pkg/odo/cli/component/link.go index d59544d0baf..11f4dc675f9 100644 --- a/pkg/odo/cli/component/link.go +++ b/pkg/odo/cli/component/link.go @@ -157,6 +157,7 @@ func NewCmdLink(name, fullName string) *cobra.Command { }, } + linkCmd.PersistentFlags().BoolVarP(&o.inlined, "inlined", "", false, "sientgagdsk") linkCmd.PersistentFlags().StringVar(&o.port, "port", "", "Port of the backend to which to link") linkCmd.PersistentFlags().BoolVarP(&o.wait, "wait", "w", false, "If enabled the link will return only when the component is fully running after the link is created") linkCmd.PersistentFlags().BoolVar(&o.waitForTarget, "wait-for-target", false, "If enabled, the link command will wait for the service to be provisioned (has no effect when linking to a component)") diff --git a/pkg/odo/cli/service/create.go b/pkg/odo/cli/service/create.go index 7770be183bd..db211df8279 100644 --- a/pkg/odo/cli/service/create.go +++ b/pkg/odo/cli/service/create.go @@ -61,6 +61,8 @@ type CreateOptions struct { fromFile string // Backend is the service provider backend providing the service requested by the user Backend ServiceProviderBackend + + inlined bool } // NewCreateOptions creates a new CreateOptions instance @@ -139,6 +141,7 @@ func NewCmdServiceCreate(name, fullName string) *cobra.Command { }, } + serviceCreateCmd.Flags().BoolVar(&o.inlined, "inlined", false, "something") 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") diff --git a/pkg/odo/cli/service/list_operator.go b/pkg/odo/cli/service/list_operator.go index 3dbf9bc375f..3d9eee575a9 100644 --- a/pkg/odo/cli/service/list_operator.go +++ b/pkg/odo/cli/service/list_operator.go @@ -68,7 +68,7 @@ func (o *ServiceListOptions) listOperatorServices() (err error) { var devfileList map[string]unstructured.Unstructured var devfileComponent string if o.EnvSpecificInfo != nil { - devfileList, err = svc.ListDevfileServices(o.EnvSpecificInfo.GetDevfileObj()) + devfileList, err = svc.ListDevfileServices(o.EnvSpecificInfo.GetDevfileObj(), o.componentContext) if err != nil { return fmt.Errorf("error reading devfile") } diff --git a/pkg/odo/cli/service/operator_backend.go b/pkg/odo/cli/service/operator_backend.go index 86256d10efc..723ba6605fe 100644 --- a/pkg/odo/cli/service/operator_backend.go +++ b/pkg/odo/cli/service/operator_backend.go @@ -24,7 +24,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -// This CompleteServiceCreate contains logic to complete the "odo service create" call for the case of Operator backend +// 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 o.interactive = false @@ -215,14 +215,28 @@ func (b *OperatorBackend) RunServiceCreate(o *CreateOptions) (err error) { return nil } else { - crdYaml, err := yaml.Marshal(b.CustomResourceDefinition) - if err != nil { - return err - } - err = svc.AddKubernetesComponentToDevfile(string(crdYaml), o.ServiceName, o.EnvSpecificInfo.GetDevfileObj()) - if err != nil { - return err + if o.inlined { + crdYaml, err := yaml.Marshal(b.CustomResourceDefinition) + if err != nil { + return err + } + + err = svc.AddKubernetesComponentToDevfile(string(crdYaml), o.ServiceName, o.EnvSpecificInfo.GetDevfileObj()) + if err != nil { + return err + } + + } else { + crdYaml, err := yaml.Marshal(b.CustomResourceDefinition) + if err != nil { + return err + } + + err = svc.AddKubernetesComponent(string(crdYaml), o.ServiceName, o.componentContext, o.EnvSpecificInfo.GetDevfileObj()) + if err != nil { + return err + } } if log.IsJSON() { @@ -253,7 +267,7 @@ func (b *OperatorBackend) DeleteService(o *DeleteOptions, name string, applicati return err } - err = svc.DeleteKubernetesComponentFromDevfile(instanceName, o.EnvSpecificInfo.GetDevfileObj()) + err = svc.DeleteKubernetesComponentFromDevfile(instanceName, o.EnvSpecificInfo.GetDevfileObj(), o.componentContext) if err != nil { return errors.Wrap(err, "failed to delete service from the devfile") } @@ -285,7 +299,7 @@ func (b *OperatorBackend) DescribeService(o *DescribeOptions, serviceName, app s } } - devfileList, err := svc.ListDevfileServices(o.EnvSpecificInfo.GetDevfileObj()) + devfileList, err := svc.ListDevfileServices(o.EnvSpecificInfo.GetDevfileObj(), o.componentContext) if err != nil { return err } diff --git a/pkg/service/link.go b/pkg/service/link.go index 1b08eed0780..74dbab6ed1e 100644 --- a/pkg/service/link.go +++ b/pkg/service/link.go @@ -7,6 +7,7 @@ import ( devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/generator" + devfilefs "github.com/devfile/library/pkg/testingutil/filesystem" "github.com/ghodss/yaml" applabels "github.com/openshift/odo/pkg/application/labels" componentlabels "github.com/openshift/odo/pkg/component/labels" @@ -26,22 +27,22 @@ import ( // PushLinks updates Link(s) from Kubernetes Inlined component in a devfile by creating new ones or removing old ones // returns true if the component needs to be restarted (when a link has been created or deleted) // if service binding operator is not present, it will call pushLinksWithoutOperator to create the links without it. -func PushLinks(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string, deployment *v1.Deployment) (bool, error) { +func PushLinks(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string, deployment *v1.Deployment, context string) (bool, error) { serviceBindingSupport, err := client.IsServiceBindingSupported() if err != nil { return false, err } if !serviceBindingSupport { - return pushLinksWithoutOperator(client, k8sComponents, labels, deployment) + return pushLinksWithoutOperator(client, k8sComponents, labels, deployment, context) } - return pushLinksWithOperator(client, k8sComponents, labels, deployment) + return pushLinksWithOperator(client, k8sComponents, labels, deployment, context) } // pushLinksWithOperator creates links or deletes links (if service binding operator is installed) between components and services // returns true if the component needs to be restarted (a secret was generated and added to the deployment) -func pushLinksWithOperator(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string, deployment *v1.Deployment) (bool, error) { +func pushLinksWithOperator(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string, deployment *v1.Deployment, context string) (bool, error) { ownerReference := generator.GetOwnerReference(deployment) deployed, err := ListDeployedServices(client, labels) @@ -61,6 +62,12 @@ func pushLinksWithOperator(client *kclient.Client, k8sComponents []devfile.Compo for _, c := range k8sComponents { // get the string representation of the YAML definition of a CRD strCRD := c.Kubernetes.Inlined + if c.Kubernetes.Uri != "" { + strCRD, err = getDataFromURI(c.Kubernetes.Uri, context, devfilefs.DefaultFs{}) + if err != nil { + return false, err + } + } // convert the YAML definition into map[string]interface{} since it's needed to create dynamic resource d := NewDynamicCRD() @@ -119,7 +126,7 @@ func pushLinksWithOperator(client *kclient.Client, k8sComponents []devfile.Compo // pushLinksWithoutOperator creates links or deletes links (if service binding operator is not installed) between components and services // returns true if the component needs to be restarted (a secret was generated and added to the deployment) -func pushLinksWithoutOperator(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string, deployment *v1.Deployment) (bool, error) { +func pushLinksWithoutOperator(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string, deployment *v1.Deployment, context string) (bool, error) { // check csv support before proceeding csvSupport, err := IsCSVSupported() @@ -146,6 +153,12 @@ func pushLinksWithoutOperator(client *kclient.Client, k8sComponents []devfile.Co for _, c := range k8sComponents { // get the string representation of the YAML definition of a CRD strCRD := c.Kubernetes.Inlined + if c.Kubernetes.Uri != "" { + strCRD, err = getDataFromURI(c.Kubernetes.Uri, context, devfilefs.DefaultFs{}) + if err != nil { + return false, err + } + } // convert the YAML definition into map[string]interface{} since it's needed to create dynamic resource d := NewDynamicCRD() diff --git a/pkg/service/service.go b/pkg/service/service.go index 7ddbfb1d249..cf1bef9c909 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -3,11 +3,15 @@ package service import ( "encoding/json" "fmt" + "net/url" + "os" + "path/filepath" "strings" devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser/data/v2/common" parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common" + devfilefs "github.com/devfile/library/pkg/testingutil/filesystem" "github.com/openshift/odo/pkg/kclient" "github.com/openshift/odo/pkg/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -42,6 +46,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" +const uriFolder = ".kubernetes" + // 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. @@ -567,7 +573,11 @@ func IsDefined(name string, devfileObj parser.DevfileObj) (bool, error) { } // ListDevfileLinks returns the names of the links defined in a Devfile -func ListDevfileLinks(devfileObj parser.DevfileObj) ([]string, error) { +func ListDevfileLinks(devfileObj parser.DevfileObj, context string) ([]string, error) { + return listDevfileLinks(devfileObj, context, devfilefs.DefaultFs{}) +} + +func listDevfileLinks(devfileObj parser.DevfileObj, context string, fs devfilefs.Filesystem) ([]string, error) { if devfileObj.Data == nil { return nil, nil } @@ -579,8 +589,15 @@ func ListDevfileLinks(devfileObj parser.DevfileObj) ([]string, error) { } var services []string for _, c := range components { + inlined := c.Kubernetes.Inlined + if c.Kubernetes.Uri != "" { + inlined, err = getDataFromURI(c.Kubernetes.Uri, context, fs) + if err != nil { + return []string{}, err + } + } var u unstructured.Unstructured - err = yaml.Unmarshal([]byte(c.Kubernetes.Inlined), &u) + err = yaml.Unmarshal([]byte(inlined), &u) if err != nil { return nil, err } @@ -610,8 +627,12 @@ func ListDevfileLinks(devfileObj parser.DevfileObj) ([]string, error) { return services, nil } -// ListDevfileServices returns the services defined in a Devfile -func ListDevfileServices(devfileObj parser.DevfileObj) (map[string]unstructured.Unstructured, error) { +// ListDevfileServices returns the names of the services defined in a Devfile +func ListDevfileServices(devfileObj parser.DevfileObj, componentContext string) (map[string]unstructured.Unstructured, error) { + return listDevfileServices(devfileObj, componentContext, devfilefs.DefaultFs{}) +} + +func listDevfileServices(devfileObj parser.DevfileObj, componentContext string, fs devfilefs.Filesystem) (map[string]unstructured.Unstructured, error) { if devfileObj.Data == nil { return nil, nil } @@ -623,8 +644,15 @@ func ListDevfileServices(devfileObj parser.DevfileObj) (map[string]unstructured. } services := map[string]unstructured.Unstructured{} for _, c := range components { + inlined := c.Kubernetes.Inlined + if c.Kubernetes.Uri != "" { + inlined, err = getDataFromURI(c.Kubernetes.Uri, componentContext, fs) + if err != nil { + return nil, err + } + } var u unstructured.Unstructured - err = yaml.Unmarshal([]byte(c.Kubernetes.Inlined), &u) + err = yaml.Unmarshal([]byte(inlined), &u) if err != nil { return nil, err } @@ -634,7 +662,11 @@ func ListDevfileServices(devfileObj parser.DevfileObj) (map[string]unstructured. } // FindDevfileServiceBinding returns the name of the ServiceBinding defined in a Devfile matching kind and name -func FindDevfileServiceBinding(devfileObj parser.DevfileObj, kind string, name string) (string, bool, error) { +func FindDevfileServiceBinding(devfileObj parser.DevfileObj, kind string, name, context string) (string, bool, error) { + return findDevfileServiceBinding(devfileObj, kind, name, context, devfilefs.DefaultFs{}) +} + +func findDevfileServiceBinding(devfileObj parser.DevfileObj, kind string, name, context string, fs devfilefs.Filesystem) (string, bool, error) { if devfileObj.Data == nil { return "", false, nil } @@ -646,15 +678,22 @@ func FindDevfileServiceBinding(devfileObj parser.DevfileObj, kind string, name s } for _, c := range components { + inlined := c.Kubernetes.Inlined + if c.Kubernetes.Uri != "" { + inlined, err = getDataFromURI(c.Kubernetes.Uri, context, fs) + if err != nil { + return "", false, err + } + } var u unstructured.Unstructured - err = yaml.Unmarshal([]byte(c.Kubernetes.Inlined), &u) + err = yaml.Unmarshal([]byte(inlined), &u) if err != nil { return "", false, err } if isLinkResource(u.GetKind()) { var sbr servicebinding.ServiceBinding - err = yaml.Unmarshal([]byte(c.Kubernetes.Inlined), &sbr) + err = yaml.Unmarshal([]byte(inlined), &sbr) if err != nil { return "", false, err } @@ -693,8 +732,57 @@ func AddKubernetesComponentToDevfile(crd, name string, devfileObj parser.Devfile return devfileObj.WriteYamlDevfile() } +// AddKubernetesComponent adds the crd information to a separate file and adds the uri information to a devfile component +func AddKubernetesComponent(crd, name, componentContext string, devfile parser.DevfileObj) error { + return addKubernetesComponent(crd, name, componentContext, devfile, devfilefs.DefaultFs{}) +} + +// AddKubernetesComponent adds the crd information to a separate file and adds the uri information to a devfile component +func addKubernetesComponent(crd, name, componentContext string, devfileObj parser.DevfileObj, fs devfilefs.Filesystem) error { + filePath := filepath.Join(componentContext, uriFolder, name+".yaml") + if _, err := fs.Stat(filepath.Join(componentContext, uriFolder)); os.IsNotExist(err) { + err = fs.MkdirAll(filepath.Join(componentContext, uriFolder), os.ModePerm) + if err != nil { + return err + } + } + + if _, err := fs.Stat(filePath); !os.IsNotExist(err) { + return fmt.Errorf("the file %q already exists", filePath) + } + + err := fs.WriteFile(filePath, []byte(crd), 0755) + if err != nil { + return err + } + + err = devfileObj.Data.AddComponents([]devfile.Component{{ + Name: name, + ComponentUnion: devfile.ComponentUnion{ + Kubernetes: &devfile.KubernetesComponent{ + K8sLikeComponent: devfile.K8sLikeComponent{ + BaseComponent: devfile.BaseComponent{}, + K8sLikeComponentLocation: devfile.K8sLikeComponentLocation{ + Uri: filepath.Join(name + ".yaml"), + }, + }, + }, + }, + }}) + if err != nil { + return err + } + + return devfileObj.WriteYamlDevfile() +} + // DeleteKubernetesComponentFromDevfile deletes an inlined Kubernetes component from devfile, if one exists -func DeleteKubernetesComponentFromDevfile(name string, devfileObj parser.DevfileObj) error { +func DeleteKubernetesComponentFromDevfile(name string, devfileObj parser.DevfileObj, componentContext string) error { + return deleteKubernetesComponentFromDevfile(name, devfileObj, componentContext, devfilefs.DefaultFs{}) +} + +// deleteKubernetesComponentFromDevfile deletes an inlined Kubernetes component from devfile, if one exists +func deleteKubernetesComponentFromDevfile(name string, devfileObj parser.DevfileObj, componentContext string, fs devfilefs.Filesystem) error { components, err := devfileObj.Data.GetComponents(common.DevfileOptions{}) if err != nil { return err @@ -707,6 +795,19 @@ func DeleteKubernetesComponentFromDevfile(name string, devfileObj parser.Devfile if err != nil { return err } + + if c.Kubernetes.Uri != "" { + parsedURL, err := url.Parse(c.Kubernetes.Uri) + if err != nil { + return err + } + if len(parsedURL.Host) == 0 || len(parsedURL.Scheme) == 0 { + err := fs.Remove(filepath.Join(componentContext, uriFolder, c.Kubernetes.Uri)) + if err != nil { + return err + } + } + } found = true break } @@ -793,7 +894,7 @@ func (d *DynamicCRD) AddComponentLabelsToCRD(labels map[string]string) { } // PushServices updates service(s) from Kubernetes Inlined component in a devfile by creating new ones or removing old ones -func PushServices(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string) error { +func PushServices(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string, context string) error { // check csv support before proceeding csvSupported, err := IsCSVSupported() @@ -816,8 +917,15 @@ func PushServices(client *kclient.Client, k8sComponents []devfile.Component, lab // create an object on the kubernetes cluster for all the Kubernetes Inlined components for _, c := range k8sComponents { + inlined := c.Kubernetes.Inlined + if c.Kubernetes.Uri != "" { + inlined, err = getDataFromURI(c.Kubernetes.Uri, context, devfilefs.DefaultFs{}) + if err != nil { + return err + } + } // get the string representation of the YAML definition of a CRD - strCRD := c.Kubernetes.Inlined + strCRD := inlined // convert the YAML definition into map[string]interface{} since it's needed to create dynamic resource d := NewDynamicCRD() @@ -908,7 +1016,7 @@ func ListDeployedServices(client *kclient.Client, labels map[string]string) (map // UpdateServicesWithOwnerReferences adds an owner reference to an inlined Kubernetes resource (except service binding objects) // if not already present in the list of owner references -func UpdateServicesWithOwnerReferences(client *kclient.Client, k8sComponents []devfile.Component, ownerReference metav1.OwnerReference) error { +func UpdateServicesWithOwnerReferences(client *kclient.Client, k8sComponents []devfile.Component, ownerReference metav1.OwnerReference, context string) error { csvSupport, err := client.IsCSVSupported() if err != nil { return err @@ -921,6 +1029,12 @@ func UpdateServicesWithOwnerReferences(client *kclient.Client, k8sComponents []d for _, c := range k8sComponents { // get the string representation of the YAML definition of a CRD strCRD := c.Kubernetes.Inlined + if c.Kubernetes.Uri != "" { + strCRD, err = getDataFromURI(c.Kubernetes.Uri, context, devfilefs.DefaultFs{}) + if err != nil { + return err + } + } // convert the YAML definition into map[string]interface{} since it's needed to create dynamic resource d := NewDynamicCRD() @@ -1026,3 +1140,29 @@ func createOperatorService(client *kclient.Client, d *DynamicCRD, labels map[str } return cr, kind, err } + +// getDataFromURI gets the data from the given URI +// if the uri is a local path, we use the componentContext to complete the local path +func getDataFromURI(uri, componentContext string, fs devfilefs.Filesystem) (string, error) { + + parsedURL, err := url.Parse(uri) + if err != nil { + return "", err + } + if len(parsedURL.Host) != 0 && len(parsedURL.Scheme) != 0 { + params := util.HTTPRequestParams{ + URL: uri, + } + dataBytes, err := util.DownloadFileInMemoryWithCache(params, 1) + if err != nil { + return "", err + } + return string(dataBytes), nil + } else { + dataBytes, err := fs.ReadFile(filepath.Join(componentContext, uriFolder, uri)) + if err != nil { + return "", err + } + return string(dataBytes), nil + } +} diff --git a/pkg/service/service_test.go b/pkg/service/service_test.go index 48826720720..505851371c3 100644 --- a/pkg/service/service_test.go +++ b/pkg/service/service_test.go @@ -1,16 +1,18 @@ package service import ( + "os" + "path/filepath" + "reflect" "sort" + "testing" "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" + "github.com/openshift/odo/pkg/testingutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "reflect" - "testing" - "github.com/devfile/library/pkg/devfile/parser" devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" "github.com/devfile/library/pkg/devfile/parser/data" @@ -22,7 +24,12 @@ type inlinedComponent struct { inlined string } -func getDevfileData(t *testing.T, inlined []inlinedComponent) data.DevfileData { +type uriComponent struct { + name string + uri string +} + +func getDevfileData(t *testing.T, inlined []inlinedComponent, uriComp []uriComponent) data.DevfileData { devfileData, err := data.NewDevfileData(string(data.APISchemaVersion200)) if err != nil { t.Error(err) @@ -46,6 +53,25 @@ func getDevfileData(t *testing.T, inlined []inlinedComponent) data.DevfileData { t.Error(err) } } + for _, component := range uriComp { + err = devfileData.AddComponents([]v1alpha2.Component{{ + Name: component.name, + ComponentUnion: devfile.ComponentUnion{ + Kubernetes: &devfile.KubernetesComponent{ + K8sLikeComponent: devfile.K8sLikeComponent{ + BaseComponent: devfile.BaseComponent{}, + K8sLikeComponentLocation: devfile.K8sLikeComponentLocation{ + Uri: component.uri, + }, + }, + }, + }, + }, + }) + if err != nil { + t.Error(err) + } + } return devfileData } @@ -69,7 +95,7 @@ func TestAddKubernetesComponentToDevfile(t *testing.T) { crd: "test CRD", name: "testName", devfileObj: parser.DevfileObj{ - Data: getDevfileData(t, nil), + Data: getDevfileData(t, nil, nil), Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), }, }, @@ -109,6 +135,13 @@ func TestAddKubernetesComponentToDevfile(t *testing.T) { func TestDeleteKubernetesComponentFromDevfile(t *testing.T) { fs := devfileFileSystem.NewFakeFs() + testFolderName := "someFolder" + testFileName, err := setup(testFolderName, fs) + if err != nil { + t.Errorf("unexpected error : %v", err) + return + } + type args struct { name string devfileObj parser.DevfileObj @@ -129,7 +162,42 @@ func TestDeleteKubernetesComponentFromDevfile(t *testing.T) { name: "testName", inlined: "test CRD", }, - }), + }, nil), + Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), + }, + }, + wantErr: false, + want: []v1alpha2.Component{}, + }, + { + name: "Case 2: Remove a uri based component from devfile.yaml", + args: args{ + name: "testName", + devfileObj: parser.DevfileObj{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APISchemaVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]v1alpha2.Component{{ + Name: "testName", + ComponentUnion: devfile.ComponentUnion{ + Kubernetes: &devfile.KubernetesComponent{ + K8sLikeComponent: devfile.K8sLikeComponent{ + BaseComponent: devfile.BaseComponent{}, + K8sLikeComponentLocation: devfile.K8sLikeComponentLocation{ + Uri: filepath.Base(testFileName.Name()), + }, + }, + }, + }, + }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), }, }, @@ -139,7 +207,7 @@ func TestDeleteKubernetesComponentFromDevfile(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := DeleteKubernetesComponentFromDevfile(tt.args.name, tt.args.devfileObj); (err != nil) != tt.wantErr { + if err := deleteKubernetesComponentFromDevfile(tt.args.name, tt.args.devfileObj, testFolderName, fs); (err != nil) != tt.wantErr { t.Errorf("DeleteKubernetesComponentFromDevfile() error = %v, wantErr %v", err, tt.wantErr) } got, err := tt.args.devfileObj.Data.GetComponents(common.DevfileOptions{}) @@ -155,6 +223,28 @@ func TestDeleteKubernetesComponentFromDevfile(t *testing.T) { func TestListDevfileServices(t *testing.T) { fs := devfileFileSystem.NewFakeFs() + + testFolderName := "someFolder" + testFileName, err := setup(testFolderName, fs) + if err != nil { + t.Errorf("unexpected error : %v", err) + return + } + + uriData := ` +apiVersion: redis.redis.opstreelabs.in/v1beta1 +kind: Redis +metadata: + name: redis +spec: + kubernetesConfig: + image: quay.io/opstree/redis:v6.2` + + err = fs.WriteFile(testFileName.Name(), []byte(uriData), os.ModePerm) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + tests := []struct { name string devfileObj parser.DevfileObj @@ -164,7 +254,7 @@ func TestListDevfileServices(t *testing.T) { { name: "No service in devfile", devfileObj: parser.DevfileObj{ - Data: getDevfileData(t, nil), + Data: getDevfileData(t, nil, nil), Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), }, wantKeys: []string{}, @@ -174,17 +264,6 @@ func TestListDevfileServices(t *testing.T) { name: "Services including service bindings in devfile", devfileObj: parser.DevfileObj{ Data: getDevfileData(t, []inlinedComponent{ - { - name: "service1", - inlined: ` -apiVersion: redis.redis.opstreelabs.in/v1beta1 -kind: Redis -metadata: - name: redis -spec: - kubernetesConfig: - image: quay.io/opstree/redis:v6.2`, - }, { name: "link1", inlined: ` @@ -206,6 +285,11 @@ spec: name: redis version: v1beta1`, }, + }, []uriComponent{ + { + name: "service1", + uri: filepath.Base(testFileName.Name()), + }, }), }, wantKeys: []string{"Redis/service1", "ServiceBinding/link1"}, @@ -225,7 +309,7 @@ spec: } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, gotErr := ListDevfileServices(tt.devfileObj) + got, gotErr := listDevfileServices(tt.devfileObj, testFolderName, fs) gotKeys := getKeys(got) if !reflect.DeepEqual(gotKeys, tt.wantKeys) { t.Errorf("%s: got %v, expect %v", t.Name(), gotKeys, tt.wantKeys) @@ -239,6 +323,28 @@ spec: func TestListDevfileLinks(t *testing.T) { fs := devfileFileSystem.NewFakeFs() + + testFolderName := "someFolder" + testFileName, err := setup(testFolderName, fs) + if err != nil { + t.Errorf("unexpected error : %v", err) + return + } + + uriData := ` +apiVersion: redis.redis.opstreelabs.in/v1beta1 +kind: Redis +metadata: + name: redis +spec: + kubernetesConfig: + image: quay.io/opstree/redis:v6.2` + + err = fs.WriteFile(testFileName.Name(), []byte(uriData), os.ModePerm) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + tests := []struct { name string devfileObj parser.DevfileObj @@ -248,7 +354,7 @@ func TestListDevfileLinks(t *testing.T) { { name: "No service in devfile", devfileObj: parser.DevfileObj{ - Data: getDevfileData(t, nil), + Data: getDevfileData(t, nil, nil), Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), }, want: nil, @@ -258,37 +364,26 @@ func TestListDevfileLinks(t *testing.T) { name: "Services including service bindings in devfile", devfileObj: parser.DevfileObj{ Data: getDevfileData(t, []inlinedComponent{ - { - name: "service1", - inlined: ` -apiVersion: redis.redis.opstreelabs.in/v1beta1 -kind: Redis -metadata: - name: redis -spec: - kubernetesConfig: - image: quay.io/opstree/redis:v6.2`, - }, { name: "link1", inlined: ` apiVersion: binding.operators.coreos.com/v1alpha1 kind: ServiceBinding metadata: - name: nodejs-prj1-api-vtzg-redis-redis + name: nodejs-prj1-api-vtzg-redis-redis spec: - application: - group: apps - name: nodejs-prj1-api-vtzg-app - resource: deployments - version: v1 - bindAsFiles: false - detectBindingResources: true - services: - - group: redis.redis.opstreelabs.in - kind: Redis - name: redis - version: v1beta1`, + application: + group: apps + name: nodejs-prj1-api-vtzg-app + resource: deployments + version: v1 + bindAsFiles: false + detectBindingResources: true + services: + - group: redis.redis.opstreelabs.in + kind: Redis + name: redis + version: v1beta1`, }, { name: "link2", @@ -296,20 +391,25 @@ spec: apiVersion: binding.operators.coreos.com/v1alpha1 kind: ServiceBinding metadata: - name: nodejs-prj1-api-vtzg-redis-redis + name: nodejs-prj1-api-vtzg-redis-redis spec: - application: - group: apps - name: nodejs-prj1-api-vtzg-app - resource: deployments - version: v1 - bindAsFiles: false - detectBindingResources: true - services: - - group: redis.redis.opstreelabs.in - kind: Service - name: other - version: v1beta1`, + application: + group: apps + name: nodejs-prj1-api-vtzg-app + resource: deployments + version: v1 + bindAsFiles: false + detectBindingResources: true + services: + - group: redis.redis.opstreelabs.in + kind: Service + name: other + version: v1beta1`, + }, + }, []uriComponent{ + { + name: "service1", + uri: filepath.Base(testFileName.Name()), }, }), Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), @@ -321,7 +421,7 @@ spec: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, gotErr := ListDevfileLinks(tt.devfileObj) + got, gotErr := listDevfileLinks(tt.devfileObj, testFolderName, fs) if !reflect.DeepEqual(got, tt.want) { t.Errorf("%s: got %v, expect %v", t.Name(), got, tt.want) } @@ -334,39 +434,50 @@ spec: func TestFindDevfileServiceBinding(t *testing.T) { fs := devfileFileSystem.NewFakeFs() - devfileObj := parser.DevfileObj{ - Data: getDevfileData(t, []inlinedComponent{ - { - name: "service1", - inlined: ` + + testFolderName := "someFolder" + testFileName, err := setup(testFolderName, fs) + if err != nil { + t.Errorf("unexpected error : %v", err) + return + } + + uriData := ` apiVersion: redis.redis.opstreelabs.in/v1beta1 kind: Redis metadata: - name: redis + name: redis spec: - kubernetesConfig: - image: quay.io/opstree/redis:v6.2`, - }, + kubernetesConfig: + image: quay.io/opstree/redis:v6.2` + + err = fs.WriteFile(testFileName.Name(), []byte(uriData), os.ModePerm) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + devfileObj := parser.DevfileObj{ + Data: getDevfileData(t, []inlinedComponent{ { name: "link1", inlined: ` apiVersion: binding.operators.coreos.com/v1alpha1 kind: ServiceBinding metadata: - name: nodejs-prj1-api-vtzg-redis-redis + name: nodejs-prj1-api-vtzg-redis-redis spec: - application: - group: apps - name: nodejs-prj1-api-vtzg-app - resource: deployments - version: v1 - bindAsFiles: false - detectBindingResources: true - services: - - group: redis.redis.opstreelabs.in - kind: Redis - name: redis - version: v1beta1`, + application: + group: apps + name: nodejs-prj1-api-vtzg-app + resource: deployments + version: v1 + bindAsFiles: false + detectBindingResources: true + services: + - group: redis.redis.opstreelabs.in + kind: Redis + name: redis + version: v1beta1`, }, { name: "link2", @@ -374,20 +485,25 @@ spec: apiVersion: binding.operators.coreos.com/v1alpha1 kind: ServiceBinding metadata: - name: nodejs-prj1-api-vtzg-redis-redis + name: nodejs-prj1-api-vtzg-redis-redis spec: - application: - group: apps - name: nodejs-prj1-api-vtzg-app - resource: deployments - version: v1 - bindAsFiles: false - detectBindingResources: true - services: - - group: redis.redis.opstreelabs.in - kind: Service - name: other - version: v1beta1`, + application: + group: apps + name: nodejs-prj1-api-vtzg-app + resource: deployments + version: v1 + bindAsFiles: false + detectBindingResources: true + services: + - group: redis.redis.opstreelabs.in + kind: Service + name: other + version: v1beta1`, + }, + }, []uriComponent{ + { + name: "service1", + uri: filepath.Base(testFileName.Name()), }, }), Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), @@ -428,7 +544,7 @@ spec: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, gotOK, gotErr := FindDevfileServiceBinding(devfileObj, tt.args.kind, tt.args.name) + got, gotOK, gotErr := findDevfileServiceBinding(devfileObj, tt.args.kind, tt.args.name, testFolderName, fs) if !reflect.DeepEqual(got, tt.want) { t.Errorf("%s: got %v, expect %v", t.Name(), got, tt.want) } @@ -441,3 +557,92 @@ spec: }) } } + +func setup(testFolderName string, fs devfileFileSystem.Filesystem) (devfileFileSystem.File, error) { + err := fs.MkdirAll(testFolderName, os.ModePerm) + if err != nil { + return nil, err + } + err = fs.MkdirAll(filepath.Join(testFolderName, uriFolder), os.ModePerm) + if err != nil { + return nil, err + } + testFileName, err := fs.Create(filepath.Join(testFolderName, uriFolder, "example.yaml")) + if err != nil { + return nil, err + } + return testFileName, nil +} + +func Test_addKubernetesComponent(t *testing.T) { + + type args struct { + crd string + name string + componentContext string + devfileObj parser.DevfileObj + fs devfileFileSystem.Filesystem + uriFolderExists bool + fileAlreadyExists bool + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "case 1: the uri folder doesn't exist", + args: args{ + crd: "example", + name: "redis-service", + componentContext: "/", + }, + }, + { + name: "case 2: the uri folder exist", + args: args{ + crd: "example", + name: "redis-service", + uriFolderExists: true, + }, + }, + { + name: "case 3: the file already exists", + args: args{ + crd: "example", + name: "redis-service", + uriFolderExists: true, + fileAlreadyExists: true, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fs := devfileFileSystem.NewFakeFs() + tt.args.devfileObj = testingutil.GetTestDevfileObj(fs) + tt.args.fs = fs + + if tt.args.uriFolderExists || tt.args.fileAlreadyExists { + err := fs.MkdirAll(uriFolder, os.ModePerm) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + defer os.RemoveAll(uriFolder) + } + + if tt.args.fileAlreadyExists { + testFileName, err := fs.Create(filepath.Join(uriFolder, tt.args.name+".yaml")) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + defer os.RemoveAll(testFileName.Name()) + } + + if err := addKubernetesComponent(tt.args.crd, tt.args.name, tt.args.componentContext, tt.args.devfileObj, tt.args.fs); (err != nil) != tt.wantErr { + t.Errorf("addKubernetesComponent() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/tests/integration/operatorhub/cmd_link_test.go b/tests/integration/operatorhub/cmd_link_test.go index 03228b0059b..52cf845976c 100644 --- a/tests/integration/operatorhub/cmd_link_test.go +++ b/tests/integration/operatorhub/cmd_link_test.go @@ -2,6 +2,7 @@ package integration import ( "fmt" + "io/ioutil" "os" "path/filepath" "regexp" @@ -98,6 +99,14 @@ var _ = Describe("odo link command tests for OperatorHub", func() { Expect(stdOut).To(ContainSubstring(svcFullName)) }) + It("should not insert the link definition in devfile.yaml when the inlined flag is not used", func() { + devfilePath := filepath.Join(commonVar.Context, "devfile.yaml") + content, err := ioutil.ReadFile(devfilePath) + Expect(err).To(BeNil()) + matchInOutput := []string{"inlined", "ServiceBinding"} + helper.DontMatchAllInOutput(string(content), matchInOutput) + }) + When("odo push is executed", func() { BeforeEach(func() { helper.Cmd("odo", "push", "--context", commonVar.Context).ShouldPass() @@ -141,6 +150,14 @@ var _ = Describe("odo link command tests for OperatorHub", func() { Expect(stdOut).To(ContainSubstring(svcFullName)) }) + It("should not insert the link definition in devfile.yaml when the inlined flag is not used", func() { + devfilePath := filepath.Join(commonVar.Context, "devfile.yaml") + content, err := ioutil.ReadFile(devfilePath) + Expect(err).To(BeNil()) + matchInOutput := []string{"inlined", "Redis", "redis", "ServiceBinding"} + helper.DontMatchAllInOutput(string(content), matchInOutput) + }) + When("odo push is executed", func() { BeforeEach(func() { helper.Cmd("odo", "push", "--context", commonVar.Context).ShouldPass() @@ -164,6 +181,78 @@ var _ = Describe("odo link command tests for OperatorHub", func() { }) }) }) + + When("a link between the component and the service is created inline", func() { + + BeforeEach(func() { + helper.Cmd("odo", "link", svcFullName, "--context", commonVar.Context, "--inlined").ShouldPass() + }) + + It("should insert service definition in devfile.yaml when the inlined flag is used", func() { + devfilePath := filepath.Join(commonVar.Context, "devfile.yaml") + content, err := ioutil.ReadFile(devfilePath) + Expect(err).To(BeNil()) + matchInOutput := []string{"kubernetes", "inlined", "ServiceBinding"} + helper.MatchAllInOutput(string(content), matchInOutput) + }) + + It("should find the link in odo describe", func() { + stdOut := helper.Cmd("odo", "describe", "--context", commonVar.Context).ShouldPass().Out() + Expect(stdOut).To(ContainSubstring(svcFullName)) + }) + + When("odo push is executed", func() { + BeforeEach(func() { + helper.Cmd("odo", "push", "--context", commonVar.Context).ShouldPass() + name := commonVar.CliRunner.GetRunningPodNameByComponent(componentName, commonVar.Project) + Expect(name).To(Not(BeEmpty())) + }) + + It("should find the link in odo describe", func() { + stdOut := helper.Cmd("odo", "describe", "--context", commonVar.Context).ShouldPass().Out() + Expect(stdOut).To(ContainSubstring(svcFullName)) + Expect(stdOut).To(ContainSubstring("Environment Variables")) + Expect(stdOut).To(ContainSubstring("REDIS_CLUSTERIP")) + }) + }) + }) + + When("a link with between the component and the service is created with --bind-as-files and --inlined", func() { + + var bindingName string + BeforeEach(func() { + bindingName = "sbr-" + helper.RandString(6) + helper.Cmd("odo", "link", svcFullName, "--bind-as-files", "--name", bindingName, "--context", commonVar.Context, "--inlined").ShouldPass() + }) + + It("should insert service definition in devfile.yaml when the inlined flag is used", func() { + devfilePath := filepath.Join(commonVar.Context, "devfile.yaml") + content, err := ioutil.ReadFile(devfilePath) + Expect(err).To(BeNil()) + matchInOutput := []string{"kubernetes", "inlined", "Redis", "redis", "ServiceBinding"} + helper.MatchAllInOutput(string(content), matchInOutput) + }) + + It("should display the link in odo describe", func() { + stdOut := helper.Cmd("odo", "describe", "--context", commonVar.Context).ShouldPass().Out() + Expect(stdOut).To(ContainSubstring(svcFullName)) + }) + + When("odo push is executed", func() { + BeforeEach(func() { + helper.Cmd("odo", "push", "--context", commonVar.Context).ShouldPass() + name := commonVar.CliRunner.GetRunningPodNameByComponent(componentName, commonVar.Project) + Expect(name).To(Not(BeEmpty())) + }) + + It("should display the link in odo describe", func() { + stdOut := helper.Cmd("odo", "describe", "--context", commonVar.Context).ShouldPass().Out() + Expect(stdOut).To(ContainSubstring(svcFullName)) + Expect(stdOut).To(ContainSubstring("Files")) + Expect(stdOut).To(ContainSubstring("/bindings/" + bindingName + "/clusterIP")) + }) + }) + }) }) When("getting sources, a devfile defining a component, a service and a link, and executing odo push", func() { diff --git a/tests/integration/operatorhub/cmd_service_test.go b/tests/integration/operatorhub/cmd_service_test.go index 3935e36e0c6..a0be06f83ce 100644 --- a/tests/integration/operatorhub/cmd_service_test.go +++ b/tests/integration/operatorhub/cmd_service_test.go @@ -59,7 +59,7 @@ var _ = Describe("odo service command tests for OperatorHub", func() { if os.Getenv("KUBERNETES") == "true" { Skip("This is a OpenShift specific scenario, skipping") } - projectName = util.GetEnvWithDefault("REDHAT_POSTGRES_OPERATOR_PROJECT", "odo-operator-test") + projectName = util.GetEnvWithDefault("REDHAT_POSTGRES_OPERATOR_PROJECT", "blah") helper.GetCliRunner().SetProject(projectName) operators := helper.Cmd("odo", "catalog", "list", "services").ShouldPass().Out() postgresOperator = regexp.MustCompile(`postgresql-operator\.*[a-z][0-9]\.[0-9]\.[0-9]`).FindString(operators) @@ -266,14 +266,14 @@ var _ = Describe("odo service command tests for OperatorHub", func() { }) }) - When("a Redis instance is created with no name", func() { + When("a Redis instance is created with no name and inlined flag is used", func() { var stdOut string BeforeEach(func() { - stdOut = helper.Cmd("odo", "service", "create", fmt.Sprintf("%s/Redis", redisOperator), "--project", commonVar.Project).ShouldPass().Out() + stdOut = helper.Cmd("odo", "service", "create", fmt.Sprintf("%s/Redis", redisOperator), "--project", commonVar.Project, "--inlined").ShouldPass().Out() Expect(stdOut).To(ContainSubstring("Successfully added service to the configuration")) }) - It("should insert service definition in devfile.yaml", func() { + It("should insert service definition in devfile.yaml when the inlined flag is used", func() { devfilePath := filepath.Join(commonVar.Context, "devfile.yaml") content, err := ioutil.ReadFile(devfilePath) Expect(err).To(BeNil()) @@ -410,6 +410,14 @@ var _ = Describe("odo service command tests for OperatorHub", func() { helper.Cmd("odo", "service", "delete", svcFullName, "-f").ShouldRun() }) + It("should not insert service definition in devfile.yaml when the inlined flag is not used", func() { + devfilePath := filepath.Join(commonVar.Context, "devfile.yaml") + content, err := ioutil.ReadFile(devfilePath) + Expect(err).To(BeNil()) + matchInOutput := []string{"redis", "Redis", "inlined"} + helper.DontMatchAllInOutput(string(content), matchInOutput) + }) + It("should be listed as Not pushed", func() { stdOut := helper.Cmd("odo", "service", "list").ShouldPass().Out() helper.MatchAllInOutput(stdOut, []string{svcFullName, "Not pushed"})