github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/client/cli/cmd/list.go (about)

     1  package cmd
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  
     9  	"github.com/spf13/cobra"
    10  	"google.golang.org/grpc"
    11  
    12  	"github.com/datawire/dlib/dlog"
    13  	"github.com/telepresenceio/telepresence/rpc/v2/connector"
    14  	"github.com/telepresenceio/telepresence/v2/pkg/agentconfig"
    15  	"github.com/telepresenceio/telepresence/v2/pkg/client"
    16  	"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann"
    17  	"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect"
    18  	"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon"
    19  	"github.com/telepresenceio/telepresence/v2/pkg/client/cli/intercept"
    20  	"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output"
    21  	"github.com/telepresenceio/telepresence/v2/pkg/errcat"
    22  )
    23  
    24  type listCommand struct {
    25  	onlyIntercepts    bool
    26  	onlyAgents        bool
    27  	onlyInterceptable bool
    28  	debug             bool
    29  	namespace         string
    30  	watch             bool
    31  }
    32  
    33  type workloadJSONOutput struct {
    34  	*connector.WorkloadInfo
    35  	Sidecar *agentconfig.Sidecar `json:"sidecar,omitempty"`
    36  }
    37  
    38  func list() *cobra.Command {
    39  	s := &listCommand{}
    40  	cmd := &cobra.Command{
    41  		Use:  "list",
    42  		Args: cobra.NoArgs,
    43  
    44  		Short: "List current intercepts",
    45  		RunE:  s.list,
    46  		Annotations: map[string]string{
    47  			ann.Session: ann.Required,
    48  		},
    49  	}
    50  	flags := cmd.Flags()
    51  	flags.BoolVarP(&s.onlyIntercepts, "intercepts", "i", false, "intercepts only")
    52  	flags.BoolVarP(&s.onlyAgents, "agents", "a", false, "with installed agents only")
    53  	flags.BoolVarP(&s.onlyInterceptable, "only-interceptable", "o", true, "interceptable workloads only")
    54  	flags.BoolVar(&s.debug, "debug", false, "include debugging information")
    55  	flags.StringVarP(&s.namespace, "namespace", "n", "", "If present, the namespace scope for this CLI request")
    56  
    57  	flags.BoolVarP(&s.watch, "watch", "w", false, "watch a namespace. --agents and --intercepts are disabled if this flag is set")
    58  	wf := flags.Lookup("watch")
    59  	wf.Hidden = true
    60  	wf.Deprecated = `Use "--output json-stream" instead of "--watch"`
    61  
    62  	_ = cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    63  		shellCompDir := cobra.ShellCompDirectiveNoFileComp
    64  		if err := connect.InitCommand(cmd); err != nil {
    65  			shellCompDir |= cobra.ShellCompDirectiveError
    66  			return nil, shellCompDir
    67  		}
    68  		ctx := cmd.Context()
    69  		userD := daemon.GetUserClient(ctx)
    70  		resp, err := userD.GetNamespaces(ctx, &connector.GetNamespacesRequest{
    71  			ForClientAccess: false,
    72  			Prefix:          toComplete,
    73  		})
    74  		if err != nil {
    75  			dlog.Debugf(cmd.Context(), "error getting namespaces: %v", err)
    76  			shellCompDir |= cobra.ShellCompDirectiveError
    77  			return nil, shellCompDir
    78  		}
    79  		return resp.Namespaces, shellCompDir
    80  	})
    81  	return cmd
    82  }
    83  
    84  type watchWorkloadStreamResponse struct {
    85  	workloadInfoSnapshot *connector.WorkloadInfoSnapshot
    86  	err                  error
    87  }
    88  
    89  // list requests a list current intercepts from the daemon.
    90  func (s *listCommand) list(cmd *cobra.Command, _ []string) error {
    91  	if err := connect.InitCommand(cmd); err != nil {
    92  		return err
    93  	}
    94  	stdout := cmd.OutOrStdout()
    95  	ctx := cmd.Context()
    96  	userD := daemon.GetUserClient(ctx)
    97  	var filter connector.ListRequest_Filter
    98  	switch {
    99  	case s.onlyIntercepts:
   100  		filter = connector.ListRequest_INTERCEPTS
   101  	case s.onlyAgents:
   102  		filter = connector.ListRequest_INSTALLED_AGENTS
   103  	case s.onlyInterceptable:
   104  		filter = connector.ListRequest_INTERCEPTABLE
   105  	default:
   106  		filter = connector.ListRequest_EVERYTHING
   107  	}
   108  
   109  	cfg := client.GetConfig(ctx)
   110  	maxRecSize := int64(1024 * 1024 * 20) // Default to 20 Mb here. List can be quit long.
   111  	if mz := cfg.Grpc().MaxReceiveSize(); mz > 0 {
   112  		if mz > maxRecSize {
   113  			maxRecSize = mz
   114  		}
   115  	}
   116  
   117  	formattedOutput := output.WantsFormatted(cmd)
   118  	if !output.WantsStream(cmd) {
   119  		r, err := userD.List(ctx, &connector.ListRequest{Filter: filter, Namespace: s.namespace}, grpc.MaxCallRecvMsgSize(int(maxRecSize)))
   120  		if err != nil {
   121  			return err
   122  		}
   123  		s.printList(ctx, r.Workloads, stdout, formattedOutput)
   124  		return nil
   125  	}
   126  
   127  	stream, streamErr := userD.WatchWorkloads(ctx, &connector.WatchWorkloadsRequest{Namespaces: []string{s.namespace}}, grpc.MaxCallRecvMsgSize(int(maxRecSize)))
   128  	if streamErr != nil {
   129  		return streamErr
   130  	}
   131  
   132  	ch := make(chan *watchWorkloadStreamResponse)
   133  	go func() {
   134  		for {
   135  			snap, err := stream.Recv()
   136  			ch <- &watchWorkloadStreamResponse{
   137  				workloadInfoSnapshot: snap,
   138  				err:                  err,
   139  			}
   140  			if err != nil {
   141  				close(ch)
   142  				break
   143  			}
   144  		}
   145  	}()
   146  
   147  	for {
   148  		select {
   149  		case r, ok := <-ch:
   150  			if !ok {
   151  				return nil
   152  			}
   153  			if r.err != nil {
   154  				return errcat.NoDaemonLogs.Newf("%v", r.err)
   155  			}
   156  			s.printList(ctx, r.workloadInfoSnapshot.Workloads, stdout, formattedOutput)
   157  		case <-ctx.Done():
   158  			return nil
   159  		}
   160  	}
   161  }
   162  
   163  func (s *listCommand) printList(ctx context.Context, workloads []*connector.WorkloadInfo, stdout io.Writer, formattedOut bool) {
   164  	if len(workloads) == 0 {
   165  		if formattedOut {
   166  			output.Object(ctx, []struct{}{}, false)
   167  		} else {
   168  			fmt.Fprintln(stdout, "No Workloads (Deployments, StatefulSets, or ReplicaSets)")
   169  		}
   170  		return
   171  	}
   172  
   173  	state := func(workload *connector.WorkloadInfo) string {
   174  		if iis := workload.InterceptInfos; len(iis) > 0 {
   175  			return intercept.DescribeIntercepts(ctx, iis, "", s.debug)
   176  		}
   177  		ai := workload.Sidecar
   178  		if ai != nil {
   179  			return "ready to intercept (traffic-agent already installed)"
   180  		}
   181  		if workload.NotInterceptableReason != "" {
   182  			return "not interceptable (traffic-agent not installed): " + workload.NotInterceptableReason
   183  		} else {
   184  			return "ready to intercept (traffic-agent not yet installed)"
   185  		}
   186  	}
   187  
   188  	if formattedOut {
   189  		o := make([]*workloadJSONOutput, len(workloads))
   190  		for i, v := range workloads {
   191  			l := workloadJSONOutput{WorkloadInfo: v}
   192  
   193  			if v.Sidecar != nil {
   194  				var sidecar agentconfig.Sidecar
   195  				_ = json.Unmarshal(v.Sidecar.Json, &sidecar)
   196  				l.Sidecar = &sidecar
   197  			}
   198  
   199  			o[i] = &l
   200  		}
   201  
   202  		output.Object(ctx, o, false)
   203  	} else {
   204  		includeNs := false
   205  		ns := s.namespace
   206  		for _, dep := range workloads {
   207  			depNs := dep.Namespace
   208  			if depNs == "" {
   209  				// Local-only, so use namespace of first intercept
   210  				depNs = dep.InterceptInfos[0].Spec.Namespace
   211  			}
   212  			if ns != "" && depNs != ns {
   213  				includeNs = true
   214  				break
   215  			}
   216  			ns = depNs
   217  		}
   218  		nameLen := 0
   219  		for _, dep := range workloads {
   220  			n := dep.Name
   221  			if n == "" {
   222  				// Local-only, so use name of first intercept
   223  				n = dep.InterceptInfos[0].Spec.Name
   224  			}
   225  			nl := len(n)
   226  			if includeNs {
   227  				nl += len(dep.Namespace) + 1
   228  			}
   229  			if nl > nameLen {
   230  				nameLen = nl
   231  			}
   232  		}
   233  		for _, workload := range workloads {
   234  			if workload.Name == "" {
   235  				// Local-only, so use name of first intercept
   236  				n := workload.InterceptInfos[0].Spec.Name
   237  				if includeNs {
   238  					n += "." + workload.Namespace
   239  				}
   240  				fmt.Fprintf(stdout, "%-*s: local-only intercept\n", nameLen, n)
   241  			} else {
   242  				n := workload.Name
   243  				if includeNs {
   244  					n += "." + workload.Namespace
   245  				}
   246  				fmt.Fprintf(stdout, "%-*s: %s\n", nameLen, n, state(workload))
   247  			}
   248  		}
   249  	}
   250  }