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

Implement "odo list" #6043

Merged
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
5 changes: 5 additions & 0 deletions docs/website/docs/command-reference/list-binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ The name of the service binding is prefixed with `*` when the service binding is

To get more information about a specific service binding, you can run the command `odo describe binding --name <name>` (see [`odo describe binding` command reference](./describe-binding.md)).

## Available flags

* `--namespace` - Namespace to list the bindings from (optional). By default, the current namespace defined in kubeconfig is used
* `-o json` - Outputs the list in JSON format. See [JSON output](json-output.md) for more information

## Running the Command

To list all the service bindings, you can run `odo list binding`:
Expand Down
35 changes: 35 additions & 0 deletions docs/website/docs/command-reference/list-component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: odo list component
---

`odo list component` command is useful for getting information about components running on a specific namespace.

If the command is executed from a directory containing a Devfile, it also displays the component
defined in the Devfile as part of the list, prefixed with a star(*).

For each component, the command displays:
- its name,
- its project type,
- on which mode it is running (None, Dev, Deploy, or both), note that None is only applicable to the component
defined in the local Devfile,
- by which application the component has been deployed.

## Available flags

* `--namespace` - Namespace to list the components from (optional). By default, the current namespace defined in kubeconfig is used
* `-o json` - Outputs the list in JSON format. See [JSON output](json-output.md) for more information

:::tip use of cache

`odo list component` makes use of cache for performance reasons. This is the same cache that is referred by `kubectl` command
when you do `kubectl api-resources --cached=true`. As a result, if you were to install an Operator/CRD on the
Kubernetes cluster, and create a resource from it using odo, you might not see it in the `odo list component` output. This
would be the case for 10 minutes timeframe for which the cache is considered valid. Beyond this 10 minutes, the
cache is updated anyway.

If you would like to invalidate the cache before the 10 minutes timeframe, you could manually delete it by doing:
```shell
rm -rf ~/.kube/cache/discovery/api.crc.testing_6443/
```
Above example shows how to invalidate the cache for a CRC cluster. Note that you will have to modify the `api.crc.
testing_6443` part based on the cluster you are working against.
29 changes: 2 additions & 27 deletions docs/website/docs/command-reference/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,9 @@
title: odo list
---

`odo list` command is useful for getting information about components running on a specific namespace.

If the command is executed from a directory containing a Devfile, it also displays the command
defined in the Devfile as part of the list, prefixed with a star(*).

For each component, the command displays:
- its name,
- its project type,
- on which mode it is running (None, Dev, Deploy, or both), not that None is only applicable to the component
defined in the local Devfile,
- by which application the component has been deployed.
`odo list` command combines the `odo list binding` and `odo list component` commands.

## Available flags

* `--namespace` - Namespace to list the components from (optional). By default, the current namespace defined in kubeconfig is used
* `--namespace` - Namespace to list the resources from (optional). By default, the current namespace defined in kubeconfig is used
* `-o json` - Outputs the list in JSON format. See [JSON output](json-output.md) for more information

:::tip use of cache

`odo list` makes use of cache for performance reasons. This is the same cache that is referred by `kubectl` command
when you do `kubectl api-resources --cached=true`. As a result, if you were to install an Operator/CRD on the
Kubernetes cluster, and create a resource from it using odo, you might not see it in the `odo list` output. This
would be the case for 10 minutes timeframe for which the cache is considered valid. Beyond this 10 minutes, the
cache is updated anyway.

If you would like to invalidate the cache before the 10 minutes timeframe, you could manually delete it by doing:
```shell
rm -rf ~/.kube/cache/discovery/api.crc.testing_6443/
```
Above example shows how to invalidate the cache for a CRC cluster. Note that you will have to modify the `api.crc.
testing_6443` part based on the cluster you are working against.
26 changes: 26 additions & 0 deletions pkg/component/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,32 @@ func ListAllClusterComponents(client kclient.ClientInterface, namespace string)
return components, nil
}

