istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/ztunnelconfig/ztunnelconfig.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package ztunnelconfig 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "os" 22 "regexp" 23 "sort" 24 "strings" 25 26 "github.com/hashicorp/go-multierror" 27 "github.com/spf13/cobra" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/types" 31 "k8s.io/kubectl/pkg/util/podutils" 32 33 "istio.io/istio/istioctl/pkg/cli" 34 "istio.io/istio/istioctl/pkg/completion" 35 ambientutil "istio.io/istio/istioctl/pkg/util/ambient" 36 ztunnelDump "istio.io/istio/istioctl/pkg/writer/ztunnel/configdump" 37 "istio.io/istio/pkg/config" 38 "istio.io/istio/pkg/kube" 39 "istio.io/istio/pkg/log" 40 "istio.io/istio/pkg/slices" 41 ) 42 43 const ( 44 jsonOutput = "json" 45 yamlOutput = "yaml" 46 summaryOutput = "short" 47 48 defaultProxyAdminPort = 15000 49 ) 50 51 func ZtunnelConfig(ctx cli.Context) *cobra.Command { 52 configCmd := &cobra.Command{ 53 Use: "ztunnel-config", 54 Short: "Update or retrieve current Ztunnel configuration.", 55 Long: "A group of commands used to update or retrieve Ztunnel configuration from a Ztunnel instance.", 56 Example: ` # Retrieve summary about workload configuration 57 istioctl x ztunnel-config workload 58 59 # Retrieve summary about certificates 60 istioctl x ztunnel-config certificates`, 61 Aliases: []string{"zc"}, 62 } 63 64 configCmd.AddCommand(logCmd(ctx)) 65 configCmd.AddCommand(workloadConfigCmd(ctx)) 66 configCmd.AddCommand(certificatesConfigCmd(ctx)) 67 configCmd.AddCommand(servicesCmd(ctx)) 68 configCmd.AddCommand(policiesCmd(ctx)) 69 configCmd.AddCommand(allCmd(ctx)) 70 configCmd.AddCommand(connectionsCmd(ctx)) 71 72 return configCmd 73 } 74 75 type Command struct { 76 Name string 77 } 78 79 func certificatesConfigCmd(ctx cli.Context) *cobra.Command { 80 common := new(commonFlags) 81 cmd := &cobra.Command{ 82 Use: "certificate", 83 Short: "Retrieves certificate for the specified Ztunnel pod.", 84 Long: `Retrieve information about certificates for the Ztunnel instance.`, 85 Example: ` # Retrieve summary about workload configuration for a randomly chosen ztunnel. 86 istioctl x ztunnel-config certificates 87 88 # Retrieve full certificate dump of workloads for a given Ztunnel instance. 89 istioctl x ztunnel-config certificates <ztunnel-name[.namespace]> -o json 90 `, 91 Aliases: []string{"certificates", "certs", "cert"}, 92 Args: common.validateArgs, 93 RunE: runConfigDump(ctx, common, func(cw *ztunnelDump.ConfigWriter) error { 94 switch common.outputFormat { 95 case summaryOutput: 96 return cw.PrintSecretSummary() 97 case jsonOutput, yamlOutput: 98 return cw.PrintSecretDump(common.outputFormat) 99 default: 100 return fmt.Errorf("output format %q not supported", common.outputFormat) 101 } 102 }), 103 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 104 } 105 106 common.attach(cmd) 107 108 return cmd 109 } 110 111 func servicesCmd(ctx cli.Context) *cobra.Command { 112 var serviceNamespace string 113 common := new(commonFlags) 114 cmd := &cobra.Command{ 115 Use: "service", 116 Short: "Retrieves services for the specified Ztunnel pod.", 117 Long: `Retrieve information about services for the Ztunnel instance.`, 118 Example: ` # Retrieve summary about services configuration for a randomly chosen ztunnel. 119 istioctl x ztunnel-config services 120 121 # Retrieve full certificate dump of workloads for a given Ztunnel instance. 122 istioctl x ztunnel-config services <ztunnel-name[.namespace]> -o json 123 `, 124 Aliases: []string{"services", "s", "svc"}, 125 Args: common.validateArgs, 126 RunE: runConfigDump(ctx, common, func(cw *ztunnelDump.ConfigWriter) error { 127 filter := ztunnelDump.ServiceFilter{ 128 Namespace: serviceNamespace, 129 } 130 switch common.outputFormat { 131 case summaryOutput: 132 return cw.PrintServiceSummary(filter) 133 case jsonOutput, yamlOutput: 134 return cw.PrintServiceDump(filter, common.outputFormat) 135 default: 136 return fmt.Errorf("output format %q not supported", common.outputFormat) 137 } 138 }), 139 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 140 } 141 142 common.attach(cmd) 143 cmd.PersistentFlags().StringVar(&serviceNamespace, "service-namespace", "", 144 "Filter services by namespace field") 145 146 return cmd 147 } 148 149 func policiesCmd(ctx cli.Context) *cobra.Command { 150 var policyNamespace string 151 common := new(commonFlags) 152 cmd := &cobra.Command{ 153 Use: "policy", 154 Short: "Retrieves policies for the specified Ztunnel pod.", 155 Long: `Retrieve information about policies for the Ztunnel instance.`, 156 Example: ` # Retrieve summary about policy configuration for a randomly chosen ztunnel. 157 istioctl x ztunnel-config policies 158 159 # Retrieve full policy dump of workloads for a given Ztunnel instance. 160 istioctl x ztunnel-config policies <ztunnel-name[.namespace]> -o json 161 `, 162 Aliases: []string{"policies", "p", "pol"}, 163 Args: common.validateArgs, 164 RunE: runConfigDump(ctx, common, func(cw *ztunnelDump.ConfigWriter) error { 165 filter := ztunnelDump.PolicyFilter{ 166 Namespace: policyNamespace, 167 } 168 switch common.outputFormat { 169 case summaryOutput: 170 return cw.PrintPolicySummary(filter) 171 case jsonOutput, yamlOutput: 172 return cw.PrintPolicyDump(filter, common.outputFormat) 173 default: 174 return fmt.Errorf("output format %q not supported", common.outputFormat) 175 } 176 }), 177 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 178 } 179 180 common.attach(cmd) 181 cmd.PersistentFlags().StringVar(&policyNamespace, "policy-namespace", "", 182 "Filter policies by namespace field") 183 184 return cmd 185 } 186 187 func allCmd(ctx cli.Context) *cobra.Command { 188 common := new(commonFlags) 189 cmd := &cobra.Command{ 190 Use: "all", 191 Short: "Retrieves all configuration for the specified Ztunnel pod.", 192 Long: `Retrieve information about all configuration for the Ztunnel instance.`, 193 Example: ` # Retrieve summary about all configuration for a randomly chosen ztunnel. 194 istioctl x ztunnel-config all 195 196 # Retrieve full configuration dump of workloads for a given Ztunnel instance. 197 istioctl x ztunnel-config policies <ztunnel-name[.namespace]> -o json 198 `, 199 Args: common.validateArgs, 200 RunE: runConfigDump(ctx, common, func(cw *ztunnelDump.ConfigWriter) error { 201 switch common.outputFormat { 202 case summaryOutput: 203 return cw.PrintFullSummary() 204 case jsonOutput, yamlOutput: 205 return cw.PrintFullDump(common.outputFormat) 206 default: 207 return fmt.Errorf("output format %q not supported", common.outputFormat) 208 } 209 }), 210 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 211 } 212 213 common.attach(cmd) 214 215 return cmd 216 } 217 218 func workloadConfigCmd(ctx cli.Context) *cobra.Command { 219 var workloadsNamespace string 220 var workloadNode string 221 var verboseProxyConfig bool 222 223 var address string 224 225 common := new(commonFlags) 226 cmd := &cobra.Command{ 227 Use: "workload [<type>/]<name>[.<namespace>]", 228 Short: "Retrieves workload configuration for the specified Ztunnel pod.", 229 Long: `Retrieve information about workload configuration for the Ztunnel instance.`, 230 Example: ` # Retrieve summary about workload configuration for a randomly chosen ztunnel. 231 istioctl x ztunnel-config workload 232 233 # Retrieve summary of workloads on node XXXX for a given Ztunnel instance. 234 istioctl x ztunnel-config workload <ztunnel-name[.namespace]> --node ambient-worker 235 236 # Retrieve full workload dump of workloads with address XXXX for a given Ztunnel instance. 237 istioctl x ztunnel-config workload <ztunnel-name[.namespace]> --address 0.0.0.0 -o json 238 239 # Retrieve Ztunnel config dump separately and inspect from file. 240 kubectl exec -it $ZTUNNEL -n istio-system -- curl localhost:15000/config_dump > ztunnel-config.json 241 istioctl x ztunnel-config workloads --file ztunnel-config.json 242 243 # Retrieve workload summary for a specific namespace 244 istioctl x ztunnel-config workloads <ztunnel-name[.namespace]> --workloads-namespace foo 245 `, 246 Aliases: []string{"w", "workloads"}, 247 Args: common.validateArgs, 248 RunE: runConfigDump(ctx, common, func(cw *ztunnelDump.ConfigWriter) error { 249 filter := ztunnelDump.WorkloadFilter{ 250 Namespace: workloadsNamespace, 251 Address: address, 252 Node: workloadNode, 253 Verbose: verboseProxyConfig, 254 } 255 256 switch common.outputFormat { 257 case summaryOutput: 258 return cw.PrintWorkloadSummary(filter) 259 case jsonOutput, yamlOutput: 260 return cw.PrintWorkloadDump(filter, common.outputFormat) 261 default: 262 return fmt.Errorf("output format %q not supported", common.outputFormat) 263 } 264 }), 265 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 266 } 267 268 common.attach(cmd) 269 cmd.PersistentFlags().StringVar(&address, "address", "", "Filter workloads by address field") 270 cmd.PersistentFlags().BoolVar(&verboseProxyConfig, "verbose", true, "Output more information") 271 cmd.PersistentFlags().StringVar(&workloadsNamespace, "workload-namespace", "", 272 "Filter workloads by namespace field") 273 cmd.PersistentFlags().StringVar(&workloadNode, "workload-node", "", 274 "Filter workloads by node") 275 276 return cmd 277 } 278 279 func connectionsCmd(ctx cli.Context) *cobra.Command { 280 var workloadsNamespace string 281 var direction string 282 var raw bool 283 284 common := new(commonFlags) 285 cmd := &cobra.Command{ 286 Use: "connections [<type>/]<name>[.<namespace>]", 287 Hidden: true, 288 Short: "Retrieves connections for the specified Ztunnel pod.", 289 Long: `Retrieve information about connections for the Ztunnel instance.`, 290 Example: ` # Retrieve summary about connections for the ztunnel on a specific node. 291 istioctl x ztunnel-config connections --node ambient-worker 292 293 # Retrieve summary of connections for a given Ztunnel instance. 294 istioctl x ztunnel-config workload <ztunnel-name[.namespace]> 295 `, 296 Aliases: []string{"cons"}, 297 Args: common.validateArgs, 298 RunE: runConfigDump(ctx, common, func(cw *ztunnelDump.ConfigWriter) error { 299 filter := ztunnelDump.ConnectionsFilter{ 300 Namespace: workloadsNamespace, 301 Direction: direction, 302 Raw: raw, 303 } 304 305 switch common.outputFormat { 306 case summaryOutput: 307 return cw.PrintConnectionsSummary(filter) 308 case jsonOutput, yamlOutput: 309 return cw.PrintConnectionsDump(filter, common.outputFormat) 310 default: 311 return fmt.Errorf("output format %q not supported", common.outputFormat) 312 } 313 }), 314 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 315 } 316 317 common.attach(cmd) 318 cmd.PersistentFlags().StringVar(&direction, "direction", "", "Filter workloads by direction (inbound or outbound)") 319 cmd.PersistentFlags().BoolVar(&raw, "raw", false, "If set, show IP addresses instead of names") 320 cmd.PersistentFlags().StringVar(&workloadsNamespace, "workload-namespace", "", 321 "Filter workloads by namespace field") 322 323 return cmd 324 } 325 326 // Level is an enumeration of all supported log levels. 327 type Level int 328 329 const ( 330 defaultLoggerName = "level" 331 ) 332 333 const ( 334 // OffLevel disables logging 335 OffLevel Level = iota 336 // CriticalLevel enables critical level logging 337 CriticalLevel 338 // ErrorLevel enables error level logging 339 ErrorLevel 340 // WarningLevel enables warning level logging 341 WarningLevel 342 // InfoLevel enables info level logging 343 InfoLevel 344 // DebugLevel enables debug level logging 345 DebugLevel 346 // TraceLevel enables trace level logging 347 TraceLevel 348 ) 349 350 var levelToString = map[Level]string{ 351 TraceLevel: "trace", 352 DebugLevel: "debug", 353 InfoLevel: "info", 354 WarningLevel: "warning", 355 ErrorLevel: "error", 356 CriticalLevel: "critical", 357 OffLevel: "off", 358 } 359 360 var stringToLevel = map[string]Level{ 361 "trace": TraceLevel, 362 "debug": DebugLevel, 363 "info": InfoLevel, 364 "warning": WarningLevel, 365 "warn": WarningLevel, 366 "error": ErrorLevel, 367 "critical": CriticalLevel, 368 "off": OffLevel, 369 } 370 371 var ( 372 loggerLevelString = "" 373 reset = false 374 ) 375 376 func ztunnelLogLevel(level string) string { 377 switch level { 378 case "warning": 379 return "warn" 380 case "critical": 381 return "error" 382 default: 383 return level 384 } 385 } 386 387 func logCmd(ctx cli.Context) *cobra.Command { 388 common := new(commonFlags) 389 390 cmd := &cobra.Command{ 391 Use: "log [<type>/]<name>[.<namespace>]", 392 Short: "Retrieves logging levels of the Ztunnel instance in the specified pod.", 393 Long: "Retrieve information about logging levels of the Ztunnel instance in the specified pod, and update optionally.", 394 Example: ` # Retrieve information about logging levels from all Ztunnel pods 395 istioctl x ztunnel-config log 396 397 # Update levels of the all loggers for a specific Ztunnel pod 398 istioctl x ztunnel-config log <pod-name[.namespace]> --level off 399 400 # Update levels of the specified loggers for all Ztunnl pods 401 istioctl x ztunnel-config log --level access:debug,info 402 403 # Reset levels of all the loggers to default value (warning) for a specific Ztunnel pod. 404 istioctl x ztunnel-config log <pod-name[.namespace]> -r 405 `, 406 Aliases: []string{"o"}, 407 Args: func(cmd *cobra.Command, args []string) error { 408 if err := common.validateArgs(cmd, args); err != nil { 409 return err 410 } 411 if reset && loggerLevelString != "" { 412 cmd.Println(cmd.UsageString()) 413 return fmt.Errorf("--level cannot be combined with --reset") 414 } 415 if common.outputFormat != "" && common.outputFormat != summaryOutput { 416 return fmt.Errorf("--output is not applicable for this command") 417 } 418 return nil 419 }, 420 RunE: func(c *cobra.Command, args []string) error { 421 kubeClient, err := ctx.CLIClient() 422 if err != nil { 423 return err 424 } 425 var podNames []string 426 var podNamespace string 427 if len(args) == 1 { 428 podName, ns, err := getComponentPodName(ctx, args[0]) 429 if err != nil { 430 return err 431 } 432 ztunnelPod := ambientutil.IsZtunnelPod(kubeClient, podName, ns) 433 if !ztunnelPod { 434 return fmt.Errorf("workloads command is only supported by Ztunnel proxies: %v", podName) 435 } 436 podNames = []string{podName} 437 podNamespace = ns 438 } else { 439 var err error 440 podNames, podNamespace, err = ctx.InferPodsFromTypedResource("daemonset/ztunnel", ctx.IstioNamespace()) 441 if err != nil { 442 return err 443 } 444 } 445 446 destLoggerLevels := map[string]Level{} 447 if reset { 448 log.Warn("log level reset; using default value \"info\" for Ztunnel") 449 loggerLevelString = "info" 450 } else if loggerLevelString != "" { 451 levels := strings.Split(loggerLevelString, ",") 452 for _, ol := range levels { 453 if !strings.Contains(ol, ":") && !strings.Contains(ol, "=") { 454 level, ok := stringToLevel[ol] 455 if ok { 456 destLoggerLevels = map[string]Level{ 457 defaultLoggerName: level, 458 } 459 } else { 460 return fmt.Errorf("unrecognized logging level: %v", ol) 461 } 462 } else { 463 logParts := strings.Split(ol, "::") // account for any specified namespace 464 loggerAndLevelOnly := logParts[len(logParts)-1] 465 loggerLevel := regexp.MustCompile(`[:=]`).Split(loggerAndLevelOnly, 2) 466 // TODO validate ztunnel logger name when available: https://github.com/istio/ztunnel/issues/426 467 level, ok := stringToLevel[loggerLevel[1]] 468 if !ok { 469 return fmt.Errorf("unrecognized logging level: %v", loggerLevel[1]) 470 } 471 destLoggerLevels[loggerLevel[0]] = level 472 } 473 } 474 } 475 476 var errs *multierror.Error 477 for _, podName := range podNames { 478 q := "level=" + ztunnelLogLevel(loggerLevelString) 479 if reset { 480 q += "&reset" 481 } 482 resp, err := setupZtunnelLogs(kubeClient, q, podName, podNamespace, common.proxyAdminPort) 483 if err == nil { 484 _, _ = fmt.Fprintf(c.OutOrStdout(), "%v.%v:\n%v\n", podName, podNamespace, resp) 485 } else { 486 errs = multierror.Append(fmt.Errorf("%v.%v: %v", podName, podNamespace, err)) 487 } 488 } 489 if err := multierror.Flatten(errs.ErrorOrNil()); err != nil { 490 return err 491 } 492 return nil 493 }, 494 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 495 } 496 497 levelListString := fmt.Sprintf("[%s, %s, %s, %s, %s, %s, %s]", 498 levelToString[TraceLevel], 499 levelToString[DebugLevel], 500 levelToString[InfoLevel], 501 levelToString[WarningLevel], 502 levelToString[ErrorLevel], 503 levelToString[CriticalLevel], 504 levelToString[OffLevel]) 505 common.attach(cmd) 506 cmd.PersistentFlags().BoolVarP(&reset, "reset", "r", reset, "Reset levels to default value (warning).") 507 cmd.PersistentFlags().StringVar(&loggerLevelString, "level", loggerLevelString, 508 fmt.Sprintf("Comma-separated minimum per-logger level of messages to output, in the form of"+ 509 " [<logger>:]<level>,[<logger>:]<level>,... or <level> to change all active loggers, "+ 510 "where logger components can be listed by running \"istioctl x ztunnel-config log <pod-name[.namespace]>\""+ 511 ", and level can be one of %s", levelListString)) 512 return cmd 513 } 514 515 func setupZtunnelLogs(kubeClient kube.CLIClient, param, podName, podNamespace string, port int) (string, error) { 516 path := "logging" 517 if param != "" { 518 path = path + "?" + param 519 } 520 // "Envoy" applies despite this being ztunnel 521 result, err := kubeClient.EnvoyDoWithPort(context.TODO(), podName, podNamespace, "POST", path, port) 522 if err != nil { 523 return "", fmt.Errorf("failed to execute command on Ztunnel: %v", err) 524 } 525 return string(result), nil 526 } 527 528 // getComponentPodName returns the pod name and namespace of the Istio component 529 func getComponentPodName(ctx cli.Context, podflag string) (string, string, error) { 530 return getPodNameWithNamespace(ctx, podflag, ctx.IstioNamespace()) 531 } 532 533 func getPodNameWithNamespace(ctx cli.Context, podflag, ns string) (string, string, error) { 534 var podName, podNamespace string 535 podName, podNamespace, err := ctx.InferPodInfoFromTypedResource(podflag, ns) 536 if err != nil { 537 return "", "", err 538 } 539 return podName, podNamespace, nil 540 } 541 542 func setupZtunnelConfigDumpWriter(kubeClient kube.CLIClient, podName, podNamespace string, out io.Writer) (*ztunnelDump.ConfigWriter, error) { 543 debug, err := extractZtunnelConfigDump(kubeClient, podName, podNamespace) 544 if err != nil { 545 return nil, err 546 } 547 return setupConfigdumpZtunnelConfigWriter(debug, out) 548 } 549 550 func readFile(filename string) ([]byte, error) { 551 file := os.Stdin 552 if filename != "-" { 553 var err error 554 file, err = os.Open(filename) 555 if err != nil { 556 return nil, err 557 } 558 } 559 defer func() { 560 if err := file.Close(); err != nil { 561 log.Errorf("failed to close %s: %s", filename, err) 562 } 563 }() 564 return io.ReadAll(file) 565 } 566 567 func extractZtunnelConfigDump(kubeClient kube.CLIClient, podName, podNamespace string) ([]byte, error) { 568 path := "config_dump" 569 debug, err := kubeClient.EnvoyDoWithPort(context.TODO(), podName, podNamespace, "GET", path, 15000) 570 if err != nil { 571 return nil, fmt.Errorf("failed to execute command on %s.%s Ztunnel: %v", podName, podNamespace, err) 572 } 573 return debug, err 574 } 575 576 func setupConfigdumpZtunnelConfigWriter(debug []byte, out io.Writer) (*ztunnelDump.ConfigWriter, error) { 577 cw := &ztunnelDump.ConfigWriter{Stdout: out, FullDump: debug} 578 err := cw.Prime(debug) 579 if err != nil { 580 return nil, err 581 } 582 return cw, nil 583 } 584 585 func setupFileZtunnelConfigdumpWriter(filename string, out io.Writer) (*ztunnelDump.ConfigWriter, error) { 586 data, err := readFile(filename) 587 if err != nil { 588 return nil, err 589 } 590 return setupConfigdumpZtunnelConfigWriter(data, out) 591 } 592 593 func runConfigDump(ctx cli.Context, common *commonFlags, f func(cw *ztunnelDump.ConfigWriter) error) func(c *cobra.Command, args []string) error { 594 return func(c *cobra.Command, args []string) error { 595 var podName, podNamespace string 596 kubeClient, err := ctx.CLIClient() 597 if err != nil { 598 return err 599 } 600 var configWriter *ztunnelDump.ConfigWriter 601 if common.configDumpFile != "" { 602 configWriter, err = setupFileZtunnelConfigdumpWriter(common.configDumpFile, c.OutOrStdout()) 603 } else { 604 lookup := "daemonset/ztunnel" 605 if len(args) > 0 { 606 lookup = args[0] 607 } 608 if common.node != "" { 609 nsn, err := PodOnNodeFromDaemonset(common.node, "ztunnel", ctx.IstioNamespace(), kubeClient) 610 if err != nil { 611 return err 612 } 613 podName, podNamespace = nsn.Name, nsn.Namespace 614 } else { 615 if podName, podNamespace, err = getComponentPodName(ctx, lookup); err != nil { 616 return err 617 } 618 } 619 ztunnelPod := ambientutil.IsZtunnelPod(kubeClient, podName, podNamespace) 620 if !ztunnelPod { 621 return fmt.Errorf("workloads command is only supported by Ztunnel proxies: %v", podName) 622 } 623 configWriter, err = setupZtunnelConfigDumpWriter(kubeClient, podName, podNamespace, c.OutOrStdout()) 624 } 625 if err != nil { 626 return err 627 } 628 return f(configWriter) 629 } 630 } 631 632 func PodOnNodeFromDaemonset(node string, name, namespace string, client kube.Client) (types.NamespacedName, error) { 633 ds, err := client.Kube().AppsV1().DaemonSets(namespace).Get(context.Background(), name, metav1.GetOptions{}) 634 if err != nil { 635 return types.NamespacedName{}, err 636 } 637 selector := ds.Spec.Selector 638 if selector == nil { 639 return types.NamespacedName{}, fmt.Errorf("selector is required") 640 } 641 642 sel := selector.MatchLabels 643 kv := []string{} 644 for k, v := range sel { 645 kv = append(kv, k+"="+v) 646 } 647 podsr, err := client.Kube().CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ 648 TypeMeta: metav1.TypeMeta{}, 649 LabelSelector: strings.Join(kv, ","), 650 FieldSelector: "spec.nodeName=" + node, 651 }) 652 if err != nil { 653 return types.NamespacedName{}, err 654 } 655 pods := slices.Reference(podsr.Items) 656 if len(pods) > 0 { 657 // We need to pass in a sorter, and the one used by `kubectl logs` is good enough. 658 sortBy := func(pods []*corev1.Pod) sort.Interface { return podutils.ByLogging(pods) } 659 sort.Sort(sortBy(pods)) 660 return config.NamespacedName(pods[0]), nil 661 } 662 return types.NamespacedName{}, fmt.Errorf("no pods found") 663 } 664 665 type commonFlags struct { 666 // output format (json, yaml or short) 667 outputFormat string 668 669 proxyAdminPort int 670 671 configDumpFile string 672 673 node string 674 } 675 676 func (c *commonFlags) attach(cmd *cobra.Command) { 677 cmd.PersistentFlags().IntVar(&c.proxyAdminPort, "proxy-admin-port", defaultProxyAdminPort, "Ztunnel proxy admin port") 678 cmd.PersistentFlags().StringVarP(&c.outputFormat, "output", "o", summaryOutput, "Output format: one of json|yaml|short") 679 cmd.PersistentFlags().StringVar(&c.node, "node", "", "Filter workloads by node field") 680 cmd.PersistentFlags().StringVarP(&c.configDumpFile, "file", "f", "", 681 "Ztunnel config dump JSON file") 682 } 683 684 func (c *commonFlags) validateArgs(cmd *cobra.Command, args []string) error { 685 set := 0 686 if c.configDumpFile != "" { 687 set++ 688 } 689 if len(args) == 1 { 690 set++ 691 } 692 if c.node != "" { 693 set++ 694 } 695 if set > 1 { 696 cmd.Println(cmd.UsageString()) 697 return fmt.Errorf("at most one of --file, --node, or pod name must be passed") 698 } 699 return nil 700 }