From bb2ccc2c70e2b55005c97a9b6469de5dd8de7f34 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Fri, 26 Aug 2022 14:59:38 +0200 Subject: [PATCH 1/5] Move 'starter_project.go' from 'pkg/component' to 'pkg/registry' Functions in this file are only called from the 'registry' package, so it makes sense for this file to belong to this package. Furthermore, this paves the way to calling 'alizer.DetectName' from 'component.go', thus avoiding a cyclic dependency between packages. --- pkg/registry/registry.go | 3 +-- pkg/{component => registry}/starter_project.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) rename pkg/{component => registry}/starter_project.go (99%) diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index c9228b15582..b7444ad9f6d 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -14,7 +14,6 @@ import ( "github.com/devfile/registry-support/registry-library/library" "github.com/redhat-developer/odo/pkg/api" - "github.com/redhat-developer/odo/pkg/component" "github.com/redhat-developer/odo/pkg/devfile" "github.com/redhat-developer/odo/pkg/devfile/location" "github.com/redhat-developer/odo/pkg/log" @@ -51,7 +50,7 @@ func (o RegistryClient) DownloadFileInMemory(params dfutil.HTTPRequestParams) ([ // DownloadStarterProject downloads a starter project referenced in devfile // This will first remove the content of the contextDir func (o RegistryClient) DownloadStarterProject(starterProject *devfilev1.StarterProject, decryptedToken string, contextDir string, verbose bool) error { - return component.DownloadStarterProject(starterProject, decryptedToken, contextDir, verbose) + return DownloadStarterProject(starterProject, decryptedToken, contextDir, verbose) } // GetDevfileRegistries gets devfile registries from preference file, diff --git a/pkg/component/starter_project.go b/pkg/registry/starter_project.go similarity index 99% rename from pkg/component/starter_project.go rename to pkg/registry/starter_project.go index 7d06f96cb98..2c189afabd4 100644 --- a/pkg/component/starter_project.go +++ b/pkg/registry/starter_project.go @@ -1,4 +1,4 @@ -package component +package registry import ( "errors" From 0fe155fd9088e64977eba8c79abfa524c0740041 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Fri, 26 Aug 2022 15:05:08 +0200 Subject: [PATCH 2/5] Introduce central logic for determining component names in 'pkg/component/component.go' This rewrites the 'component#GatherName' function that was already there, but not used, to meet the expectations, i.e.: - use 'metadata.name' field (after sanitizing it) if it is defined in the Devfile - otherwise, use Alizer to detect the name. Under the hood, this leverages the 'alizer#DetectName' introduced in 83ad3ee, which means that: -- use Alizer to detect the name automatically -- otherwise, use the name of the Devfile base directory after sanitizing it --- pkg/component/component.go | 55 +++++++------ pkg/component/component_test.go | 142 +++++++++++++++++++++++++++++++- 2 files changed, 170 insertions(+), 27 deletions(-) diff --git a/pkg/component/component.go b/pkg/component/component.go index b0e2a21d07f..f96862be402 100644 --- a/pkg/component/component.go +++ b/pkg/component/component.go @@ -11,16 +11,17 @@ import ( "github.com/devfile/api/v2/pkg/devfile" "github.com/devfile/library/pkg/devfile/parser" "github.com/devfile/library/pkg/devfile/parser/data" - dfutil "github.com/devfile/library/pkg/util" + "k8s.io/klog" + "github.com/redhat-developer/odo/pkg/alizer" "github.com/redhat-developer/odo/pkg/api" "github.com/redhat-developer/odo/pkg/kclient" "github.com/redhat-developer/odo/pkg/labels" odolabels "github.com/redhat-developer/odo/pkg/labels" + "github.com/redhat-developer/odo/pkg/util" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/klog" ) const ( @@ -42,32 +43,36 @@ func GetComponentTypeFromDevfileMetadata(metadata devfile.DevfileMetadata) strin return componentType } -// GatherName parses the Devfile and retrieves an appropriate name in two ways. -// 1. If metadata.name exists, we use it -// 2. If metadata.name does NOT exist, we use the folder name where the devfile.yaml is located -func GatherName(devObj parser.DevfileObj, devfilePath string) (string, error) { - - metadata := devObj.Data.GetMetadata() - - klog.V(4).Infof("metadata.Name: %s", metadata.Name) - - // 1. Use metadata.name if it exists - if metadata.Name != "" { - - // Remove any suffix's that end with `-`. This is because many Devfile's use the original v1 Devfile pattern of - // having names such as "foo-bar-" in order to prepend container names such as "foo-bar-container1" - return strings.TrimSuffix(metadata.Name, "-"), nil +// GatherName computes and returns what should be used as name for the Devfile object specified. +// +// If a non-blank name is available in the Devfile metadata (which is optional), it is sanitized and returned. +// +// Otherwise, it uses Alizer to detect the name, from the project build tools (pom.xml, package.json, ...), +// or from the component directory name. +func GatherName(contextDir string, devfileObj *parser.DevfileObj) (string, error) { + var name string + if devfileObj != nil { + name = devfileObj.GetMetadataName() + if name == "" || strings.TrimSpace(name) == "" { + // Use Alizer if Devfile has no (optional) metadata.name field. + // We need to pass in the Devfile base directory (not the path to the devfile.yaml). + // Name returned by alizer.DetectName is expected to be already sanitized. + return alizer.DetectName(filepath.Dir(devfileObj.Ctx.GetAbsPath())) + } + } else { + // Fallback to the context dir name + baseDir, err := filepath.Abs(contextDir) + if err != nil { + return "", err + } + name = filepath.Base(baseDir) } - // 2. Use the folder name as a last resort if nothing else exists - sourcePath, err := dfutil.GetAbsPath(devfilePath) - if err != nil { - return "", fmt.Errorf("unable to get source path: %w", err) - } - klog.V(4).Infof("Source path: %s", sourcePath) - klog.V(4).Infof("devfile dir: %s", filepath.Dir(sourcePath)) + //sanitize the name + s := util.GetDNS1123Name(name) + klog.V(3).Infof("name of component is %q, and sanitized name is %q", name, s) - return filepath.Base(filepath.Dir(sourcePath)), nil + return s, nil } // GetOnePod gets a pod using the component and app name diff --git a/pkg/component/component_test.go b/pkg/component/component_test.go index 119b2d10a07..c3eeb6891f6 100644 --- a/pkg/component/component_test.go +++ b/pkg/component/component_test.go @@ -2,17 +2,28 @@ package component import ( "errors" + "os" + "path" + "path/filepath" "reflect" "testing" devfilepkg "github.com/devfile/api/v2/pkg/devfile" + "github.com/devfile/library/pkg/devfile" + "github.com/devfile/library/pkg/devfile/parser" + devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" + "github.com/devfile/library/pkg/devfile/parser/data" + "github.com/devfile/library/pkg/testingutil/filesystem" + dfutil "github.com/devfile/library/pkg/util" "github.com/golang/mock/gomock" "github.com/kylelemons/godebug/pretty" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/redhat-developer/odo/pkg/kclient" "github.com/redhat-developer/odo/pkg/labels" - - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/redhat-developer/odo/pkg/testingutil" + "github.com/redhat-developer/odo/pkg/util" "github.com/redhat-developer/odo/pkg/api" ) @@ -345,3 +356,130 @@ func TestGetRunningModes(t *testing.T) { }) } } + +func TestGatherName(t *testing.T) { + type devfileProvider func() (*parser.DevfileObj, string, error) + fakeDevfileWithNameProvider := func(name string) devfileProvider { + return func() (*parser.DevfileObj, string, error) { + dData, err := data.NewDevfileData(string(data.APISchemaVersion220)) + if err != nil { + return nil, "", err + } + dData.SetMetadata(devfilepkg.DevfileMetadata{Name: name}) + return &parser.DevfileObj{ + Ctx: devfileCtx.FakeContext(filesystem.NewFakeFs(), parser.OutputDevfileYamlPath), + Data: dData, + }, "", nil + } + } + + fs := filesystem.DefaultFs{} + //realDevfileWithNameProvider creates a real temporary directory and writes a devfile with the given name to it. + //It is the responsibility of the caller to remove the directory. + realDevfileWithNameProvider := func(name string) devfileProvider { + return func() (*parser.DevfileObj, string, error) { + dir, err := fs.TempDir("", "Component_GatherName_") + if err != nil { + return nil, dir, err + } + + originalDevfile := testingutil.GetTestDevfileObjFromFile("devfile.yaml") + originalDevfilePath := originalDevfile.Ctx.GetAbsPath() + + stat, err := os.Stat(originalDevfilePath) + if err != nil { + return nil, dir, err + } + dPath := path.Join(dir, "devfile.yaml") + err = dfutil.CopyFile(originalDevfilePath, dPath, stat) + if err != nil { + return nil, dir, err + } + + var d parser.DevfileObj + d, _, err = devfile.ParseDevfileAndValidate(parser.ParserArgs{Path: dPath}) + if err != nil { + return nil, dir, err + } + + err = d.SetMetadataName(name) + + return &d, dir, err + } + } + + wantDevfileDirectoryName := func(contextDir string, d *parser.DevfileObj) string { + return util.GetDNS1123Name(filepath.Base(filepath.Dir(d.Ctx.GetAbsPath()))) + } + + for _, tt := range []struct { + name string + devfileProviderFunc devfileProvider + wantErr bool + want func(contextDir string, d *parser.DevfileObj) string + }{ + { + name: "compliant name", + devfileProviderFunc: fakeDevfileWithNameProvider("my-component-name"), + want: func(contextDir string, d *parser.DevfileObj) string { return "my-component-name" }, + }, + { + name: "un-sanitized name", + devfileProviderFunc: fakeDevfileWithNameProvider("name with spaces"), + want: func(contextDir string, d *parser.DevfileObj) string { return "name-with-spaces" }, + }, + { + name: "all numeric name", + devfileProviderFunc: fakeDevfileWithNameProvider("123456789"), + // "x" prefix added by util.GetDNS1123Name + want: func(contextDir string, d *parser.DevfileObj) string { return "x123456789" }, + }, + { + name: "no name", + devfileProviderFunc: realDevfileWithNameProvider(""), + want: wantDevfileDirectoryName, + }, + { + name: "blank name", + devfileProviderFunc: realDevfileWithNameProvider(" "), + want: wantDevfileDirectoryName, + }, + { + name: "passing no devfile should use the context directory name", + devfileProviderFunc: func() (*parser.DevfileObj, string, error) { + dir, err := fs.TempDir("", "Component_GatherName_") + if err != nil { + return nil, dir, err + } + return nil, dir, nil + }, + want: func(contextDir string, _ *parser.DevfileObj) string { + return util.GetDNS1123Name(filepath.Base(contextDir)) + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + d, dir, dErr := tt.devfileProviderFunc() + if dir != "" { + defer func(fs filesystem.Filesystem, path string) { + if err := fs.RemoveAll(path); err != nil { + t.Logf("error while attempting to remove temporary directory %q: %v", path, err) + } + }(fs, dir) + } + if dErr != nil { + t.Errorf("error when building test Devfile object: %v", dErr) + return + } + + got, err := GatherName(dir, d) + if (err != nil) != tt.wantErr { + t.Errorf("error = %v, wantErr %v", err, tt.wantErr) + } + want := tt.want(dir, d) + if !reflect.DeepEqual(got, want) { + t.Errorf("GatherName() = %q, want = %q", got, want) + } + }) + } +} From 2d9af3e8bb9f05a34b5b330ebc6fbbeb533b5017 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Fri, 26 Aug 2022 15:09:02 +0200 Subject: [PATCH 3/5] Compute and store the component name in the CLI context, and pass it as needed As commented out in [1], the context should ideally be built and passed down to the business clients structs. [1] https://github.com/redhat-developer/odo/pull/6015#discussion_r957005479 --- pkg/binding/add.go | 3 +- pkg/binding/binding_test.go | 10 +- pkg/binding/interface.go | 1 + pkg/binding/mock.go | 8 +- pkg/component/component.go | 15 +- pkg/component/delete/delete.go | 8 +- pkg/component/delete/delete_test.go | 4 +- pkg/component/delete/interface.go | 4 +- pkg/component/delete/mock.go | 16 +- pkg/deploy/deploy.go | 30 +-- pkg/deploy/interface.go | 2 +- pkg/deploy/mock.go | 8 +- pkg/dev/dev.go | 13 +- pkg/dev/interface.go | 2 + pkg/dev/mock.go | 16 +- .../kubernetes/component/commandhandler.go | 6 +- pkg/odo/cli/add/binding/binding.go | 4 +- pkg/odo/cli/delete/component/component.go | 8 +- .../cli/delete/component/component_test.go | 14 +- pkg/odo/cli/deploy/deploy.go | 6 +- pkg/odo/cli/describe/component.go | 5 +- pkg/odo/cli/dev/dev.go | 22 +- pkg/odo/cli/list/component/list.go | 3 +- pkg/odo/cli/list/list.go | 3 +- pkg/odo/cli/logs/logs.go | 13 +- pkg/odo/genericclioptions/context.go | 22 +- pkg/odo/genericclioptions/context_test.go | 212 ++++++++++++------ pkg/watch/interface.go | 2 +- pkg/watch/mock.go | 8 +- pkg/watch/watch.go | 6 +- 30 files changed, 310 insertions(+), 164 deletions(-) diff --git a/pkg/binding/add.go b/pkg/binding/add.go index c34a878fa49..b76bed65338 100644 --- a/pkg/binding/add.go +++ b/pkg/binding/add.go @@ -100,6 +100,7 @@ func (o *BindingClient) AskNamingStrategy(flags map[string]string) (string, erro } func (o *BindingClient) AddBindingToDevfile( + componentName string, bindingName string, bindAsFiles bool, serviceNs string, @@ -112,7 +113,7 @@ func (o *BindingClient) AddBindingToDevfile( return obj, err } - deploymentName := fmt.Sprintf("%s-app", obj.GetMetadataName()) + deploymentName := fmt.Sprintf("%s-app", componentName) deploymentGVK, err := o.kubernetesClient.GetDeploymentAPIVersion() if err != nil { return obj, err diff --git a/pkg/binding/binding_test.go b/pkg/binding/binding_test.go index f1a0c2986b4..620348960c9 100644 --- a/pkg/binding/binding_test.go +++ b/pkg/binding/binding_test.go @@ -395,7 +395,15 @@ func TestBindingClient_AddBindingToDevfile(t *testing.T) { o := &BindingClient{ kubernetesClient: tt.fields.kubernetesClient(ctrl), } - got, err := o.AddBindingToDevfile(tt.args.bindingName, tt.args.bindAsFiles, tt.args.namespace, tt.args.namingStrategy, tt.args.unstructuredService, tt.args.obj) + got, err := o.AddBindingToDevfile( + tt.args.obj.GetMetadataName(), + tt.args.bindingName, + tt.args.bindAsFiles, + tt.args.namespace, + tt.args.namingStrategy, + tt.args.unstructuredService, + tt.args.obj, + ) if (err != nil) != tt.wantErr { t.Errorf("AddBindingToDevfile() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/binding/interface.go b/pkg/binding/interface.go index f2dcace6378..f1970b5cdb8 100644 --- a/pkg/binding/interface.go +++ b/pkg/binding/interface.go @@ -39,6 +39,7 @@ type Client interface { AskNamingStrategy(flags map[string]string) (string, error) // AddBindingToDevfile adds the ServiceBinding manifest to the devfile AddBindingToDevfile( + componentName string, bindingName string, bindAsFiles bool, serviceNs string, diff --git a/pkg/binding/mock.go b/pkg/binding/mock.go index 873ec147dee..02e70b680ea 100644 --- a/pkg/binding/mock.go +++ b/pkg/binding/mock.go @@ -56,18 +56,18 @@ func (mr *MockClientMockRecorder) AddBinding(flags, bindingName, bindAsFiles, se } // AddBindingToDevfile mocks base method. -func (m *MockClient) AddBindingToDevfile(bindingName string, bindAsFiles bool, serviceNs, namingStrategy string, unstructuredService unstructured.Unstructured, obj parser.DevfileObj) (parser.DevfileObj, error) { +func (m *MockClient) AddBindingToDevfile(componentName, bindingName string, bindAsFiles bool, serviceNs, namingStrategy string, unstructuredService unstructured.Unstructured, obj parser.DevfileObj) (parser.DevfileObj, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddBindingToDevfile", bindingName, bindAsFiles, serviceNs, namingStrategy, unstructuredService, obj) + ret := m.ctrl.Call(m, "AddBindingToDevfile", componentName, bindingName, bindAsFiles, serviceNs, namingStrategy, unstructuredService, obj) ret0, _ := ret[0].(parser.DevfileObj) ret1, _ := ret[1].(error) return ret0, ret1 } // AddBindingToDevfile indicates an expected call of AddBindingToDevfile. -func (mr *MockClientMockRecorder) AddBindingToDevfile(bindingName, bindAsFiles, serviceNs, namingStrategy, unstructuredService, obj interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) AddBindingToDevfile(componentName, bindingName, bindAsFiles, serviceNs, namingStrategy, unstructuredService, obj interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBindingToDevfile", reflect.TypeOf((*MockClient)(nil).AddBindingToDevfile), bindingName, bindAsFiles, serviceNs, namingStrategy, unstructuredService, obj) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBindingToDevfile", reflect.TypeOf((*MockClient)(nil).AddBindingToDevfile), componentName, bindingName, bindAsFiles, serviceNs, namingStrategy, unstructuredService, obj) } // AskBindAsFiles mocks base method. diff --git a/pkg/component/component.go b/pkg/component/component.go index f96862be402..0291480764d 100644 --- a/pkg/component/component.go +++ b/pkg/component/component.go @@ -200,20 +200,19 @@ func ListAllClusterComponents(client kclient.ClientInterface, namespace string) return components, nil } -func ListAllComponents(client kclient.ClientInterface, namespace string, devObj parser.DevfileObj) ([]api.ComponentAbstract, string, error) { +func ListAllComponents(client kclient.ClientInterface, namespace string, devObj parser.DevfileObj, componentName string) ([]api.ComponentAbstract, string, error) { devfileComponents, err := ListAllClusterComponents(client, namespace) if err != nil { return nil, "", err } - var localComponent api.ComponentAbstract + localComponent := api.ComponentAbstract{ + Name: componentName, + ManagedBy: "", + RunningIn: []api.RunningMode{}, + } if devObj.Data != nil { - localComponent = api.ComponentAbstract{ - Name: devObj.Data.GetMetadata().Name, - ManagedBy: "", - RunningIn: []api.RunningMode{}, - Type: GetComponentTypeFromDevfileMetadata(devObj.Data.GetMetadata()), - } + localComponent.Type = GetComponentTypeFromDevfileMetadata(devObj.Data.GetMetadata()) } componentInDevfile := "" diff --git a/pkg/component/delete/delete.go b/pkg/component/delete/delete.go index 2bd6b24b622..744ff44fb26 100644 --- a/pkg/component/delete/delete.go +++ b/pkg/component/delete/delete.go @@ -90,12 +90,10 @@ func references(list []unstructured.Unstructured, ownerRef metav1.OwnerReference } // ListResourcesToDeleteFromDevfile parses all the devfile components and returns a list of resources that are present on the cluster and can be deleted -func (do DeleteComponentClient) ListResourcesToDeleteFromDevfile(devfileObj parser.DevfileObj, appName string, mode string) (isInnerLoopDeployed bool, resources []unstructured.Unstructured, err error) { +func (do DeleteComponentClient) ListResourcesToDeleteFromDevfile(devfileObj parser.DevfileObj, appName string, componentName string, mode string) (isInnerLoopDeployed bool, resources []unstructured.Unstructured, err error) { var deployment *v1.Deployment if mode == odolabels.ComponentDevMode || mode == odolabels.ComponentAnyMode { // Inner Loop - // Fetch the deployment of the devfile component - componentName := devfileObj.GetMetadataName() var deploymentName string deploymentName, err = util.NamespaceKubernetesObject(componentName, appName) if err != nil { @@ -151,11 +149,11 @@ func (do DeleteComponentClient) ListResourcesToDeleteFromDevfile(devfileObj pars } // ExecutePreStopEvents executes preStop events if any, as a precondition to deleting a devfile component deployment -func (do *DeleteComponentClient) ExecutePreStopEvents(devfileObj parser.DevfileObj, appName string) error { +func (do *DeleteComponentClient) ExecutePreStopEvents(devfileObj parser.DevfileObj, appName string, componentName string) error { if !libdevfile.HasPreStopEvents(devfileObj) { return nil } - componentName := devfileObj.GetMetadataName() + klog.V(4).Infof("Gathering information for component: %q", componentName) klog.V(3).Infof("Checking component status for %q", componentName) diff --git a/pkg/component/delete/delete_test.go b/pkg/component/delete/delete_test.go index 31e5bb3fea7..d42117023ce 100644 --- a/pkg/component/delete/delete_test.go +++ b/pkg/component/delete/delete_test.go @@ -545,7 +545,7 @@ func TestDeleteComponentClient_ListResourcesToDeleteFromDevfile(t *testing.T) { do := DeleteComponentClient{ kubeClient: tt.fields.kubeClient(ctrl), } - gotIsInnerLoopDeployed, gotResources, err := do.ListResourcesToDeleteFromDevfile(tt.args.devfileObj, tt.args.appName, tt.args.mode) + gotIsInnerLoopDeployed, gotResources, err := do.ListResourcesToDeleteFromDevfile(tt.args.devfileObj, tt.args.appName, tt.args.devfileObj.GetMetadataName(), tt.args.mode) if (err != nil) != tt.wantErr { t.Errorf("ListResourcesToDeleteFromDevfile() error = %v, wantErr %v", err, tt.wantErr) return @@ -702,7 +702,7 @@ func TestDeleteComponentClient_ExecutePreStopEvents(t *testing.T) { do := &DeleteComponentClient{ kubeClient: tt.fields.kubeClient(ctrl), } - if err := do.ExecutePreStopEvents(tt.args.devfileObj, tt.args.appName); (err != nil) != tt.wantErr { + if err := do.ExecutePreStopEvents(tt.args.devfileObj, tt.args.appName, tt.args.devfileObj.GetMetadataName()); (err != nil) != tt.wantErr { t.Errorf("DeleteComponent() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/pkg/component/delete/interface.go b/pkg/component/delete/interface.go index 9566fae59c1..af00d235e77 100644 --- a/pkg/component/delete/interface.go +++ b/pkg/component/delete/interface.go @@ -12,9 +12,9 @@ type Client interface { // set wait to true to wait for all the dependencies to be deleted DeleteResources(resources []unstructured.Unstructured, wait bool) []unstructured.Unstructured // ExecutePreStopEvents executes preStop events if any, as a precondition to deleting a devfile component deployment - ExecutePreStopEvents(devfileObj parser.DevfileObj, appName string) error + ExecutePreStopEvents(devfileObj parser.DevfileObj, appName string, componentName string) error // ListResourcesToDeleteFromDevfile parses all the devfile components and returns a list of resources that are present on the cluster that can be deleted, // and a bool that indicates if the devfile component has been pushed to the innerloop // the mode indicates which component to list, either Dev, Deploy or Any (using constant labels.Component*Mode) - ListResourcesToDeleteFromDevfile(devfileObj parser.DevfileObj, appName string, mode string) (bool, []unstructured.Unstructured, error) + ListResourcesToDeleteFromDevfile(devfileObj parser.DevfileObj, appName string, componentName string, mode string) (bool, []unstructured.Unstructured, error) } diff --git a/pkg/component/delete/mock.go b/pkg/component/delete/mock.go index 786f8649fd4..db9bc2d40d4 100644 --- a/pkg/component/delete/mock.go +++ b/pkg/component/delete/mock.go @@ -50,17 +50,17 @@ func (mr *MockClientMockRecorder) DeleteResources(resources, wait interface{}) * } // ExecutePreStopEvents mocks base method. -func (m *MockClient) ExecutePreStopEvents(devfileObj parser.DevfileObj, appName string) error { +func (m *MockClient) ExecutePreStopEvents(devfileObj parser.DevfileObj, appName, componentName string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ExecutePreStopEvents", devfileObj, appName) + ret := m.ctrl.Call(m, "ExecutePreStopEvents", devfileObj, appName, componentName) ret0, _ := ret[0].(error) return ret0 } // ExecutePreStopEvents indicates an expected call of ExecutePreStopEvents. -func (mr *MockClientMockRecorder) ExecutePreStopEvents(devfileObj, appName interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) ExecutePreStopEvents(devfileObj, appName, componentName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecutePreStopEvents", reflect.TypeOf((*MockClient)(nil).ExecutePreStopEvents), devfileObj, appName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecutePreStopEvents", reflect.TypeOf((*MockClient)(nil).ExecutePreStopEvents), devfileObj, appName, componentName) } // ListClusterResourcesToDelete mocks base method. @@ -79,9 +79,9 @@ func (mr *MockClientMockRecorder) ListClusterResourcesToDelete(componentName, na } // ListResourcesToDeleteFromDevfile mocks base method. -func (m *MockClient) ListResourcesToDeleteFromDevfile(devfileObj parser.DevfileObj, appName, mode string) (bool, []unstructured.Unstructured, error) { +func (m *MockClient) ListResourcesToDeleteFromDevfile(devfileObj parser.DevfileObj, appName, componentName, mode string) (bool, []unstructured.Unstructured, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListResourcesToDeleteFromDevfile", devfileObj, appName, mode) + ret := m.ctrl.Call(m, "ListResourcesToDeleteFromDevfile", devfileObj, appName, componentName, mode) ret0, _ := ret[0].(bool) ret1, _ := ret[1].([]unstructured.Unstructured) ret2, _ := ret[2].(error) @@ -89,7 +89,7 @@ func (m *MockClient) ListResourcesToDeleteFromDevfile(devfileObj parser.DevfileO } // ListResourcesToDeleteFromDevfile indicates an expected call of ListResourcesToDeleteFromDevfile. -func (mr *MockClientMockRecorder) ListResourcesToDeleteFromDevfile(devfileObj, appName, mode interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) ListResourcesToDeleteFromDevfile(devfileObj, appName, componentName, mode interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListResourcesToDeleteFromDevfile", reflect.TypeOf((*MockClient)(nil).ListResourcesToDeleteFromDevfile), devfileObj, appName, mode) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListResourcesToDeleteFromDevfile", reflect.TypeOf((*MockClient)(nil).ListResourcesToDeleteFromDevfile), devfileObj, appName, componentName, mode) } diff --git a/pkg/deploy/deploy.go b/pkg/deploy/deploy.go index 54142a52f25..0129b497bd6 100644 --- a/pkg/deploy/deploy.go +++ b/pkg/deploy/deploy.go @@ -26,28 +26,30 @@ func NewDeployClient(kubeClient kclient.ClientInterface) *DeployClient { } } -func (o *DeployClient) Deploy(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path string, appName string) error { - deployHandler := newDeployHandler(fs, devfileObj, path, o.kubeClient, appName) +func (o *DeployClient) Deploy(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path string, appName string, componentName string) error { + deployHandler := newDeployHandler(fs, devfileObj, path, o.kubeClient, appName, componentName) return libdevfile.Deploy(devfileObj, deployHandler) } type deployHandler struct { - fs filesystem.Filesystem - devfileObj parser.DevfileObj - path string - kubeClient kclient.ClientInterface - appName string + fs filesystem.Filesystem + devfileObj parser.DevfileObj + path string + kubeClient kclient.ClientInterface + appName string + componentName string } var _ libdevfile.Handler = (*deployHandler)(nil) -func newDeployHandler(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path string, kubeClient kclient.ClientInterface, appName string) *deployHandler { +func newDeployHandler(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path string, kubeClient kclient.ClientInterface, appName string, componentName string) *deployHandler { return &deployHandler{ - fs: fs, - devfileObj: devfileObj, - path: path, - kubeClient: kubeClient, - appName: appName, + fs: fs, + devfileObj: devfileObj, + path: path, + kubeClient: kubeClient, + appName: appName, + componentName: componentName, } } @@ -58,7 +60,7 @@ func (o *deployHandler) ApplyImage(img v1alpha2.Component) error { // ApplyKubernetes applies inline Kubernetes YAML from the devfile.yaml file func (o *deployHandler) ApplyKubernetes(kubernetes v1alpha2.Component) error { - return component.ApplyKubernetes(odolabels.ComponentDeployMode, o.appName, o.devfileObj, kubernetes, o.kubeClient, o.path) + return component.ApplyKubernetes(odolabels.ComponentDeployMode, o.appName, o.componentName, o.devfileObj, kubernetes, o.kubeClient, o.path) } // Execute will deploy the listed information in the `exec` section of devfile.yaml diff --git a/pkg/deploy/interface.go b/pkg/deploy/interface.go index ae3a8b1e115..bf324ffab6f 100644 --- a/pkg/deploy/interface.go +++ b/pkg/deploy/interface.go @@ -10,5 +10,5 @@ type Client interface { // Deploy resources from a devfile located in path, for the specified appName. // The filesystem specified is used to download and store the Dockerfiles needed to build the necessary container images, // in case such Dockerfiles are referenced as remote URLs in the Devfile. - Deploy(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path string, appName string) error + Deploy(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path string, appName string, componentName string) error } diff --git a/pkg/deploy/mock.go b/pkg/deploy/mock.go index cfa1308d771..87effd7a2a3 100644 --- a/pkg/deploy/mock.go +++ b/pkg/deploy/mock.go @@ -36,15 +36,15 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder { } // Deploy mocks base method. -func (m *MockClient) Deploy(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path, appName string) error { +func (m *MockClient) Deploy(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path, appName, componentName string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Deploy", fs, devfileObj, path, appName) + ret := m.ctrl.Call(m, "Deploy", fs, devfileObj, path, appName, componentName) ret0, _ := ret[0].(error) return ret0 } // Deploy indicates an expected call of Deploy. -func (mr *MockClientMockRecorder) Deploy(fs, devfileObj, path, appName interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Deploy(fs, devfileObj, path, appName, componentName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockClient)(nil).Deploy), fs, devfileObj, path, appName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockClient)(nil).Deploy), fs, devfileObj, path, appName, componentName) } diff --git a/pkg/dev/dev.go b/pkg/dev/dev.go index 2697a05c7d4..f0862d40d3f 100644 --- a/pkg/dev/dev.go +++ b/pkg/dev/dev.go @@ -15,7 +15,7 @@ import ( "k8s.io/klog/v2" "github.com/redhat-developer/odo/pkg/devfile/adapters" - "github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes/component" + k8sComponent "github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes/component" "github.com/redhat-developer/odo/pkg/watch" ) @@ -47,6 +47,7 @@ func NewDevClient( func (o *DevClient) Start( devfileObj parser.DevfileObj, + componentName string, namespace string, ignorePaths []string, path string, @@ -58,10 +59,11 @@ func (o *DevClient) Start( fs filesystem.Filesystem, ) (watch.ComponentStatus, error) { klog.V(4).Infoln("Creating new adapter") - adapter := component.NewKubernetesAdapter( + + adapter := k8sComponent.NewKubernetesAdapter( o.kubernetesClient, o.prefClient, o.portForwardClient, o.bindingClient, - component.AdapterContext{ - ComponentName: devfileObj.GetMetadataName(), + k8sComponent.AdapterContext{ + ComponentName: componentName, Context: path, AppName: "app", Devfile: devfileObj, @@ -98,6 +100,7 @@ func (o *DevClient) Start( func (o *DevClient) Watch( devfilePath string, devfileObj parser.DevfileObj, + componentName string, path string, ignorePaths []string, out io.Writer, @@ -120,7 +123,7 @@ func (o *DevClient) Watch( watchParameters := watch.WatchParameters{ DevfilePath: devfilePath, Path: path, - ComponentName: devfileObj.GetMetadataName(), + ComponentName: componentName, ApplicationName: "app", DevfileWatchHandler: h.RegenerateAdapterAndPush, EnvSpecificInfo: envSpecificInfo, diff --git a/pkg/dev/interface.go b/pkg/dev/interface.go index a91c7c66128..e296fc34dd2 100644 --- a/pkg/dev/interface.go +++ b/pkg/dev/interface.go @@ -19,6 +19,7 @@ type Client interface { // Returns the status of the started component Start( devfileObj parser.DevfileObj, + componentName string, namespace string, ignorePaths []string, path string, @@ -40,6 +41,7 @@ type Client interface { Watch( devfilePath string, devfileObj parser.DevfileObj, + componentName string, path string, ignorePaths []string, out io.Writer, diff --git a/pkg/dev/mock.go b/pkg/dev/mock.go index 098afd40253..b79135498b5 100644 --- a/pkg/dev/mock.go +++ b/pkg/dev/mock.go @@ -40,32 +40,32 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder { } // Start mocks base method. -func (m *MockClient) Start(devfileObj parser.DevfileObj, namespace string, ignorePaths []string, path string, debug bool, buildCommand, runCommand string, randomPorts bool, errOut io.Writer, fs filesystem.Filesystem) (watch.ComponentStatus, error) { +func (m *MockClient) Start(devfileObj parser.DevfileObj, componentName, namespace string, ignorePaths []string, path string, debug bool, buildCommand, runCommand string, randomPorts bool, errOut io.Writer, fs filesystem.Filesystem) (watch.ComponentStatus, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Start", devfileObj, namespace, ignorePaths, path, debug, buildCommand, runCommand, randomPorts, errOut, fs) + ret := m.ctrl.Call(m, "Start", devfileObj, componentName, namespace, ignorePaths, path, debug, buildCommand, runCommand, randomPorts, errOut, fs) ret0, _ := ret[0].(watch.ComponentStatus) ret1, _ := ret[1].(error) return ret0, ret1 } // Start indicates an expected call of Start. -func (mr *MockClientMockRecorder) Start(devfileObj, namespace, ignorePaths, path, debug, buildCommand, runCommand, randomPorts, errOut, fs interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Start(devfileObj, componentName, namespace, ignorePaths, path, debug, buildCommand, runCommand, randomPorts, errOut, fs interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockClient)(nil).Start), devfileObj, namespace, ignorePaths, path, debug, buildCommand, runCommand, randomPorts, errOut, fs) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockClient)(nil).Start), devfileObj, componentName, namespace, ignorePaths, path, debug, buildCommand, runCommand, randomPorts, errOut, fs) } // Watch mocks base method. -func (m *MockClient) Watch(devfilePath string, devfileObj parser.DevfileObj, path string, ignorePaths []string, out io.Writer, h Handler, ctx context.Context, debug bool, buildCommand, runCommand string, variables map[string]string, randomPorts, watchFiles bool, errOut io.Writer, componentStatus watch.ComponentStatus) error { +func (m *MockClient) Watch(devfilePath string, devfileObj parser.DevfileObj, componentName, path string, ignorePaths []string, out io.Writer, h Handler, ctx context.Context, debug bool, buildCommand, runCommand string, variables map[string]string, randomPorts, watchFiles bool, errOut io.Writer, componentStatus watch.ComponentStatus) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Watch", devfilePath, devfileObj, path, ignorePaths, out, h, ctx, debug, buildCommand, runCommand, variables, randomPorts, watchFiles, errOut, componentStatus) + ret := m.ctrl.Call(m, "Watch", devfilePath, devfileObj, componentName, path, ignorePaths, out, h, ctx, debug, buildCommand, runCommand, variables, randomPorts, watchFiles, errOut, componentStatus) ret0, _ := ret[0].(error) return ret0 } // Watch indicates an expected call of Watch. -func (mr *MockClientMockRecorder) Watch(devfilePath, devfileObj, path, ignorePaths, out, h, ctx, debug, buildCommand, runCommand, variables, randomPorts, watchFiles, errOut, componentStatus interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Watch(devfilePath, devfileObj, componentName, path, ignorePaths, out, h, ctx, debug, buildCommand, runCommand, variables, randomPorts, watchFiles, errOut, componentStatus interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Watch", reflect.TypeOf((*MockClient)(nil).Watch), devfilePath, devfileObj, path, ignorePaths, out, h, ctx, debug, buildCommand, runCommand, variables, randomPorts, watchFiles, errOut, componentStatus) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Watch", reflect.TypeOf((*MockClient)(nil).Watch), devfilePath, devfileObj, componentName, path, ignorePaths, out, h, ctx, debug, buildCommand, runCommand, variables, randomPorts, watchFiles, errOut, componentStatus) } // MockHandler is a mock of Handler interface. diff --git a/pkg/devfile/adapters/kubernetes/component/commandhandler.go b/pkg/devfile/adapters/kubernetes/component/commandhandler.go index c5918663c08..c34b9056d81 100644 --- a/pkg/devfile/adapters/kubernetes/component/commandhandler.go +++ b/pkg/devfile/adapters/kubernetes/component/commandhandler.go @@ -42,7 +42,7 @@ func (a *adapterHandler) ApplyImage(img devfilev1.Component) error { } func (a *adapterHandler) ApplyKubernetes(kubernetes devfilev1.Component) error { - return ApplyKubernetes(odolabels.ComponentDevMode, a.AppName, a.Devfile, kubernetes, a.kubeClient, a.parameters.Path) + return ApplyKubernetes(odolabels.ComponentDevMode, a.AppName, a.ComponentName, a.Devfile, kubernetes, a.kubeClient, a.parameters.Path) } func (a *adapterHandler) Execute(devfileCmd devfilev1.Command) error { @@ -137,7 +137,7 @@ func (a *adapterHandler) Execute(devfileCmd devfilev1.Command) error { // kubernetes: the kubernetes devfile component to be deployed // kubeClient: Kubernetes client to be used to deploy the resource // path: path to the context directory -func ApplyKubernetes(mode, appName string, devfile parser.DevfileObj, kubernetes devfilev1.Component, kubeClient kclient.ClientInterface, path string) error { +func ApplyKubernetes(mode, appName string, componentName string, devfile parser.DevfileObj, kubernetes devfilev1.Component, kubeClient kclient.ClientInterface, path string) error { // Validate if the GVRs represented by Kubernetes inlined components are supported by the underlying cluster _, err := ValidateResourceExist(kubeClient, devfile, kubernetes, path) if err != nil { @@ -146,7 +146,7 @@ func ApplyKubernetes(mode, appName string, devfile parser.DevfileObj, kubernetes // Get the most common labels that's applicable to all resources being deployed. // Set the mode. Regardless of what Kubernetes resource we are deploying. - labels := odolabels.GetLabels(devfile.Data.GetMetadata().Name, appName, mode, false) + labels := odolabels.GetLabels(componentName, appName, mode, false) klog.V(4).Infof("Injecting labels: %+v into k8s artifact", labels) diff --git a/pkg/odo/cli/add/binding/binding.go b/pkg/odo/cli/add/binding/binding.go index af235a793d8..58d1a080cf9 100644 --- a/pkg/odo/cli/add/binding/binding.go +++ b/pkg/odo/cli/add/binding/binding.go @@ -123,7 +123,7 @@ func (o *AddBindingOptions) Run(_ context.Context) error { } componentName = workloadName } else { - componentName = o.EnvSpecificInfo.GetDevfileObj().GetMetadataName() + componentName = o.GetComponentName() } bindingName, err := o.clientset.BindingClient.AskBindingName(serviceName, componentName, o.flags) @@ -144,7 +144,7 @@ func (o *AddBindingOptions) Run(_ context.Context) error { if withDevfile { var devfileobj parser.DevfileObj devfileobj, err = o.clientset.BindingClient.AddBindingToDevfile( - bindingName, bindAsFiles, ns, namingStrategy, serviceMap[service], o.EnvSpecificInfo.GetDevfileObj()) + componentName, bindingName, bindAsFiles, ns, namingStrategy, serviceMap[service], o.EnvSpecificInfo.GetDevfileObj()) if err != nil { return err } diff --git a/pkg/odo/cli/delete/component/component.go b/pkg/odo/cli/delete/component/component.go index 2f4f80c08fa..dcfa3b3704d 100644 --- a/pkg/odo/cli/delete/component/component.go +++ b/pkg/odo/cli/delete/component/component.go @@ -122,12 +122,14 @@ func (o *ComponentOptions) deleteNamedComponent() error { // deleteDevfileComponent deletes all the components defined by the devfile in the current directory func (o *ComponentOptions) deleteDevfileComponent() error { devfileObj := o.EnvSpecificInfo.GetDevfileObj() - componentName := devfileObj.GetMetadataName() + + componentName := o.GetComponentName() + namespace := o.GetProject() appName := "app" log.Info("Searching resources to delete, please wait...") - isInnerLoopDeployed, devfileResources, err := o.clientset.DeleteClient.ListResourcesToDeleteFromDevfile(devfileObj, appName, labels.ComponentAnyMode) + isInnerLoopDeployed, devfileResources, err := o.clientset.DeleteClient.ListResourcesToDeleteFromDevfile(devfileObj, appName, componentName, labels.ComponentAnyMode) if err != nil { return err } @@ -146,7 +148,7 @@ func (o *ComponentOptions) deleteDevfileComponent() error { // if innerloop deployment resource is present, then execute preStop events if isInnerLoopDeployed { - err = o.clientset.DeleteClient.ExecutePreStopEvents(devfileObj, appName) + err = o.clientset.DeleteClient.ExecutePreStopEvents(devfileObj, appName, componentName) if err != nil { log.Errorf("Failed to execute preStop events") } diff --git a/pkg/odo/cli/delete/component/component_test.go b/pkg/odo/cli/delete/component/component_test.go index 30588bd55a6..ab3123b2756 100644 --- a/pkg/odo/cli/delete/component/component_test.go +++ b/pkg/odo/cli/delete/component/component_test.go @@ -130,9 +130,9 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) { name: "deleting a component with access to devfile", deleteClient: func(ctrl *gomock.Controller) _delete.Client { deleteClient := _delete.NewMockClient(ctrl) - deleteClient.EXPECT().ListResourcesToDeleteFromDevfile(gomock.Any(), appName, labels.ComponentAnyMode).Return(true, resources, nil) + deleteClient.EXPECT().ListResourcesToDeleteFromDevfile(gomock.Any(), appName, gomock.Any(), labels.ComponentAnyMode).Return(true, resources, nil) deleteClient.EXPECT().ListClusterResourcesToDelete(compName, projectName).Return(resources, nil) - deleteClient.EXPECT().ExecutePreStopEvents(gomock.Any(), gomock.Any()).Return(nil) + deleteClient.EXPECT().ExecutePreStopEvents(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) deleteClient.EXPECT().DeleteResources(resources, false).Return([]unstructured.Unstructured{}) return deleteClient }, @@ -145,9 +145,9 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) { name: "deleting a component should not fail even if ExecutePreStopEvents fails", deleteClient: func(ctrl *gomock.Controller) _delete.Client { deleteClient := _delete.NewMockClient(ctrl) - deleteClient.EXPECT().ListResourcesToDeleteFromDevfile(gomock.Any(), appName, labels.ComponentAnyMode).Return(true, resources, nil) + deleteClient.EXPECT().ListResourcesToDeleteFromDevfile(gomock.Any(), appName, gomock.Any(), labels.ComponentAnyMode).Return(true, resources, nil) deleteClient.EXPECT().ListClusterResourcesToDelete(compName, projectName).Return(resources, nil) - deleteClient.EXPECT().ExecutePreStopEvents(gomock.Any(), appName).Return(errors.New("some error")) + deleteClient.EXPECT().ExecutePreStopEvents(gomock.Any(), appName, gomock.Any()).Return(errors.New("some error")) deleteClient.EXPECT().DeleteResources(resources, false).Return(nil) return deleteClient }, @@ -160,7 +160,7 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) { name: "deleting a component should fail if ListResourcesToDeleteFromDevfile fails", deleteClient: func(ctrl *gomock.Controller) _delete.Client { deleteClient := _delete.NewMockClient(ctrl) - deleteClient.EXPECT().ListResourcesToDeleteFromDevfile(gomock.Any(), appName, labels.ComponentAnyMode).Return(false, nil, errors.New("some error")) + deleteClient.EXPECT().ListResourcesToDeleteFromDevfile(gomock.Any(), appName, gomock.Any(), labels.ComponentAnyMode).Return(false, nil, errors.New("some error")) return deleteClient }, fields: fields{ @@ -172,7 +172,7 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) { name: "deleting a component should be aborted if forceFlag is not passed", deleteClient: func(ctrl *gomock.Controller) _delete.Client { deleteClient := _delete.NewMockClient(ctrl) - deleteClient.EXPECT().ListResourcesToDeleteFromDevfile(gomock.Any(), appName, labels.ComponentAnyMode).Return(true, resources, nil) + deleteClient.EXPECT().ListResourcesToDeleteFromDevfile(gomock.Any(), appName, gomock.Any(), labels.ComponentAnyMode).Return(true, resources, nil) return deleteClient }, fields: fields{ @@ -184,7 +184,7 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) { name: "nothing to delete", deleteClient: func(ctrl *gomock.Controller) _delete.Client { deleteClient := _delete.NewMockClient(ctrl) - deleteClient.EXPECT().ListResourcesToDeleteFromDevfile(gomock.Any(), appName, labels.ComponentAnyMode).Return(false, nil, nil) + deleteClient.EXPECT().ListResourcesToDeleteFromDevfile(gomock.Any(), appName, gomock.Any(), labels.ComponentAnyMode).Return(false, nil, nil) return deleteClient }, fields: fields{ diff --git a/pkg/odo/cli/deploy/deploy.go b/pkg/odo/cli/deploy/deploy.go index f4840865be8..d8efdea4017 100644 --- a/pkg/odo/cli/deploy/deploy.go +++ b/pkg/odo/cli/deploy/deploy.go @@ -115,7 +115,9 @@ func (o *DeployOptions) Validate() error { // Run contains the logic for the odo command func (o *DeployOptions) Run(ctx context.Context) error { devfileObj := o.EnvSpecificInfo.GetDevfileObj() - devfileName := devfileObj.GetMetadataName() + + devfileName := o.GetComponentName() + path := filepath.Dir(o.EnvSpecificInfo.GetDevfilePath()) appName := o.GetApplication() namespace := o.GetProject() @@ -129,7 +131,7 @@ func (o *DeployOptions) Run(ctx context.Context) error { "odo version: "+version.VERSION) // Run actual deploy command to be used - err := o.clientset.DeployClient.Deploy(o.clientset.FS, devfileObj, path, appName) + err := o.clientset.DeployClient.Deploy(o.clientset.FS, devfileObj, path, appName, devfileName) if err == nil { log.Info("\nYour Devfile has been successfully deployed") diff --git a/pkg/odo/cli/describe/component.go b/pkg/odo/cli/describe/component.go index cc07de1f377..63b77498a6b 100644 --- a/pkg/odo/cli/describe/component.go +++ b/pkg/odo/cli/describe/component.go @@ -139,7 +139,10 @@ func (o *ComponentOptions) describeDevfileComponent() (result api.Component, dev if err != nil { return api.Component{}, nil, err } - runningIn, err := component.GetRunningModes(o.clientset.KubernetesClient, devfileObj.GetMetadataName()) + + componentName := o.GetComponentName() + + runningIn, err := component.GetRunningModes(o.clientset.KubernetesClient, componentName) if err != nil { if !errors.As(err, &component.NoComponentFoundError{}) { return api.Component{}, nil, err diff --git a/pkg/odo/cli/dev/dev.go b/pkg/odo/cli/dev/dev.go index 90f6832a295..9cd05dbe59e 100644 --- a/pkg/odo/cli/dev/dev.go +++ b/pkg/odo/cli/dev/dev.go @@ -168,8 +168,8 @@ func (o *DevOptions) Run(ctx context.Context) (err error) { var ( devFileObj = o.Context.EnvSpecificInfo.GetDevfileObj() path = filepath.Dir(o.Context.EnvSpecificInfo.GetDevfilePath()) - devfileName = devFileObj.GetMetadataName() namespace = o.GetProject() + devfileName = o.GetComponentName() ) // Output what the command is doing / information @@ -198,7 +198,19 @@ func (o *DevOptions) Run(ctx context.Context) (err error) { o.ignorePaths = ignores log.Section("Deploying to the cluster in developer mode") - componentStatus, err := o.clientset.DevClient.Start(devFileObj, namespace, o.ignorePaths, path, o.debugFlag, o.buildCommandFlag, o.runCommandFlag, o.randomPortsFlag, o.errOut, o.clientset.FS) + componentStatus, err := o.clientset.DevClient.Start( + devFileObj, + devfileName, + namespace, + o.ignorePaths, + path, + o.debugFlag, + o.buildCommandFlag, + o.runCommandFlag, + o.randomPortsFlag, + o.errOut, + o.clientset.FS, + ) if err != nil { return err } @@ -206,7 +218,7 @@ func (o *DevOptions) Run(ctx context.Context) (err error) { scontext.SetComponentType(ctx, component.GetComponentTypeFromDevfileMetadata(devFileObj.Data.GetMetadata())) scontext.SetLanguage(ctx, devFileObj.Data.GetMetadata().Language) scontext.SetProjectType(ctx, devFileObj.Data.GetMetadata().ProjectType) - scontext.SetDevfileName(ctx, devFileObj.GetMetadataName()) + scontext.SetDevfileName(ctx, devfileName) d := Handler{ clientset: *o.clientset, @@ -216,6 +228,7 @@ func (o *DevOptions) Run(ctx context.Context) (err error) { err = o.clientset.DevClient.Watch( o.GetDevfilePath(), devFileObj, + devfileName, path, o.ignorePaths, o.out, @@ -282,7 +295,8 @@ func (o *DevOptions) HandleSignal() error { func (o *DevOptions) Cleanup(commandError error) { if commandError != nil { devFileObj := o.Context.EnvSpecificInfo.GetDevfileObj() - _ = o.clientset.WatchClient.CleanupDevResources(devFileObj, log.GetStdout()) + componentName := o.GetComponentName() + _ = o.clientset.WatchClient.CleanupDevResources(devFileObj, componentName, log.GetStdout()) } } diff --git a/pkg/odo/cli/list/component/list.go b/pkg/odo/cli/list/component/list.go index a3902be865b..19e5bb90445 100644 --- a/pkg/odo/cli/list/component/list.go +++ b/pkg/odo/cli/list/component/list.go @@ -116,7 +116,8 @@ func (lo *ListOptions) RunForJsonOutput(ctx context.Context) (out interface{}, e } func (lo *ListOptions) run(ctx context.Context) (api.ResourcesList, error) { - devfileComponents, componentInDevfile, err := component.ListAllComponents(lo.clientset.KubernetesClient, lo.namespaceFilter, lo.EnvSpecificInfo.GetDevfileObj()) + devfileComponents, componentInDevfile, err := component.ListAllComponents( + lo.clientset.KubernetesClient, lo.namespaceFilter, lo.EnvSpecificInfo.GetDevfileObj(), lo.GetComponentName()) if err != nil { return api.ResourcesList{}, err } diff --git a/pkg/odo/cli/list/list.go b/pkg/odo/cli/list/list.go index f0ad135b4d5..9ffadb47d16 100644 --- a/pkg/odo/cli/list/list.go +++ b/pkg/odo/cli/list/list.go @@ -125,7 +125,8 @@ func (lo *ListOptions) RunForJsonOutput(ctx context.Context) (out interface{}, e } func (lo *ListOptions) run() (list api.ResourcesList, err error) { - devfileComponents, componentInDevfile, err := component.ListAllComponents(lo.clientset.KubernetesClient, lo.namespaceFilter, lo.EnvSpecificInfo.GetDevfileObj()) + devfileComponents, componentInDevfile, err := component.ListAllComponents( + lo.clientset.KubernetesClient, lo.namespaceFilter, lo.EnvSpecificInfo.GetDevfileObj(), lo.GetComponentName()) if err != nil { return api.ResourcesList{}, err } diff --git a/pkg/odo/cli/logs/logs.go b/pkg/odo/cli/logs/logs.go index 6779c5173fb..eb3a60c8ade 100644 --- a/pkg/odo/cli/logs/logs.go +++ b/pkg/odo/cli/logs/logs.go @@ -5,8 +5,6 @@ import ( "context" "errors" "fmt" - "github.com/fatih/color" - odolabels "github.com/redhat-developer/odo/pkg/labels" "io" "os" "strconv" @@ -14,16 +12,21 @@ import ( "sync" "sync/atomic" + "github.com/fatih/color" + + odolabels "github.com/redhat-developer/odo/pkg/labels" + "github.com/redhat-developer/odo/pkg/log" "github.com/redhat-developer/odo/pkg/devfile/location" odoutil "github.com/redhat-developer/odo/pkg/odo/util" + "github.com/spf13/cobra" + ktemplates "k8s.io/kubectl/pkg/util/templates" + "github.com/redhat-developer/odo/pkg/odo/cmdline" "github.com/redhat-developer/odo/pkg/odo/genericclioptions" "github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset" - "github.com/spf13/cobra" - ktemplates "k8s.io/kubectl/pkg/util/templates" ) const RecommendedCommandName = "logs" @@ -88,7 +91,7 @@ func (o *LogsOptions) Complete(cmdline cmdline.Cmdline, _ []string) error { return fmt.Errorf("unable to create context: %v", err) } - o.componentName = o.Context.EnvSpecificInfo.GetDevfileObj().GetMetadataName() + o.componentName = o.Context.GetComponentName() o.clientset.KubernetesClient.SetNamespace(o.Context.GetProject()) diff --git a/pkg/odo/genericclioptions/context.go b/pkg/odo/genericclioptions/context.go index 68ecff8b450..e1cdb64ff37 100644 --- a/pkg/odo/genericclioptions/context.go +++ b/pkg/odo/genericclioptions/context.go @@ -3,6 +3,9 @@ package genericclioptions import ( "fmt" + "github.com/devfile/library/pkg/devfile/parser" + + "github.com/redhat-developer/odo/pkg/component" "github.com/redhat-developer/odo/pkg/devfile" "github.com/redhat-developer/odo/pkg/devfile/location" "github.com/redhat-developer/odo/pkg/devfile/validate" @@ -40,6 +43,8 @@ type internalCxt struct { application string // componentContext is the value passed with the `--context` flag componentContext string + // componentName is the name of the component (computed either from the Devfile metadata, or detected by Alizer, or built from the current directory) + componentName string // outputFlag is the value passed with the `-o` flag outputFlag string // The path of the detected devfile @@ -123,7 +128,8 @@ func New(parameters CreateParameters) (*Context, error) { if isDevfile { ctx.devfilePath = devfilePath // Parse devfile and validate - devObj, err := devfile.ParseAndValidateFromFileWithVariables(ctx.devfilePath, parameters.variables) + var devObj parser.DevfileObj + devObj, err = devfile.ParseAndValidateFromFileWithVariables(ctx.devfilePath, parameters.variables) if err != nil { return nil, fmt.Errorf("failed to parse the devfile %s: %w", ctx.devfilePath, err) } @@ -132,11 +138,21 @@ func New(parameters CreateParameters) (*Context, error) { return nil, err } ctx.EnvSpecificInfo.SetDevfileObj(devObj) + + ctx.componentName, err = component.GatherName(parameters.componentContext, &devObj) + if err != nil { + return nil, err + } } else { return &Context{ internalCxt: ctx, }, NewNoDevfileError(".") } + } else { + ctx.componentName, err = component.GatherName(".", nil) + if err != nil { + return nil, err + } } return &Context{ @@ -163,6 +179,10 @@ func (o *Context) GetApplication() string { return o.application } +func (o *Context) GetComponentName() string { + return o.componentName +} + func (o *Context) GetOutputFlag() string { return o.outputFlag } diff --git a/pkg/odo/genericclioptions/context_test.go b/pkg/odo/genericclioptions/context_test.go index 1d3906e259e..15a7cc9ee09 100644 --- a/pkg/odo/genericclioptions/context_test.go +++ b/pkg/odo/genericclioptions/context_test.go @@ -3,6 +3,7 @@ package genericclioptions import ( "os" "path/filepath" + "runtime" "strings" "testing" @@ -111,7 +112,7 @@ func TestNew(t *testing.T) { tests := []struct { name string input input - expected *Context + expected func() *Context expectedErr string }{ { @@ -128,15 +129,18 @@ func TestNew(t *testing.T) { }, }, expectedErr: "", - expected: &Context{ - internalCxt: internalCxt{ - project: "myproject", - application: "app", - // empty when no devfile - componentContext: "", - outputFlag: "", - devfilePath: "", - }, + expected: func() *Context { + return &Context{ + internalCxt: internalCxt{ + project: "myproject", + application: "app", + componentName: getTestBaseName(), + // empty when no devfile + componentContext: "", + outputFlag: "", + devfilePath: "", + }, + } }, }, { @@ -151,15 +155,18 @@ func TestNew(t *testing.T) { }, }, expectedErr: "", - expected: &Context{ - internalCxt: internalCxt{ - project: "", - application: "app", - // empty when no devfile - componentContext: "", - outputFlag: "", - devfilePath: "", - }, + expected: func() *Context { + return &Context{ + internalCxt: internalCxt{ + project: "", + application: "app", + componentName: getTestBaseName(), + // empty when no devfile + componentContext: "", + outputFlag: "", + devfilePath: "", + }, + } }, }, { @@ -176,14 +183,17 @@ func TestNew(t *testing.T) { }, }, expectedErr: "", - expected: &Context{ - internalCxt: internalCxt{ - project: "", - application: "app", - componentContext: "", - outputFlag: "", - devfilePath: "", - }, + expected: func() *Context { + return &Context{ + internalCxt: internalCxt{ + project: "", + application: "app", + componentName: getTestBaseName(), + componentContext: "", + outputFlag: "", + devfilePath: "", + }, + } }, }, { @@ -200,14 +210,17 @@ func TestNew(t *testing.T) { }, }, expectedErr: "The current directory does not represent an odo component", - expected: &Context{ - internalCxt: internalCxt{ - project: "myproject", - application: "app", - componentContext: filepath.Join(prefixDir, "myapp"), - outputFlag: "", - devfilePath: "", - }, + expected: func() *Context { + return &Context{ + internalCxt: internalCxt{ + project: "myproject", + application: "app", + componentName: "", + componentContext: filepath.Join(prefixDir, "myapp"), + outputFlag: "", + devfilePath: "", + }, + } }, }, { @@ -226,14 +239,17 @@ func TestNew(t *testing.T) { }, }, expectedErr: "", - expected: &Context{ - internalCxt: internalCxt{ - project: "myproject", - application: "app", - componentContext: filepath.Join(prefixDir, "myapp"), - outputFlag: "", - devfilePath: filepath.Join(prefixDir, "myapp", ".devfile.yaml"), - }, + expected: func() *Context { + return &Context{ + internalCxt: internalCxt{ + project: "myproject", + application: "app", + componentName: "nodejs-prj1-api-abhz", + componentContext: filepath.Join(prefixDir, "myapp"), + outputFlag: "", + devfilePath: filepath.Join(prefixDir, "myapp", ".devfile.yaml"), + }, + } }, }, { @@ -252,15 +268,76 @@ func TestNew(t *testing.T) { }, }, expectedErr: "", - expected: &Context{ - internalCxt: internalCxt{ - project: "myproject", - application: "app", - componentContext: filepath.Join(prefixDir, "myapp"), - outputFlag: "", - devfilePath: filepath.Join(prefixDir, "myapp", "devfile.yaml"), + expected: func() *Context { + return &Context{ + internalCxt: internalCxt{ + project: "myproject", + application: "app", + componentName: "nodejs-prj1-api-abhz", + componentContext: filepath.Join(prefixDir, "myapp"), + outputFlag: "", + devfilePath: filepath.Join(prefixDir, "myapp", "devfile.yaml"), + }, + } + }, + }, + { + name: "component flag not set, needDevfile, .devfile.yaml is present", + input: input{ + needDevfile: true, + isOffline: true, + workingDir: filepath.Join(prefixDir, "myapp"), + projectFlag: "myproject", + outputFlag: "", + allFlagSet: false, + populateWorkingDir: func(fs filesystem.Filesystem) { + _ = fs.MkdirAll(filepath.Join(prefixDir, "myapp", ".odo", "env"), 0755) + _ = fs.WriteFile(filepath.Join(prefixDir, "myapp", ".odo", "env", "env.yaml"), []byte{}, 0644) + _ = fs.WriteFile(filepath.Join(prefixDir, "myapp", ".devfile.yaml"), []byte(devfileYAML), 0644) }, }, + expectedErr: "", + expected: func() *Context { + return &Context{ + internalCxt: internalCxt{ + project: "myproject", + application: "app", + componentName: "nodejs-prj1-api-abhz", + componentContext: filepath.Join(prefixDir, "myapp"), + outputFlag: "", + devfilePath: filepath.Join(prefixDir, "myapp", ".devfile.yaml"), + }, + } + }, + }, + { + name: "component flag not set, needDevfile, devfile.yaml is present", + input: input{ + needDevfile: true, + isOffline: true, + workingDir: filepath.Join(prefixDir, "myapp"), + projectFlag: "myproject", + outputFlag: "", + allFlagSet: false, + populateWorkingDir: func(fs filesystem.Filesystem) { + _ = fs.MkdirAll(filepath.Join(prefixDir, "myapp", ".odo", "env"), 0755) + _ = fs.WriteFile(filepath.Join(prefixDir, "myapp", ".odo", "env", "env.yaml"), []byte{}, 0644) + _ = fs.WriteFile(filepath.Join(prefixDir, "myapp", "devfile.yaml"), []byte(devfileYAML), 0644) + }, + }, + expectedErr: "", + expected: func() *Context { + return &Context{ + internalCxt: internalCxt{ + project: "myproject", + application: "app", + componentName: "nodejs-prj1-api-abhz", + componentContext: filepath.Join(prefixDir, "myapp"), + outputFlag: "", + devfilePath: filepath.Join(prefixDir, "myapp", "devfile.yaml"), + }, + } + }, }, } @@ -328,31 +405,40 @@ func TestNew(t *testing.T) { return } - if tt.expected != nil && result == nil { + expected := tt.expected() + if expected != nil && result == nil { t.Errorf("Expected non nil value, got nil result") } - if tt.expected == nil && result != nil { + if expected == nil && result != nil { t.Errorf("Expected nil value, got non nil result") } - if tt.expected != nil && result != nil { - if result.project != tt.expected.project { - t.Errorf("Expected project %s, got %s", tt.expected.project, result.project) + if expected != nil && result != nil { + if result.project != expected.project { + t.Errorf("Expected project %s, got %s", expected.project, result.project) + } + if result.application != expected.application { + t.Errorf("Expected application %s, got %s", expected.application, result.application) } - if result.application != tt.expected.application { - t.Errorf("Expected application %s, got %s", tt.expected.application, result.application) + if result.componentName != expected.componentName { + t.Errorf("Expected componentName %s, got %s", expected.componentName, result.componentName) } - if result.componentContext != tt.expected.componentContext { - t.Errorf("Expected component context %s, got %s", tt.expected.componentContext, result.componentContext) + if result.componentContext != expected.componentContext { + t.Errorf("Expected component context %s, got %s", expected.componentContext, result.componentContext) } - if result.outputFlag != tt.expected.outputFlag { - t.Errorf("Expected output flag %s, got %s", tt.expected.outputFlag, result.outputFlag) + if result.outputFlag != expected.outputFlag { + t.Errorf("Expected output flag %s, got %s", expected.outputFlag, result.outputFlag) } - if result.devfilePath != tt.expected.devfilePath { - t.Errorf("Expected devfilePath %s, got %s", tt.expected.devfilePath, result.devfilePath) + if result.devfilePath != expected.devfilePath { + t.Errorf("Expected devfilePath %s, got %s", expected.devfilePath, result.devfilePath) } } }) } } + +func getTestBaseName() string { + _, b, _, _ := runtime.Caller(0) + return filepath.Base(filepath.Dir(b)) +} diff --git a/pkg/watch/interface.go b/pkg/watch/interface.go index df574b1c75e..d04def6e1a3 100644 --- a/pkg/watch/interface.go +++ b/pkg/watch/interface.go @@ -15,5 +15,5 @@ type Client interface { // as it is generally done for a Kubernetes resource) WatchAndPush(out io.Writer, parameters WatchParameters, ctx context.Context, componentStatus ComponentStatus) error // CleanupDevResources deletes the component created using the devfileObj and writes any outputs to out - CleanupDevResources(devfileObj parser.DevfileObj, out io.Writer) error + CleanupDevResources(devfileObj parser.DevfileObj, componentName string, out io.Writer) error } diff --git a/pkg/watch/mock.go b/pkg/watch/mock.go index 72904a5ffba..5dda6c34d42 100644 --- a/pkg/watch/mock.go +++ b/pkg/watch/mock.go @@ -37,17 +37,17 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder { } // CleanupDevResources mocks base method. -func (m *MockClient) CleanupDevResources(devfileObj parser.DevfileObj, out io.Writer) error { +func (m *MockClient) CleanupDevResources(devfileObj parser.DevfileObj, componentName string, out io.Writer) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CleanupDevResources", devfileObj, out) + ret := m.ctrl.Call(m, "CleanupDevResources", devfileObj, componentName, out) ret0, _ := ret[0].(error) return ret0 } // CleanupDevResources indicates an expected call of CleanupDevResources. -func (mr *MockClientMockRecorder) CleanupDevResources(devfileObj, out interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) CleanupDevResources(devfileObj, componentName, out interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanupDevResources", reflect.TypeOf((*MockClient)(nil).CleanupDevResources), devfileObj, out) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanupDevResources", reflect.TypeOf((*MockClient)(nil).CleanupDevResources), devfileObj, componentName, out) } // WatchAndPush mocks base method. diff --git a/pkg/watch/watch.go b/pkg/watch/watch.go index 59cfadf7cb4..1392137977a 100644 --- a/pkg/watch/watch.go +++ b/pkg/watch/watch.go @@ -570,16 +570,16 @@ func processEvents( return nil, nil } -func (o *WatchClient) CleanupDevResources(devfileObj parser.DevfileObj, out io.Writer) error { +func (o *WatchClient) CleanupDevResources(devfileObj parser.DevfileObj, componentName string, out io.Writer) error { fmt.Fprintln(out, "Cleaning resources, please wait") - isInnerLoopDeployed, resources, err := o.deleteClient.ListResourcesToDeleteFromDevfile(devfileObj, "app", labels.ComponentDevMode) + isInnerLoopDeployed, resources, err := o.deleteClient.ListResourcesToDeleteFromDevfile(devfileObj, "app", componentName, labels.ComponentDevMode) if err != nil { fmt.Fprintf(out, "failed to delete inner loop resources: %v", err) return err } // if innerloop deployment resource is present, then execute preStop events if isInnerLoopDeployed { - err = o.deleteClient.ExecutePreStopEvents(devfileObj, "app") + err = o.deleteClient.ExecutePreStopEvents(devfileObj, "app", componentName) if err != nil { fmt.Fprint(out, "Failed to execute preStop events") } From 88c360bfcb6365993ff9102c3b65781693c20263 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Fri, 26 Aug 2022 15:15:18 +0200 Subject: [PATCH 4/5] Enrich relevant integration test cases For the sake of both performance and readability, only the tests that break in the absence of a 'metadata.name' field in their Devfiles have been updated (to test this specific case). --- .../devfile-with-service-binding-files.yaml | 2 +- .../devfile-with-volume-components.yaml | 2 +- tests/helper/helper_filesystem.go | 34 + tests/integration/cmd_dev_debug_test.go | 289 +-- tests/integration/cmd_dev_test.go | 1830 +++++++++-------- 5 files changed, 1207 insertions(+), 950 deletions(-) diff --git a/tests/examples/source/devfiles/nodejs/devfile-with-service-binding-files.yaml b/tests/examples/source/devfiles/nodejs/devfile-with-service-binding-files.yaml index a10ea4618ea..6587999b88b 100644 --- a/tests/examples/source/devfiles/nodejs/devfile-with-service-binding-files.yaml +++ b/tests/examples/source/devfiles/nodejs/devfile-with-service-binding-files.yaml @@ -74,7 +74,7 @@ metadata: displayName: Node.js Runtime icon: https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg language: javascript - name: my-nodejs-app + name: nodejs projectType: nodejs tags: - NodeJS diff --git a/tests/examples/source/devfiles/nodejs/devfile-with-volume-components.yaml b/tests/examples/source/devfiles/nodejs/devfile-with-volume-components.yaml index b6ed4846a25..33d6a0ddef0 100644 --- a/tests/examples/source/devfiles/nodejs/devfile-with-volume-components.yaml +++ b/tests/examples/source/devfiles/nodejs/devfile-with-volume-components.yaml @@ -1,6 +1,6 @@ schemaVersion: 2.0.0 metadata: - name: test-devfile + name: nodejs starterProjects: - name: nodejs-starter git: diff --git a/tests/helper/helper_filesystem.go b/tests/helper/helper_filesystem.go index f5772c4ed74..88e04d693c9 100644 --- a/tests/helper/helper_filesystem.go +++ b/tests/helper/helper_filesystem.go @@ -9,7 +9,9 @@ import ( "strings" "time" + "github.com/devfile/library/pkg/devfile/parser" dfutil "github.com/devfile/library/pkg/util" + "k8s.io/utils/pointer" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -285,3 +287,35 @@ func AppendToFile(filepath string, s string) error { } return nil } + +// DevfileUpdater is a helper type that can mutate a Devfile object. +// It is intended to be used in conjunction with the UpdateDevfileContent function. +type DevfileUpdater func(*parser.DevfileObj) error + +// DevfileMetadataNameRemover removes the 'metadata.name' field from the given Devfile +var DevfileMetadataNameRemover = func(d *parser.DevfileObj) error { + return d.SetMetadataName("") +} + +// UpdateDevfileContent parses the Devfile at the given path, then updates its content using the given handlers, and writes the updated Devfile to the given path. +// +// The handlers are invoked in the order they are provided. +// +// No operation is performed if no handler function is specified. +// +// See DevfileMetadataNameRemover for an example of handler function that can operate on the Devfile content. +func UpdateDevfileContent(path string, handlers []DevfileUpdater) { + if len(handlers) == 0 { + //Nothing to do => skip + return + } + + d, err := parser.ParseDevfile(parser.ParserArgs{Path: path, FlattenedDevfile: pointer.BoolPtr(false)}) + Expect(err).NotTo(HaveOccurred()) + for _, h := range handlers { + err = h(&d) + Expect(err).NotTo(HaveOccurred()) + } + err = d.WriteYamlDevfile() + Expect(err).NotTo(HaveOccurred()) +} diff --git a/tests/integration/cmd_dev_debug_test.go b/tests/integration/cmd_dev_debug_test.go index 45b22cef950..12154a06792 100644 --- a/tests/integration/cmd_dev_debug_test.go +++ b/tests/integration/cmd_dev_debug_test.go @@ -63,72 +63,98 @@ var _ = Describe("odo dev debug command tests", func() { }) }) - When("a composite command is used as debug command", func() { - const devfileCmpName = "nodejs" - var session helper.DevSession - var stdout []byte - var stderr []byte - var ports map[string]string - BeforeEach(func() { - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileCompositeRunAndDebug.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - var err error - session, stdout, stderr, ports, err = helper.StartDevMode(nil, "--debug") - Expect(err).ToNot(HaveOccurred()) - }) + for _, devfileHandlerCtx := range []struct { + name string + cmpName string + devfileHandler func(path string) + }{ + { + name: "with metadata.name", + // cmpName from Devfile + cmpName: "nodejs", + }, + { + name: "without metadata.name", + // cmpName is returned by alizer.DetectName + cmpName: "nodejs-starter", + devfileHandler: func(path string) { + helper.UpdateDevfileContent(path, []helper.DevfileUpdater{helper.DevfileMetadataNameRemover}) + }, + }, + } { + devfileHandlerCtx := devfileHandlerCtx + When("a composite command is used as debug command - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + var session helper.DevSession + var stdout []byte + var stderr []byte + var ports map[string]string + BeforeEach(func() { + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileCompositeRunAndDebug.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + var err error + session, stdout, stderr, ports, err = helper.StartDevMode(nil, "--debug") + Expect(err).ToNot(HaveOccurred()) + }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) - It("should run successfully", func() { - By("verifying from the output that all commands have been executed", func() { - helper.MatchAllInOutput(string(stdout), []string{ - "Building your application in container on cluster", - "Executing the application (command: mkdir)", - "Executing the application (command: echo)", - "Executing the application (command: install)", - "Executing the application (command: start-debug)", + It("should run successfully", func() { + By("verifying from the output that all commands have been executed", func() { + helper.MatchAllInOutput(string(stdout), []string{ + "Building your application in container on cluster", + "Executing the application (command: mkdir)", + "Executing the application (command: echo)", + "Executing the application (command: install)", + "Executing the application (command: start-debug)", + }) }) - }) - By("verifying that any command that did not succeed in the middle has logged such information correctly", func() { - helper.MatchAllInOutput(string(stderr), []string{ - "Devfile command \"echo\" exited with an error status", - "intentional-error-message", + By("verifying that any command that did not succeed in the middle has logged such information correctly", func() { + helper.MatchAllInOutput(string(stderr), []string{ + "Devfile command \"echo\" exited with an error status", + "intentional-error-message", + }) }) - }) - By("building the application only once", func() { - // Because of the Spinner, the "Building your application in container on cluster" is printed twice in the captured stdout. - // The bracket allows to match the last occurrence with the command execution timing information. - Expect(strings.Count(string(stdout), "Building your application in container on cluster (command: install) [")). - To(BeNumerically("==", 1), "\nOUTPUT: "+string(stdout)+"\n") - }) + By("building the application only once", func() { + // Because of the Spinner, the "Building your application in container on cluster" is printed twice in the captured stdout. + // The bracket allows to match the last occurrence with the command execution timing information. + Expect(strings.Count(string(stdout), "Building your application in container on cluster (command: install) [")). + To(BeNumerically("==", 1), "\nOUTPUT: "+string(stdout)+"\n") + }) - By("verifying that the command did run successfully", func() { - // Verify the command executed successfully - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - res := commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( - podName, - "runtime", - commonVar.Project, - []string{"stat", "/projects/testfolder"}, - func(cmdOp string, err error) bool { - return err == nil - }, - ) - Expect(res).To(BeTrue()) - }) + By("verifying that the command did run successfully", func() { + // Verify the command executed successfully + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + res := commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + podName, + "runtime", + commonVar.Project, + []string{"stat", "/projects/testfolder"}, + func(cmdOp string, err error) bool { + return err == nil + }, + ) + Expect(res).To(BeTrue()) + }) - By("expecting a ws connection when tried to connect on default debug port locally", func() { - // 400 response expected because the endpoint expects a websocket request and we are doing a HTTP GET - // We are just using this to validate if nodejs agent is listening on the other side - helper.HttpWaitForWithStatus("http://"+ports["5858"], "WebSockets request was expected", 12, 5, 400) + By("expecting a ws connection when tried to connect on default debug port locally", func() { + // 400 response expected because the endpoint expects a websocket request and we are doing a HTTP GET + // We are just using this to validate if nodejs agent is listening on the other side + helper.HttpWaitForWithStatus("http://"+ports["5858"], "WebSockets request was expected", 12, 5, 400) + }) }) }) - }) + } + When("a composite apply command is used as debug command", func() { deploymentName := "my-component" var session helper.DevSession @@ -196,79 +222,104 @@ var _ = Describe("odo dev debug command tests", func() { }) }) - When("running build and debug commands as composite in different containers and a shared volume", func() { - const devfileCmpName = "nodejs" - var session helper.DevSession - var stdout []byte - var stderr []byte - var ports map[string]string - BeforeEach(func() { - helper.CopyExampleDevFile( - filepath.Join("source", "devfiles", "nodejs", "devfileCompositeBuildRunDebugInMultiContainersAndSharedVolume.yaml"), - filepath.Join(commonVar.Context, "devfile.yaml")) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - var err error - session, stdout, stderr, ports, err = helper.StartDevMode(nil, "--debug") - Expect(err).ToNot(HaveOccurred()) - }) + for _, devfileHandlerCtx := range []struct { + name string + cmpName string + devfileHandler func(path string) + }{ + { + name: "with metadata.name", + // cmpName from Devfile + cmpName: "nodejs", + }, + { + name: "without metadata.name", + //cmpName is returned by alizer.DetectName + cmpName: "nodejs-starter", + devfileHandler: func(path string) { + helper.UpdateDevfileContent(path, []helper.DevfileUpdater{helper.DevfileMetadataNameRemover}) + }, + }, + } { + devfileHandlerCtx := devfileHandlerCtx + When("running build and debug commands as composite in different containers and a shared volume - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + var session helper.DevSession + var stdout []byte + var stderr []byte + var ports map[string]string + BeforeEach(func() { + helper.CopyExampleDevFile( + filepath.Join("source", "devfiles", "nodejs", "devfileCompositeBuildRunDebugInMultiContainersAndSharedVolume.yaml"), + filepath.Join(commonVar.Context, "devfile.yaml")) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + var err error + session, stdout, stderr, ports, err = helper.StartDevMode(nil, "--debug") + Expect(err).ToNot(HaveOccurred()) + }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) - It("should run successfully", func() { - By("verifying from the output that all commands have been executed", func() { - helper.MatchAllInOutput(string(stdout), []string{ - "Building your application in container on cluster (command: mkdir)", - "Building your application in container on cluster (command: sleep-cmd-build)", - "Building your application in container on cluster (command: build-cmd)", - "Executing the application (command: sleep-cmd-run)", - "Executing the application (command: echo-with-error)", - "Executing the application (command: check-build-result)", - "Executing the application (command: start-debug)", + It("should run successfully", func() { + By("verifying from the output that all commands have been executed", func() { + helper.MatchAllInOutput(string(stdout), []string{ + "Building your application in container on cluster (command: mkdir)", + "Building your application in container on cluster (command: sleep-cmd-build)", + "Building your application in container on cluster (command: build-cmd)", + "Executing the application (command: sleep-cmd-run)", + "Executing the application (command: echo-with-error)", + "Executing the application (command: check-build-result)", + "Executing the application (command: start-debug)", + }) }) - }) - By("verifying that any command that did not succeed in the middle has logged such information correctly", func() { - helper.MatchAllInOutput(string(stderr), []string{ - "Devfile command \"echo-with-error\" exited with an error status", - "intentional-error-message", + By("verifying that any command that did not succeed in the middle has logged such information correctly", func() { + helper.MatchAllInOutput(string(stderr), []string{ + "Devfile command \"echo-with-error\" exited with an error status", + "intentional-error-message", + }) }) - }) - By("building the application only once per exec command in the build command", func() { - // Because of the Spinner, the "Building your application in container on cluster" is printed twice in the captured stdout. - // The bracket allows to match the last occurrence with the command execution timing information. - out := string(stdout) - for _, cmd := range []string{"mkdir", "sleep-cmd-build", "build-cmd"} { - Expect(strings.Count(out, fmt.Sprintf("Building your application in container on cluster (command: %s) [", cmd))). - To(BeNumerically("==", 1), "\nOUTPUT: "+string(stdout)+"\n") - } - }) + By("building the application only once per exec command in the build command", func() { + // Because of the Spinner, the "Building your application in container on cluster" is printed twice in the captured stdout. + // The bracket allows to match the last occurrence with the command execution timing information. + out := string(stdout) + for _, cmd := range []string{"mkdir", "sleep-cmd-build", "build-cmd"} { + Expect(strings.Count(out, fmt.Sprintf("Building your application in container on cluster (command: %s) [", cmd))). + To(BeNumerically("==", 1), "\nOUTPUT: "+string(stdout)+"\n") + } + }) - By("verifying that the command did run successfully", func() { - // Verify the command executed successfully - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - res := commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( - podName, - "runtime", - commonVar.Project, - []string{"stat", "/projects/testfolder"}, - func(cmdOp string, err error) bool { - return err == nil - }, - ) - Expect(res).To(BeTrue()) - }) + By("verifying that the command did run successfully", func() { + // Verify the command executed successfully + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + res := commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + podName, + "runtime", + commonVar.Project, + []string{"stat", "/projects/testfolder"}, + func(cmdOp string, err error) bool { + return err == nil + }, + ) + Expect(res).To(BeTrue()) + }) - By("expecting a ws connection when tried to connect on default debug port locally", func() { - // 400 response expected because the endpoint expects a websocket request and we are doing a HTTP GET - // We are just using this to validate if nodejs agent is listening on the other side - helper.HttpWaitForWithStatus("http://"+ports["5858"], "WebSockets request was expected", 12, 5, 400) + By("expecting a ws connection when tried to connect on default debug port locally", func() { + // 400 response expected because the endpoint expects a websocket request and we are doing a HTTP GET + // We are just using this to validate if nodejs agent is listening on the other side + helper.HttpWaitForWithStatus("http://"+ports["5858"], "WebSockets request was expected", 12, 5, 400) + }) }) }) - }) + } When("a component without debug command is bootstrapped", func() { BeforeEach(func() { diff --git a/tests/integration/cmd_dev_test.go b/tests/integration/cmd_dev_test.go index a231b80d71b..3a2f8b53af5 100644 --- a/tests/integration/cmd_dev_test.go +++ b/tests/integration/cmd_dev_test.go @@ -609,227 +609,200 @@ ComponentSettings: }) }) - When("Devfile 2.1.0 is used", func() { - // from devfile - devfileCmpName := "nodejs" - BeforeEach(func() { - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-variables.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - }) - - When("doing odo dev", func() { - var session helper.DevSession + for _, devfileHandlerCtx := range []struct { + name string + cmpName string + devfileHandler func(path string) + }{ + { + name: "with metadata.name", + // cmpName from Devfile + cmpName: "nodejs", + }, + { + name: "without metadata.name", + // cmpName is returned by alizer.DetectName + cmpName: "nodejs-starter", + devfileHandler: func(path string) { + helper.UpdateDevfileContent(path, []helper.DevfileUpdater{helper.DevfileMetadataNameRemover}) + }, + }, + } { + devfileHandlerCtx := devfileHandlerCtx + When("Devfile 2.1.0 is used - "+devfileHandlerCtx.name, func() { + var devfileCmpName string BeforeEach(func() { - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-variables.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } }) - It("should check if the env variable has a correct value", func() { - envVars := commonVar.CliRunner.GetEnvsDevFileDeployment(devfileCmpName, "app", commonVar.Project) - // check if the env variable has a correct value. This value was substituted from in devfile from variable - Expect(envVars["FOO"]).To(Equal("bar")) - }) - }) + When("doing odo dev", func() { + var session helper.DevSession + BeforeEach(func() { + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) - When("doing odo dev with --var flag", func() { - var session helper.DevSession - BeforeEach(func() { - var err error - session, _, _, _, err = helper.StartDevMode(nil, "--var", "VALUE_TEST=baz") - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() + It("should check if the env variable has a correct value", func() { + envVars := commonVar.CliRunner.GetEnvsDevFileDeployment(devfileCmpName, "app", commonVar.Project) + // check if the env variable has a correct value. This value was substituted from in devfile from variable + Expect(envVars["FOO"]).To(Equal("bar")) + }) }) - It("should check if the env variable has a correct value", func() { - envVars := commonVar.CliRunner.GetEnvsDevFileDeployment(devfileCmpName, "app", commonVar.Project) - // check if the env variable has a correct value. This value was substituted from in devfile from variable - Expect(envVars["FOO"]).To(Equal("baz")) - }) - }) + When("doing odo dev with --var flag", func() { + var session helper.DevSession + BeforeEach(func() { + var err error + session, _, _, _, err = helper.StartDevMode(nil, "--var", "VALUE_TEST=baz") + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) - When("doing odo dev with --var-file flag", func() { - var session helper.DevSession - varfilename := "vars.txt" - BeforeEach(func() { - var err error - err = helper.CreateFileWithContent(varfilename, "VALUE_TEST=baz") - Expect(err).ToNot(HaveOccurred()) - session, _, _, _, err = helper.StartDevMode(nil, "--var-file", "vars.txt") - Expect(err).ToNot(HaveOccurred()) + It("should check if the env variable has a correct value", func() { + envVars := commonVar.CliRunner.GetEnvsDevFileDeployment(devfileCmpName, "app", commonVar.Project) + // check if the env variable has a correct value. This value was substituted from in devfile from variable + Expect(envVars["FOO"]).To(Equal("baz")) + }) }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - helper.DeleteFile(varfilename) + + When("doing odo dev with --var-file flag", func() { + var session helper.DevSession + varfilename := "vars.txt" + BeforeEach(func() { + var err error + err = helper.CreateFileWithContent(varfilename, "VALUE_TEST=baz") + Expect(err).ToNot(HaveOccurred()) + session, _, _, _, err = helper.StartDevMode(nil, "--var-file", "vars.txt") + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() + helper.DeleteFile(varfilename) + }) + + It("should check if the env variable has a correct value", func() { + envVars := commonVar.CliRunner.GetEnvsDevFileDeployment(devfileCmpName, "app", commonVar.Project) + // check if the env variable has a correct value. This value was substituted from in devfile from variable + Expect(envVars["FOO"]).To(Equal("baz")) + }) }) - It("should check if the env variable has a correct value", func() { - envVars := commonVar.CliRunner.GetEnvsDevFileDeployment(devfileCmpName, "app", commonVar.Project) - // check if the env variable has a correct value. This value was substituted from in devfile from variable - Expect(envVars["FOO"]).To(Equal("baz")) + When("doing odo dev with --var-file flag and setting value in env", func() { + var session helper.DevSession + varfilename := "vars.txt" + BeforeEach(func() { + var err error + _ = os.Setenv("VALUE_TEST", "baz") + err = helper.CreateFileWithContent(varfilename, "VALUE_TEST") + Expect(err).ToNot(HaveOccurred()) + session, _, _, _, err = helper.StartDevMode(nil, "--var-file", "vars.txt") + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() + helper.DeleteFile(varfilename) + _ = os.Unsetenv("VALUE_TEST") + }) + + It("should check if the env variable has a correct value", func() { + envVars := commonVar.CliRunner.GetEnvsDevFileDeployment(devfileCmpName, "app", commonVar.Project) + // check if the env variable has a correct value. This value was substituted from in devfile from variable + Expect(envVars["FOO"]).To(Equal("baz")) + }) }) }) - When("doing odo dev with --var-file flag and setting value in env", func() { - var session helper.DevSession - varfilename := "vars.txt" + When("running odo dev and single env var is set - "+devfileHandlerCtx.name, func() { + var devfileCmpName string BeforeEach(func() { - var err error - _ = os.Setenv("VALUE_TEST", "baz") - err = helper.CreateFileWithContent(varfilename, "VALUE_TEST") - Expect(err).ToNot(HaveOccurred()) - session, _, _, _, err = helper.StartDevMode(nil, "--var-file", "vars.txt") - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - helper.DeleteFile(varfilename) - _ = os.Unsetenv("VALUE_TEST") + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-command-single-env.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } }) - It("should check if the env variable has a correct value", func() { - envVars := commonVar.CliRunner.GetEnvsDevFileDeployment(devfileCmpName, "app", commonVar.Project) - // check if the env variable has a correct value. This value was substituted from in devfile from variable - Expect(envVars["FOO"]).To(Equal("baz")) + It("should be able to exec command", func() { + err := helper.RunDevMode(nil, nil, func(session *gexec.Session, out, err []byte, ports map[string]string) { + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") + helper.MatchAllInOutput(output, []string{"test_env_variable", "test_build_env_variable"}) + }) + Expect(err).ToNot(HaveOccurred()) }) }) - }) - When("running odo dev and single env var is set", func() { - devfileCmpName := "nodejs" - BeforeEach(func() { - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-command-single-env.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - }) - - It("should be able to exec command", func() { - err := helper.RunDevMode(nil, nil, func(session *gexec.Session, out, err []byte, ports map[string]string) { - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") - helper.MatchAllInOutput(output, []string{"test_env_variable", "test_build_env_variable"}) + When("running odo dev and multiple env variables are set - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + BeforeEach(func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-command-multiple-envs.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } }) - Expect(err).ToNot(HaveOccurred()) - }) - }) - When("running odo dev and multiple env variables are set", func() { - devfileCmpName := "nodejs" - BeforeEach(func() { - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-command-multiple-envs.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - }) - - It("should be able to exec command", func() { - err := helper.RunDevMode(nil, nil, func(session *gexec.Session, out, err []byte, ports map[string]string) { - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") - helper.MatchAllInOutput(output, []string{"test_build_env_variable1", "test_build_env_variable2", "test_env_variable1", "test_env_variable2"}) + It("should be able to exec command", func() { + err := helper.RunDevMode(nil, nil, func(session *gexec.Session, out, err []byte, ports map[string]string) { + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") + helper.MatchAllInOutput(output, []string{"test_build_env_variable1", "test_build_env_variable2", "test_env_variable1", "test_env_variable2"}) + }) + Expect(err).ToNot(HaveOccurred()) }) - Expect(err).ToNot(HaveOccurred()) }) - }) - When("doing odo dev and there is a env variable with spaces", func() { - devfileCmpName := "nodejs" - BeforeEach(func() { - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-command-env-with-space.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - }) - - It("should be able to exec command", func() { - err := helper.RunDevMode(nil, nil, func(session *gexec.Session, out, err []byte, ports map[string]string) { - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") - helper.MatchAllInOutput(output, []string{"build env variable with space", "env with space"}) + When("doing odo dev and there is a env variable with spaces - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + BeforeEach(func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-command-env-with-space.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } }) - Expect(err).ToNot(HaveOccurred()) - }) - }) - - When("creating local files and dir and running odo dev", func() { - var newDirPath, newFilePath, stdOut, podName string - var session helper.DevSession - // from devfile - devfileCmpName := "nodejs" - BeforeEach(func() { - newFilePath = filepath.Join(commonVar.Context, "foobar.txt") - newDirPath = filepath.Join(commonVar.Context, "testdir") - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - // Create a new file that we plan on deleting later... - if err := helper.CreateFileWithContent(newFilePath, "hello world"); err != nil { - fmt.Printf("the foobar.txt file was not created, reason %v", err.Error()) - } - // Create a new directory - helper.MakeDir(newDirPath) - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) - It("should correctly propagate changes to the container", func() { - - // Check to see if it's been pushed (foobar.txt abd directory testdir) - podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - - stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") - helper.MatchAllInOutput(stdOut, []string{"foobar.txt", "testdir"}) - }) - - When("deleting local files and dir and waiting for sync", func() { - BeforeEach(func() { - // Now we delete the file and dir and push - helper.DeleteDir(newFilePath) - helper.DeleteDir(newDirPath) - _, _, _, err := session.WaitSync() + It("should be able to exec command", func() { + err := helper.RunDevMode(nil, nil, func(session *gexec.Session, out, err []byte, ports map[string]string) { + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") + helper.MatchAllInOutput(output, []string{"build env variable with space", "env with space"}) + }) Expect(err).ToNot(HaveOccurred()) }) - It("should not list deleted dir and file in container", func() { - podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - // Then check to see if it's truly been deleted - stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") - helper.DontMatchAllInOutput(stdOut, []string{"foobar.txt", "testdir"}) - }) }) - }) - When("Starting a PostgreSQL service", func() { - BeforeEach(func() { - if helper.IsKubernetesCluster() { - Skip("Operators have not been setup on Kubernetes cluster yet. Remove this once the issue has been fixed.") - } - // Ensure that the operators are installed - commonVar.CliRunner.EnsureOperatorIsInstalled("service-binding-operator") - commonVar.CliRunner.EnsureOperatorIsInstalled("cloud-native-postgresql") - Eventually(func() string { - out, _ := commonVar.CliRunner.GetBindableKinds() - return out - }, 120, 3).Should(ContainSubstring("Cluster")) - addBindableKind := commonVar.CliRunner.Run("apply", "-f", helper.GetExamplePath("manifests", "bindablekind-instance.yaml")) - Expect(addBindableKind.ExitCode()).To(BeEquivalentTo(0)) - }) - - When("creating local files and dir and running odo dev", func() { + When("creating local files and dir and running odo dev - "+devfileHandlerCtx.name, func() { var newDirPath, newFilePath, stdOut, podName string var session helper.DevSession - // from devfile - devfileCmpName := "my-nodejs-app" + var devfileCmpName string BeforeEach(func() { newFilePath = filepath.Join(commonVar.Context, "foobar.txt") newDirPath = filepath.Join(commonVar.Context, "testdir") - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-service-binding-files.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } // Create a new file that we plan on deleting later... if err := helper.CreateFileWithContent(newFilePath, "hello world"); err != nil { fmt.Printf("the foobar.txt file was not created, reason %v", err.Error()) @@ -871,332 +844,438 @@ ComponentSettings: }) }) }) - }) - When("adding local files to gitignore and running odo dev", func() { - var gitignorePath, newDirPath, newFilePath1, newFilePath2, newFilePath3, stdOut, podName string - var session helper.DevSession - // from devfile - devfileCmpName := "nodejs" - BeforeEach(func() { - gitignorePath = filepath.Join(commonVar.Context, ".gitignore") - newFilePath1 = filepath.Join(commonVar.Context, "foobar.txt") - newDirPath = filepath.Join(commonVar.Context, "testdir") - newFilePath2 = filepath.Join(newDirPath, "foobar.txt") - newFilePath3 = filepath.Join(newDirPath, "baz.txt") - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - if err := helper.CreateFileWithContent(newFilePath1, "hello world"); err != nil { - fmt.Printf("the foobar.txt file was not created, reason %v", err.Error()) - } - // Create a new directory - helper.MakeDir(newDirPath) - if err := helper.CreateFileWithContent(newFilePath2, "hello world"); err != nil { - fmt.Printf("the foobar.txt file was not created, reason %v", err.Error()) - } - if err := helper.CreateFileWithContent(newFilePath3, "hello world"); err != nil { - fmt.Printf("the foobar.txt file was not created, reason %v", err.Error()) - } - if err := helper.CreateFileWithContent(gitignorePath, "foobar.txt"); err != nil { - fmt.Printf("the .gitignore file was not created, reason %v", err.Error()) - } - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) + When("Starting a PostgreSQL service", func() { + BeforeEach(func() { + if helper.IsKubernetesCluster() { + Skip("Operators have not been setup on Kubernetes cluster yet. Remove this once the issue has been fixed.") + } + // Ensure that the operators are installed + commonVar.CliRunner.EnsureOperatorIsInstalled("service-binding-operator") + commonVar.CliRunner.EnsureOperatorIsInstalled("cloud-native-postgresql") + Eventually(func() string { + out, _ := commonVar.CliRunner.GetBindableKinds() + return out + }, 120, 3).Should(ContainSubstring("Cluster")) + addBindableKind := commonVar.CliRunner.Run("apply", "-f", helper.GetExamplePath("manifests", "bindablekind-instance.yaml")) + Expect(addBindableKind.ExitCode()).To(BeEquivalentTo(0)) + }) - checkSyncedFiles := func(podName string) { - stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") - helper.MatchAllInOutput(stdOut, []string{"testdir"}) - helper.DontMatchAllInOutput(stdOut, []string{"foobar.txt"}) - stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects/testdir") - helper.MatchAllInOutput(stdOut, []string{"baz.txt"}) - helper.DontMatchAllInOutput(stdOut, []string{"foobar.txt"}) - } + When("creating local files and dir and running odo dev - "+devfileHandlerCtx.name, func() { + var newDirPath, newFilePath, stdOut, podName string + var session helper.DevSession + var devfileCmpName string + BeforeEach(func() { + newFilePath = filepath.Join(commonVar.Context, "foobar.txt") + newDirPath = filepath.Join(commonVar.Context, "testdir") + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-service-binding-files.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + // Create a new file that we plan on deleting later... + if err := helper.CreateFileWithContent(newFilePath, "hello world"); err != nil { + fmt.Printf("the foobar.txt file was not created, reason %v", err.Error()) + } + // Create a new directory + helper.MakeDir(newDirPath) + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) + + It("should correctly propagate changes to the container", func() { + + // Check to see if it's been pushed (foobar.txt abd directory testdir) + podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + + stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") + helper.MatchAllInOutput(stdOut, []string{"foobar.txt", "testdir"}) + }) - It("should not sync ignored files to the container", func() { - podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - checkSyncedFiles(podName) + When("deleting local files and dir and waiting for sync", func() { + BeforeEach(func() { + // Now we delete the file and dir and push + helper.DeleteDir(newFilePath) + helper.DeleteDir(newDirPath) + _, _, _, err := session.WaitSync() + Expect(err).ToNot(HaveOccurred()) + }) + It("should not list deleted dir and file in container", func() { + podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + // Then check to see if it's truly been deleted + stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") + helper.DontMatchAllInOutput(stdOut, []string{"foobar.txt", "testdir"}) + }) + }) + }) }) - When("modifying /testdir/baz.txt file", func() { + When("adding local files to gitignore and running odo dev", func() { + var gitignorePath, newDirPath, newFilePath1, newFilePath2, newFilePath3, stdOut, podName string + var session helper.DevSession + var devfileCmpName string BeforeEach(func() { - helper.ReplaceString(newFilePath3, "hello world", "hello world!!!") + gitignorePath = filepath.Join(commonVar.Context, ".gitignore") + newFilePath1 = filepath.Join(commonVar.Context, "foobar.txt") + newDirPath = filepath.Join(commonVar.Context, "testdir") + newFilePath2 = filepath.Join(newDirPath, "foobar.txt") + newFilePath3 = filepath.Join(newDirPath, "baz.txt") + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + if err := helper.CreateFileWithContent(newFilePath1, "hello world"); err != nil { + fmt.Printf("the foobar.txt file was not created, reason %v", err.Error()) + } + // Create a new directory + helper.MakeDir(newDirPath) + if err := helper.CreateFileWithContent(newFilePath2, "hello world"); err != nil { + fmt.Printf("the foobar.txt file was not created, reason %v", err.Error()) + } + if err := helper.CreateFileWithContent(newFilePath3, "hello world"); err != nil { + fmt.Printf("the foobar.txt file was not created, reason %v", err.Error()) + } + if err := helper.CreateFileWithContent(gitignorePath, "foobar.txt"); err != nil { + fmt.Printf("the .gitignore file was not created, reason %v", err.Error()) + } + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() }) - It("should synchronize it only", func() { - _, _, _, _ = session.WaitSync() + checkSyncedFiles := func(podName string) { + stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") + helper.MatchAllInOutput(stdOut, []string{"testdir"}) + helper.DontMatchAllInOutput(stdOut, []string{"foobar.txt"}) + stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects/testdir") + helper.MatchAllInOutput(stdOut, []string{"baz.txt"}) + helper.DontMatchAllInOutput(stdOut, []string{"foobar.txt"}) + } + + It("should not sync ignored files to the container", func() { podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) checkSyncedFiles(podName) }) - }) - When("modifying /foobar.txt file", func() { - BeforeEach(func() { - helper.ReplaceString(newFilePath1, "hello world", "hello world!!!") + When("modifying /testdir/baz.txt file", func() { + BeforeEach(func() { + helper.ReplaceString(newFilePath3, "hello world", "hello world!!!") + }) + + It("should synchronize it only", func() { + _, _, _, _ = session.WaitSync() + podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + checkSyncedFiles(podName) + }) }) - It("should not synchronize it", func() { - session.CheckNotSynced(10 * time.Second) + When("modifying /foobar.txt file", func() { + BeforeEach(func() { + helper.ReplaceString(newFilePath1, "hello world", "hello world!!!") + }) + + It("should not synchronize it", func() { + session.CheckNotSynced(10 * time.Second) + }) + }) + + When("modifying /testdir/foobar.txt file", func() { + BeforeEach(func() { + helper.ReplaceString(newFilePath2, "hello world", "hello world!!!") + }) + + It("should not synchronize it", func() { + session.CheckNotSynced(10 * time.Second) + }) }) }) - When("modifying /testdir/foobar.txt file", func() { + When("devfile has sourcemappings and running odo dev - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + var session helper.DevSession BeforeEach(func() { - helper.ReplaceString(newFilePath2, "hello world", "hello world!!!") + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileSourceMapping.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() }) - It("should not synchronize it", func() { - session.CheckNotSynced(10 * time.Second) + It("should sync files to the correct location", func() { + // Verify source code was synced to /test instead of /projects + var statErr error + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + podName, + "runtime", + commonVar.Project, + []string{"stat", "/test/server.js"}, + func(cmdOp string, err error) bool { + statErr = err + return err == nil + }, + ) + Expect(statErr).ToNot(HaveOccurred()) }) }) - }) - When("devfile has sourcemappings and running odo dev", func() { - devfileCmpName := "nodejs" - var session helper.DevSession - BeforeEach(func() { - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileSourceMapping.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) + When("project and clonePath is present in devfile and running odo dev - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + var session helper.DevSession + BeforeEach(func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + // devfile with clonePath set in project field + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-projects.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } - It("should sync files to the correct location", func() { - // Verify source code was synced to /test instead of /projects - var statErr error - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( - podName, - "runtime", - commonVar.Project, - []string{"stat", "/test/server.js"}, - func(cmdOp string, err error) bool { - statErr = err - return err == nil - }, - ) - Expect(statErr).ToNot(HaveOccurred()) - }) - }) + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) - When("project and clonePath is present in devfile and running odo dev", func() { - devfileCmpName := "nodejs" - var session helper.DevSession - BeforeEach(func() { - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - // devfile with clonePath set in project field - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-projects.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + It("should sync to the correct dir in container", func() { + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + // source code is synced to $PROJECTS_ROOT/clonePath + // $PROJECTS_ROOT is /projects by default, if sourceMapping is set it is same as sourceMapping + // for devfile-with-projects.yaml, sourceMapping is apps and clonePath is webapp + // so source code would be synced to /apps/webapp + output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/apps/webapp") + helper.MatchAllInOutput(output, []string{"package.json"}) - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() + // Verify the sync env variables are correct + helper.VerifyContainerSyncEnv(podName, "runtime", commonVar.Project, "/apps/webapp", "/apps", commonVar.CliRunner) + }) }) - It("should sync to the correct dir in container", func() { - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - // source code is synced to $PROJECTS_ROOT/clonePath - // $PROJECTS_ROOT is /projects by default, if sourceMapping is set it is same as sourceMapping - // for devfile-with-projects.yaml, sourceMapping is apps and clonePath is webapp - // so source code would be synced to /apps/webapp - output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/apps/webapp") - helper.MatchAllInOutput(output, []string{"package.json"}) + When("devfile project field is present and running odo dev - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + var session helper.DevSession + BeforeEach(func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-projects.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } - // Verify the sync env variables are correct - helper.VerifyContainerSyncEnv(podName, "runtime", commonVar.Project, "/apps/webapp", "/apps", commonVar.CliRunner) - }) - }) + // reset clonePath and change the workdir accordingly, it should sync to project name + helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"), "clonePath: webapp/", "# clonePath: webapp/") + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) - When("devfile project field is present and running odo dev", func() { - devfileCmpName := "nodejs" - var session helper.DevSession - BeforeEach(func() { - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-projects.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + It("should sync to the correct dir in container", func() { + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/apps/nodeshift") + helper.MatchAllInOutput(output, []string{"package.json"}) - // reset clonePath and change the workdir accordingly, it should sync to project name - helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"), "clonePath: webapp/", "# clonePath: webapp/") - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() + // Verify the sync env variables are correct + helper.VerifyContainerSyncEnv(podName, "runtime", commonVar.Project, "/apps/nodeshift", "/apps", commonVar.CliRunner) + }) }) - It("should sync to the correct dir in container", func() { - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/apps/nodeshift") - helper.MatchAllInOutput(output, []string{"package.json"}) + When("multiple projects are present - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + var session helper.DevSession + BeforeEach(func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-multiple-projects.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - // Verify the sync env variables are correct - helper.VerifyContainerSyncEnv(podName, "runtime", commonVar.Project, "/apps/nodeshift", "/apps", commonVar.CliRunner) - }) - }) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } - When("multiple project are present", func() { - devfileCmpName := "nodejs" - var session helper.DevSession - BeforeEach(func() { - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-multiple-projects.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) - It("should sync to the correct dir in container", func() { - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - // for devfile-with-multiple-projects.yaml source mapping is not set so $PROJECTS_ROOT is /projects - // multiple projects, so source code would sync to the first project /projects/webapp - output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects/webapp") - helper.MatchAllInOutput(output, []string{"package.json"}) + It("should sync to the correct dir in container", func() { + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + // for devfile-with-multiple-projects.yaml source mapping is not set so $PROJECTS_ROOT is /projects + // multiple projects, so source code would sync to the first project /projects/webapp + output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects/webapp") + helper.MatchAllInOutput(output, []string{"package.json"}) - // Verify the sync env variables are correct - helper.VerifyContainerSyncEnv(podName, "runtime", commonVar.Project, "/projects/webapp", "/projects", commonVar.CliRunner) + // Verify the sync env variables are correct + helper.VerifyContainerSyncEnv(podName, "runtime", commonVar.Project, "/projects/webapp", "/projects", commonVar.CliRunner) + }) }) - }) - When("no project is present", func() { - devfileCmpName := "nodejs" - var session helper.DevSession - BeforeEach(func() { - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) + When("no project is present - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + var session helper.DevSession + BeforeEach(func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) - It("should sync to the correct dir in container", func() { + It("should sync to the correct dir in container", func() { - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") - helper.MatchAllInOutput(output, []string{"package.json"}) + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects") + helper.MatchAllInOutput(output, []string{"package.json"}) - // Verify the sync env variables are correct - helper.VerifyContainerSyncEnv(podName, "runtime", commonVar.Project, "/projects", "/projects", commonVar.CliRunner) + // Verify the sync env variables are correct + helper.VerifyContainerSyncEnv(podName, "runtime", commonVar.Project, "/projects", "/projects", commonVar.CliRunner) + }) }) - }) - When("running odo dev with devfile contain volume", func() { - devfileCmpName := "nodejs" - var session helper.DevSession - BeforeEach(func() { - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-volumes.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) + When("running odo dev with devfile contain volume - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + var session helper.DevSession + BeforeEach(func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-volumes.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) - It("should create pvc and reuse if it shares the same devfile volume name", func() { - var statErr error - var cmdOutput string - // Check to see if it's been pushed (foobar.txt abd directory testdir) - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + It("should create pvc and reuse if it shares the same devfile volume name", func() { + var statErr error + var cmdOutput string + // Check to see if it's been pushed (foobar.txt abd directory testdir) + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( - podName, - "runtime2", - commonVar.Project, - []string{"cat", "/myvol/myfile.log"}, - func(cmdOp string, err error) bool { - cmdOutput = cmdOp - statErr = err - return err == nil - }, - ) - Expect(statErr).ToNot(HaveOccurred()) - Expect(cmdOutput).To(ContainSubstring("hello")) + commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + podName, + "runtime2", + commonVar.Project, + []string{"cat", "/myvol/myfile.log"}, + func(cmdOp string, err error) bool { + cmdOutput = cmdOp + statErr = err + return err == nil + }, + ) + Expect(statErr).ToNot(HaveOccurred()) + Expect(cmdOutput).To(ContainSubstring("hello")) - commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( - podName, - "runtime2", - commonVar.Project, - []string{"stat", "/data2"}, - func(cmdOp string, err error) bool { - statErr = err - return err == nil - }, - ) - Expect(statErr).ToNot(HaveOccurred()) - }) + commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + podName, + "runtime2", + commonVar.Project, + []string{"stat", "/data2"}, + func(cmdOp string, err error) bool { + statErr = err + return err == nil + }, + ) + Expect(statErr).ToNot(HaveOccurred()) + }) - It("check the volume name and mount paths for the containers", func() { - deploymentName, err := util.NamespaceKubernetesObject(devfileCmpName, "app") - Expect(err).To(BeNil()) + It("check the volume name and mount paths for the containers", func() { + deploymentName, err := util.NamespaceKubernetesObject(devfileCmpName, "app") + Expect(err).To(BeNil()) - volumesMatched := false + volumesMatched := false - // check the volume name and mount paths for the containers - volNamesAndPaths := commonVar.CliRunner.GetVolumeMountNamesandPathsFromContainer(deploymentName, "runtime", commonVar.Project) - volNamesAndPathsArr := strings.Fields(volNamesAndPaths) - for _, volNamesAndPath := range volNamesAndPathsArr { - volNamesAndPathArr := strings.Split(volNamesAndPath, ":") + // check the volume name and mount paths for the containers + volNamesAndPaths := commonVar.CliRunner.GetVolumeMountNamesandPathsFromContainer(deploymentName, "runtime", commonVar.Project) + volNamesAndPathsArr := strings.Fields(volNamesAndPaths) + for _, volNamesAndPath := range volNamesAndPathsArr { + volNamesAndPathArr := strings.Split(volNamesAndPath, ":") - if strings.Contains(volNamesAndPathArr[0], "myvol") && volNamesAndPathArr[1] == "/data" { - volumesMatched = true + if strings.Contains(volNamesAndPathArr[0], "myvol") && volNamesAndPathArr[1] == "/data" { + volumesMatched = true + } } - } - Expect(volumesMatched).To(Equal(true)) + Expect(volumesMatched).To(Equal(true)) + }) }) - }) - When("running odo dev with devfile containing volume-component", func() { - devfileCmpName := "test-devfile" - var session helper.DevSession - BeforeEach(func() { - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-volume-components.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) + When("running odo dev with devfile containing volume-component - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + var session helper.DevSession + BeforeEach(func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-volume-components.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) - It("should successfully use the volume components in container components", func() { + It("should successfully use the volume components in container components", func() { - // Verify the pvc size for firstvol - storageSize := commonVar.CliRunner.GetPVCSize(devfileCmpName, "firstvol", commonVar.Project) - // should be the default size - Expect(storageSize).To(ContainSubstring("1Gi")) + // Verify the pvc size for firstvol + storageSize := commonVar.CliRunner.GetPVCSize(devfileCmpName, "firstvol", commonVar.Project) + // should be the default size + Expect(storageSize).To(ContainSubstring("1Gi")) - // Verify the pvc size for secondvol - storageSize = commonVar.CliRunner.GetPVCSize(devfileCmpName, "secondvol", commonVar.Project) - // should be the specified size in the devfile volume component - Expect(storageSize).To(ContainSubstring("200Mi")) + // Verify the pvc size for secondvol + storageSize = commonVar.CliRunner.GetPVCSize(devfileCmpName, "secondvol", commonVar.Project) + // should be the specified size in the devfile volume component + Expect(storageSize).To(ContainSubstring("200Mi")) + }) }) - }) + } Describe("devfile contains composite apply command", func() { const ( @@ -1329,244 +1408,288 @@ CMD ["npm", "start"] })) url = server.URL - helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"), "./Dockerfile", url) - }) - - AfterEach(func() { - server.Close() - }) - - It("should not build images when odo dev is run", func() { - _, sessionOut, _, err := helper.DevModeShouldFail(env, "failed to retrieve "+url) - Expect(err).To(BeNil()) - Expect(sessionOut).NotTo(ContainSubstring("build -t quay.io/unknown-account/myimage -f ")) - Expect(sessionOut).NotTo(ContainSubstring("push quay.io/unknown-account/myimage")) - }) - }) - } - }) - }) - - When("running odo dev and devfile with composite command", func() { - devfileCmpName := "nodejs" - var session helper.DevSession - BeforeEach(func() { - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileCompositeCommands.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) - - It("should execute all commands in composite command", func() { - // Verify the command executed successfully - var statErr error - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( - podName, - "runtime", - commonVar.Project, - []string{"stat", "/projects/testfolder"}, - func(cmdOp string, err error) bool { - statErr = err - return err == nil - }, - ) - Expect(statErr).ToNot(HaveOccurred()) - }) - }) - - When("running odo dev and composite command is marked as parallel:true", func() { - devfileCmpName := "nodejs" - var session helper.DevSession - BeforeEach(func() { - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileCompositeCommandsParallel.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) - - It("should execute all commands in composite command", func() { - // Verify the command executed successfully - var statErr error - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( - podName, - "runtime", - commonVar.Project, - []string{"stat", "/projects/testfolder"}, - func(cmdOp string, err error) bool { - statErr = err - return err == nil - }, - ) - Expect(statErr).ToNot(HaveOccurred()) - }) - }) - - When("running odo dev and composite command are nested", func() { - devfileCmpName := "nodejs" - var session helper.DevSession - BeforeEach(func() { - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileNestedCompCommands.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - var err error - session, _, _, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) - - It("should execute all commands in composite commmand", func() { - // Verify the command executed successfully - var statErr error - podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( - podName, - "runtime", - commonVar.Project, - []string{"stat", "/projects/testfolder"}, - func(cmdOp string, err error) bool { - statErr = err - return err == nil - }, - ) - Expect(statErr).ToNot(HaveOccurred()) - }) - }) - - When("running odo dev and composite command is used as a run command", func() { - var session helper.DevSession - var stdout []byte - var stderr []byte - const devfileCmpName = "nodejs" - BeforeEach(func() { - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileCompositeRunAndDebug.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - var err error - session, stdout, stderr, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) + helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"), "./Dockerfile", url) + }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) + AfterEach(func() { + server.Close() + }) - It("should run successfully", func() { - By("verifying from the output that all commands have been executed", func() { - helper.MatchAllInOutput(string(stdout), []string{ - "Building your application in container on cluster", - "Executing the application (command: mkdir)", - "Executing the application (command: echo)", - "Executing the application (command: install)", - "Executing the application (command: start)", + It("should not build images when odo dev is run", func() { + _, sessionOut, _, err := helper.DevModeShouldFail(env, "failed to retrieve "+url) + Expect(err).To(BeNil()) + Expect(sessionOut).NotTo(ContainSubstring("build -t quay.io/unknown-account/myimage -f ")) + Expect(sessionOut).NotTo(ContainSubstring("push quay.io/unknown-account/myimage")) + }) }) - }) + } + }) + }) - By("verifying that any command that did not succeed in the middle has logged such information correctly", func() { - helper.MatchAllInOutput(string(stderr), []string{ - "Devfile command \"echo\" exited with an error status", - "intentional-error-message", - }) + for _, devfileHandlerCtx := range []struct { + name string + cmpName string + devfileHandler func(path string) + }{ + { + name: "with metadata.name", + // cmpName from Devfile + cmpName: "nodejs", + }, + { + name: "without metadata.name", + // cmpName is returned by alizer.DetectName + cmpName: "nodejs-starter", + devfileHandler: func(path string) { + helper.UpdateDevfileContent(path, []helper.DevfileUpdater{helper.DevfileMetadataNameRemover}) + }, + }, + } { + devfileHandlerCtx := devfileHandlerCtx + When("running odo dev and devfile with composite command - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + var session helper.DevSession + BeforeEach(func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileCompositeCommands.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) }) - - By("building the application only once", func() { - // Because of the Spinner, the "Building your application in container on cluster" is printed twice in the captured stdout. - // The bracket allows to match the last occurrence with the command execution timing information. - Expect(strings.Count(string(stdout), "Building your application in container on cluster (command: install) [")). - To(BeNumerically("==", 1), "\nOUTPUT: "+string(stdout)+"\n") + AfterEach(func() { + session.Stop() + session.WaitEnd() }) - By("verifying that the command did run successfully", func() { + It("should execute all commands in composite command", func() { + // Verify the command executed successfully + var statErr error podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - res := commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( podName, "runtime", commonVar.Project, []string{"stat", "/projects/testfolder"}, func(cmdOp string, err error) bool { + statErr = err return err == nil }, ) - Expect(res).To(BeTrue()) + Expect(statErr).ToNot(HaveOccurred()) }) }) - }) - - When("running build and run commands as composite in different containers and a shared volume", func() { - var session helper.DevSession - var stdout []byte - var stderr []byte - const devfileCmpName = "nodejs" - BeforeEach(func() { - helper.CopyExampleDevFile( - filepath.Join("source", "devfiles", "nodejs", "devfileCompositeBuildRunDebugInMultiContainersAndSharedVolume.yaml"), - filepath.Join(commonVar.Context, "devfile.yaml")) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - var err error - session, stdout, stderr, _, err = helper.StartDevMode(nil) - Expect(err).ToNot(HaveOccurred()) - }) - AfterEach(func() { - session.Stop() - session.WaitEnd() - }) - - It("should run successfully", func() { - By("verifying from the output that all commands have been executed", func() { - helper.MatchAllInOutput(string(stdout), []string{ - "Building your application in container on cluster (command: mkdir)", - "Building your application in container on cluster (command: sleep-cmd-build)", - "Building your application in container on cluster (command: build-cmd)", - "Executing the application (command: sleep-cmd-run)", - "Executing the application (command: echo-with-error)", - "Executing the application (command: check-build-result)", - "Executing the application (command: start)", - }) + When("running odo dev and composite command is marked as parallel:true - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + var session helper.DevSession + BeforeEach(func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileCompositeCommandsParallel.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() }) - By("verifying that any command that did not succeed in the middle has logged such information correctly", func() { - helper.MatchAllInOutput(string(stderr), []string{ - "Devfile command \"echo-with-error\" exited with an error status", - "intentional-error-message", - }) + It("should execute all commands in composite command", func() { + // Verify the command executed successfully + var statErr error + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + podName, + "runtime", + commonVar.Project, + []string{"stat", "/projects/testfolder"}, + func(cmdOp string, err error) bool { + statErr = err + return err == nil + }, + ) + Expect(statErr).ToNot(HaveOccurred()) }) + }) - By("building the application only once per exec command in the build command", func() { - // Because of the Spinner, the "Building your application in container on cluster" is printed twice in the captured stdout. - // The bracket allows to match the last occurrence with the command execution timing information. - out := string(stdout) - for _, cmd := range []string{"mkdir", "sleep-cmd-build", "build-cmd"} { - Expect(strings.Count(out, fmt.Sprintf("Building your application in container on cluster (command: %s) [", cmd))). - To(BeNumerically("==", 1), "\nOUTPUT: "+string(stdout)+"\n") + When("running odo dev and composite command are nested - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + var session helper.DevSession + BeforeEach(func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileNestedCompCommands.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) } + var err error + session, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + session.Stop() + session.WaitEnd() }) - By("verifying that the command did run successfully", func() { + It("should execute all commands in composite commmand", func() { + // Verify the command executed successfully + var statErr error podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) - res := commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( podName, "runtime", commonVar.Project, []string{"stat", "/projects/testfolder"}, func(cmdOp string, err error) bool { + statErr = err return err == nil }, ) - Expect(res).To(BeTrue()) + Expect(statErr).ToNot(HaveOccurred()) }) }) - }) + + When("running odo dev and composite command is used as a run command - "+devfileHandlerCtx.name, func() { + var session helper.DevSession + var stdout []byte + var stderr []byte + var devfileCmpName string + BeforeEach(func() { + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileCompositeRunAndDebug.yaml"), filepath.Join(commonVar.Context, "devfile.yaml")) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + var err error + session, stdout, stderr, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) + + It("should run successfully", func() { + By("verifying from the output that all commands have been executed", func() { + helper.MatchAllInOutput(string(stdout), []string{ + "Building your application in container on cluster", + "Executing the application (command: mkdir)", + "Executing the application (command: echo)", + "Executing the application (command: install)", + "Executing the application (command: start)", + }) + }) + + By("verifying that any command that did not succeed in the middle has logged such information correctly", func() { + helper.MatchAllInOutput(string(stderr), []string{ + "Devfile command \"echo\" exited with an error status", + "intentional-error-message", + }) + }) + + By("building the application only once", func() { + // Because of the Spinner, the "Building your application in container on cluster" is printed twice in the captured stdout. + // The bracket allows to match the last occurrence with the command execution timing information. + Expect(strings.Count(string(stdout), "Building your application in container on cluster (command: install) [")). + To(BeNumerically("==", 1), "\nOUTPUT: "+string(stdout)+"\n") + }) + + By("verifying that the command did run successfully", func() { + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + res := commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + podName, + "runtime", + commonVar.Project, + []string{"stat", "/projects/testfolder"}, + func(cmdOp string, err error) bool { + return err == nil + }, + ) + Expect(res).To(BeTrue()) + }) + }) + }) + + When("running build and run commands as composite in different containers and a shared volume - "+devfileHandlerCtx.name, func() { + var session helper.DevSession + var stdout []byte + var stderr []byte + var devfileCmpName string + BeforeEach(func() { + helper.CopyExampleDevFile( + filepath.Join("source", "devfiles", "nodejs", "devfileCompositeBuildRunDebugInMultiContainersAndSharedVolume.yaml"), + filepath.Join(commonVar.Context, "devfile.yaml")) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + var err error + session, stdout, stderr, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + session.Stop() + session.WaitEnd() + }) + + It("should run successfully", func() { + By("verifying from the output that all commands have been executed", func() { + helper.MatchAllInOutput(string(stdout), []string{ + "Building your application in container on cluster (command: mkdir)", + "Building your application in container on cluster (command: sleep-cmd-build)", + "Building your application in container on cluster (command: build-cmd)", + "Executing the application (command: sleep-cmd-run)", + "Executing the application (command: echo-with-error)", + "Executing the application (command: check-build-result)", + "Executing the application (command: start)", + }) + }) + + By("verifying that any command that did not succeed in the middle has logged such information correctly", func() { + helper.MatchAllInOutput(string(stderr), []string{ + "Devfile command \"echo-with-error\" exited with an error status", + "intentional-error-message", + }) + }) + + By("building the application only once per exec command in the build command", func() { + // Because of the Spinner, the "Building your application in container on cluster" is printed twice in the captured stdout. + // The bracket allows to match the last occurrence with the command execution timing information. + out := string(stdout) + for _, cmd := range []string{"mkdir", "sleep-cmd-build", "build-cmd"} { + Expect(strings.Count(out, fmt.Sprintf("Building your application in container on cluster (command: %s) [", cmd))). + To(BeNumerically("==", 1), "\nOUTPUT: "+string(stdout)+"\n") + } + }) + + By("verifying that the command did run successfully", func() { + podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project) + res := commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + podName, + "runtime", + commonVar.Project, + []string{"stat", "/projects/testfolder"}, + func(cmdOp string, err error) bool { + return err == nil + }, + ) + Expect(res).To(BeTrue()) + }) + }) + }) + } When("running odo dev and prestart events are defined", func() { BeforeEach(func() { @@ -1686,190 +1809,214 @@ CMD ["npm", "start"] }) }) - When("running odo dev with alternative commands", func() { - - type testCase struct { - buildCmd string - runCmd string - devAdditionalOpts []string - checkFunc func(stdout, stderr string) - } - testForCmd := func(tt testCase) { - err := helper.RunDevMode(tt.devAdditionalOpts, nil, func(session *gexec.Session, outContents []byte, errContents []byte, ports map[string]string) { - stdout := string(outContents) - stderr := string(errContents) - - By("checking the output of the command", func() { - helper.MatchAllInOutput(stdout, []string{ - fmt.Sprintf("Building your application in container on cluster (command: %s)", tt.buildCmd), - fmt.Sprintf("Executing the application (command: %s)", tt.runCmd), + for _, devfileHandlerCtx := range []struct { + name string + cmpName string + devfileHandler func(path string) + }{ + { + name: "with metadata.name", + //cmpName from Devfile + cmpName: "nodejs", + }, + { + name: "without metadata.name", + //cmpName is returned by alizer.DetectName + cmpName: "nodejs-starter", + devfileHandler: func(path string) { + helper.UpdateDevfileContent(path, []helper.DevfileUpdater{helper.DevfileMetadataNameRemover}) + }, + }, + } { + devfileHandlerCtx := devfileHandlerCtx + When("running odo dev with alternative commands - "+devfileHandlerCtx.name, func() { + + type testCase struct { + buildCmd string + runCmd string + devAdditionalOpts []string + checkFunc func(stdout, stderr string) + } + testForCmd := func(tt testCase) { + err := helper.RunDevMode(tt.devAdditionalOpts, nil, func(session *gexec.Session, outContents []byte, errContents []byte, ports map[string]string) { + stdout := string(outContents) + stderr := string(errContents) + + By("checking the output of the command", func() { + helper.MatchAllInOutput(stdout, []string{ + fmt.Sprintf("Building your application in container on cluster (command: %s)", tt.buildCmd), + fmt.Sprintf("Executing the application (command: %s)", tt.runCmd), + }) }) - }) - - if tt.checkFunc != nil { - tt.checkFunc(stdout, stderr) - } - - By("verifying the exposed application endpoint", func() { - url := fmt.Sprintf("http://%s", ports["3000"]) - resp, err := http.Get(url) - Expect(err).ToNot(HaveOccurred()) - defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) - helper.MatchAllInOutput(string(body), []string{"Hello from Node.js Starter Application!"}) - }) + if tt.checkFunc != nil { + tt.checkFunc(stdout, stderr) + } - }) - Expect(err).ToNot(HaveOccurred()) - } + By("verifying the exposed application endpoint", func() { + url := fmt.Sprintf("http://%s", ports["3000"]) + resp, err := http.Get(url) + Expect(err).ToNot(HaveOccurred()) + defer resp.Body.Close() - remoteFileChecker := func(path string) bool { - return commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( - commonVar.CliRunner.GetRunningPodNameByComponent("nodejs", commonVar.Project), - "runtime", - commonVar.Project, - []string{"stat", path}, - func(cmdOp string, err error) bool { - return err == nil - }, - ) - } + body, _ := io.ReadAll(resp.Body) + helper.MatchAllInOutput(string(body), []string{"Hello from Node.js Starter Application!"}) + }) - BeforeEach(func() { - helper.CopyExampleDevFile( - filepath.Join("source", "devfiles", "nodejs", "devfile-with-alternative-commands.yaml"), - filepath.Join(commonVar.Context, "devfile.yaml")) - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - }) + }) + Expect(err).ToNot(HaveOccurred()) + } - When("running odo dev with a build command", func() { - buildCmdTestFunc := func(buildCmd string, checkFunc func(stdout, stderr string)) { - testForCmd( - testCase{ - buildCmd: buildCmd, - runCmd: "devrun", - devAdditionalOpts: []string{"--build-command", buildCmd}, - checkFunc: checkFunc, + remoteFileChecker := func(path string) bool { + return commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + commonVar.CliRunner.GetRunningPodNameByComponent(devfileHandlerCtx.cmpName, commonVar.Project), + "runtime", + commonVar.Project, + []string{"stat", path}, + func(cmdOp string, err error) bool { + return err == nil }, ) } - It("should error out if called with an invalid command", func() { - output := helper.Cmd("odo", "dev", "--random-ports", "--build-command", "build-command-does-not-exist").ShouldFail().Err() - Expect(output).To(ContainSubstring("no build command with name \"build-command-does-not-exist\" found in Devfile")) + BeforeEach(func() { + helper.CopyExampleDevFile( + filepath.Join("source", "devfiles", "nodejs", "devfile-with-alternative-commands.yaml"), + filepath.Join(commonVar.Context, "devfile.yaml")) + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } }) - It("should error out if called with a command of another kind", func() { - // devrun is a valid run command, not a build command - output := helper.Cmd("odo", "dev", "--random-ports", "--build-command", "devrun").ShouldFail().Err() - Expect(output).To(ContainSubstring("no build command with name \"devrun\" found in Devfile")) - }) + When("running odo dev with a build command", func() { + buildCmdTestFunc := func(buildCmd string, checkFunc func(stdout, stderr string)) { + testForCmd( + testCase{ + buildCmd: buildCmd, + runCmd: "devrun", + devAdditionalOpts: []string{"--build-command", buildCmd}, + checkFunc: checkFunc, + }, + ) + } + + It("should error out if called with an invalid command", func() { + output := helper.Cmd("odo", "dev", "--random-ports", "--build-command", "build-command-does-not-exist").ShouldFail().Err() + Expect(output).To(ContainSubstring("no build command with name \"build-command-does-not-exist\" found in Devfile")) + }) - It("should execute the custom non-default build command successfully", func() { - buildCmdTestFunc("my-custom-build", func(stdout, stderr string) { - By("checking that it did not execute the default build command", func() { - helper.DontMatchAllInOutput(stdout, []string{ - "Building your application in container on cluster (command: devbuild)", + It("should error out if called with a command of another kind", func() { + // devrun is a valid run command, not a build command + output := helper.Cmd("odo", "dev", "--random-ports", "--build-command", "devrun").ShouldFail().Err() + Expect(output).To(ContainSubstring("no build command with name \"devrun\" found in Devfile")) + }) + + It("should execute the custom non-default build command successfully", func() { + buildCmdTestFunc("my-custom-build", func(stdout, stderr string) { + By("checking that it did not execute the default build command", func() { + helper.DontMatchAllInOutput(stdout, []string{ + "Building your application in container on cluster (command: devbuild)", + }) }) - }) - By("verifying that the custom command ran successfully", func() { - Expect(remoteFileChecker("/projects/file-from-my-custom-build")).To(BeTrue()) + By("verifying that the custom command ran successfully", func() { + Expect(remoteFileChecker("/projects/file-from-my-custom-build")).To(BeTrue()) + }) }) }) - }) - It("should execute the default build command successfully if specified explicitly", func() { - // devbuild is the default build command - buildCmdTestFunc("devbuild", func(stdout, stderr string) { - By("checking that it did not execute the custom build command", func() { - helper.DontMatchAllInOutput(stdout, []string{ - "Building your application in container on cluster (command: my-custom-build)", + It("should execute the default build command successfully if specified explicitly", func() { + // devbuild is the default build command + buildCmdTestFunc("devbuild", func(stdout, stderr string) { + By("checking that it did not execute the custom build command", func() { + helper.DontMatchAllInOutput(stdout, []string{ + "Building your application in container on cluster (command: my-custom-build)", + }) }) }) }) }) - }) - When("running odo dev with a run command", func() { - runCmdTestFunc := func(runCmd string, checkFunc func(stdout, stderr string)) { - testForCmd( - testCase{ - buildCmd: "devbuild", - runCmd: runCmd, - devAdditionalOpts: []string{"--run-command", runCmd}, - checkFunc: checkFunc, - }, - ) - } + When("running odo dev with a run command", func() { + runCmdTestFunc := func(runCmd string, checkFunc func(stdout, stderr string)) { + testForCmd( + testCase{ + buildCmd: "devbuild", + runCmd: runCmd, + devAdditionalOpts: []string{"--run-command", runCmd}, + checkFunc: checkFunc, + }, + ) + } - It("should error out if called with an invalid command", func() { - output := helper.Cmd("odo", "dev", "--random-ports", "--run-command", "run-command-does-not-exist").ShouldFail().Err() - Expect(output).To(ContainSubstring("no run command with name \"run-command-does-not-exist\" found in Devfile")) - }) + It("should error out if called with an invalid command", func() { + output := helper.Cmd("odo", "dev", "--random-ports", "--run-command", "run-command-does-not-exist").ShouldFail().Err() + Expect(output).To(ContainSubstring("no run command with name \"run-command-does-not-exist\" found in Devfile")) + }) - It("should error out if called with a command of another kind", func() { - // devbuild is a valid build command, not a run command - output := helper.Cmd("odo", "dev", "--random-ports", "--run-command", "devbuild").ShouldFail().Err() - Expect(output).To(ContainSubstring("no run command with name \"devbuild\" found in Devfile")) - }) + It("should error out if called with a command of another kind", func() { + // devbuild is a valid build command, not a run command + output := helper.Cmd("odo", "dev", "--random-ports", "--run-command", "devbuild").ShouldFail().Err() + Expect(output).To(ContainSubstring("no run command with name \"devbuild\" found in Devfile")) + }) - It("should execute the custom non-default run command successfully", func() { - runCmdTestFunc("my-custom-run", func(stdout, stderr string) { - By("checking that it did not execute the default run command", func() { - helper.DontMatchAllInOutput(stdout, []string{ - "Executing the application (command: devrun)", + It("should execute the custom non-default run command successfully", func() { + runCmdTestFunc("my-custom-run", func(stdout, stderr string) { + By("checking that it did not execute the default run command", func() { + helper.DontMatchAllInOutput(stdout, []string{ + "Executing the application (command: devrun)", + }) }) - }) - By("verifying that the custom command ran successfully", func() { - Expect(remoteFileChecker("/projects/file-from-my-custom-run")).To(BeTrue()) + By("verifying that the custom command ran successfully", func() { + Expect(remoteFileChecker("/projects/file-from-my-custom-run")).To(BeTrue()) + }) }) }) - }) - It("should execute the default run command successfully if specified explicitly", func() { - // devrun is the default run command - runCmdTestFunc("devrun", func(stdout, stderr string) { - By("checking that it did not execute the custom run command", func() { - helper.DontMatchAllInOutput(stdout, []string{ - "Executing the application (command: my-custom-run)", + It("should execute the default run command successfully if specified explicitly", func() { + // devrun is the default run command + runCmdTestFunc("devrun", func(stdout, stderr string) { + By("checking that it did not execute the custom run command", func() { + helper.DontMatchAllInOutput(stdout, []string{ + "Executing the application (command: my-custom-run)", + }) }) }) }) }) - }) - It("should execute the custom non-default build and run commands successfully", func() { - buildCmd := "my-custom-build" - runCmd := "my-custom-run" + It("should execute the custom non-default build and run commands successfully", func() { + buildCmd := "my-custom-build" + runCmd := "my-custom-run" - testForCmd( - testCase{ - buildCmd: buildCmd, - runCmd: runCmd, - devAdditionalOpts: []string{"--build-command", buildCmd, "--run-command", runCmd}, - checkFunc: func(stdout, stderr string) { - By("checking that it did not execute the default build and run commands", func() { - helper.DontMatchAllInOutput(stdout, []string{ - "Building your application in container on cluster (command: devbuild)", - "Executing the application (command: devrun)", + testForCmd( + testCase{ + buildCmd: buildCmd, + runCmd: runCmd, + devAdditionalOpts: []string{"--build-command", buildCmd, "--run-command", runCmd}, + checkFunc: func(stdout, stderr string) { + By("checking that it did not execute the default build and run commands", func() { + helper.DontMatchAllInOutput(stdout, []string{ + "Building your application in container on cluster (command: devbuild)", + "Executing the application (command: devrun)", + }) }) - }) - By("verifying that the custom build command ran successfully", func() { - Expect(remoteFileChecker("/projects/file-from-my-custom-build")).To(BeTrue()) - }) + By("verifying that the custom build command ran successfully", func() { + Expect(remoteFileChecker("/projects/file-from-my-custom-build")).To(BeTrue()) + }) - By("verifying that the custom run command ran successfully", func() { - Expect(remoteFileChecker("/projects/file-from-my-custom-run")).To(BeTrue()) - }) + By("verifying that the custom run command ran successfully", func() { + Expect(remoteFileChecker("/projects/file-from-my-custom-run")).To(BeTrue()) + }) + }, }, - }, - ) + ) + }) }) - }) + } // Tests https://github.com/redhat-developer/odo/issues/3838 When("java-springboot application is created and running odo dev", func() { @@ -2137,40 +2284,65 @@ CMD ["npm", "start"] }) }) - When("a container component defines a Command or Args", func() { - devfileCmpName := "nodejs" - BeforeEach(func() { - helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) - helper.CopyExampleDevFile( - filepath.Join("source", "devfiles", "nodejs", "issue-5620-devfile-with-container-command-args.yaml"), - filepath.Join(commonVar.Context, "devfile.yaml")) - }) - - It("should run odo dev successfully (#5620)", func() { - devSession, stdoutBytes, stderrBytes, _, err := helper.StartDevMode(nil) - Expect(err).ShouldNot(HaveOccurred()) - defer devSession.Stop() - const errorMessage = "Failed to create the component:" - helper.DontMatchAllInOutput(string(stdoutBytes), []string{errorMessage}) - helper.DontMatchAllInOutput(string(stderrBytes), []string{errorMessage}) + for _, devfileHandlerCtx := range []struct { + name string + cmpName string + devfileHandler func(path string) + }{ + { + name: "with metadata.name", + // cmpName from Devfile + cmpName: "nodejs", + }, + { + name: "without metadata.name", + // cmpName is returned by alizer.DetectName + cmpName: "nodejs-starter", + devfileHandler: func(path string) { + helper.UpdateDevfileContent(path, []helper.DevfileUpdater{helper.DevfileMetadataNameRemover}) + }, + }, + } { + devfileHandlerCtx := devfileHandlerCtx + When("a container component defines a Command or Args - "+devfileHandlerCtx.name, func() { + var devfileCmpName string + BeforeEach(func() { + helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context) + helper.CopyExampleDevFile( + filepath.Join("source", "devfiles", "nodejs", "issue-5620-devfile-with-container-command-args.yaml"), + filepath.Join(commonVar.Context, "devfile.yaml")) + devfileCmpName = devfileHandlerCtx.cmpName + if devfileHandlerCtx.devfileHandler != nil { + devfileHandlerCtx.devfileHandler(filepath.Join(commonVar.Context, "devfile.yaml")) + } + }) - // the command has been started directly in the background. Check the PID stored in a specific file. - commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( - commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project), - "runtime", - commonVar.Project, - []string{ - remotecmd.ShellExecutable, "-c", - fmt.Sprintf("kill -0 $(cat %s/.odo_cmd_run.pid) 2>/dev/null ; echo -n $?", - strings.TrimSuffix(storage.SharedDataMountPath, "/")), - }, - func(stdout string, err error) bool { - Expect(err).ShouldNot(HaveOccurred()) - Expect(stdout).To(Equal("0")) - return err == nil - }) + It("should run odo dev successfully (#5620)", func() { + devSession, stdoutBytes, stderrBytes, _, err := helper.StartDevMode(nil) + Expect(err).ShouldNot(HaveOccurred()) + defer devSession.Stop() + const errorMessage = "Failed to create the component:" + helper.DontMatchAllInOutput(string(stdoutBytes), []string{errorMessage}) + helper.DontMatchAllInOutput(string(stderrBytes), []string{errorMessage}) + + // the command has been started directly in the background. Check the PID stored in a specific file. + commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project), + "runtime", + commonVar.Project, + []string{ + remotecmd.ShellExecutable, "-c", + fmt.Sprintf("kill -0 $(cat %s/.odo_cmd_run.pid) 2>/dev/null ; echo -n $?", + strings.TrimSuffix(storage.SharedDataMountPath, "/")), + }, + func(stdout string, err error) bool { + Expect(err).ShouldNot(HaveOccurred()) + Expect(stdout).To(Equal("0")) + return err == nil + }) + }) }) - }) + } When("a component with multiple endpoints is run", func() { stateFile := ".odo/devstate.json" From 3aa7bc0f17fbb912c033c9706165cf968ae5058d Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Fri, 26 Aug 2022 15:22:08 +0200 Subject: [PATCH 5/5] Add test case for 'odo dev' when a project with no source code is used with no 'metadata.name' in the Devfile The rationale behind this is to purposely make the Alizer library unable to detect the project. Per the requirements, this would force us to use the project directory name as component name. This highlights an interesting behavior if the project directory name is all-numeric (as is the case in our tests); our sanitization logic automatically prepends an "x" prefix to the directory name, so it can be used as a valid name for the component. --- .../nodejs/devfile-no-metadata-name.yaml | 49 +++++++++++++++++++ tests/integration/cmd_dev_test.go | 40 +++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 tests/examples/source/devfiles/nodejs/devfile-no-metadata-name.yaml diff --git a/tests/examples/source/devfiles/nodejs/devfile-no-metadata-name.yaml b/tests/examples/source/devfiles/nodejs/devfile-no-metadata-name.yaml new file mode 100644 index 00000000000..6d6ce3d6d53 --- /dev/null +++ b/tests/examples/source/devfiles/nodejs/devfile-no-metadata-name.yaml @@ -0,0 +1,49 @@ +schemaVersion: 2.0.0 +metadata: + projectType: nodejs + language: nodejs +starterProjects: + - name: nodejs-starter + git: + remotes: + origin: "https://github.com/odo-devfiles/nodejs-ex.git" +components: + - name: runtime + container: + image: registry.access.redhat.com/ubi8/nodejs-12:1-36 + memoryLimit: 1024Mi + endpoints: + - name: "3000-tcp" + targetPort: 3000 + mountSources: true +commands: + - id: devbuild + exec: + component: runtime + commandLine: npm install + workingDir: ${PROJECTS_ROOT} + group: + kind: build + isDefault: true + - id: build + exec: + component: runtime + commandLine: npm install + workingDir: ${PROJECTS_ROOT} + group: + kind: build + - id: devrun + exec: + component: runtime + commandLine: npm start + workingDir: ${PROJECTS_ROOT} + group: + kind: run + isDefault: true + - id: run + exec: + component: runtime + commandLine: npm start + workingDir: ${PROJECTS_ROOT} + group: + kind: run diff --git a/tests/integration/cmd_dev_test.go b/tests/integration/cmd_dev_test.go index 3a2f8b53af5..11d3c77ff26 100644 --- a/tests/integration/cmd_dev_test.go +++ b/tests/integration/cmd_dev_test.go @@ -2462,4 +2462,44 @@ CMD ["npm", "start"] }) }) }) + + Describe("Devfile with no metadata.name", func() { + + BeforeEach(func() { + helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-no-metadata-name.yaml"), + filepath.Join(commonVar.Context, "devfile.yaml")) + }) + + When("running odo dev against a component with no source code", func() { + var devSession helper.DevSession + BeforeEach(func() { + var err error + devSession, _, _, _, err = helper.StartDevMode(nil) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + devSession.Stop() + }) + + It("should use the directory as component name", func() { + // when no further source code is available, directory name is returned by alizer.DetectName as component name; + // and since it is all-numeric in our tests, an "x" prefix is added by util.GetDNS1123Name (called by alizer.DetectName) + cmpName := "x" + filepath.Base(commonVar.Context) + commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod( + commonVar.CliRunner.GetRunningPodNameByComponent(cmpName, commonVar.Project), + "runtime", + commonVar.Project, + []string{ + remotecmd.ShellExecutable, "-c", + fmt.Sprintf("cat %s/.odo_cmd_devrun.pid", strings.TrimSuffix(storage.SharedDataMountPath, "/")), + }, + func(stdout string, err error) bool { + Expect(err).ShouldNot(HaveOccurred()) + Expect(stdout).NotTo(BeEmpty()) + return err == nil + }) + }) + }) + }) })