Skip to content

Commit

Permalink
odo describe component (#5725)
Browse files Browse the repository at this point in the history
* odo describe component

* More fields on named describe

* Doc

* Update pkg/odo/cli/describe/component.go

Co-authored-by: Parthvi Vala <pvala@redhat.com>

* Update pkg/odo/cli/describe/component.go

Co-authored-by: Parthvi Vala <pvala@redhat.com>

* Update pkg/odo/cli/describe/component.go

Co-authored-by: Parthvi Vala <pvala@redhat.com>

* Add Describef

* Parthvi review

* Fix rebase

Co-authored-by: Parthvi Vala <pvala@redhat.com>
  • Loading branch information
feloy and valaparthvi authored May 12, 2022
1 parent 312faae commit cb1546a
Show file tree
Hide file tree
Showing 10 changed files with 355 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 <component_name> [--namespace <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.
15 changes: 14 additions & 1 deletion pkg/api/component.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package api

import "strings"

type RunningMode string
type RunningModeList []RunningMode

Expand All @@ -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)
}
Expand All @@ -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"`
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/api/devfile-data.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pkg/api/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
74 changes: 65 additions & 9 deletions pkg/component/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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'
Expand Down Expand Up @@ -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 {
Expand All @@ -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 != "" {
Expand All @@ -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
}
35 changes: 17 additions & 18 deletions pkg/component/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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{},
},
Expand All @@ -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},
},
Expand All @@ -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},
},
Expand All @@ -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},
},
Expand All @@ -266,19 +265,19 @@ 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},
},
}
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
Expand Down
10 changes: 10 additions & 0 deletions pkg/log/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit cb1546a

Please sign in to comment.