blob: 99aff6c8c107f79b63a69a6f18fc7c9362ffc629 [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
29 "github.com/spf13/pflag"
30 "google.golang.org/grpc"
31 cliflag "k8s.io/component-base/cli/flag"
32 "k8s.io/kubectl/pkg/cmd/plugin"
33 "k8s.io/kubectl/pkg/util/logs"
34 "k8s.io/kubernetes/pkg/kubectl/cmd"
35
Serge Bazanski31370b02021-01-07 16:31:14 +010036 "source.monogon.dev/metropolis/pkg/logtree"
37 apb "source.monogon.dev/metropolis/proto/api"
Lorenz Brun878f5f92020-05-12 16:15:39 +020038)
39
40func main() {
Serge Bazanskiefdb6e92020-07-13 17:19:27 +020041 ctx := context.Background()
Serge Bazanski216fe7b2021-05-21 18:36:16 +020042 // Hardcode localhost since this should never be used to interface with a
43 // production node because of missing encryption & authentication
Lorenz Brun878f5f92020-05-12 16:15:39 +020044 grpcClient, err := grpc.Dial("localhost:7837", grpc.WithInsecure())
45 if err != nil {
46 fmt.Printf("Failed to dial debug service (is it running): %v\n", err)
47 }
Serge Bazanskiefdb6e92020-07-13 17:19:27 +020048 debugClient := apb.NewNodeDebugServiceClient(grpcClient)
Lorenz Brun878f5f92020-05-12 16:15:39 +020049 if len(os.Args) < 2 {
50 fmt.Println("Please specify a subcommand")
51 os.Exit(1)
52 }
53
54 logsCmd := flag.NewFlagSet("logs", flag.ExitOnError)
Serge Bazanskib0272182020-11-02 18:39:44 +010055 logsTailN := logsCmd.Int("tail", -1, "Get last n lines (-1 = whole buffer, 0 = disable)")
56 logsStream := logsCmd.Bool("follow", false, "Stream log entries live from the system")
57 logsRecursive := logsCmd.Bool("recursive", false, "Get entries from entire DN subtree")
Lorenz Brun878f5f92020-05-12 16:15:39 +020058 logsCmd.Usage = func() {
Serge Bazanskib0272182020-11-02 18:39:44 +010059 fmt.Fprintf(os.Stderr, "Usage: %s %s [options] dn\n", os.Args[0], os.Args[1])
Lorenz Brun878f5f92020-05-12 16:15:39 +020060 flag.PrintDefaults()
61
Serge Bazanskib0272182020-11-02 18:39:44 +010062 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 +020063 }
64 conditionCmd := flag.NewFlagSet("condition", flag.ExitOnError)
65 conditionCmd.Usage = func() {
66 fmt.Fprintf(os.Stderr, "Usage: %s %s [options] component_path\n", os.Args[0], os.Args[1])
67 flag.PrintDefaults()
68
69 fmt.Fprintf(os.Stderr, "Example:\n %s %s IPAssigned\n", os.Args[0], os.Args[1])
70 }
Serge Bazanskib0272182020-11-02 18:39:44 +010071
Lorenz Brun09c275b2021-03-30 12:47:09 +020072 traceCmd := flag.NewFlagSet("trace", flag.ExitOnError)
73 traceCmd.Usage = func() {
74 fmt.Fprintf(os.Stderr, "Usage: %v %v [options] tracer\n", os.Args[0], os.Args[1])
75 flag.PrintDefaults()
76 }
77 functionFilter := traceCmd.String("function_filter", "", "Only trace functions matched by this filter (comma-separated, supports wildcards via *)")
78 functionGraphFilter := traceCmd.String("function_graph_filter", "", "Only trace functions matched by this filter and their children (syntax same as function_filter)")
79
Lorenz Brun9d6c4c72021-07-20 21:16:27 +020080 loadimageCmd := flag.NewFlagSet("loadimage", flag.ExitOnError)
81 loadimageCmd.Usage = func() {
82 fmt.Fprintf(os.Stderr, "Usage: %v %v [options] image\n", os.Args[0], os.Args[1])
83 flag.PrintDefaults()
84
85 fmt.Fprintf(os.Stderr, "Example: %v %v [options] helloworld_oci.tar.gz\n", os.Args[0], os.Args[1])
86 }
87
Lorenz Brun878f5f92020-05-12 16:15:39 +020088 switch os.Args[1] {
89 case "logs":
90 logsCmd.Parse(os.Args[2:])
Serge Bazanskib0272182020-11-02 18:39:44 +010091 dn := logsCmd.Arg(0)
92 req := &apb.GetLogsRequest{
93 Dn: dn,
94 BacklogMode: apb.GetLogsRequest_BACKLOG_DISABLE,
95 StreamMode: apb.GetLogsRequest_STREAM_DISABLE,
96 Filters: nil,
97 }
98
99 switch *logsTailN {
100 case 0:
101 case -1:
102 req.BacklogMode = apb.GetLogsRequest_BACKLOG_ALL
103 default:
104 req.BacklogMode = apb.GetLogsRequest_BACKLOG_COUNT
105 req.BacklogCount = int64(*logsTailN)
106 }
107
108 if *logsStream {
109 req.StreamMode = apb.GetLogsRequest_STREAM_UNBUFFERED
110 }
111
112 if *logsRecursive {
113 req.Filters = append(req.Filters, &apb.LogFilter{
114 Filter: &apb.LogFilter_WithChildren_{WithChildren: &apb.LogFilter_WithChildren{}},
115 })
116 }
117
118 stream, err := debugClient.GetLogs(ctx, req)
Lorenz Brun878f5f92020-05-12 16:15:39 +0200119 if err != nil {
120 fmt.Fprintf(os.Stderr, "Failed to get logs: %v\n", err)
121 os.Exit(1)
122 }
Serge Bazanskib0272182020-11-02 18:39:44 +0100123 for {
124 res, err := stream.Recv()
125 if err != nil {
126 if err == io.EOF {
127 os.Exit(0)
128 }
129 fmt.Fprintf(os.Stderr, "Failed to stream logs: %v\n", err)
130 os.Exit(1)
131 }
132 for _, entry := range res.BacklogEntries {
133 entry, err := logtree.LogEntryFromProto(entry)
134 if err != nil {
135 fmt.Printf("error decoding entry: %v", err)
136 continue
137 }
138 fmt.Println(entry.String())
139 }
Lorenz Brun878f5f92020-05-12 16:15:39 +0200140 }
Lorenz Brun878f5f92020-05-12 16:15:39 +0200141 case "kubectl":
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200142 // Always get a kubeconfig with cluster-admin (group system:masters),
143 // kubectl itself can impersonate
Lorenz Brun764a2de2021-11-22 16:26:36 +0100144 kubeconfigFile, err := os.CreateTemp("", "dbg_kubeconfig")
Lorenz Brun878f5f92020-05-12 16:15:39 +0200145 if err != nil {
146 fmt.Fprintf(os.Stderr, "Failed to create kubeconfig temp file: %v\n", err)
147 os.Exit(1)
148 }
149 defer kubeconfigFile.Close()
150 defer os.Remove(kubeconfigFile.Name())
151
Serge Bazanskiefdb6e92020-07-13 17:19:27 +0200152 res, err := debugClient.GetDebugKubeconfig(ctx, &apb.GetDebugKubeconfigRequest{Id: "debug-user", Groups: []string{"system:masters"}})
Lorenz Brun878f5f92020-05-12 16:15:39 +0200153 if err != nil {
154 fmt.Fprintf(os.Stderr, "Failed to get kubeconfig: %v\n", err)
155 os.Exit(1)
156 }
157 if _, err := kubeconfigFile.WriteString(res.DebugKubeconfig); err != nil {
158 fmt.Fprintf(os.Stderr, "Failed to write kubeconfig: %v\n", err)
159 os.Exit(1)
160 }
161
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200162 // This magic sets up everything as if this was just the kubectl
163 // binary. It sets the KUBECONFIG environment variable so that it knows
164 // where the Kubeconfig is located and forcibly overwrites the
165 // arguments so that the "wrapper" arguments are not visible to its
166 // flags parser.
167 // The base code is straight from:
168 // https://github.com/kubernetes/kubernetes/blob/master/cmd/kubectl/kubectl.go
Lorenz Brun878f5f92020-05-12 16:15:39 +0200169 os.Setenv("KUBECONFIG", kubeconfigFile.Name())
170 rand.Seed(time.Now().UnixNano())
171 pflag.CommandLine.SetNormalizeFunc(cliflag.WordSepNormalizeFunc)
172 pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
173 logs.InitLogs()
174 defer logs.FlushLogs()
175 command := cmd.NewDefaultKubectlCommandWithArgs(cmd.NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes), os.Args[2:], os.Stdin, os.Stdout, os.Stderr)
176 command.SetArgs(os.Args[2:])
177 if err := command.Execute(); err != nil {
178 os.Exit(1)
179 }
Lorenz Brun09c275b2021-03-30 12:47:09 +0200180 case "trace":
181 traceCmd.Parse(os.Args[2:])
182 tracer := traceCmd.Arg(0)
183 var fgf []string
184 var ff []string
185 if len(*functionGraphFilter) > 0 {
186 fgf = strings.Split(*functionGraphFilter, ",")
187 }
188 if len(*functionFilter) > 0 {
189 ff = strings.Split(*functionFilter, ",")
190 }
191 req := apb.TraceRequest{
192 GraphFunctionFilter: fgf,
193 FunctionFilter: ff,
194 Tracer: tracer,
195 }
196 traceEvents, err := debugClient.Trace(ctx, &req)
197 if err != nil {
198 fmt.Fprintf(os.Stderr, "failed to trace: %v", err)
199 os.Exit(1)
200 }
201 for {
202 traceEvent, err := traceEvents.Recv()
203 if err != nil {
204 if err == io.EOF {
205 break
206 }
207 fmt.Fprintf(os.Stderr, "stream aborted unexpectedly: %v", err)
208 os.Exit(1)
209 }
210 fmt.Println(traceEvent.RawLine)
211 }
Lorenz Brun9d6c4c72021-07-20 21:16:27 +0200212 case "loadimage":
213 loadimageCmd.Parse(os.Args[2:])
214 imagePath := loadimageCmd.Arg(0)
215 image, err := os.Open(imagePath)
216 if err != nil {
217 fmt.Fprintf(os.Stderr, "failed to open image file: %v\n", err)
218 os.Exit(1)
219 }
220 defer image.Close()
221
222 loadStream, err := debugClient.LoadImage(ctx)
223 if err != nil {
224 fmt.Fprintf(os.Stderr, "failed to start loading image: %v\n", err)
225 os.Exit(1)
226 }
227 buf := make([]byte, 64*1024)
228 for {
229 n, err := image.Read(buf)
230 if err != io.EOF && err != nil {
231 fmt.Fprintf(os.Stderr, "failed to read image: %v\n", err)
232 os.Exit(1)
233 }
234 if err == io.EOF && n == 0 {
235 break
236 }
237 if err := loadStream.Send(&apb.ImagePart{DataPart: buf[:n]}); err != nil {
238 fmt.Fprintf(os.Stderr, "failed to send image part: %v\n", err)
239 os.Exit(1)
240 }
241 }
242 if _, err := loadStream.CloseAndRecv(); err != nil {
243 fmt.Fprintf(os.Stderr, "image failed to load: %v\n", err)
244 }
245 fmt.Fprintf(os.Stderr, "Image loaded into Metropolis node\n")
Lorenz Brun878f5f92020-05-12 16:15:39 +0200246 }
247}