func ListAllComponents(client kclient.ClientInterface, namespace string, devObj parser.DevfileObj) ([]api.ComponentAbstract, string, error) {
devfileComponents, err := ListAllClusterComponents(client, namespace)
if err != nil {
return nil, "", err
}

var localComponent api.ComponentAbstract
if devObj.Data != nil {
localComponent = api.ComponentAbstract{
Name: devObj.Data.GetMetadata().Name,
ManagedBy: "",
RunningIn: []api.RunningMode{},
Type: GetComponentTypeFromDevfileMetadata(devObj.Data.GetMetadata()),
}
}

componentInDevfile := ""
if localComponent.Name != "" {
if !Contains(localComponent, devfileComponents) {
devfileComponents = append(devfileComponents, localComponent)
}
componentInDevfile = localComponent.Name
}
Comment on lines +204 to +220
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to print the local component/binding if --namespace is passed? I find it a little confusing to display the binding/component running in some other namespace to be displayed when I explicitly ask for some other namespace.

➜  102 odo list binding --namespace operators
 ✓  Listing ServiceBindings from the namespace "operators" [3ms]
 NAME                            APPLICATION                     SERVICES                                                 RUNNING IN 
 * my-nodejs-app-cluster-sample  my-nodejs-app-app (Deployment)  cluster-sample (Cluster.postgresql.k8s.enterprisedb.io)  None       
➜  102 odo list binding                      
 ✓  Listing ServiceBindings from the namespace "default" [9ms]
 NAME                            APPLICATION                     SERVICES                                                 RUNNING IN 
 * my-nodejs-app-cluster-sample  my-nodejs-app-app (Deployment)  cluster-sample (Cluster.postgresql.k8s.enterprisedb.io)  None       

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kadel what's your opinion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we decide to, I would prefer to make this change as part as another PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it seems a bit confusing to display the local component/binding when requesting some other namespace (especially since the first line printed is Listing {ServiceBindings,resources} from the namespace "$ns").
But as discussed, it is okay to change this in a separate PR.

return devfileComponents, componentInDevfile, nil
}

func getResourcesForComponent(client kclient.ClientInterface, name string, namespace string) ([]unstructured.Unstructured, error) {
selector := labels.GetSelector(name, "app", labels.ComponentAnyMode, false)
resourceList, err := client.GetAllResourcesFromSelector(selector, namespace)
Expand Down
16 changes: 14 additions & 2 deletions pkg/odo/cli/list/binding/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ type BindingListOptions struct {

// working directory
contextDir string

// Local variables
namespaceFilter string

// Flags
namespaceFlag string
}

var _ genericclioptions.Runnable = (*BindingListOptions)(nil)
Expand Down Expand Up @@ -69,8 +75,13 @@ func (o *BindingListOptions) Complete(cmdline cmdline.Cmdline, args []string) (e
return err
}

// this ensures that the current namespace is used
o.clientset.KubernetesClient.SetNamespace(o.GetProject())
if o.namespaceFlag != "" {
o.namespaceFilter = o.namespaceFlag
} else {
o.namespaceFilter = o.GetProject()
}

o.clientset.KubernetesClient.SetNamespace(o.namespaceFilter)
return nil
}

Expand Down Expand Up @@ -125,6 +136,7 @@ func NewCmdBindingList(name, fullName string) *cobra.Command {
},
}
clientset.Add(bindingListCmd, clientset.KUBERNETES, clientset.BINDING)
bindingListCmd.Flags().StringVar(&o.namespaceFlag, "namespace", "", "Namespace for odo to scan for bindings")
machineoutput.UsedByCommand(bindingListCmd)
return bindingListCmd
}
Expand Down
212 changes: 212 additions & 0 deletions pkg/odo/cli/list/component/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package component

import (
"context"
"errors"
"fmt"

"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/spf13/cobra"

"github.com/redhat-developer/odo/pkg/api"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/odo/cli/ui"

dfutil "github.com/devfile/library/pkg/util"

"github.com/redhat-developer/odo/pkg/component"

"github.com/redhat-developer/odo/pkg/log"
"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/redhat-developer/odo/pkg/odo/util/completion"

ktemplates "k8s.io/kubectl/pkg/util/templates"
)

// RecommendedCommandName is the recommended list name
const RecommendedCommandName = "component"

var listExample = ktemplates.Examples(` # List all components in the application
%[1]s
`)

// ListOptions ...
type ListOptions struct {
// Context
*genericclioptions.Context

// Clients
clientset *clientset.Clientset

// Local variables
namespaceFilter string

// Flags
namespaceFlag string
}

var _ genericclioptions.Runnable = (*ListOptions)(nil)
var _ genericclioptions.JsonOutputter = (*ListOptions)(nil)

// NewListOptions ...
func NewListOptions() *ListOptions {
return &ListOptions{}
}

func (o *ListOptions) SetClientset(clientset *clientset.Clientset) {
o.clientset = clientset
}

