Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

odo describe component #5725

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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