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 }