blob: e9e43be90e373a06b34a7ccda211b6d9ecc99bc6 [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 "os"
Lorenz Brun09c275b2021-03-30 12:47:09 +020025 "strings"
Lorenz Brun878f5f92020-05-12 16:15:39 +020026
Lorenz Brun878f5f92020-05-12 16:15:39 +020027 "google.golang.org/grpc"
Tim Windelschmidta3558212024-04-18 23:41:38 +020028 "google.golang.org/grpc/credentials/insecure"
Lorenz Brund13c1c62022-03-30 19:58:58 +020029 "k8s.io/cli-runtime/pkg/genericclioptions"
30 "k8s.io/component-base/cli"
31 "k8s.io/kubectl/pkg/cmd"
Lorenz Brun878f5f92020-05-12 16:15:39 +020032 "k8s.io/kubectl/pkg/cmd/plugin"
Lorenz Brund13c1c62022-03-30 19:58:58 +020033 "k8s.io/kubectl/pkg/cmd/util"
Lorenz Brun878f5f92020-05-12 16:15:39 +020034
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020035 "source.monogon.dev/osbase/logtree"
Serge Bazanskida114862023-03-29 17:46:42 +020036
Serge Bazanski31370b02021-01-07 16:31:14 +010037 apb "source.monogon.dev/metropolis/proto/api"
Serge Bazanskida114862023-03-29 17:46:42 +020038 cpb "source.monogon.dev/metropolis/proto/common"
Lorenz Brun878f5f92020-05-12 16:15:39 +020039)
40
41func main() {
Serge Bazanskiefdb6e92020-07-13 17:19:27 +020042 ctx := context.Background()
Serge Bazanski216fe7b2021-05-21 18:36:16 +020043 // Hardcode localhost since this should never be used to interface with a
44 // production node because of missing encryption & authentication
Tim Windelschmidta3558212024-04-18 23:41:38 +020045 grpcClient, err := grpc.Dial("localhost:7837", grpc.WithTransportCredentials(insecure.NewCredentials()))
Lorenz Brun878f5f92020-05-12 16:15:39 +020046 if err != nil {
47 fmt.Printf("Failed to dial debug service (is it running): %v\n", err)
48 }
Serge Bazanskiefdb6e92020-07-13 17:19:27 +020049 debugClient := apb.NewNodeDebugServiceClient(grpcClient)
Lorenz Brun878f5f92020-05-12 16:15:39 +020050 if len(os.Args) < 2 {
51 fmt.Println("Please specify a subcommand")
52 os.Exit(1)
53 }
54
55 logsCmd := flag.NewFlagSet("logs", flag.ExitOnError)
Serge Bazanskib0272182020-11-02 18:39:44 +010056 logsTailN := logsCmd.Int("tail", -1, "Get last n lines (-1 = whole buffer, 0 = disable)")
57 logsStream := logsCmd.Bool("follow", false, "Stream log entries live from the system")
58 logsRecursive := logsCmd.Bool("recursive", false, "Get entries from entire DN subtree")
Lorenz Brun878f5f92020-05-12 16:15:39 +020059 logsCmd.Usage = func() {
Serge Bazanskib0272182020-11-02 18:39:44 +010060 fmt.Fprintf(os.Stderr, "Usage: %s %s [options] dn\n", os.Args[0], os.Args[1])
Lorenz Brun878f5f92020-05-12 16:15:39 +020061 flag.PrintDefaults()
62
Serge Bazanskib0272182020-11-02 18:39:44 +010063 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 +020064 }
65 conditionCmd := flag.NewFlagSet("condition", flag.ExitOnError)
66 conditionCmd.Usage = func() {
67 fmt.Fprintf(os.Stderr, "Usage: %s %s [options] component_path\n", os.Args[0], os.Args[1])
68 flag.PrintDefaults()
69
70 fmt.Fprintf(os.Stderr, "Example:\n %s %s IPAssigned\n", os.Args[0], os.Args[1])
71 }
Serge Bazanskib0272182020-11-02 18:39:44 +010072
Lorenz Brun09c275b2021-03-30 12:47:09 +020073 traceCmd := flag.NewFlagSet("trace", flag.ExitOnError)
74 traceCmd.Usage = func() {
75 fmt.Fprintf(os.Stderr, "Usage: %v %v [options] tracer\n", os.Args[0], os.Args[1])
76 flag.PrintDefaults()
77 }
78 functionFilter := traceCmd.String("function_filter", "", "Only trace functions matched by this filter (comma-separated, supports wildcards via *)")
79 functionGraphFilter := traceCmd.String("function_graph_filter", "", "Only trace functions matched by this filter and their children (syntax same as function_filter)")
80
Lorenz Brun9d6c4c72021-07-20 21:16:27 +020081 loadimageCmd := flag.NewFlagSet("loadimage", flag.ExitOnError)
82 loadimageCmd.Usage = func() {
83 fmt.Fprintf(os.Stderr, "Usage: %v %v [options] image\n", os.Args[0], os.Args[1])
84 flag.PrintDefaults()
85
86 fmt.Fprintf(os.Stderr, "Example: %v %v [options] helloworld_oci.tar.gz\n", os.Args[0], os.Args[1])
87 }
88
Lorenz Brun878f5f92020-05-12 16:15:39 +020089 switch os.Args[1] {
90 case "logs":
91 logsCmd.Parse(os.Args[2:])
Serge Bazanskib0272182020-11-02 18:39:44 +010092 dn := logsCmd.Arg(0)
93 req := &apb.GetLogsRequest{
94 Dn: dn,
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010095 BacklogMode: apb.GetLogsRequest_BACKLOG_MODE_DISABLE,
96 StreamMode: apb.GetLogsRequest_STREAM_MODE_DISABLE,
Serge Bazanskib0272182020-11-02 18:39:44 +010097 Filters: nil,
98 }
99
100 switch *logsTailN {
101 case 0:
102 case -1:
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +0100103 req.BacklogMode = apb.GetLogsRequest_BACKLOG_MODE_ALL
Serge Bazanskib0272182020-11-02 18:39:44 +0100104 default:
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +0100105 req.BacklogMode = apb.GetLogsRequest_BACKLOG_MODE_COUNT
Serge Bazanskib0272182020-11-02 18:39:44 +0100106 req.BacklogCount = int64(*logsTailN)
107 }
108
109 if *logsStream {
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +0100110 req.StreamMode = apb.GetLogsRequest_STREAM_MODE_UNBUFFERED
Serge Bazanskib0272182020-11-02 18:39:44 +0100111 }
112
113 if *logsRecursive {
Serge Bazanskida114862023-03-29 17:46:42 +0200114 req.Filters = append(req.Filters, &cpb.LogFilter{
115 Filter: &cpb.LogFilter_WithChildren_{WithChildren: &cpb.LogFilter_WithChildren{}},
Serge Bazanskib0272182020-11-02 18:39:44 +0100116 })
117 }
118
119 stream, err := debugClient.GetLogs(ctx, req)
Lorenz Brun878f5f92020-05-12 16:15:39 +0200120 if err != nil {
121 fmt.Fprintf(os.Stderr, "Failed to get logs: %v\n", err)
122 os.Exit(1)
123 }
Serge Bazanskib0272182020-11-02 18:39:44 +0100124 for {
125 res, err := stream.Recv()
126 if err != nil {
127 if err == io.EOF {
128 os.Exit(0)
129 }
130 fmt.Fprintf(os.Stderr, "Failed to stream logs: %v\n", err)
131 os.Exit(1)
132 }
133 for _, entry := range res.BacklogEntries {
134 entry, err := logtree.LogEntryFromProto(entry)
135 if err != nil {
136 fmt.Printf("error decoding entry: %v", err)
137 continue
138 }
139 fmt.Println(entry.String())
140 }
Serge Bazanskida114862023-03-29 17:46:42 +0200141 for _, entry := range res.StreamEntries {
142 entry, err := logtree.LogEntryFromProto(entry)
143 if err != nil {
144 fmt.Printf("error decoding entry: %v", err)
145 continue
146 }
147 fmt.Println(entry.String())
148 }
Lorenz Brun878f5f92020-05-12 16:15:39 +0200149 }
Lorenz Brun878f5f92020-05-12 16:15:39 +0200150 case "kubectl":
Leopold45c196c2022-06-08 15:04:15 +0200151 // Pop "kubectl" arg (the k8s cli library internally parses os.Args).
152 os.Args = os.Args[1:]
153
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200154 // Always get a kubeconfig with cluster-admin (group system:masters),
155 // kubectl itself can impersonate
Lorenz Brun764a2de2021-11-22 16:26:36 +0100156 kubeconfigFile, err := os.CreateTemp("", "dbg_kubeconfig")
Lorenz Brun878f5f92020-05-12 16:15:39 +0200157 if err != nil {
158 fmt.Fprintf(os.Stderr, "Failed to create kubeconfig temp file: %v\n", err)
159 os.Exit(1)
160 }
161 defer kubeconfigFile.Close()
162 defer os.Remove(kubeconfigFile.Name())
163
Serge Bazanskiefdb6e92020-07-13 17:19:27 +0200164 res, err := debugClient.GetDebugKubeconfig(ctx, &apb.GetDebugKubeconfigRequest{Id: "debug-user", Groups: []string{"system:masters"}})
Lorenz Brun878f5f92020-05-12 16:15:39 +0200165 if err != nil {
166 fmt.Fprintf(os.Stderr, "Failed to get kubeconfig: %v\n", err)
167 os.Exit(1)
168 }
169 if _, err := kubeconfigFile.WriteString(res.DebugKubeconfig); err != nil {
170 fmt.Fprintf(os.Stderr, "Failed to write kubeconfig: %v\n", err)
171 os.Exit(1)
172 }
173
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200174 // This magic sets up everything as if this was just the kubectl
175 // binary. It sets the KUBECONFIG environment variable so that it knows
176 // where the Kubeconfig is located and forcibly overwrites the
177 // arguments so that the "wrapper" arguments are not visible to its
178 // flags parser.
179 // The base code is straight from:
180 // https://github.com/kubernetes/kubernetes/blob/master/cmd/kubectl/kubectl.go
Lorenz Brun878f5f92020-05-12 16:15:39 +0200181 os.Setenv("KUBECONFIG", kubeconfigFile.Name())
Lorenz Brund13c1c62022-03-30 19:58:58 +0200182 command := cmd.NewDefaultKubectlCommandWithArgs(cmd.KubectlOptions{
183 PluginHandler: cmd.NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes),
184 Arguments: os.Args[2:],
185 ConfigFlags: genericclioptions.NewConfigFlags(true).WithDiscoveryBurst(300).WithDiscoveryQPS(50.0),
186 IOStreams: genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr},
187 })
188 if err := cli.RunNoErrOutput(command); err != nil {
189 // Pretty-print the error and exit with an error.
190 util.CheckErr(err)
Lorenz Brun878f5f92020-05-12 16:15:39 +0200191 }
Lorenz Brun09c275b2021-03-30 12:47:09 +0200192 case "trace":
193 traceCmd.Parse(os.Args[2:])
194 tracer := traceCmd.Arg(0)
195 var fgf []string
196 var ff []string
197 if len(*functionGraphFilter) > 0 {
198 fgf = strings.Split(*functionGraphFilter, ",")
199 }
200 if len(*functionFilter) > 0 {
201 ff = strings.Split(*functionFilter, ",")
202 }
203 req := apb.TraceRequest{
204 GraphFunctionFilter: fgf,
205 FunctionFilter: ff,
206 Tracer: tracer,
207 }
208 traceEvents, err := debugClient.Trace(ctx, &req)
209 if err != nil {
210 fmt.Fprintf(os.Stderr, "failed to trace: %v", err)
211 os.Exit(1)
212 }
213 for {
214 traceEvent, err := traceEvents.Recv()
215 if err != nil {
216 if err == io.EOF {
217 break
218 }
219 fmt.Fprintf(os.Stderr, "stream aborted unexpectedly: %v", err)
220 os.Exit(1)
221 }
222 fmt.Println(traceEvent.RawLine)
223 }
Lorenz Brun9d6c4c72021-07-20 21:16:27 +0200224 case "loadimage":
225 loadimageCmd.Parse(os.Args[2:])
226 imagePath := loadimageCmd.Arg(0)
227 image, err := os.Open(imagePath)
228 if err != nil {
229 fmt.Fprintf(os.Stderr, "failed to open image file: %v\n", err)
230 os.Exit(1)
231 }
232 defer image.Close()
233
234 loadStream, err := debugClient.LoadImage(ctx)
235 if err != nil {
236 fmt.Fprintf(os.Stderr, "failed to start loading image: %v\n", err)
237 os.Exit(1)
238 }
239 buf := make([]byte, 64*1024)
240 for {
241 n, err := image.Read(buf)
242 if err != io.EOF && err != nil {
243 fmt.Fprintf(os.Stderr, "failed to read image: %v\n", err)
244 os.Exit(1)
245 }
246 if err == io.EOF && n == 0 {
247 break
248 }
249 if err := loadStream.Send(&apb.ImagePart{DataPart: buf[:n]}); err != nil {
250 fmt.Fprintf(os.Stderr, "failed to send image part: %v\n", err)
251 os.Exit(1)
252 }
253 }
254 if _, err := loadStream.CloseAndRecv(); err != nil {
255 fmt.Fprintf(os.Stderr, "image failed to load: %v\n", err)
256 }
257 fmt.Fprintf(os.Stderr, "Image loaded into Metropolis node\n")
Lorenz Brun878f5f92020-05-12 16:15:39 +0200258 }
259}