diff --git a/pkg/config/url.go b/pkg/config/url.go index 9827625664e..bbcccababc5 100644 --- a/pkg/config/url.go +++ b/pkg/config/url.go @@ -21,6 +21,8 @@ func (lc *LocalConfig) GetPorts() ([]string, error) { func (lc *LocalConfig) CompleteURL(url *localConfigProvider.LocalURL) error { var err error + url.Kind = localConfigProvider.ROUTE + ports, err := lc.GetPorts() if err != nil { return err diff --git a/pkg/envinfo/envinfo.go b/pkg/envinfo/envinfo.go index 6015c0156c8..0aefad0f2b3 100644 --- a/pkg/envinfo/envinfo.go +++ b/pkg/envinfo/envinfo.go @@ -67,6 +67,8 @@ const ( // EnvInfo holds all the env specific information relevant to a specific Component. type EnvInfo struct { devfileObj parser.DevfileObj + isRouteSupported bool + updateURL bool // this indicates that the URL create operation should be an update operation componentSettings ComponentSettings `yaml:"ComponentSettings,omitempty"` } @@ -424,6 +426,11 @@ func (ei *EnvInfo) GetDevfileObj() parser.DevfileObj { return ei.devfileObj } +// SetIsRouteSupported sets the isRouteSupported value for the envinfo +func (ei *EnvInfo) SetIsRouteSupported(isRouteSupported bool) { + ei.isRouteSupported = isRouteSupported +} + // GetLink returns the EnvInfoLink, returns default if nil func (ei *EnvInfo) GetLink() []EnvInfoLink { if ei.componentSettings.Link == nil { diff --git a/pkg/envinfo/url.go b/pkg/envinfo/url.go index ae45cf63fdd..070d740a0b7 100644 --- a/pkg/envinfo/url.go +++ b/pkg/envinfo/url.go @@ -42,6 +42,14 @@ func (ei *EnvInfo) GetPorts() ([]string, error) { // CompleteURL completes the given URL with default values func (ei *EnvInfo) CompleteURL(url *localConfigProvider.LocalURL) error { + if url.Kind == "" { + if !ei.isRouteSupported { + url.Kind = localConfigProvider.INGRESS + } else { + url.Kind = localConfigProvider.ROUTE + } + } + if len(url.Path) > 0 && (strings.HasPrefix(url.Path, "/") || strings.HasPrefix(url.Path, "\\")) { if len(url.Path) <= 1 { url.Path = "" @@ -59,16 +67,32 @@ func (ei *EnvInfo) CompleteURL(url *localConfigProvider.LocalURL) error { return err } if url.Port == -1 { - var err error - url.Port, err = util.GetValidPortNumber(ei.GetName(), url.Port, ports) - if err != nil { - return err + if len(ports) > 1 { + return fmt.Errorf("port for the component %s is required as it exposes %d ports: %s", ei.GetName(), len(ports), strings.Trim(strings.Replace(fmt.Sprint(ports), " ", ",", -1), "[]")) + } else if len(ports) <= 0 { + return fmt.Errorf("no port is exposed by the component %s, please specify a port", ei.GetName()) + } else { + url.Port, err = strconv.Atoi(strings.Split(ports[0], "/")[0]) + if err != nil { + return err + } } } // get the name for the URL if not provided if len(url.Name) == 0 { - url.Name = util.GetURLName(ei.GetName(), url.Port) + foundURL, err := findInvalidEndpoint(ei, url.Port) + if err != nil { + return err + } + + if foundURL.Name != "" { + // found an URL that can be overridden or more info can be added to it + url.Name = foundURL.Name + ei.updateURL = true + } else { + url.Name = util.GetURLName(ei.GetName(), url.Port) + } } containerComponents, err := ei.devfileObj.Data.GetDevfileContainerComponents(common.DevfileOptions{}) @@ -131,7 +155,7 @@ func (ei *EnvInfo) ValidateURL(url localConfigProvider.LocalURL) error { } } for _, endpoint := range component.Container.Endpoints { - if endpoint.Name == url.Name { + if endpoint.Name == url.Name && !ei.updateURL { return fmt.Errorf("url %v already exist in devfile endpoint entry under container %v", url.Name, component.Name) } containerPortMap[endpoint.TargetPort] = component.Name @@ -184,13 +208,15 @@ func (ei *EnvInfo) ValidateURL(url localConfigProvider.LocalURL) error { } } - urls, err := ei.ListURLs() - if err != nil { - return err - } - for _, localURL := range urls { - if url.Name == localURL.Name { - errorList = append(errorList, fmt.Sprintf("URL %s already exists", url.Name)) + if !ei.updateURL { + urls, err := ei.ListURLs() + if err != nil { + return err + } + for _, localURL := range urls { + if url.Name == localURL.Name { + errorList = append(errorList, fmt.Sprintf("URL %s already exists", url.Name)) + } } } @@ -216,20 +242,29 @@ func (ei *EnvInfo) GetURL(name string) (*localConfigProvider.LocalURL, error) { // CreateURL write the given url to the env.yaml and devfile func (esi *EnvSpecificInfo) CreateURL(url localConfigProvider.LocalURL) error { - newEndpointEntry := devfilev1.Endpoint{ - Name: url.Name, - Path: url.Path, - Secure: url.Secure, - Exposure: devfilev1.PublicEndpointExposure, - TargetPort: url.Port, - Protocol: devfilev1.EndpointProtocol(strings.ToLower(url.Protocol)), - } - err := addEndpointInDevfile(esi.devfileObj, newEndpointEntry, url.Container) - if err != nil { - return errors.Wrapf(err, "failed to write endpoints information into devfile") + if !esi.updateURL { + newEndpointEntry := devfilev1.Endpoint{ + Name: url.Name, + Path: url.Path, + Secure: url.Secure, + Exposure: devfilev1.PublicEndpointExposure, + TargetPort: url.Port, + Protocol: devfilev1.EndpointProtocol(strings.ToLower(url.Protocol)), + } + + err := addEndpointInDevfile(esi.devfileObj, newEndpointEntry, url.Container) + if err != nil { + return errors.Wrapf(err, "failed to write endpoints information into devfile") + } + } else { + err := updateEndpointInDevfile(esi.devfileObj, url) + if err != nil { + return err + } } - err = esi.SetConfiguration("url", localConfigProvider.LocalURL{Name: url.Name, Host: url.Host, TLSSecret: url.TLSSecret, Kind: url.Kind}) + + err := esi.SetConfiguration("url", localConfigProvider.LocalURL{Name: url.Name, Host: url.Host, TLSSecret: url.TLSSecret, Kind: url.Kind}) if err != nil { return errors.Wrapf(err, "failed to persist the component settings to env file") } @@ -358,3 +393,65 @@ func removeEndpointInDevfile(devObj parser.DevfileObj, urlName string) error { } return devObj.WriteYamlDevfile() } + +// updateEndpointInDevfile updates the endpoint of the given URL in the devfile +func updateEndpointInDevfile(devObj parser.DevfileObj, url localConfigProvider.LocalURL) error { + components, err := devObj.Data.GetComponents(common.DevfileOptions{}) + if err != nil { + return err + } + for _, component := range components { + if component.Container != nil && component.Name == url.Container { + for j := range component.ComponentUnion.Container.Endpoints { + endpoint := component.ComponentUnion.Container.Endpoints[j] + + if endpoint.Name == url.Name { + // fill the default values + if endpoint.Exposure == "" { + endpoint.Exposure = devfilev1.PublicEndpointExposure + } + if endpoint.Path == "" { + endpoint.Path = "/" + } + if endpoint.Protocol == "" { + endpoint.Protocol = devfilev1.HTTPEndpointProtocol + } + + // prevent write unless required + if endpoint.Exposure != devfilev1.PublicEndpointExposure || url.Secure != endpoint.Secure || + url.Path != endpoint.Path || url.Protocol != string(endpoint.Protocol) { + endpoint = devfilev1.Endpoint{ + Name: url.Name, + Path: url.Path, + Secure: url.Secure, + Exposure: devfilev1.PublicEndpointExposure, + TargetPort: url.Port, + Protocol: devfilev1.EndpointProtocol(strings.ToLower(url.Protocol)), + } + component.ComponentUnion.Container.Endpoints[j] = endpoint + devObj.Data.UpdateComponent(component) + return devObj.WriteYamlDevfile() + } + return nil + } + } + } + } + return fmt.Errorf("url %s not found for updating", url.Name) +} + +// findInvalidEndpoint finds the URLs which are invalid for the current cluster e.g +// route urls on a vanilla k8s based cluster +// urls with no host information on a vanilla k8s based cluster +func findInvalidEndpoint(ei *EnvInfo, port int) (localConfigProvider.LocalURL, error) { + urls, err := ei.ListURLs() + if err != nil { + return localConfigProvider.LocalURL{}, err + } + for _, url := range urls { + if url.Kind == localConfigProvider.ROUTE && url.Port == port && !ei.isRouteSupported { + return url, nil + } + } + return localConfigProvider.LocalURL{}, nil +} diff --git a/pkg/envinfo/url_test.go b/pkg/envinfo/url_test.go index 1903de65994..8e68df7dace 100644 --- a/pkg/envinfo/url_test.go +++ b/pkg/envinfo/url_test.go @@ -5,7 +5,10 @@ import ( "reflect" "testing" + v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser" + devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" + parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common" "github.com/devfile/library/pkg/testingutil/filesystem" "github.com/kylelemons/godebug/pretty" "github.com/openshift/odo/pkg/localConfigProvider" @@ -18,6 +21,7 @@ func TestEnvInfo_CompleteURL(t *testing.T) { type fields struct { devfileObj parser.DevfileObj componentSettings ComponentSettings + isRouteSupported bool } type args struct { url localConfigProvider.LocalURL @@ -27,6 +31,7 @@ func TestEnvInfo_CompleteURL(t *testing.T) { fields fields args args wantedURL localConfigProvider.LocalURL + updateURL bool wantErr bool }{ { @@ -45,6 +50,7 @@ func TestEnvInfo_CompleteURL(t *testing.T) { Port: 0, Secure: false, Path: "/data", + Kind: localConfigProvider.INGRESS, Container: "runtime", }, }, @@ -64,6 +70,7 @@ func TestEnvInfo_CompleteURL(t *testing.T) { Port: 0, Secure: false, Path: "/", + Kind: localConfigProvider.INGRESS, Container: "runtime", }, }, @@ -83,6 +90,7 @@ func TestEnvInfo_CompleteURL(t *testing.T) { Port: 0, Secure: false, Path: "/data", + Kind: localConfigProvider.INGRESS, Container: "runtime", }, }, @@ -101,6 +109,7 @@ func TestEnvInfo_CompleteURL(t *testing.T) { Port: 0, Secure: false, Path: "/", + Kind: localConfigProvider.INGRESS, Container: "runtime", }, }, @@ -121,10 +130,10 @@ func TestEnvInfo_CompleteURL(t *testing.T) { Port: 3000, Secure: false, Path: "/data", + Kind: localConfigProvider.INGRESS, Container: "runtime", }, }, - { name: "case 6: complete the container based on the matching port in the devfile", fields: fields{ @@ -141,6 +150,7 @@ func TestEnvInfo_CompleteURL(t *testing.T) { Port: 8080, Secure: false, Path: "/", + Kind: localConfigProvider.INGRESS, Container: "runtime-debug", }, }, @@ -161,6 +171,7 @@ func TestEnvInfo_CompleteURL(t *testing.T) { Port: 8080, Secure: false, Path: "/", + Kind: localConfigProvider.INGRESS, Container: "runtime-debug", }, }, @@ -187,6 +198,7 @@ func TestEnvInfo_CompleteURL(t *testing.T) { Name: "url-1", Port: 8080, Secure: false, + Kind: localConfigProvider.INGRESS, Path: "/", }, wantErr: true, @@ -209,6 +221,125 @@ func TestEnvInfo_CompleteURL(t *testing.T) { Port: 8080, Secure: false, Path: "/", + Kind: localConfigProvider.INGRESS, + Container: "runtime", + }, + }, + { + name: "case 10: user doesn't provide an port and no ports are exposed by the devfile", + fields: fields{ + devfileObj: parser.DevfileObj{ + Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]v1.Component{ + odoTestingUtil.GetFakeContainerComponent("runtime"), + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), + }, + componentSettings: ComponentSettings{ + Name: "nodejs", + }, + }, + args: args{ + url: localConfigProvider.LocalURL{ + Port: -1, + }, + }, + wantedURL: localConfigProvider.LocalURL{}, + wantErr: true, + }, + { + name: "case 11: user doesn't provide an port and multiple ports are exposed by the devfile", + fields: fields{ + devfileObj: odoTestingUtil.GetTestDevfileObjWithMultipleEndpoints(fs), + componentSettings: ComponentSettings{ + Name: "nodejs", + }, + }, + args: args{ + url: localConfigProvider.LocalURL{ + Port: -1, + }, + }, + wantedURL: localConfigProvider.LocalURL{}, + wantErr: true, + }, + { + name: "case 12: complete the url kind if not provided and route is supported", + fields: fields{ + devfileObj: odoTestingUtil.GetTestDevfileObj(fs), + componentSettings: ComponentSettings{ + Name: "nodejs", + }, + isRouteSupported: true, + }, + args: args{ + url: localConfigProvider.LocalURL{ + Port: 8080, + }, + }, + wantedURL: localConfigProvider.LocalURL{ + Name: "nodejs-8080", + Port: 8080, + Secure: false, + Path: "/", + Kind: localConfigProvider.ROUTE, + Container: "runtime", + }, + }, + { + name: "case 13: use an existing url when an invalid URL exists and no name and port is provided", + fields: fields{ + devfileObj: odoTestingUtil.GetTestDevfileObj(fs), + componentSettings: ComponentSettings{ + Name: "nodejs", + }, + isRouteSupported: false, + }, + args: args{ + url: localConfigProvider.LocalURL{ + Port: -1, + }, + }, + updateURL: true, + wantedURL: localConfigProvider.LocalURL{ + Name: "port-3030", + Port: 3000, + Secure: false, + Path: "/", + Kind: localConfigProvider.INGRESS, + Container: "runtime", + }, + }, + { + name: "case 14: use an existing url when an invalid URL exists and no name is provided but port is provided", + fields: fields{ + devfileObj: odoTestingUtil.GetTestDevfileObjWithMultipleEndpoints(fs), + componentSettings: ComponentSettings{ + Name: "nodejs", + }, + isRouteSupported: false, + }, + args: args{ + url: localConfigProvider.LocalURL{ + Port: 3030, + }, + }, + updateURL: true, + wantedURL: localConfigProvider.LocalURL{ + Name: "port-3030", + Port: 3030, + Secure: false, + Path: "/", + Kind: localConfigProvider.INGRESS, Container: "runtime", }, }, @@ -218,6 +349,7 @@ func TestEnvInfo_CompleteURL(t *testing.T) { ei := &EnvInfo{ devfileObj: tt.fields.devfileObj, componentSettings: tt.fields.componentSettings, + isRouteSupported: tt.fields.isRouteSupported, } err := ei.CompleteURL(&tt.args.url) @@ -236,6 +368,10 @@ func TestEnvInfo_CompleteURL(t *testing.T) { if !reflect.DeepEqual(tt.args.url, tt.wantedURL) { t.Errorf("url doesn't match the required url: %v", pretty.Compare(tt.args.url, tt.wantedURL)) } + + if tt.updateURL != ei.updateURL { + t.Errorf("url update property doesn't match the required: %v", tt.updateURL) + } }) } } @@ -251,10 +387,11 @@ func TestEnvInfo_ValidateURL(t *testing.T) { url localConfigProvider.LocalURL } tests := []struct { - name string - fields fields - args args - wantErr bool + name string + fields fields + args args + updateURL bool + wantErr bool }{ { name: "case 1: container not found", @@ -436,12 +573,30 @@ func TestEnvInfo_ValidateURL(t *testing.T) { }, wantErr: false, }, + { + name: "case 13: url exists but we are updating it", + fields: fields{ + devfileObj: odoTestingUtil.GetTestDevfileObj(fs), + }, + args: args{ + url: localConfigProvider.LocalURL{ + Name: "port-3030", + TLSSecret: "blah", + Secure: true, + Host: "com", + Kind: localConfigProvider.INGRESS, + }, + }, + updateURL: true, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ei := &EnvInfo{ devfileObj: tt.fields.devfileObj, componentSettings: tt.fields.componentSettings, + updateURL: tt.updateURL, } if err := ei.ValidateURL(tt.args.url); (err != nil) != tt.wantErr { t.Errorf("ValidateURL() error = %v, wantErr %v", err, tt.wantErr) @@ -744,3 +899,213 @@ func TestEnvInfo_ListURLs(t *testing.T) { }) } } + +func Test_findInvalidEndpoint(t *testing.T) { + fs := filesystem.NewFakeFs() + + type args struct { + ei *EnvInfo + port int + } + tests := []struct { + name string + args args + want localConfigProvider.LocalURL + wantErr bool + }{ + { + name: "case 1: find an invalid URL when route resources are not available", + args: args{ + ei: &EnvInfo{ + isRouteSupported: false, + devfileObj: odoTestingUtil.GetTestDevfileObjWithMultipleEndpoints(fs), + }, + port: 3030, + }, + want: localConfigProvider.LocalURL{ + Name: "port-3030", + Port: 3030, + Secure: false, + Kind: localConfigProvider.ROUTE, + Path: "/", + Container: "runtime", + }, + wantErr: false, + }, + { + name: "case 2: route resources are available", + args: args{ + ei: &EnvInfo{ + isRouteSupported: true, + devfileObj: odoTestingUtil.GetTestDevfileObjWithMultipleEndpoints(fs), + }, + port: 3030, + }, + want: localConfigProvider.LocalURL{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := findInvalidEndpoint(tt.args.ei, tt.args.port) + if (err != nil) != tt.wantErr { + t.Errorf("findInvalidEndpoint() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("findInvalidEndpoint() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_updateEndpointInDevfile(t *testing.T) { + fs := filesystem.NewFakeFs() + + type args struct { + devObj parser.DevfileObj + url localConfigProvider.LocalURL + } + tests := []struct { + name string + args args + wantEndpoint v1.Endpoint + wantErr bool + }{ + { + name: "case 1: update the endpoint when protocol is different", + args: args{ + devObj: odoTestingUtil.DevfileObjWithSecureEndpoints(fs), + url: localConfigProvider.LocalURL{ + Name: "port-3030", + Port: 3030, + Protocol: string(v1.WSSEndpointProtocol), + Container: "runtime", + }, + }, + wantEndpoint: v1.Endpoint{ + Name: "port-3030", + TargetPort: 3030, + Protocol: v1.WSSEndpointProtocol, + Exposure: v1.PublicEndpointExposure, + }, + wantErr: false, + }, + { + name: "case 2: update the endpoint when exposure is different", + args: args{ + devObj: odoTestingUtil.DevfileObjWithInternalNoneEndpoints(fs), + url: localConfigProvider.LocalURL{ + Name: "port-3030", + Port: 3030, + Container: "runtime", + }, + }, + wantEndpoint: v1.Endpoint{ + Name: "port-3030", + TargetPort: 3030, + Exposure: v1.PublicEndpointExposure, + }, + wantErr: false, + }, + { + name: "case 3: update the endpoint when path is different", + args: args{ + devObj: odoTestingUtil.GetTestDevfileObjWithPath(fs), + url: localConfigProvider.LocalURL{ + Name: "port-3030", + Port: 3000, + Path: "/user", + Container: "runtime", + }, + }, + wantEndpoint: v1.Endpoint{ + Name: "port-3030", + TargetPort: 3000, + Path: "/user", + Exposure: v1.PublicEndpointExposure, + }, + wantErr: false, + }, + { + name: "case 4: update the endpoint when secure is different", + args: args{ + devObj: odoTestingUtil.GetTestDevfileObj(fs), + url: localConfigProvider.LocalURL{ + Name: "port-3030", + Port: 3000, + Secure: true, + Container: "runtime", + }, + }, + wantEndpoint: v1.Endpoint{ + Name: "port-3030", + TargetPort: 3000, + Secure: true, + Exposure: v1.PublicEndpointExposure, + }, + wantErr: false, + }, + { + name: "case 5: avoid a write when values are default", + args: args{ + devObj: odoTestingUtil.GetTestDevfileObj(fs), + url: localConfigProvider.LocalURL{ + Name: "port-3030", + Port: 3000, + Container: "runtime", + Protocol: string(v1.HTTPEndpointProtocol), + Path: "/", + }, + }, + wantEndpoint: v1.Endpoint{ + Name: "port-3030", + TargetPort: 3000, + }, + wantErr: false, + }, + { + name: "case 6: url not found", + args: args{ + devObj: odoTestingUtil.GetTestDevfileObj(fs), + url: localConfigProvider.LocalURL{ + Name: "port-303", + Port: 3000, + Container: "runtime", + Protocol: string(v1.HTTPEndpointProtocol), + Path: "/", + }, + }, + wantEndpoint: v1.Endpoint{ + Name: "port-3030", + TargetPort: 3000, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := updateEndpointInDevfile(tt.args.devObj, tt.args.url); (err != nil) != tt.wantErr { + t.Errorf("updateEndpointInDevfile() error = %v, wantErr %v", err, tt.wantErr) + } + + components, err := tt.args.devObj.Data.GetComponents(parsercommon.DevfileOptions{}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + for _, component := range components { + if component.Container != nil { + for _, endpoint := range component.Container.Endpoints { + if endpoint.Name == tt.args.url.Name { + // prevent write unless required + if !reflect.DeepEqual(tt.wantEndpoint, endpoint) { + t.Errorf("expected endpoint doesn't match got: %v", pretty.Compare(tt.wantEndpoint, endpoint)) + } + } + } + } + } + }) + } +} diff --git a/pkg/odo/cli/url/create.go b/pkg/odo/cli/url/create.go index 2a1c125b6af..79fd74f2ff0 100644 --- a/pkg/odo/cli/url/create.go +++ b/pkg/odo/cli/url/create.go @@ -77,31 +77,20 @@ func NewURLCreateOptions() *CreateOptions { // Complete completes CreateOptions after they've been Created func (o *CreateOptions) Complete(_ string, cmd *cobra.Command, args []string) (err error) { o.Context, err = genericclioptions.New(genericclioptions.CreateParameters{ - Cmd: cmd, - DevfilePath: o.DevfilePath, - ComponentContext: o.GetComponentContext(), - IsNow: o.now, + Cmd: cmd, + DevfilePath: o.DevfilePath, + ComponentContext: o.GetComponentContext(), + IsNow: o.now, + CheckRouteAvailability: true, }) if err != nil { return err } - o.Client, err = genericclioptions.Client() - if err != nil { - return err - } - - isRouteSupported, err := o.Client.IsRouteSupported() - if err != nil { - return err - } - var urlType localConfigProvider.URLKind - if o.wantIngress || (!isRouteSupported) { + if o.wantIngress { urlType = localConfigProvider.INGRESS - } else { - urlType = localConfigProvider.ROUTE } // get the name diff --git a/pkg/odo/genericclioptions/context.go b/pkg/odo/genericclioptions/context.go index 8fc80db6459..f5aed900b56 100644 --- a/pkg/odo/genericclioptions/context.go +++ b/pkg/odo/genericclioptions/context.go @@ -54,10 +54,11 @@ type internalCxt struct { // CreateParameters defines the options which can be provided while creating the context type CreateParameters struct { - Cmd *cobra.Command - DevfilePath string - ComponentContext string - IsNow bool + Cmd *cobra.Command + DevfilePath string + ComponentContext string + IsNow bool + CheckRouteAvailability bool } // New creates a context based on the given parameters @@ -88,6 +89,20 @@ func New(parameters CreateParameters, toggles ...bool) (context *Context, err er } context.EnvSpecificInfo.SetDevfileObj(devObj) + + context.Client, err = Client() + if err != nil { + return nil, err + } + context.resolveNamespace(context.EnvSpecificInfo) + + if parameters.CheckRouteAvailability { + isRouteSupported, err := context.Client.IsRouteSupported() + if err != nil { + return nil, err + } + context.EnvSpecificInfo.SetIsRouteSupported(isRouteSupported) + } context.LocalConfigProvider = context.EnvSpecificInfo } else { if parameters.IsNow { diff --git a/pkg/util/url_test.go b/pkg/util/url_test.go index 8fa2550e76c..6d93e80cb3b 100644 --- a/pkg/util/url_test.go +++ b/pkg/util/url_test.go @@ -67,7 +67,7 @@ func TestGetValidPortNumber(t *testing.T) { if err == nil && !tt.wantErr { if !reflect.DeepEqual(gotPortNumber, tt.wantedPort) { - t.Errorf("Create() = %#v, want %#v", gotPortNumber, tt.wantedPort) + t.Errorf("GetValidPortNumber() = %#v, want %#v", gotPortNumber, tt.wantedPort) } } else if err == nil && tt.wantErr { t.Error("error was expected, but no error was returned") diff --git a/tests/integration/devfile/cmd_devfile_url_test.go b/tests/integration/devfile/cmd_devfile_url_test.go index e752d83ff0e..3b71e5bce87 100644 --- a/tests/integration/devfile/cmd_devfile_url_test.go +++ b/tests/integration/devfile/cmd_devfile_url_test.go @@ -326,6 +326,10 @@ var _ = Describe("odo devfile url command tests", func() { helper.CmdShouldPass("odo", "url", "create", url1, "--port", "3000") + fileOutput, err := helper.ReadFile(filepath.Join(commonVar.Context, "devfile.yaml")) + Expect(err).To(BeNil()) + helper.MatchAllInOutput(fileOutput, []string{"3000-tcp", "3000"}) + helper.CmdShouldPass("odo", "push", "--project", commonVar.Project) pushStdOut := helper.CmdShouldPass("odo", "push", "--project", commonVar.Project) helper.DontMatchAllInOutput(pushStdOut, []string{"successfully deleted", "created"}) @@ -374,4 +378,35 @@ var _ = Describe("odo devfile url command tests", func() { Expect(output).Should(ContainSubstring(url1)) }) }) + + Context("Testing URLs for Kubernetes specific scenarios", func() { + JustBeforeEach(func() { + if os.Getenv("KUBERNETES") != "true" { + Skip("This is a Kubernetes specific scenario, skipping") + } + }) + + It("should use an existing URL when there are URLs with no host defined in the env file with same port", func() { + url1 := helper.RandString(5) + + helper.CmdShouldPass("odo", "create", "nodejs", "--project", commonVar.Project, componentName) + + 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")) + + helper.CmdShouldPass("odo", "url", "create", "--host", "com", "--port", "3000") + fileOutput, err := helper.ReadFile(filepath.Join(commonVar.Context, "devfile.yaml")) + Expect(err).To(BeNil()) + helper.MatchAllInOutput(fileOutput, []string{"3000-tcp", "3000"}) + count := strings.Count(fileOutput, "targetPort") + Expect(count).To(Equal(1)) + + helper.CmdShouldPass("odo", "url", "create", url1, "--host", "com", "--port", "8080") + fileOutput, err = helper.ReadFile(filepath.Join(commonVar.Context, "devfile.yaml")) + Expect(err).To(BeNil()) + helper.MatchAllInOutput(fileOutput, []string{url1, "8080"}) + count = strings.Count(fileOutput, "targetPort") + Expect(count).To(Equal(2)) + }) + }) })