blob: f7eb1a4872b1dad47574e2c1b1a7992807dff864 [file] [log] [blame]
Lorenz Brun878f5f92020-05-12 16:15:39 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package main
18
19import (
20 "context"
21 "flag"
22 "fmt"
Serge Bazanskib0272182020-11-02 18:39:44 +010023 "io"
Lorenz Brun878f5f92020-05-12 16:15:39 +020024 "math/rand"
25 "os"
Lorenz Brun09c275b2021-03-30 12:47:09 +020026 "strings"
Lorenz Brun878f5f92020-05-12 16:15:39 +020027 "time"
28
Lorenz Brun878f5f92020-05-12 16:15:39 +020029 "google.golang.org/grpc"
Lorenz Brund13c1c62022-03-30 19:58:58 +020030 "k8s.io/cli-runtime/pkg/genericclioptions"
31 "k8s.io/component-base/cli"
32 "k8s.io/kubectl/pkg/cmd"
Lorenz Brun878f5f92020-05-12 16:15:39 +020033 "k8s.io/kubectl/pkg/cmd/plugin"
Lorenz Brund13c1c62022-03-30 19:58:58 +020034 "k8s.io/kubectl/pkg/cmd/util"
Lorenz Brun878f5f92020-05-12 16:15:39 +020035
Serge Bazanski31370b02021-01-07 16:31:14 +010036 "source.monogon.dev/metropolis/pkg/logtree"
Serge Bazanskida114862023-03-29 17:46:42 +020037
Serge Bazanski31370b02021-01-07 16:31:14 +010038 apb "source.monogon.dev/metropolis/proto/api"
Serge Bazanskida114862023-03-29 17:46:42 +020039 cpb "source.monogon.dev/metropolis/proto/common"
Lorenz Brun878f5f92020-05-12 16:15:39 +020040)
41
42func main() {
Serge Bazanskiefdb6e92020-07-13 17:19:27 +020043 ctx := context.Background()
Serge Bazanski216fe7b2021-05-21 18:36:16 +020044 // Hardcode localhost since this should never be used to interface with a
45 // production node because of missing encryption & authentication
Lorenz Brun878f5f92020-05-12 16:15:39 +020046 grpcClient, err := grpc.Dial("localhost:7837", grpc.WithInsecure())
47 if err != nil {
48 fmt.Printf("Failed to dial debug service (is it running): %v\n", err)
49 }
Serge Bazanskiefdb6e92020-07-13 17:19:27 +020050 debugClient := apb.NewNodeDebugServiceClient(grpcClient)
Lorenz Brun878f5f92020-05-12 16:15:39 +020051 if len(os.Args) < 2 {
52 fmt.Println("Please specify a subcommand")
53 os.Exit(1)
54 }
55
56 logsCmd := flag.NewFlagSet("logs", flag.ExitOnError)
Serge Bazanskib0272182020-11-02 18:39:44 +010057 logsTailN := logsCmd.Int("tail", -1, "Get last n lines (-1 = whole buffer, 0 = disable)")
58 logsStream := logsCmd.Bool("follow", false, "Stream log entries live from the system")
59 logsRecursive := logsCmd.Bool("recursive", false, "Get entries from entire DN subtree")
Lorenz Brun878f5f92020-05-12 16:15:39 +020060 logsCmd.Usage = func() {
Serge Bazanskib0272182020-11-02 18:39:44 +010061 fmt.Fprintf(os.Stderr, "Usage: %s %s [options] dn\n", os.Args[0], os.Args[1])
Lorenz Brun878f5f92020-05-12 16:15:39 +020062 flag.PrintDefaults()
63
Serge Bazanskib0272182020-11-02 18:39:44 +010064 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 +020065 }
66 conditionCmd := flag.NewFlagSet("condition", flag.ExitOnError)
67 conditionCmd.Usage = func() {
68 fmt.Fprintf(os.Stderr, "Usage: %s %s [options] component_path\n", os.Args[0], os.Args[1])
69 flag.PrintDefaults()
70
71 fmt.Fprintf(os.Stderr, "Example:\n %s %s IPAssigned\n", os.Args[0], os.Args[1])
72 }
Serge Bazanskib0272182020-11-02 18:39:44 +010073
Lorenz Brun09c275b2021-03-30 12:47:09 +020074 traceCmd := flag.NewFlagSet("trace", flag.ExitOnError)
75 traceCmd.Usage = func() {
76 fmt.Fprintf(os.Stderr, "Usage: %v %v [options] tracer\n", os.Args[0], os.Args[1])
77 flag.PrintDefaults()
78 }
79 functionFilter := traceCmd.String("function_filter", "", "Only trace functions matched by this filter (comma-separated, supports wildcards via *)")
80 functionGraphFilter := traceCmd.String("function_graph_filter", "", "Only trace functions matched by this filter and their children (syntax same as function_filter)")
81
Lorenz Brun9d6c4c72021-07-20 21:16:27 +020082 loadimageCmd := flag.NewFlagSet("loadimage", flag.ExitOnError)
83 loadimageCmd.Usage = func() {
84 fmt.Fprintf(os.Stderr, "Usage: %v %v [options] image\n", os.Args[0], os.Args[1])
85 flag.PrintDefaults()
86
87 fmt.Fprintf(os.Stderr, "Example: %v %v [options] helloworld_oci.tar.gz\n", os.Args[0], os.Args[1])
88 }
89
Lorenz Brun878f5f92020-05-12 16:15:39 +020090 switch os.Args[1] {
91 case "logs":
92 logsCmd.Parse(os.Args[2:])
Serge Bazanskib0272182020-11-02 18:39:44 +010093 dn := logsCmd.Arg(0)
94 req := &apb.GetLogsRequest{
95 Dn: dn,
96 BacklogMode: apb.GetLogsRequest_BACKLOG_DISABLE,
97 StreamMode: apb.GetLogsRequest_STREAM_DISABLE,
98 Filters: nil,
99 }
100
101 switch *logsTailN {
102 case 0:
103 case -1:
104 req.BacklogMode = apb.GetLogsRequest_BACKLOG_ALL
105 default:
106 req.BacklogMode = apb.GetLogsRequest_BACKLOG_COUNT
107 req.BacklogCount = int64(*logsTailN)
108 }
109
110 if *logsStream {
111 req.StreamMode = apb.GetLogsRequest_STREAM_UNBUFFERED
112 }
113
114 if *logsRecursive {
Serge Bazanskida114862023-03-29 17:46:42 +0200115 req.Filters = append(req.Filters, &cpb.LogFilter{
116 Filter: &cpb.LogFilter_WithChildren_{WithChildren: &cpb.LogFilter_WithChildren{}},
Serge Bazanskib0272182020-11-02 18:39:44 +0100117 })
118 }
119
120 stream, err := debugClient.GetLogs(ctx, req)
Lorenz Brun878f5f92020-05-12 16:15:39 +0200121 if err != nil {
122 fmt.Fprintf(os.Stderr, "Failed to get logs: %v\n", err)
123 os.Exit(1)
124 }
Serge Bazanskib0272182020-11-02 18:39:44 +0100125 for {
126 res, err := stream.Recv()
127 if err != nil {
128 if err == io.EOF {
129 os.Exit(0)
130 }
131 fmt.Fprintf(os.Stderr, "Failed to stream logs: %v\n", err)
132 os.Exit(1)
133 }
134 for _, entry := range res.BacklogEntries {
135 entry, err := logtree.LogEntryFromProto(entry)
136 if err != nil {
137 fmt.Printf("error decoding entry: %v", err)
138 continue
139 }
140 fmt.Println(entry.String())
141 }
Serge Bazanskida114862023-03-29 17:46:42 +0200142 for _, entry := range res.StreamEntries {
143 entry, err := logtree.LogEntryFromProto(entry)
144 if err != nil {
145 fmt.Printf("error decoding entry: %v", err)
146 continue
147 }
148 fmt.Println(entry.String())
149 }
Lorenz Brun878f5f92020-05-12 16:15:39 +0200150 }
Lorenz Brun878f5f92020-05-12 16:15:39 +0200151 case "kubectl":
Leopold45c196c2022-06-08 15:04:15 +0200152 // Pop "kubectl" arg (the k8s cli library internally parses os.Args).
153 os.Args = os.Args[1:]
154
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200155 // Always get a kubeconfig with cluster-admin (group system:masters),
156 // kubectl itself can impersonate
Lorenz Brun764a2de2021-11-22 16:26:36 +0100157 kubeconfigFile, err := os.CreateTemp("", "dbg_kubeconfig")
Lorenz Brun878f5f92020-05-12 16:15:39 +0200158 if err != nil {
159 fmt.Fprintf(os.Stderr, "Failed to create kubeconfig temp file: %v\n", err)
160 os.Exit(1)
161 }
162 defer kubeconfigFile.Close()
163 defer os.Remove(kubeconfigFile.Name())
164
Serge Bazanskiefdb6e92020-07-13 17:19:27 +0200165 res, err := debugClient.GetDebugKubeconfig(ctx, &apb.GetDebugKubeconfigRequest{Id: "debug-user", Groups: []string{"system:masters"}})
Lorenz Brun878f5f92020-05-12 16:15:39 +0200166 if err != nil {
167 fmt.Fprintf(os.Stderr, "Failed to get kubeconfig: %v\n", err)
168 os.Exit(1)
169 }
170 if _, err := kubeconfigFile.WriteString(res.DebugKubeconfig); err != nil {
171 fmt.Fprintf(os.Stderr, "Failed to write kubeconfig: %v\n", err)
172 os.Exit(1)
173 }
174
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200175 // This magic sets up everything as if this was just the kubectl
176 // binary. It sets the KUBECONFIG environment variable so that it knows
177 // where the Kubeconfig is located and forcibly overwrites the
178 // arguments so that the "wrapper" arguments are not visible to its
179 // flags parser.
180 // The base code is straight from:
181 // https://github.com/kubernetes/kubernetes/blob/master/cmd/kubectl/kubectl.go
Lorenz Brun878f5f92020-05-12 16:15:39 +0200182 os.Setenv("KUBECONFIG", kubeconfigFile.Name())
183 rand.Seed(time.Now().UnixNano())
Lorenz Brund13c1c62022-03-30 19:58:58 +0200184 command := cmd.NewDefaultKubectlCommandWithArgs(cmd.KubectlOptions{
185 PluginHandler: cmd.NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes),
186 Arguments: os.Args[2:],
187 ConfigFlags: genericclioptions.NewConfigFlags(true).WithDiscoveryBurst(300).WithDiscoveryQPS(50.0),
188 IOStreams: genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr},
189 })
190 if err := cli.RunNoErrOutput(command); err != nil {
191 // Pretty-print the error and exit with an error.
192 util.CheckErr(err)
Lorenz Brun878f5f92020-05-12 16:15:39 +0200193 }
Lorenz Brun09c275b2021-03-30 12:47:09 +0200194 case "trace":
195 traceCmd.Parse(os.Args[2:])
196 tracer := traceCmd.Arg(0)
197 var fgf []string
198 var ff []string
199 if len(*functionGraphFilter) > 0 {
200 fgf = strings.Split(*functionGraphFilter, ",")
201 }
202 if len(*functionFilter) > 0 {
203 ff = strings.Split(*functionFilter, ",")
204 }
205 req := apb.TraceRequest{
206 GraphFunctionFilter: fgf,
207 FunctionFilter: ff,
208 Tracer: tracer,
209 }
210 traceEvents, err := debugClient.Trace(ctx, &req)
211 if err != nil {
212 fmt.Fprintf(os.Stderr, "failed to trace: %v", err)
213 os.Exit(1)
214 }
215 for {
216 traceEvent, err := traceEvents.Recv()
217 if err != nil {
218 if err == io.EOF {
219 break
220 }
221 fmt.Fprintf(os.Stderr, "stream aborted unexpectedly: %v", err)
222 os.Exit(1)
223 }
224 fmt.Println(traceEvent.RawLine)
225 }
Lorenz Brun9d6c4c72021-07-20 21:16:27 +0200226 case "loadimage":
227 loadimageCmd.Parse(os.Args[2:])
228 imagePath := loadimageCmd.Arg(0)
229 image, err := os.Open(imagePath)
230 if err != nil {
231 fmt.Fprintf(os.Stderr, "failed to open image file: %v\n", err)
232 os.Exit(1)
233 }
234 defer image.Close()
235
236 loadStream, err := debugClient.LoadImage(ctx)
237 if err != nil {
238 fmt.Fprintf(os.Stderr, "failed to start loading image: %v\n", err)
239 os.Exit(1)
240 }
241 buf := make([]byte, 64*1024)
242 for {
243 n, err := image.Read(buf)
244 if err != io.EOF && err != nil {
245 fmt.Fprintf(os.Stderr, "failed to read image: %v\n", err)
246 os.Exit(1)
247 }
248 if err == io.EOF && n == 0 {
249 break
250 }
251 if err := loadStream.Send(&apb.ImagePart{DataPart: buf[:n]}); err != nil {
252 fmt.Fprintf(os.Stderr, "failed to send image part: %v\n", err)
253 os.Exit(1)
254 }
255 }
256 if _, err := loadStream.CloseAndRecv(); err != nil {
257 fmt.Fprintf(os.Stderr, "image failed to load: %v\n", err)
258 }
259 fmt.Fprintf(os.Stderr, "Image loaded into Metropolis node\n")
Lorenz Brun878f5f92020-05-12 16:15:39 +0200260 }
261}