blob: 0ee1c6e4342c51e7e6753124b6bd22ff139e3483 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Lorenz Brun878f5f92020-05-12 16:15:39 +02002// SPDX-License-Identifier: Apache-2.0
Lorenz Brun878f5f92020-05-12 16:15:39 +02003
4package main
5
6import (
7 "context"
8 "flag"
9 "fmt"
Serge Bazanskib0272182020-11-02 18:39:44 +010010 "io"
Lorenz Brun878f5f92020-05-12 16:15:39 +020011 "os"
Lorenz Brun09c275b2021-03-30 12:47:09 +020012 "strings"
Lorenz Brun878f5f92020-05-12 16:15:39 +020013
Lorenz Brun878f5f92020-05-12 16:15:39 +020014 "google.golang.org/grpc"
Tim Windelschmidta3558212024-04-18 23:41:38 +020015 "google.golang.org/grpc/credentials/insecure"
Lorenz Brund13c1c62022-03-30 19:58:58 +020016 "k8s.io/cli-runtime/pkg/genericclioptions"
17 "k8s.io/component-base/cli"
18 "k8s.io/kubectl/pkg/cmd"
Lorenz Brun878f5f92020-05-12 16:15:39 +020019 "k8s.io/kubectl/pkg/cmd/plugin"
Lorenz Brund13c1c62022-03-30 19:58:58 +020020 "k8s.io/kubectl/pkg/cmd/util"
Lorenz Brun878f5f92020-05-12 16:15:39 +020021
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020022 "source.monogon.dev/osbase/logtree"
Serge Bazanskida114862023-03-29 17:46:42 +020023
Serge Bazanski31370b02021-01-07 16:31:14 +010024 apb "source.monogon.dev/metropolis/proto/api"
Serge Bazanskida114862023-03-29 17:46:42 +020025 cpb "source.monogon.dev/metropolis/proto/common"
Lorenz Brun878f5f92020-05-12 16:15:39 +020026)
27
28func main() {
Serge Bazanskiefdb6e92020-07-13 17:19:27 +020029 ctx := context.Background()
Serge Bazanski216fe7b2021-05-21 18:36:16 +020030 // Hardcode localhost since this should never be used to interface with a
31 // production node because of missing encryption & authentication
Tim Windelschmidt9bd9bd42025-02-14 17:08:52 +010032 grpcClient, err := grpc.NewClient("localhost:7837", grpc.WithTransportCredentials(insecure.NewCredentials()))
Lorenz Brun878f5f92020-05-12 16:15:39 +020033 if err != nil {
Tim Windelschmidt9bd9bd42025-02-14 17:08:52 +010034 fmt.Printf("Failed create debug service: %v\n", err)
Lorenz Brun878f5f92020-05-12 16:15:39 +020035 }
Serge Bazanskiefdb6e92020-07-13 17:19:27 +020036 debugClient := apb.NewNodeDebugServiceClient(grpcClient)
Lorenz Brun878f5f92020-05-12 16:15:39 +020037 if len(os.Args) < 2 {
38 fmt.Println("Please specify a subcommand")
39 os.Exit(1)
40 }
41
42 logsCmd := flag.NewFlagSet("logs", flag.ExitOnError)
Serge Bazanskib0272182020-11-02 18:39:44 +010043 logsTailN := logsCmd.Int("tail", -1, "Get last n lines (-1 = whole buffer, 0 = disable)")
44 logsStream := logsCmd.Bool("follow", false, "Stream log entries live from the system")
45 logsRecursive := logsCmd.Bool("recursive", false, "Get entries from entire DN subtree")
Lorenz Brun878f5f92020-05-12 16:15:39 +020046 logsCmd.Usage = func() {
Serge Bazanskib0272182020-11-02 18:39:44 +010047 fmt.Fprintf(os.Stderr, "Usage: %s %s [options] dn\n", os.Args[0], os.Args[1])
Lorenz Brun878f5f92020-05-12 16:15:39 +020048 flag.PrintDefaults()
49
Serge Bazanskib0272182020-11-02 18:39:44 +010050 fmt.Fprintf(os.Stderr, "Example:\n %s %s --tail 5 --follow init\n", os.Args[0], os.Args[1])
Lorenz Brun878f5f92020-05-12 16:15:39 +020051 }
52 conditionCmd := flag.NewFlagSet("condition", flag.ExitOnError)
53 conditionCmd.Usage = func() {
54 fmt.Fprintf(os.Stderr, "Usage: %s %s [options] component_path\n", os.Args[0], os.Args[1])
55 flag.PrintDefaults()
56
57 fmt.Fprintf(os.Stderr, "Example:\n %s %s IPAssigned\n", os.Args[0], os.Args[1])
58 }
Serge Bazanskib0272182020-11-02 18:39:44 +010059
Lorenz Brun09c275b2021-03-30 12:47:09 +020060 traceCmd := flag.NewFlagSet("trace", flag.ExitOnError)
61 traceCmd.Usage = func() {
62 fmt.Fprintf(os.Stderr, "Usage: %v %v [options] tracer\n", os.Args[0], os.Args[1])
63 flag.PrintDefaults()
64 }
65 functionFilter := traceCmd.String("function_filter", "", "Only trace functions matched by this filter (comma-separated, supports wildcards via *)")
66 functionGraphFilter := traceCmd.String("function_graph_filter", "", "Only trace functions matched by this filter and their children (syntax same as function_filter)")
67
Lorenz Brun9d6c4c72021-07-20 21:16:27 +020068 loadimageCmd := flag.NewFlagSet("loadimage", flag.ExitOnError)
69 loadimageCmd.Usage = func() {
70 fmt.Fprintf(os.Stderr, "Usage: %v %v [options] image\n", os.Args[0], os.Args[1])
71 flag.PrintDefaults()
72
73 fmt.Fprintf(os.Stderr, "Example: %v %v [options] helloworld_oci.tar.gz\n", os.Args[0], os.Args[1])
74 }
75
Lorenz Brun878f5f92020-05-12 16:15:39 +020076 switch os.Args[1] {
77 case "logs":
78 logsCmd.Parse(os.Args[2:])
Serge Bazanskib0272182020-11-02 18:39:44 +010079 dn := logsCmd.Arg(0)
Tim Windelschmidt5ffa6362025-01-28 19:20:06 +010080 req := &apb.LogsRequest{
Serge Bazanskib0272182020-11-02 18:39:44 +010081 Dn: dn,
Tim Windelschmidt5ffa6362025-01-28 19:20:06 +010082 BacklogMode: apb.LogsRequest_BACKLOG_MODE_DISABLE,
83 StreamMode: apb.LogsRequest_STREAM_MODE_DISABLE,
Serge Bazanskib0272182020-11-02 18:39:44 +010084 Filters: nil,
85 }
86
87 switch *logsTailN {
88 case 0:
89 case -1:
Tim Windelschmidt5ffa6362025-01-28 19:20:06 +010090 req.BacklogMode = apb.LogsRequest_BACKLOG_MODE_ALL
Serge Bazanskib0272182020-11-02 18:39:44 +010091 default:
Tim Windelschmidt5ffa6362025-01-28 19:20:06 +010092 req.BacklogMode = apb.LogsRequest_BACKLOG_MODE_COUNT
Serge Bazanskib0272182020-11-02 18:39:44 +010093 req.BacklogCount = int64(*logsTailN)
94 }
95
96 if *logsStream {
Tim Windelschmidt5ffa6362025-01-28 19:20:06 +010097 req.StreamMode = apb.LogsRequest_STREAM_MODE_UNBUFFERED
Serge Bazanskib0272182020-11-02 18:39:44 +010098 }
99
100 if *logsRecursive {
Serge Bazanskida114862023-03-29 17:46:42 +0200101 req.Filters = append(req.Filters, &cpb.LogFilter{
102 Filter: &cpb.LogFilter_WithChildren_{WithChildren: &cpb.LogFilter_WithChildren{}},
Serge Bazanskib0272182020-11-02 18:39:44 +0100103 })
104 }
105
Tim Windelschmidt5ffa6362025-01-28 19:20:06 +0100106 stream, err := debugClient.Logs(ctx, req)
Lorenz Brun878f5f92020-05-12 16:15:39 +0200107 if err != nil {
108 fmt.Fprintf(os.Stderr, "Failed to get logs: %v\n", err)
109 os.Exit(1)
110 }
Serge Bazanskib0272182020-11-02 18:39:44 +0100111 for {
112 res, err := stream.Recv()
113 if err != nil {
114 if err == io.EOF {
115 os.Exit(0)
116 }
117 fmt.Fprintf(os.Stderr, "Failed to stream logs: %v\n", err)
118 os.Exit(1)
119 }
120 for _, entry := range res.BacklogEntries {
121 entry, err := logtree.LogEntryFromProto(entry)
122 if err != nil {
123 fmt.Printf("error decoding entry: %v", err)
124 continue
125 }
126 fmt.Println(entry.String())
127 }
Serge Bazanskida114862023-03-29 17:46:42 +0200128 for _, entry := range res.StreamEntries {
129 entry, err := logtree.LogEntryFromProto(entry)
130 if err != nil {
131 fmt.Printf("error decoding entry: %v", err)
132 continue
133 }
134 fmt.Println(entry.String())
135 }
Lorenz Brun878f5f92020-05-12 16:15:39 +0200136 }
Lorenz Brun878f5f92020-05-12 16:15:39 +0200137 case "kubectl":
Leopold45c196c2022-06-08 15:04:15 +0200138 // Pop "kubectl" arg (the k8s cli library internally parses os.Args).
139 os.Args = os.Args[1:]
140
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200141 // Always get a kubeconfig with cluster-admin (group system:masters),
142 // kubectl itself can impersonate
Lorenz Brun764a2de2021-11-22 16:26:36 +0100143 kubeconfigFile, err := os.CreateTemp("", "dbg_kubeconfig")
Lorenz Brun878f5f92020-05-12 16:15:39 +0200144 if err != nil {
145 fmt.Fprintf(os.Stderr, "Failed to create kubeconfig temp file: %v\n", err)
146 os.Exit(1)
147 }
148 defer kubeconfigFile.Close()
149 defer os.Remove(kubeconfigFile.Name())
150
Serge Bazanskiefdb6e92020-07-13 17:19:27 +0200151 res, err := debugClient.GetDebugKubeconfig(ctx, &apb.GetDebugKubeconfigRequest{Id: "debug-user", Groups: []string{"system:masters"}})
Lorenz Brun878f5f92020-05-12 16:15:39 +0200152 if err != nil {
153 fmt.Fprintf(os.Stderr, "Failed to get kubeconfig: %v\n", err)
154 os.Exit(1)
155 }
156 if _, err := kubeconfigFile.WriteString(res.DebugKubeconfig); err != nil {
157 fmt.Fprintf(os.Stderr, "Failed to write kubeconfig: %v\n", err)
158 os.Exit(1)
159 }
160
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200161 // This magic sets up everything as if this was just the kubectl
162 // binary. It sets the KUBECONFIG environment variable so that it knows
163 // where the Kubeconfig is located and forcibly overwrites the
164 // arguments so that the "wrapper" arguments are not visible to its
165 // flags parser.
166 // The base code is straight from:
167 // https://github.com/kubernetes/kubernetes/blob/master/cmd/kubectl/kubectl.go
Lorenz Brun878f5f92020-05-12 16:15:39 +0200168 os.Setenv("KUBECONFIG", kubeconfigFile.Name())
Lorenz Brund13c1c62022-03-30 19:58:58 +0200169 command := cmd.NewDefaultKubectlCommandWithArgs(cmd.KubectlOptions{
170 PluginHandler: cmd.NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes),
171 Arguments: os.Args[2:],
172 ConfigFlags: genericclioptions.NewConfigFlags(true).WithDiscoveryBurst(300).WithDiscoveryQPS(50.0),
173 IOStreams: genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr},
174 })
175 if err := cli.RunNoErrOutput(command); err != nil {
176 // Pretty-print the error and exit with an error.
177 util.CheckErr(err)
Lorenz Brun878f5f92020-05-12 16:15:39 +0200178 }
Lorenz Brun09c275b2021-03-30 12:47:09 +0200179 case "trace":
180 traceCmd.Parse(os.Args[2:])
181 tracer := traceCmd.Arg(0)
182 var fgf []string
183 var ff []string
184 if len(*functionGraphFilter) > 0 {
185 fgf = strings.Split(*functionGraphFilter, ",")
186 }
187 if len(*functionFilter) > 0 {
188 ff = strings.Split(*functionFilter, ",")
189 }
190 req := apb.TraceRequest{
191 GraphFunctionFilter: fgf,
192 FunctionFilter: ff,
193 Tracer: tracer,
194 }
195 traceEvents, err := debugClient.Trace(ctx, &req)
196 if err != nil {
197 fmt.Fprintf(os.Stderr, "failed to trace: %v", err)
198 os.Exit(1)
199 }
200 for {
201 traceEvent, err := traceEvents.Recv()
202 if err != nil {
203 if err == io.EOF {
204 break
205 }
206 fmt.Fprintf(os.Stderr, "stream aborted unexpectedly: %v", err)
207 os.Exit(1)
208 }
209 fmt.Println(traceEvent.RawLine)
210 }
Lorenz Brun9d6c4c72021-07-20 21:16:27 +0200211 case "loadimage":
212 loadimageCmd.Parse(os.Args[2:])
213 imagePath := loadimageCmd.Arg(0)
214 image, err := os.Open(imagePath)
215 if err != nil {
216 fmt.Fprintf(os.Stderr, "failed to open image file: %v\n", err)
217 os.Exit(1)
218 }
219 defer image.Close()
220
221 loadStream, err := debugClient.LoadImage(ctx)
222 if err != nil {
223 fmt.Fprintf(os.Stderr, "failed to start loading image: %v\n", err)
224 os.Exit(1)
225 }
226 buf := make([]byte, 64*1024)
227 for {
228 n, err := image.Read(buf)
229 if err != io.EOF && err != nil {
230 fmt.Fprintf(os.Stderr, "failed to read image: %v\n", err)
231 os.Exit(1)
232 }
233 if err == io.EOF && n == 0 {
234 break
235 }
236 if err := loadStream.Send(&apb.ImagePart{DataPart: buf[:n]}); err != nil {
237 fmt.Fprintf(os.Stderr, "failed to send image part: %v\n", err)
238 os.Exit(1)
239 }
240 }
241 if _, err := loadStream.CloseAndRecv(); err != nil {
242 fmt.Fprintf(os.Stderr, "image failed to load: %v\n", err)
243 }
244 fmt.Fprintf(os.Stderr, "Image loaded into Metropolis node\n")
Lorenz Brun878f5f92020-05-12 16:15:39 +0200245 }
246}