| package main |
| |
| import ( |
| "crypto/x509" |
| "errors" |
| "fmt" |
| "io" |
| |
| "github.com/spf13/cobra" |
| |
| "source.monogon.dev/metropolis/cli/metroctl/core" |
| "source.monogon.dev/metropolis/pkg/logtree" |
| "source.monogon.dev/metropolis/proto/api" |
| cpb "source.monogon.dev/metropolis/proto/common" |
| ) |
| |
| var nodeLogsCmd = &cobra.Command{ |
| Short: "Get/stream logs from node", |
| Use: "logs [node-id]", |
| Args: cobra.MinimumNArgs(1), |
| RunE: func(cmd *cobra.Command, args []string) error { |
| ctx := cmd.Context() |
| |
| // First connect to the main management service and figure out the node's IP |
| // address. |
| cc := dialAuthenticated(ctx) |
| mgmt := api.NewManagementClient(cc) |
| nodes, err := core.GetNodes(ctx, mgmt, fmt.Sprintf("node.id == %q", args[0])) |
| if err != nil { |
| return fmt.Errorf("when getting node info: %w", err) |
| } |
| |
| if len(nodes) == 0 { |
| return fmt.Errorf("no such node") |
| } |
| if len(nodes) > 1 { |
| return fmt.Errorf("expression matched more than one node") |
| } |
| n := nodes[0] |
| if n.Status == nil || n.Status.ExternalAddress == "" { |
| return fmt.Errorf("node has no external address") |
| } |
| |
| // TODO(q3k): save CA certificate on takeover |
| info, err := mgmt.GetClusterInfo(ctx, &api.GetClusterInfoRequest{}) |
| if err != nil { |
| return fmt.Errorf("couldn't get cluster info: %w", err) |
| } |
| cacert, err := x509.ParseCertificate(info.CaCertificate) |
| if err != nil { |
| return fmt.Errorf("remote CA certificate invalid: %w", err) |
| } |
| |
| fmt.Printf("Getting logs from %s (%s)...\n", n.Id, n.Status.ExternalAddress) |
| // Dial the actual node at its management port. |
| cl := dialAuthenticatedNode(ctx, n.Id, n.Status.ExternalAddress, cacert) |
| nmgmt := api.NewNodeManagementClient(cl) |
| |
| srv, err := nmgmt.Logs(ctx, &api.GetLogsRequest{ |
| Dn: "", |
| BacklogMode: api.GetLogsRequest_BACKLOG_ALL, |
| StreamMode: api.GetLogsRequest_STREAM_DISABLE, |
| Filters: []*cpb.LogFilter{ |
| { |
| Filter: &cpb.LogFilter_WithChildren_{ |
| WithChildren: &cpb.LogFilter_WithChildren{}, |
| }, |
| }, |
| }, |
| }) |
| if err != nil { |
| return fmt.Errorf("failed to get logs: %w", err) |
| } |
| for { |
| res, err := srv.Recv() |
| if errors.Is(err, io.EOF) { |
| fmt.Println("Done.") |
| break |
| } |
| if err != nil { |
| return fmt.Errorf("log stream failed: %w", err) |
| } |
| for _, entry := range res.BacklogEntries { |
| entry, err := logtree.LogEntryFromProto(entry) |
| if err != nil { |
| fmt.Printf("invalid entry: %v\n", err) |
| continue |
| } |
| fmt.Println(entry.String()) |
| } |
| } |
| |
| return nil |
| }, |
| } |