1 package cmd
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "os"
9
10 destinationPb "github.com/linkerd/linkerd2-proxy-api/go/destination"
11 "github.com/linkerd/linkerd2/controller/api/destination"
12 pkgcmd "github.com/linkerd/linkerd2/pkg/cmd"
13 "github.com/linkerd/linkerd2/pkg/k8s"
14 "github.com/spf13/cobra"
15 "google.golang.org/grpc"
16 )
17
18 type diagProfileOptions struct {
19 destinationPod string
20 contextToken string
21 }
22
23
24
25 func (o *diagProfileOptions) validate() error {
26 return nil
27 }
28
29 func newDiagProfileOptions() *diagProfileOptions {
30 return &diagProfileOptions{}
31 }
32
33 func newCmdDiagnosticsProfile() *cobra.Command {
34 options := newDiagProfileOptions()
35
36 example := ` # Get the service profile for the service or endopint at 10.20.2.4:8080
37 linkerd diagnostics profile 10.20.2.4:8080`
38
39 cmd := &cobra.Command{
40 Use: "profile [flags] address",
41 Aliases: []string{"ep"},
42 Short: "Introspect Linkerd's service discovery state",
43 Long: `Introspect Linkerd's service discovery state.
44
45 This command provides debug information about the internal state of the
46 control-plane's destination controller. It queries the same Destination service
47 endpoint as the linkerd-proxy's, and returns the profile associated with that
48 destination.`,
49 Example: example,
50 Args: cobra.ExactArgs(1),
51 RunE: func(cmd *cobra.Command, args []string) error {
52 err := options.validate()
53 if err != nil {
54 return err
55 }
56
57 var client destinationPb.DestinationClient
58 var conn *grpc.ClientConn
59 if apiAddr != "" {
60 client, conn, err = destination.NewClient(apiAddr)
61 if err != nil {
62 fmt.Fprintf(os.Stderr, "Error creating destination client: %s\n", err)
63 os.Exit(1)
64 }
65 } else {
66 k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
67 if err != nil {
68 return err
69 }
70
71 client, conn, err = destination.NewExternalClient(cmd.Context(), controlPlaneNamespace, k8sAPI, options.destinationPod)
72 if err != nil {
73 fmt.Fprintf(os.Stderr, "Error creating destination client: %s\n", err)
74 os.Exit(1)
75 }
76 }
77
78 defer conn.Close()
79
80 profile, err := requestProfileFromAPI(client, options.contextToken, args[0])
81 if err != nil {
82 fmt.Fprintf(os.Stderr, "Destination API error: %s\n", err)
83 os.Exit(1)
84 }
85
86 return writeProfileJSON(os.Stdout, profile)
87 },
88 }
89
90 cmd.PersistentFlags().StringVar(&options.destinationPod, "destination-pod", "", "Target a specific destination Pod when there are multiple running")
91 cmd.PersistentFlags().StringVar(&options.contextToken, "token", "", "The context token to use when making the request to the destination API")
92
93 pkgcmd.ConfigureOutputFlagCompletion(cmd)
94
95 return cmd
96 }
97
98 func requestProfileFromAPI(client destinationPb.DestinationClient, token string, addr string) (*destinationPb.DestinationProfile, error) {
99 dest := &destinationPb.GetDestination{
100 Path: addr,
101 ContextToken: token,
102 }
103
104 rsp, err := client.GetProfile(context.Background(), dest)
105 if err != nil {
106 return nil, err
107 }
108
109 return rsp.Recv()
110 }
111
112 func writeProfileJSON(w io.Writer, profile *destinationPb.DestinationProfile) error {
113 b, err := json.MarshalIndent(profile, "", " ")
114 if err != nil {
115 return err
116 }
117
118 _, err = fmt.Fprintln(w, string(b))
119 return err
120 }
121
View as plain text