Skip to content

Commit 310c9bd

Browse files
authored
Add command for agent install (#695)
1 parent 252ea51 commit 310c9bd

8 files changed

Lines changed: 1017 additions & 5 deletions

File tree

cmd/agent/install.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package agent
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"runtime"
9+
"strings"
10+
11+
"github.com/alecthomas/kong"
12+
"github.com/buildkite/cli/v3/internal/cli"
13+
"github.com/buildkite/cli/v3/pkg/cmd/factory"
14+
15+
bkAgent "github.com/buildkite/cli/v3/internal/agent"
16+
)
17+
18+
var (
19+
userArch = runtime.GOARCH
20+
userOS = runtime.GOOS
21+
)
22+
23+
// InstallCmd allows users to define which agent version they want to install
24+
// We will take care of OS/arch in the command itself
25+
type InstallCmd struct {
26+
Version string `help:"Specify an agent version to install" default:"latest"`
27+
Dest string `help:"Destination directory for the binary" type:"path"`
28+
Cluster string `help:"Cluster UUID to create the agent token on (default: the \"Default\" cluster)" optional:""`
29+
NoToken bool `help:"Skip creating an agent token and config file" name:"no-token"`
30+
ConfigPath string `help:"Path to write the agent config file" type:"path"`
31+
}
32+
33+
func (i *InstallCmd) Help() string {
34+
return `Install the buildkite-agent binary locally.
35+
36+
By default, this also creates an agent token on the Default cluster and writes
37+
a minimal config file so the agent is ready to start.
38+
39+
Examples:
40+
# Install the latest version of the agent
41+
$ bk agent install
42+
43+
# Install a specific version
44+
$ bk agent install --version "3.112.0"
45+
46+
# Install to a custom location
47+
$ bk agent install --dest ~/.local/bin
48+
49+
# Install without creating a token/config
50+
$ bk agent install --no-token
51+
`
52+
}
53+
54+
func (i *InstallCmd) Run(kongCtx *kong.Context, globals cli.GlobalFlags) error {
55+
dest := i.Dest
56+
if dest == "" {
57+
dest = bkAgent.DefaultBinDir(userOS)
58+
}
59+
60+
// Check for existing installations in PATH
61+
if existing := bkAgent.FindExisting(userOS); existing != nil {
62+
destBinary := filepath.Join(dest, bkAgent.BinaryName(userOS))
63+
if existing.Path != destBinary {
64+
fmt.Printf("Warning: existing buildkite-agent found at %s", existing.Path)
65+
if existing.Version != "" {
66+
fmt.Printf(" (%s)", existing.Version)
67+
}
68+
fmt.Println()
69+
fmt.Printf(" The new install at %s may be shadowed in your PATH.\n", destBinary)
70+
fmt.Println()
71+
}
72+
}
73+
74+
if err := os.MkdirAll(dest, 0o755); err != nil {
75+
return fmt.Errorf("creating destination directory: %w", err)
76+
}
77+
78+
version := i.Version
79+
if version == "latest" {
80+
resolved, err := bkAgent.ResolveLatestVersion()
81+
if err != nil {
82+
return fmt.Errorf("resolving latest version: %w", err)
83+
}
84+
version = resolved
85+
}
86+
87+
version = strings.TrimPrefix(version, "v")
88+
89+
downloadURL := bkAgent.BuildDownloadURL(version, userOS, userArch)
90+
fmt.Printf("Downloading buildkite-agent v%s for %s/%s...\n", version, userOS, userArch)
91+
92+
tmpFile, err := bkAgent.DownloadToTemp(downloadURL)
93+
if err != nil {
94+
return fmt.Errorf("downloading agent: %w", err)
95+
}
96+
defer os.Remove(tmpFile)
97+
98+
// Verify the download checksum
99+
fmt.Println("Verifying checksum...")
100+
sumsURL := bkAgent.BuildSHA256SumsURL(version)
101+
archiveFilename := filepath.Base(downloadURL)
102+
expectedHash, err := bkAgent.FetchExpectedSHA256(sumsURL, archiveFilename)
103+
if err != nil {
104+
return fmt.Errorf("fetching checksum: %w", err)
105+
}
106+
if err := bkAgent.VerifySHA256(tmpFile, expectedHash); err != nil {
107+
return fmt.Errorf("checksum verification failed: %w", err)
108+
}
109+
110+
if err := bkAgent.ExtractBinary(tmpFile, dest, userOS); err != nil {
111+
return fmt.Errorf("extracting agent: %w", err)
112+
}
113+
114+
binaryName := bkAgent.BinaryName(userOS)
115+
fmt.Printf("Installed buildkite-agent to %s\n", filepath.Join(dest, binaryName))
116+
117+
if !i.NoToken {
118+
if err := i.createTokenAndConfig(globals); err != nil {
119+
return err
120+
}
121+
}
122+
123+
return nil
124+
}
125+
126+
func (i *InstallCmd) createTokenAndConfig(globals cli.GlobalFlags) error {
127+
f, err := factory.New(factory.WithDebug(globals.EnableDebug()))
128+
if err != nil {
129+
return fmt.Errorf("initializing API client: %w", err)
130+
}
131+
132+
ctx := context.Background()
133+
org := f.Config.OrganizationSlug()
134+
135+
clusterID, err := bkAgent.FindCluster(ctx, f, org, i.Cluster)
136+
if err != nil {
137+
return fmt.Errorf("finding default cluster: %w", err)
138+
}
139+
140+
fmt.Println("Creating agent token...")
141+
token, err := bkAgent.CreateAgentToken(ctx, f, org, clusterID, "Token created by bk agent install")
142+
if err != nil {
143+
return fmt.Errorf("creating agent token: %w", err)
144+
}
145+
146+
configPath := i.ConfigPath
147+
if configPath == "" {
148+
configPath = bkAgent.DefaultConfigPath(userOS)
149+
}
150+
151+
buildPath := bkAgent.DefaultBuildPath(userOS)
152+
if err := bkAgent.WriteAgentConfig(configPath, token, buildPath); err != nil {
153+
return fmt.Errorf("writing agent config: %w", err)
154+
}
155+
156+
fmt.Printf("Agent config written to %s\n", configPath)
157+
return nil
158+
}

0 commit comments

Comments
 (0)