diff --git a/docs/website/versioned_docs/version-3.0.0/command-reference/describe.md b/docs/website/versioned_docs/version-3.0.0/command-reference/describe.md index a545cbbcb2a..d84e563cdac 100644 --- a/docs/website/versioned_docs/version-3.0.0/command-reference/describe.md +++ b/docs/website/versioned_docs/version-3.0.0/command-reference/describe.md @@ -8,15 +8,25 @@ sidebar_position: 3 There are 2 ways to describe a component: - [Describe with access to Devfile](#describe-with-access-to-devfile) - [Describe without access to Devfile](#describe-without-access-to-devfile) -- [Available Flags](#available-flags) ## Describe with access to Devfile ```shell odo describe component ``` +This command returns information extracted from the Devfile: +- metadata (name, display name, project type, language, version, description and tags) +- supported odo features, indicating if the Devfile defines necessary information to run `odo dev`, `odo dev --debug` and `odo deploy` +- the list of container components, +- the list of Kubernetes components. + +The command also displays if the component is currently running in the cluster on Dev and/or Deploy mode. + ## Describe without access to Devfile ```shell odo describe component --name [--namespace ] ``` +The command extracts information from the labels and annotations attached to the deployed component to display the known metadata of the Devfile used to deploy the component. + +The command also displays if the component is currently running in the cluster on Dev and/or Deploy mode. diff --git a/pkg/api/component.go b/pkg/api/component.go index 914a33a9120..b834ed19bb2 100644 --- a/pkg/api/component.go +++ b/pkg/api/component.go @@ -1,5 +1,7 @@ package api +import "strings" + type RunningMode string type RunningModeList []RunningMode @@ -9,6 +11,17 @@ const ( RunningModeUnknown RunningMode = "Unknown" ) +func (o RunningModeList) String() string { + if len(o) == 0 { + return "None" + } + strs := make([]string, 0, len(o)) + for _, s := range o { + strs = append(strs, string(s)) + } + return strings.Join(strs, ", ") +} + func (u RunningModeList) Len() int { return len(u) } @@ -25,7 +38,7 @@ type Component struct { DevfilePath string `json:"devfilePath,omitempty"` DevfileData *DevfileData `json:"devfileData,omitempty"` DevForwardedPorts []ForwardedPort `json:"devForwardedPorts,omitempty"` - RunningIn []RunningMode `json:"runningIn"` + RunningIn RunningModeList `json:"runningIn"` ManagedBy string `json:"managedBy"` } diff --git a/pkg/api/devfile-data.go b/pkg/api/devfile-data.go index f08b6352a91..0c7b3dff141 100644 --- a/pkg/api/devfile-data.go +++ b/pkg/api/devfile-data.go @@ -4,8 +4,8 @@ import "github.com/devfile/library/pkg/devfile/parser/data" // DevfileData describes a devfile content type DevfileData struct { - Devfile data.DevfileData `json:"devfile"` - SupportedOdoFeatures SupportedOdoFeatures `json:"supportedOdoFeatures"` + Devfile data.DevfileData `json:"devfile"` + SupportedOdoFeatures *SupportedOdoFeatures `json:"supportedOdoFeatures,omitempty"` } // SupportedOdoFeatures indicates the support of high-level (odo) features by a devfile component diff --git a/pkg/api/utils.go b/pkg/api/utils.go index 8964125af66..a6bb228fdf3 100644 --- a/pkg/api/utils.go +++ b/pkg/api/utils.go @@ -13,8 +13,8 @@ func GetDevfileData(devfileObj parser.DevfileObj) *DevfileData { } } -func getSupportedOdoFeatures(devfileData data.DevfileData) SupportedOdoFeatures { - return SupportedOdoFeatures{ +func getSupportedOdoFeatures(devfileData data.DevfileData) *SupportedOdoFeatures { + return &SupportedOdoFeatures{ Dev: libdevfile.HasRunCommand(devfileData), Deploy: libdevfile.HasDeployCommand(devfileData), Debug: libdevfile.HasDebugCommand(devfileData), diff --git a/pkg/component/component.go b/pkg/component/component.go index 66cb51be6f7..aea5b74141d 100644 --- a/pkg/component/component.go +++ b/pkg/component/component.go @@ -10,6 +10,7 @@ import ( "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "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" "github.com/redhat-developer/odo/pkg/api" @@ -22,7 +23,10 @@ import ( "k8s.io/klog" ) -const NotAvailable = "Not available" +const ( + NotAvailable = "Not available" + UnknownValue = "Unknown" +) // GetComponentTypeFromDevfileMetadata returns component type from the devfile metadata; // it could either be projectType or language, if neither of them are set, return 'Not available' @@ -211,14 +215,11 @@ func ListAllClusterComponents(client kclient.ClientInterface, namespace string) return components, nil } -// GetRunningModes returns the list of modes on which a "name" component is deployed, by looking into namespace -// the resources deployed with matching labels, based on the "odo.dev/mode" label -func GetRunningModes(client kclient.ClientInterface, name string, namespace string) ([]api.RunningMode, error) { - mapResult := map[string]bool{} +func getResourcesForComponent(client kclient.ClientInterface, name string, namespace string) ([]unstructured.Unstructured, error) { selector := labels.GetSelector(name, "app", labels.ComponentAnyMode) resourceList, err := client.GetAllResourcesFromSelector(selector, namespace) if err != nil { - return []api.RunningMode{api.RunningModeUnknown}, nil + return nil, err } filteredList := []unstructured.Unstructured{} for _, resource := range resourceList { @@ -228,12 +229,23 @@ func GetRunningModes(client kclient.ClientInterface, name string, namespace stri } filteredList = append(filteredList, resource) } + return filteredList, nil +} + +// GetRunningModes returns the list of modes on which a "name" component is deployed, by looking into namespace +// the resources deployed with matching labels, based on the "odo.dev/mode" label +func GetRunningModes(client kclient.ClientInterface, name string) ([]api.RunningMode, error) { + list, err := getResourcesForComponent(client, name, client.GetCurrentNamespace()) + if err != nil { + return []api.RunningMode{api.RunningModeUnknown}, nil + } - if len(filteredList) == 0 { - return nil, NewNoComponentFoundError(name, namespace) + if len(list) == 0 { + return nil, NewNoComponentFoundError(name, client.GetCurrentNamespace()) } - for _, resource := range filteredList { + mapResult := map[string]bool{} + for _, resource := range list { resourceLabels := resource.GetLabels() mode := labels.GetMode(resourceLabels) if mode != "" { @@ -258,3 +270,47 @@ func Contains(component OdoComponent, components []OdoComponent) bool { } return false } + +// GetDevfileInfoFromCluster extracts information from the labels and annotations of resources to rebuild a Devfile +func GetDevfileInfoFromCluster(client kclient.ClientInterface, name string) (parser.DevfileObj, error) { + list, err := getResourcesForComponent(client, name, client.GetCurrentNamespace()) + if err != nil { + return parser.DevfileObj{}, nil + } + + if len(list) == 0 { + return parser.DevfileObj{}, nil + } + + devfileData, err := data.NewDevfileData(string(data.APISchemaVersion200)) + if err != nil { + return parser.DevfileObj{}, err + } + metadata := devfileData.GetMetadata() + metadata.Name = UnknownValue + metadata.DisplayName = UnknownValue + metadata.ProjectType = UnknownValue + metadata.Language = UnknownValue + metadata.Version = UnknownValue + metadata.Description = UnknownValue + + for _, resource := range list { + labels := resource.GetLabels() + annotations := resource.GetAnnotations() + name := odolabels.GetComponentName(labels) + if len(name) > 0 && metadata.Name == UnknownValue { + metadata.Name = name + } + typ, err := odolabels.GetProjectType(labels, annotations) + if err != nil { + continue + } + if len(typ) > 0 && metadata.ProjectType == UnknownValue { + metadata.ProjectType = typ + } + } + devfileData.SetMetadata(metadata) + return parser.DevfileObj{ + Data: devfileData, + }, nil +} diff --git a/pkg/component/component_test.go b/pkg/component/component_test.go index 7539889821f..eec15ee504d 100644 --- a/pkg/component/component_test.go +++ b/pkg/component/component_test.go @@ -171,9 +171,8 @@ func TestGetRunningModes(t *testing.T) { packageManifestResource.SetLabels(labels.Builder().WithMode(labels.ComponentDevMode).Labels()) type args struct { - client func(ctrl *gomock.Controller) kclient.ClientInterface - name string - namespace string + client func(ctrl *gomock.Controller) kclient.ClientInterface + name string } tests := []struct { name string @@ -186,11 +185,11 @@ func TestGetRunningModes(t *testing.T) { args: args{ client: func(ctrl *gomock.Controller) kclient.ClientInterface { c := kclient.NewMockClientInterface(ctrl) + c.EXPECT().GetCurrentNamespace().Return("a-namespace").AnyTimes() c.EXPECT().GetAllResourcesFromSelector(gomock.Any(), gomock.Any()).Return([]unstructured.Unstructured{}, nil) return c }, - name: "aname", - namespace: "anamespace", + name: "aname", }, want: nil, wantErr: true, @@ -200,11 +199,11 @@ func TestGetRunningModes(t *testing.T) { args: args{ client: func(ctrl *gomock.Controller) kclient.ClientInterface { c := kclient.NewMockClientInterface(ctrl) + c.EXPECT().GetCurrentNamespace().Return("a-namespace").AnyTimes() c.EXPECT().GetAllResourcesFromSelector(gomock.Any(), gomock.Any()).Return([]unstructured.Unstructured{packageManifestResource}, nil) return c }, - name: "aname", - namespace: "anamespace", + name: "aname", }, want: nil, wantErr: true, @@ -214,11 +213,11 @@ func TestGetRunningModes(t *testing.T) { args: args{ client: func(ctrl *gomock.Controller) kclient.ClientInterface { c := kclient.NewMockClientInterface(ctrl) + c.EXPECT().GetCurrentNamespace().Return("a-namespace").AnyTimes() c.EXPECT().GetAllResourcesFromSelector(gomock.Any(), gomock.Any()).Return([]unstructured.Unstructured{packageManifestResource, otherResource}, nil) return c }, - name: "aname", - namespace: "anamespace", + name: "aname", }, want: []api.RunningMode{}, }, @@ -227,11 +226,11 @@ func TestGetRunningModes(t *testing.T) { args: args{ client: func(ctrl *gomock.Controller) kclient.ClientInterface { c := kclient.NewMockClientInterface(ctrl) + c.EXPECT().GetCurrentNamespace().Return("a-namespace").AnyTimes() c.EXPECT().GetAllResourcesFromSelector(gomock.Any(), gomock.Any()).Return([]unstructured.Unstructured{packageManifestResource, otherResource, resourceDev1, resourceDev2}, nil) return c }, - name: "aname", - namespace: "anamespace", + name: "aname", }, want: []api.RunningMode{api.RunningModeDev}, }, @@ -240,11 +239,11 @@ func TestGetRunningModes(t *testing.T) { args: args{ client: func(ctrl *gomock.Controller) kclient.ClientInterface { c := kclient.NewMockClientInterface(ctrl) + c.EXPECT().GetCurrentNamespace().Return("a-namespace").AnyTimes() c.EXPECT().GetAllResourcesFromSelector(gomock.Any(), gomock.Any()).Return([]unstructured.Unstructured{packageManifestResource, otherResource, resourceDeploy1, resourceDeploy2}, nil) return c }, - name: "aname", - namespace: "anamespace", + name: "aname", }, want: []api.RunningMode{api.RunningModeDeploy}, }, @@ -253,11 +252,11 @@ func TestGetRunningModes(t *testing.T) { args: args{ client: func(ctrl *gomock.Controller) kclient.ClientInterface { c := kclient.NewMockClientInterface(ctrl) + c.EXPECT().GetCurrentNamespace().Return("a-namespace").AnyTimes() c.EXPECT().GetAllResourcesFromSelector(gomock.Any(), gomock.Any()).Return([]unstructured.Unstructured{packageManifestResource, otherResource, resourceDev1, resourceDev2, resourceDeploy1, resourceDeploy2}, nil) return c }, - name: "aname", - namespace: "anamespace", + name: "aname", }, want: []api.RunningMode{api.RunningModeDev, api.RunningModeDeploy}, }, @@ -266,11 +265,11 @@ func TestGetRunningModes(t *testing.T) { args: args{ client: func(ctrl *gomock.Controller) kclient.ClientInterface { c := kclient.NewMockClientInterface(ctrl) + c.EXPECT().GetCurrentNamespace().Return("a-namespace").AnyTimes() c.EXPECT().GetAllResourcesFromSelector(gomock.Any(), gomock.Any()).Return(nil, errors.New("error")) return c }, - name: "aname", - namespace: "anamespace", + name: "aname", }, want: []api.RunningMode{api.RunningModeUnknown}, }, @@ -278,7 +277,7 @@ func TestGetRunningModes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) - got, err := GetRunningModes(tt.args.client(ctrl), tt.args.name, tt.args.namespace) + got, err := GetRunningModes(tt.args.client(ctrl), tt.args.name) if (err != nil) != tt.wantErr { t.Errorf("error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/log/status.go b/pkg/log/status.go index a36f707b970..a72de3613f2 100644 --- a/pkg/log/status.go +++ b/pkg/log/status.go @@ -386,6 +386,16 @@ func Sbold(s string) string { return bold(fmt.Sprint(s)) } +// Describef will print out the first variable as BOLD and then the second not.. +// this is intended to be used with `odo describe` and other outputs that list +// a lot of information +func Describef(title string, format string, a ...interface{}) { + if !IsJSON() { + bold := color.New(color.Bold).SprintFunc() + fmt.Fprintf(GetStdout(), "%s%s\n", bold(title), fmt.Sprintf(format, a...)) + } +} + // Spinner creates a spinner, sets the prefix then returns it. // Remember to use .End(bool) to stop the spin / when you're done. // For example: defer s.End(false) diff --git a/pkg/odo/cli/describe/component.go b/pkg/odo/cli/describe/component.go index 05ff92b4029..e7918ac1a4d 100644 --- a/pkg/odo/cli/describe/component.go +++ b/pkg/odo/cli/describe/component.go @@ -5,12 +5,17 @@ import ( "errors" "fmt" "path/filepath" + "strings" + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/library/pkg/devfile/parser" + "github.com/devfile/library/pkg/devfile/parser/data/v2/common" "github.com/spf13/cobra" ktemplates "k8s.io/kubectl/pkg/util/templates" "github.com/redhat-developer/odo/pkg/api" "github.com/redhat-developer/odo/pkg/component" + "github.com/redhat-developer/odo/pkg/log" "github.com/redhat-developer/odo/pkg/machineoutput" "github.com/redhat-developer/odo/pkg/odo/cmdline" "github.com/redhat-developer/odo/pkg/odo/genericclioptions" @@ -81,20 +86,20 @@ func (o *ComponentOptions) Validate() (err error) { } func (o *ComponentOptions) Run(ctx context.Context) error { - result, err := o.run(ctx) + result, devfileObj, err := o.run(ctx) if err != nil { return err } - printHumanReadableOutput(result) - return nil + return printHumanReadableOutput(result, devfileObj) } // Run contains the logic for the odo command func (o *ComponentOptions) RunForJsonOutput(ctx context.Context) (out interface{}, err error) { - return o.run(ctx) + result, _, err := o.run(ctx) + return result, err } -func (o *ComponentOptions) run(ctx context.Context) (result api.Component, err error) { +func (o *ComponentOptions) run(ctx context.Context) (result api.Component, devfileObj *parser.DevfileObj, err error) { if o.nameFlag != "" { return o.describeNamedComponent(o.nameFlag) } @@ -102,35 +107,42 @@ func (o *ComponentOptions) run(ctx context.Context) (result api.Component, err e } // describeNamedComponent describes a component given its name -func (o *ComponentOptions) describeNamedComponent(name string) (result api.Component, err error) { - runningIn, err := component.GetRunningModes(o.clientset.KubernetesClient, name, o.clientset.KubernetesClient.GetCurrentNamespace()) +func (o *ComponentOptions) describeNamedComponent(name string) (result api.Component, devfileObj *parser.DevfileObj, err error) { + runningIn, err := component.GetRunningModes(o.clientset.KubernetesClient, name) + if err != nil { + return api.Component{}, nil, err + } + devfile, err := component.GetDevfileInfoFromCluster(o.clientset.KubernetesClient, name) if err != nil { - return api.Component{}, err + return api.Component{}, nil, err } return api.Component{ + DevfileData: &api.DevfileData{ + Devfile: devfile.Data, + }, RunningIn: runningIn, ManagedBy: "odo", - }, nil + }, &devfile, nil } // describeDevfileComponent describes the component defined by the devfile in the current directory -func (o *ComponentOptions) describeDevfileComponent() (result api.Component, err error) { +func (o *ComponentOptions) describeDevfileComponent() (result api.Component, devfile *parser.DevfileObj, err error) { devfileObj := o.EnvSpecificInfo.GetDevfileObj() path, err := filepath.Abs(".") if err != nil { - return api.Component{}, err + return api.Component{}, nil, err } forwardedPorts, err := o.clientset.StateClient.GetForwardedPorts() if err != nil { - return api.Component{}, err + return api.Component{}, nil, err } - runningIn, err := component.GetRunningModes(o.clientset.KubernetesClient, devfileObj.GetMetadataName(), o.clientset.KubernetesClient.GetCurrentNamespace()) + runningIn, err := component.GetRunningModes(o.clientset.KubernetesClient, devfileObj.GetMetadataName()) if err != nil { if !errors.As(err, &component.NoComponentFoundError{}) { - return api.Component{}, err + return api.Component{}, nil, err } else { // it is ok if the component is not deployed - forwardedPorts = nil + runningIn = nil } } return api.Component{ @@ -139,11 +151,76 @@ func (o *ComponentOptions) describeDevfileComponent() (result api.Component, err DevForwardedPorts: forwardedPorts, RunningIn: runningIn, ManagedBy: "odo", - }, nil + }, &devfileObj, nil +} + +func printHumanReadableOutput(cmp api.Component, devfileObj *parser.DevfileObj) error { + if cmp.DevfileData != nil { + log.Describef("Name: ", cmp.DevfileData.Devfile.GetMetadata().Name) + log.Describef("Display Name: ", cmp.DevfileData.Devfile.GetMetadata().DisplayName) + log.Describef("Project Type: ", cmp.DevfileData.Devfile.GetMetadata().ProjectType) + log.Describef("Language: ", cmp.DevfileData.Devfile.GetMetadata().Language) + log.Describef("Version: ", cmp.DevfileData.Devfile.GetMetadata().Version) + log.Describef("Description: ", cmp.DevfileData.Devfile.GetMetadata().Description) + log.Describef("Tags: ", strings.Join(cmp.DevfileData.Devfile.GetMetadata().Tags, ", ")) + fmt.Println() + } + + log.Describef("Running in: ", cmp.RunningIn.String()) + fmt.Println() + + if len(cmp.DevForwardedPorts) > 0 { + log.Info("Forwarded ports:") + for _, port := range cmp.DevForwardedPorts { + log.Printf("%s:%d -> %s:%d", port.LocalAddress, port.LocalPort, port.ContainerName, port.ContainerPort) + } + fmt.Println() + } + + log.Info("Supported odo features:") + if cmp.DevfileData != nil && cmp.DevfileData.SupportedOdoFeatures != nil { + log.Printf("Dev: %v", cmp.DevfileData.SupportedOdoFeatures.Dev) + log.Printf("Deploy: %v", cmp.DevfileData.SupportedOdoFeatures.Deploy) + log.Printf("Debug: %v", cmp.DevfileData.SupportedOdoFeatures.Debug) + } else { + log.Printf("Dev: Unknown") + log.Printf("Deploy: Unknown") + log.Printf("Debug: Unknown") + } + fmt.Println() + + err := listComponentsNames("Container components:", devfileObj, v1alpha2.ContainerComponentType) + if err != nil { + return err + } + + err = listComponentsNames("Kubernetes components:", devfileObj, v1alpha2.KubernetesComponentType) + if err != nil { + return err + } + return nil } -func printHumanReadableOutput(component api.Component) { - // TODO(feloy) #5661 +func listComponentsNames(title string, devfileObj *parser.DevfileObj, typ v1alpha2.ComponentType) error { + if devfileObj == nil { + log.Describef(title, " Unknown") + return nil + } + containers, err := devfileObj.Data.GetComponents(common.DevfileOptions{ + ComponentOptions: common.ComponentOptions{ComponentType: typ}, + }) + if err != nil { + return err + } + if len(containers) == 0 { + return nil + } + log.Info(title) + for _, container := range containers { + log.Printf("%s", container.Name) + } + fmt.Println() + return nil } // NewCmdComponent implements the component odo sub-command diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index dca67fe313d..25ec070cae6 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -165,7 +165,7 @@ func (o RegistryClient) ListDevfileStacks(registryName, devfileFlag, filterFlag if err != nil { return *catalogDevfileList, err } - devfile.SupportedOdoFeatures = devfileData.SupportedOdoFeatures + devfile.SupportedOdoFeatures = *devfileData.SupportedOdoFeatures } devfiles = append(devfiles, devfile) diff --git a/tests/integration/devfile/cmd_describe_test.go b/tests/integration/devfile/cmd_describe_test.go index 5290f29d10c..46ecb3fddba 100644 --- a/tests/integration/devfile/cmd_describe_test.go +++ b/tests/integration/devfile/cmd_describe_test.go @@ -27,7 +27,7 @@ var _ = Describe("odo describe command tests", func() { }) It("should fail", func() { - By("running odo describe component with namespace flag without name flag", func() { + By("running odo describe component -o json with namespace flag without name flag", func() { res := helper.Cmd("odo", "describe", "component", "--namespace", "default", "-o", "json").ShouldFail() stdout, stderr := res.Out(), res.Err() Expect(helper.IsJSON(stderr)).To(BeTrue()) @@ -35,7 +35,7 @@ var _ = Describe("odo describe command tests", func() { helper.JsonPathContentContain(stderr, "message", "--namespace can be used only with --name") }) - By("running odo describe component without name and without devfile in the current directory", func() { + By("running odo describe component -o json without name and without devfile in the current directory", func() { res := helper.Cmd("odo", "describe", "component", "-o", "json").ShouldFail() stdout, stderr := res.Out(), res.Err() Expect(helper.IsJSON(stderr)).To(BeTrue()) @@ -43,13 +43,34 @@ var _ = Describe("odo describe command tests", func() { helper.JsonPathContentContain(stderr, "message", "no devfile found") }) - By("running odo describe component with an unknown name", func() { + By("running odo describe component -o json with an unknown name", func() { res := helper.Cmd("odo", "describe", "component", "--name", "unknown-name", "-o", "json").ShouldFail() stdout, stderr := res.Out(), res.Err() Expect(helper.IsJSON(stderr)).To(BeTrue()) Expect(stdout).To(BeEmpty()) helper.JsonPathContentContain(stderr, "message", "no component found with name \"unknown-name\" in the namespace \""+commonVar.Project+"\"") }) + + By("running odo describe component with namespace flag without name flag", func() { + res := helper.Cmd("odo", "describe", "component", "--namespace", "default").ShouldFail() + stdout, stderr := res.Out(), res.Err() + Expect(stdout).To(BeEmpty()) + Expect(stderr).To(ContainSubstring("--namespace can be used only with --name")) + }) + + By("running odo describe component without name and without devfile in the current directory", func() { + res := helper.Cmd("odo", "describe", "component").ShouldFail() + stdout, stderr := res.Out(), res.Err() + Expect(stdout).To(BeEmpty()) + Expect(stderr).To(ContainSubstring("no devfile found")) + }) + + By("running odo describe component with an unknown name", func() { + res := helper.Cmd("odo", "describe", "component", "--name", "unknown-name").ShouldFail() + stdout, stderr := res.Out(), res.Err() + Expect(stdout).To(BeEmpty()) + Expect(stderr).To(ContainSubstring("no component found with name \"unknown-name\" in the namespace \"" + commonVar.Project + "\"")) + }) }) When("creating a component", func() { @@ -57,7 +78,7 @@ var _ = Describe("odo describe command tests", func() { helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-registry.yaml")).ShouldPass() }) - checkDevfileDescription := func(jsonContent string, devfileName string) { + checkDevfileJSONDescription := func(jsonContent string, devfileName string) { helper.JsonPathContentIs(jsonContent, "devfilePath", filepath.Join(commonVar.Context, devfileName)) helper.JsonPathContentIs(jsonContent, "devfileData.devfile.metadata.name", cmpName) helper.JsonPathContentIs(jsonContent, "devfileData.supportedOdoFeatures.dev", "true") @@ -66,24 +87,55 @@ var _ = Describe("odo describe command tests", func() { helper.JsonPathContentIs(jsonContent, "managedBy", "odo") } + checkDevfileDescription := func(content string, withUnknown bool) { + Expect(content).To(ContainSubstring("Name: " + cmpName)) + Expect(content).To(ContainSubstring("Project Type: nodejs")) + if withUnknown { + for _, v := range []string{"Version", "Display Name", "Description", "Language"} { + Expect(content).To(ContainSubstring(v + ": Unknown")) + } + } + } + It("should describe the component in the current directory", func() { - res := helper.Cmd("odo", "describe", "component", "-o", "json").ShouldPass() - stdout, stderr := res.Out(), res.Err() - Expect(helper.IsJSON(stdout)).To(BeTrue()) - Expect(stderr).To(BeEmpty()) - checkDevfileDescription(stdout, "devfile.yaml") - helper.JsonPathContentIs(stdout, "runningIn", "") - helper.JsonPathContentIs(stdout, "devForwardedPorts", "") + By("running with json output", func() { + res := helper.Cmd("odo", "describe", "component", "-o", "json").ShouldPass() + stdout, stderr := res.Out(), res.Err() + Expect(helper.IsJSON(stdout)).To(BeTrue()) + Expect(stderr).To(BeEmpty()) + checkDevfileJSONDescription(stdout, "devfile.yaml") + helper.JsonPathContentIs(stdout, "runningIn", "") + helper.JsonPathContentIs(stdout, "devForwardedPorts", "") + }) + + By("running with default output", func() { + res := helper.Cmd("odo", "describe", "component").ShouldPass() + stdout := res.Out() + checkDevfileDescription(stdout, false) + Expect(stdout).To(ContainSubstring("Running in: None")) + Expect(stdout).ToNot(ContainSubstring("Forwarded ports")) + }) }) It("should not describe the component from another directory", func() { - err := os.Chdir("/") - Expect(err).NotTo(HaveOccurred()) - res := helper.Cmd("odo", "describe", "component", "--name", cmpName, "-o", "json").ShouldFail() - stdout, stderr := res.Out(), res.Err() - Expect(helper.IsJSON(stderr)).To(BeTrue()) - Expect(stdout).To(BeEmpty()) - helper.JsonPathContentContain(stderr, "message", "no component found with name \""+cmpName+"\" in the namespace \""+commonVar.Project+"\"") + By("running with json output", func() { + err := os.Chdir("/") + Expect(err).NotTo(HaveOccurred()) + res := helper.Cmd("odo", "describe", "component", "--name", cmpName, "-o", "json").ShouldFail() + stdout, stderr := res.Out(), res.Err() + Expect(helper.IsJSON(stderr)).To(BeTrue()) + Expect(stdout).To(BeEmpty()) + helper.JsonPathContentContain(stderr, "message", "no component found with name \""+cmpName+"\" in the namespace \""+commonVar.Project+"\"") + }) + + By("running with default output", func() { + err := os.Chdir("/") + Expect(err).NotTo(HaveOccurred()) + res := helper.Cmd("odo", "describe", "component", "--name", cmpName).ShouldFail() + stdout, stderr := res.Out(), res.Err() + Expect(stdout).To(BeEmpty()) + Expect(stderr).To(ContainSubstring("no component found with name \"" + cmpName + "\" in the namespace \"" + commonVar.Project + "\"")) + }) }) When("renaming to hide devfile.yaml file", func() { @@ -93,13 +145,23 @@ var _ = Describe("odo describe command tests", func() { }) It("should describe the component in the current directory using the hidden devfile", func() { - res := helper.Cmd("odo", "describe", "component", "-o", "json").ShouldPass() - stdout, stderr := res.Out(), res.Err() - Expect(helper.IsJSON(stdout)).To(BeTrue()) - Expect(stderr).To(BeEmpty()) - checkDevfileDescription(stdout, ".devfile.yaml") - helper.JsonPathContentIs(stdout, "runningIn", "") - helper.JsonPathContentIs(stdout, "devForwardedPorts", "") + By("running with json output", func() { + res := helper.Cmd("odo", "describe", "component", "-o", "json").ShouldPass() + stdout, stderr := res.Out(), res.Err() + Expect(helper.IsJSON(stdout)).To(BeTrue()) + Expect(stderr).To(BeEmpty()) + checkDevfileJSONDescription(stdout, ".devfile.yaml") + helper.JsonPathContentIs(stdout, "runningIn", "") + helper.JsonPathContentIs(stdout, "devForwardedPorts", "") + }) + + By("running with default output", func() { + res := helper.Cmd("odo", "describe", "component").ShouldPass() + stdout := res.Out() + checkDevfileDescription(stdout, false) + Expect(stdout).To(ContainSubstring("Running in: None")) + Expect(stdout).ToNot(ContainSubstring("Forwarded ports")) + }) }) }) @@ -119,31 +181,60 @@ var _ = Describe("odo describe command tests", func() { }) It("should describe the component in dev mode", func() { - res := helper.Cmd("odo", "describe", "component", "-o", "json").ShouldPass() - stdout, stderr := res.Out(), res.Err() - Expect(helper.IsJSON(stdout)).To(BeTrue()) - Expect(stderr).To(BeEmpty()) - checkDevfileDescription(stdout, "devfile.yaml") - helper.JsonPathContentIs(stdout, "devForwardedPorts.#", "1") - helper.JsonPathContentIs(stdout, "devForwardedPorts.0.containerName", "runtime") - helper.JsonPathContentIs(stdout, "devForwardedPorts.0.localAddress", "127.0.0.1") - helper.JsonPathContentIs(stdout, "devForwardedPorts.0.localPort", ports["3000"][len("127.0.0.1:"):]) - helper.JsonPathContentIs(stdout, "devForwardedPorts.0.containerPort", "3000") + By("running with json output", func() { + res := helper.Cmd("odo", "describe", "component", "-o", "json").ShouldPass() + stdout, stderr := res.Out(), res.Err() + Expect(helper.IsJSON(stdout)).To(BeTrue()) + Expect(stderr).To(BeEmpty()) + checkDevfileJSONDescription(stdout, "devfile.yaml") + helper.JsonPathContentIs(stdout, "devForwardedPorts.#", "1") + helper.JsonPathContentIs(stdout, "devForwardedPorts.0.containerName", "runtime") + helper.JsonPathContentIs(stdout, "devForwardedPorts.0.localAddress", "127.0.0.1") + helper.JsonPathContentIs(stdout, "devForwardedPorts.0.localPort", ports["3000"][len("127.0.0.1:"):]) + helper.JsonPathContentIs(stdout, "devForwardedPorts.0.containerPort", "3000") + }) + + By("running with default output", func() { + res := helper.Cmd("odo", "describe", "component").ShouldPass() + stdout := res.Out() + checkDevfileDescription(stdout, false) + Expect(stdout).To(ContainSubstring("Forwarded ports")) + Expect(stdout).To(ContainSubstring("127.0.0.1:" + ports["3000"][len("127.0.0.1:"):] + " -> runtime:3000")) + }) }) It("should describe the component from another directory", func() { - err := os.Chdir("/") - Expect(err).NotTo(HaveOccurred()) - res := helper.Cmd("odo", "describe", "component", "--name", cmpName, "-o", "json").ShouldPass() - stdout, stderr := res.Out(), res.Err() - Expect(helper.IsJSON(stdout)).To(BeTrue()) - Expect(stderr).To(BeEmpty()) - helper.JsonPathContentIs(stdout, "devfilePath", "") - helper.JsonPathContentIs(stdout, "devfileData", "") - helper.JsonPathContentIs(stdout, "devForwardedPorts", "") - helper.JsonPathContentIs(stdout, "runningIn.#", "1") - helper.JsonPathContentIs(stdout, "runningIn.0", "Dev") - helper.JsonPathContentIs(stdout, "devForwardedPorts", "") + By("running with json output", func() { + err := os.Chdir("/") + Expect(err).NotTo(HaveOccurred()) + res := helper.Cmd("odo", "describe", "component", "--name", cmpName, "-o", "json").ShouldPass() + stdout, stderr := res.Out(), res.Err() + Expect(helper.IsJSON(stdout)).To(BeTrue()) + Expect(stderr).To(BeEmpty()) + helper.JsonPathContentIs(stdout, "devfilePath", "") + helper.JsonPathContentIs(stdout, "devfileData.devfile.metadata.name", cmpName) + helper.JsonPathContentIs(stdout, "devfileData.devfile.metadata.projectType", "nodejs") + for _, v := range []string{"version", "displayName", "description", "language"} { + helper.JsonPathContentIs(stdout, "devfileData.devfile.metadata."+v, "Unknown") + } + helper.JsonPathContentIs(stdout, "devForwardedPorts", "") + helper.JsonPathContentIs(stdout, "runningIn.#", "1") + helper.JsonPathContentIs(stdout, "runningIn.0", "Dev") + helper.JsonPathContentIs(stdout, "devForwardedPorts", "") + }) + + By("running with default output", func() { + err := os.Chdir("/") + Expect(err).NotTo(HaveOccurred()) + res := helper.Cmd("odo", "describe", "component", "--name", cmpName).ShouldPass() + stdout := res.Out() + checkDevfileDescription(stdout, true) + Expect(stdout).ToNot(ContainSubstring("Forwarded ports")) + Expect(stdout).To(ContainSubstring("Running in: Dev")) + Expect(stdout).To(ContainSubstring("Dev: Unknown")) + Expect(stdout).To(ContainSubstring("Deploy: Unknown")) + Expect(stdout).To(ContainSubstring("Debug: Unknown")) + }) }) })