Skip to content

Commit f175ad3

Browse files
committed
AGENT-580: Add agent gather bootstrap command
Add "openshift-install agent gather bootstrap" command to collect debugging data from the rendezvous host during agent-based installations. The command determines the rendezvous IP from the asset store, SSHs to the host, runs agent-gather, and pulls the resulting archive locally. As with "openshift-install gather bootstrap", the bootstrap SSH key pair is loaded automatically from the asset store, additional keys can be specified with the --key flag, and keys from the user's SSH agent or ~/.ssh/ are always included as well. Assisted-by: Claude Code
1 parent 3c87d4d commit f175ad3

3 files changed

Lines changed: 152 additions & 0 deletions

File tree

cmd/openshift-install/agent.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func newAgentCmd(ctx context.Context) *cobra.Command {
2929
agentCmd.AddCommand(newAgentCreateCmd(ctx))
3030
agentCmd.AddCommand(agent.NewWaitForCmd())
3131
agentCmd.AddCommand(newAgentGraphCmd())
32+
agentCmd.AddCommand(agent.NewGatherCmd())
3233
return agentCmd
3334
}
3435

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package agent
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"time"
8+
9+
"github.com/sirupsen/logrus"
10+
"github.com/spf13/cobra"
11+
12+
"github.com/openshift/installer/cmd/openshift-install/command"
13+
agentpkg "github.com/openshift/installer/pkg/agent"
14+
assetstore "github.com/openshift/installer/pkg/asset/store"
15+
"github.com/openshift/installer/pkg/asset/tls"
16+
)
17+
18+
var agentGatherOpts struct {
19+
sshKeys []string
20+
}
21+
22+
// NewGatherCmd creates the commands for gathering debug data from an agent-based installation.
23+
func NewGatherCmd() *cobra.Command {
24+
cmd := &cobra.Command{
25+
Use: "gather",
26+
Short: "Gather debugging data for a failed agent-based installation",
27+
Long: `Gather debugging data for a failed agent-based installation.
28+
29+
When an agent-based installation fails, this command collects debugging
30+
data from the rendezvous host to help diagnose the issue.`,
31+
RunE: func(cmd *cobra.Command, args []string) error {
32+
return cmd.Help()
33+
},
34+
}
35+
36+
cmd.AddCommand(newAgentGatherCmd())
37+
return cmd
38+
}
39+
40+
func newAgentGatherCmd() *cobra.Command {
41+
cmd := &cobra.Command{
42+
Use: "bootstrap",
43+
Short: "Gather debugging data from the rendezvous host",
44+
Args: cobra.ExactArgs(0),
45+
Run: func(_ *cobra.Command, _ []string) {
46+
cleanup := command.SetupFileHook(command.RootOpts.Dir)
47+
defer cleanup()
48+
49+
bundlePath, err := runAgentGatherCmd(command.RootOpts.Dir)
50+
if err != nil {
51+
logrus.Fatal(err)
52+
}
53+
logrus.Infof("Agent gather logs captured here %q", bundlePath)
54+
},
55+
}
56+
57+
cmd.PersistentFlags().StringArrayVar(&agentGatherOpts.sshKeys, "key", []string{},
58+
"Path to SSH private keys that should be used for authentication. "+
59+
"If no key was provided, SSH private keys from user's environment will be used")
60+
return cmd
61+
}
62+
63+
func runAgentGatherCmd(directory string) (string, error) {
64+
ctx := context.TODO()
65+
66+
store, err := assetstore.NewStore(directory)
67+
if err != nil {
68+
return "", fmt.Errorf("failed to create asset store: %w", err)
69+
}
70+
71+
rendezvousIP, err := agentpkg.FindRendezvousIPFromAssetStore(store)
72+
if err != nil {
73+
return "", fmt.Errorf("failed to determine rendezvous host: %w", err)
74+
}
75+
logrus.Infof("Rendezvous host IP: %s", rendezvousIP)
76+
77+
// add the bootstrap SSH key pair to the sshKeys list automatically
78+
bootstrapSSHKeyPair := &tls.BootstrapSSHKeyPair{}
79+
if err := store.Fetch(ctx, bootstrapSSHKeyPair); err != nil {
80+
logrus.Debugf("Failed to fetch bootstrap SSH key pair: %v", err)
81+
} else {
82+
tmpfile, err := os.CreateTemp("", "bootstrap-ssh")
83+
if err != nil {
84+
return "", err
85+
}
86+
defer os.Remove(tmpfile.Name())
87+
if _, err := tmpfile.Write(bootstrapSSHKeyPair.Private()); err != nil {
88+
return "", err
89+
}
90+
if err := tmpfile.Close(); err != nil {
91+
return "", err
92+
}
93+
agentGatherOpts.sshKeys = append(agentGatherOpts.sshKeys, tmpfile.Name())
94+
}
95+
96+
gatherID := time.Now().Format("20060102150405")
97+
98+
bundlePath, err := agentpkg.PullAgentGatherArchive(rendezvousIP, agentGatherOpts.sshKeys, directory, gatherID)
99+
if err != nil {
100+
return "", fmt.Errorf("failed to gather data from rendezvous host: %w", err)
101+
}
102+
103+
return bundlePath, nil
104+
}

pkg/agent/gather.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package agent
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"path"
7+
"path/filepath"
8+
"strconv"
9+
10+
"github.com/sirupsen/logrus"
11+
12+
gatherssh "github.com/openshift/installer/pkg/gather/ssh"
13+
)
14+
15+
// PullAgentGatherArchive SSHs to the rendezvous host and runs the
16+
// agent-gather script, pulling the resulting tar.xz archive to the
17+
// local directory.
18+
func PullAgentGatherArchive(rendezvousIP string, sshKeys []string, directory, gatherID string) (string, error) {
19+
logrus.Info("Pulling agent-gather data from the rendezvous host")
20+
21+
address := net.JoinHostPort(rendezvousIP, strconv.Itoa(22))
22+
client, err := gatherssh.NewClient("core", address, sshKeys)
23+
if err != nil {
24+
return "", fmt.Errorf("failed to create SSH client for rendezvous host %s: %w", rendezvousIP, err)
25+
}
26+
27+
// Run agent-gather with -i so it writes to a predictable path
28+
cmd := fmt.Sprintf("sudo /usr/local/bin/agent-gather -i %s", gatherID)
29+
if err := gatherssh.Run(client, cmd); err != nil {
30+
return "", fmt.Errorf("failed to run agent-gather on rendezvous host %s: %w", rendezvousIP, err)
31+
}
32+
33+
archiveName := fmt.Sprintf("agent-gather-%s.tar.xz", gatherID)
34+
remoteFile := path.Join("/home/core", archiveName)
35+
localFile := filepath.Join(directory, archiveName)
36+
if err := gatherssh.PullFileTo(client, remoteFile, localFile); err != nil {
37+
return "", fmt.Errorf("failed to pull agent-gather archive: %w", err)
38+
}
39+
40+
absPath, err := filepath.Abs(localFile)
41+
if err != nil {
42+
return "", fmt.Errorf("failed to get absolute path: %w", err)
43+
}
44+
45+
logrus.Info("Successfully pulled agent-gather data")
46+
return absPath, nil
47+
}

0 commit comments

Comments
 (0)