blob: 84df60d3f5b9e13201dd175c5cbd3a7e472ea4d1 [file] [log] [blame]
// Copyright 2020 The Monogon Project Authors.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"flag"
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"strings"
"time"
"github.com/spf13/pflag"
"google.golang.org/grpc"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/kubectl/pkg/cmd/plugin"
"k8s.io/kubectl/pkg/util/logs"
"k8s.io/kubernetes/pkg/kubectl/cmd"
"source.monogon.dev/metropolis/pkg/logtree"
apb "source.monogon.dev/metropolis/proto/api"
)
func main() {
ctx := context.Background()
// Hardcode localhost since this should never be used to interface with a
// production node because of missing encryption & authentication
grpcClient, err := grpc.Dial("localhost:7837", grpc.WithInsecure())
if err != nil {
fmt.Printf("Failed to dial debug service (is it running): %v\n", err)
}
debugClient := apb.NewNodeDebugServiceClient(grpcClient)
if len(os.Args) < 2 {
fmt.Println("Please specify a subcommand")
os.Exit(1)
}
logsCmd := flag.NewFlagSet("logs", flag.ExitOnError)
logsTailN := logsCmd.Int("tail", -1, "Get last n lines (-1 = whole buffer, 0 = disable)")
logsStream := logsCmd.Bool("follow", false, "Stream log entries live from the system")
logsRecursive := logsCmd.Bool("recursive", false, "Get entries from entire DN subtree")
logsCmd.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s %s [options] dn\n", os.Args[0], os.Args[1])
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "Example:\n %s %s --tail 5 --follow init\n", os.Args[0], os.Args[1])
}
conditionCmd := flag.NewFlagSet("condition", flag.ExitOnError)
conditionCmd.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s %s [options] component_path\n", os.Args[0], os.Args[1])
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "Example:\n %s %s IPAssigned\n", os.Args[0], os.Args[1])
}
traceCmd := flag.NewFlagSet("trace", flag.ExitOnError)
traceCmd.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %v %v [options] tracer\n", os.Args[0], os.Args[1])
flag.PrintDefaults()
}
functionFilter := traceCmd.String("function_filter", "", "Only trace functions matched by this filter (comma-separated, supports wildcards via *)")
functionGraphFilter := traceCmd.String("function_graph_filter", "", "Only trace functions matched by this filter and their children (syntax same as function_filter)")
loadimageCmd := flag.NewFlagSet("loadimage", flag.ExitOnError)
loadimageCmd.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %v %v [options] image\n", os.Args[0], os.Args[1])
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "Example: %v %v [options] helloworld_oci.tar.gz\n", os.Args[0], os.Args[1])
}
switch os.Args[1] {
case "logs":
logsCmd.Parse(os.Args[2:])
dn := logsCmd.Arg(0)
req := &apb.GetLogsRequest{
Dn: dn,
BacklogMode: apb.GetLogsRequest_BACKLOG_DISABLE,
StreamMode: apb.GetLogsRequest_STREAM_DISABLE,
Filters: nil,
}
switch *logsTailN {
case 0:
case -1:
req.BacklogMode = apb.GetLogsRequest_BACKLOG_ALL
default:
req.BacklogMode = apb.GetLogsRequest_BACKLOG_COUNT
req.BacklogCount = int64(*logsTailN)
}
if *logsStream {
req.StreamMode = apb.GetLogsRequest_STREAM_UNBUFFERED
}
if *logsRecursive {
req.Filters = append(req.Filters, &apb.LogFilter{
Filter: &apb.LogFilter_WithChildren_{WithChildren: &apb.LogFilter_WithChildren{}},
})
}
stream, err := debugClient.GetLogs(ctx, req)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get logs: %v\n", err)
os.Exit(1)
}
for {
res, err := stream.Recv()
if err != nil {
if err == io.EOF {
os.Exit(0)
}
fmt.Fprintf(os.Stderr, "Failed to stream logs: %v\n", err)
os.Exit(1)
}
for _, entry := range res.BacklogEntries {
entry, err := logtree.LogEntryFromProto(entry)
if err != nil {
fmt.Printf("error decoding entry: %v", err)
continue
}
fmt.Println(entry.String())
}
}
case "kubectl":
// Always get a kubeconfig with cluster-admin (group system:masters),
// kubectl itself can impersonate
kubeconfigFile, err := ioutil.TempFile("", "dbg_kubeconfig")
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create kubeconfig temp file: %v\n", err)
os.Exit(1)
}
defer kubeconfigFile.Close()
defer os.Remove(kubeconfigFile.Name())
res, err := debugClient.GetDebugKubeconfig(ctx, &apb.GetDebugKubeconfigRequest{Id: "debug-user", Groups: []string{"system:masters"}})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get kubeconfig: %v\n", err)
os.Exit(1)
}
if _, err := kubeconfigFile.WriteString(res.DebugKubeconfig); err != nil {
fmt.Fprintf(os.Stderr, "Failed to write kubeconfig: %v\n", err)
os.Exit(1)
}
// This magic sets up everything as if this was just the kubectl
// binary. It sets the KUBECONFIG environment variable so that it knows
// where the Kubeconfig is located and forcibly overwrites the
// arguments so that the "wrapper" arguments are not visible to its
// flags parser.
// The base code is straight from:
// https://github.com/kubernetes/kubernetes/blob/master/cmd/kubectl/kubectl.go
os.Setenv("KUBECONFIG", kubeconfigFile.Name())
rand.Seed(time.Now().UnixNano())
pflag.CommandLine.SetNormalizeFunc(cliflag.WordSepNormalizeFunc)
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
logs.InitLogs()
defer logs.FlushLogs()
command := cmd.NewDefaultKubectlCommandWithArgs(cmd.NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes), os.Args[2:], os.Stdin, os.Stdout, os.Stderr)
command.SetArgs(os.Args[2:])
if err := command.Execute(); err != nil {
os.Exit(1)
}
case "trace":
traceCmd.Parse(os.Args[2:])
tracer := traceCmd.Arg(0)
var fgf []string
var ff []string
if len(*functionGraphFilter) > 0 {
fgf = strings.Split(*functionGraphFilter, ",")
}
if len(*functionFilter) > 0 {
ff = strings.Split(*functionFilter, ",")
}
req := apb.TraceRequest{
GraphFunctionFilter: fgf,
FunctionFilter: ff,
Tracer: tracer,
}
traceEvents, err := debugClient.Trace(ctx, &req)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to trace: %v", err)
os.Exit(1)
}
for {
traceEvent, err := traceEvents.Recv()
if err != nil {
if err == io.EOF {
break
}
fmt.Fprintf(os.Stderr, "stream aborted unexpectedly: %v", err)
os.Exit(1)
}
fmt.Println(traceEvent.RawLine)
}
case "loadimage":
loadimageCmd.Parse(os.Args[2:])
imagePath := loadimageCmd.Arg(0)
image, err := os.Open(imagePath)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to open image file: %v\n", err)
os.Exit(1)
}
defer image.Close()
loadStream, err := debugClient.LoadImage(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to start loading image: %v\n", err)
os.Exit(1)
}
buf := make([]byte, 64*1024)
for {
n, err := image.Read(buf)
if err != io.EOF && err != nil {
fmt.Fprintf(os.Stderr, "failed to read image: %v\n", err)
os.Exit(1)
}
if err == io.EOF && n == 0 {
break
}
if err := loadStream.Send(&apb.ImagePart{DataPart: buf[:n]}); err != nil {
fmt.Fprintf(os.Stderr, "failed to send image part: %v\n", err)
os.Exit(1)
}
}
if _, err := loadStream.CloseAndRecv(); err != nil {
fmt.Fprintf(os.Stderr, "image failed to load: %v\n", err)
}
fmt.Fprintf(os.Stderr, "Image loaded into Metropolis node\n")
}
}