// Complete ...
func (lo *ListOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {

// Check to see if KUBECONFIG exists, and if not, error the user that we would not be able to get cluster information
// Do this before anything else, or else we will just error out with the:
// invalid configuration: no configuration has been provided, try setting KUBERNETES_MASTER environment variable
// instead
if !dfutil.CheckKubeConfigExist() {
return errors.New("KUBECONFIG not found. Unable to retrieve cluster information. Please set your Kubernetes configuration via KUBECONFIG env variable or ~/.kube/config")
}

// Create the local context and initial Kubernetes client configuration
lo.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(""))
// The command must work without Devfile
if err != nil && !genericclioptions.IsNoDevfileError(err) {
return err
}

// If the namespace flag has been passed, we will search there.
// if it hasn't, we will search from the default project / namespace.
if lo.namespaceFlag != "" {
lo.namespaceFilter = lo.namespaceFlag
} else {
lo.namespaceFilter = lo.GetProject()
}

return nil
}

// Validate ...
func (lo *ListOptions) Validate() (err error) {
return nil
}

// Run has the logic to perform the required actions as part of command
func (lo *ListOptions) Run(ctx context.Context) error {
listSpinner := log.Spinnerf("Listing components from namespace '%s'", lo.namespaceFilter)
defer listSpinner.End(false)

list, err := lo.run(ctx)
if err != nil {
return err
}

listSpinner.End(true)

HumanReadableOutput(list)
return nil
}

// Run contains the logic for the odo command
func (lo *ListOptions) RunForJsonOutput(ctx context.Context) (out interface{}, err error) {
return lo.run(ctx)
}

func (lo *ListOptions) run(ctx context.Context) (api.ResourcesList, error) {
devfileComponents, componentInDevfile, err := component.ListAllComponents(lo.clientset.KubernetesClient, lo.namespaceFilter, lo.EnvSpecificInfo.GetDevfileObj())
if err != nil {
return api.ResourcesList{}, err
}
return api.ResourcesList{
ComponentInDevfile: componentInDevfile,
Components: devfileComponents,
}, nil
}

// NewCmdList implements the list odo command
func NewCmdComponentList(name, fullName string) *cobra.Command {
o := NewListOptions()

var listCmd = &cobra.Command{
Use: name,
Short: "List all components in the current namespace",
Long: "List all components in the current namespace.",
Example: fmt.Sprintf(listExample, fullName),
Args: cobra.NoArgs,
Annotations: map[string]string{"command": "management"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
clientset.Add(listCmd, clientset.KUBERNETES)

listCmd.Flags().StringVar(&o.namespaceFlag, "namespace", "", "Namespace for odo to scan for components")

completion.RegisterCommandFlagHandler(listCmd, "path", completion.FileCompletionHandler)
machineoutput.UsedByCommand(listCmd)

return listCmd
}

func HumanReadableOutput(list api.ResourcesList) {
components := list.Components
if len(components) == 0 {
log.Error("There are no components deployed.")
return
}

t := ui.NewTable()

// Create the header and then sort accordingly
t.AppendHeader(table.Row{"NAME", "PROJECT TYPE", "RUNNING IN", "MANAGED"})
t.SortBy([]table.SortBy{
{Name: "MANAGED", Mode: table.Asc},
{Name: "NAME", Mode: table.Dsc},
})

// Go through each component and add it to the table
for _, comp := range components {

// Mark the name as yellow in the index to it's easier to see.
name := text.Colors{text.FgHiYellow}.Sprint(comp.Name)

// Get the managed by label
managedBy := comp.ManagedBy
if managedBy == "" {
managedBy = api.TypeUnknown
}

// Get the mode (dev or deploy)
mode := comp.RunningIn.String()

// Get the type of the component
componentType := comp.Type
if componentType == "" {
componentType = api.TypeUnknown
}

// If we find our local unpushed component, let's change the output appropriately.
if list.ComponentInDevfile == comp.Name {
name = fmt.Sprintf("* %s", name)

if comp.ManagedBy == "" {
managedBy = "odo"
}
}

// If we are managing that component, output it as blue (our logo colour) to indicate it's used by odo
if managedBy == "odo" {
managedBy = text.Colors{text.FgBlue}.Sprintf("odo (%s)", comp.ManagedByVersion)
} else if managedBy != "" && comp.ManagedByVersion != "" {
// this is done to maintain the color of the output
managedBy += fmt.Sprintf("(%s)", comp.ManagedByVersion)
}

t.AppendRow(table.Row{name, componentType, mode, managedBy})
}
t.Render()

}
Loading