Skip to content

Commit b636765

Browse files
authored
Merge pull request #1784 from buildkite/backport-env-parsing
Backport #1781 to 3.39
2 parents 029b29e + 4b444c6 commit b636765

11 files changed

+250
-42
lines changed

bootstrap/integration/artifact_integration_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func TestArtifactsUploadAfterCommand(t *testing.T) {
2727
})
2828

2929
// Mock out the artifact calls
30-
agent := tester.MustMock(t, "buildkite-agent")
30+
agent := tester.MockAgent(t)
3131
agent.
3232
Expect("meta-data", "exists", "buildkite:git:commit").
3333
AndExitWith(0)
@@ -56,7 +56,7 @@ func TestArtifactsUploadAfterCommandFails(t *testing.T) {
5656
})
5757

5858
// Mock out the artifact calls
59-
agent := tester.MustMock(t, "buildkite-agent")
59+
agent := tester.MockAgent(t)
6060
agent.
6161
Expect("meta-data", "exists", "buildkite:git:commit").
6262
AndExitWith(0)
@@ -91,7 +91,7 @@ func TestArtifactsUploadAfterCommandHookFails(t *testing.T) {
9191
})
9292

9393
// Mock out the artifact calls
94-
agent := tester.MustMock(t, "buildkite-agent")
94+
agent := tester.MockAgent(t)
9595
agent.
9696
Expect("meta-data", "exists", "buildkite:git:commit").
9797
AndExitWith(0)

bootstrap/integration/bootstrap_tester.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package integration
33
import (
44
"bufio"
55
"bytes"
6+
"encoding/json"
67
"fmt"
78
"io"
89
"io/ioutil"
@@ -176,6 +177,17 @@ func (b *BootstrapTester) HasMock(name string) bool {
176177
return false
177178
}
178179

180+
// MockAgent creates a mock for the buildkite-agent binary
181+
func (b *BootstrapTester) MockAgent(t *testing.T) *bintest.Mock {
182+
agent := b.MustMock(t, "buildkite-agent")
183+
agent.Expect("env").
184+
Min(0).
185+
Max(bintest.InfiniteTimes).
186+
AndCallFunc(mockEnvAsJSONOnStdout(b))
187+
188+
return agent
189+
}
190+
179191
// writeHookScript generates a buildkite-agent hook script that calls a mock binary
180192
func (b *BootstrapTester) writeHookScript(m *bintest.Mock, name string, dir string, args ...string) (string, error) {
181193
hookScript := filepath.Join(dir, name)
@@ -234,7 +246,7 @@ func (b *BootstrapTester) ExpectGlobalHook(name string) *bintest.Expectation {
234246
func (b *BootstrapTester) Run(t *testing.T, env ...string) error {
235247
// Mock out the meta-data calls to the agent after checkout
236248
if !b.HasMock("buildkite-agent") {
237-
agent := b.MustMock(t, "buildkite-agent")
249+
agent := b.MockAgent(t)
238250
agent.
239251
Expect("meta-data", "exists", "buildkite:git:commit").
240252
Optionally().
@@ -348,6 +360,31 @@ func (b *BootstrapTester) Close() error {
348360
return nil
349361
}
350362

363+
func mockEnvAsJSONOnStdout(b *BootstrapTester) func(c *bintest.Call) {
364+
return func(c *bintest.Call) {
365+
envMap := map[string]string{}
366+
367+
for _, e := range b.Env { // The env from the bootstrap tester
368+
k, v, _ := strings.Cut(e, "=")
369+
envMap[k] = v
370+
}
371+
372+
for _, e := range c.Env { // The env from the call
373+
k, v, _ := strings.Cut(e, "=")
374+
envMap[k] = v
375+
}
376+
377+
envJSON, err := json.Marshal(envMap)
378+
if err != nil {
379+
fmt.Println("Failed to marshal env map in mocked agent call:", err)
380+
c.Exit(1)
381+
}
382+
383+
c.Stdout.Write(envJSON)
384+
c.Exit(0)
385+
}
386+
}
387+
351388
type testLogWriter struct {
352389
io.Writer
353390
sync.Mutex

bootstrap/integration/checkout_integration_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
// Commit: Example Human <legit@example.com>
2424
// CommitDate: Thu Jan 15 11:05:16 2015 +0800
2525
//
26-
// hello world
26+
// hello world
2727
var commitPattern = bintest.MatchPattern(`(?ms)\Acommit [0-9a-f]+\n.*^Author:`)
2828

2929
// Enable an experiment, returning a function to restore the previous state.
@@ -72,7 +72,7 @@ func TestCheckingOutGitHubPullRequestsWithGitMirrorsExperiment(t *testing.T) {
7272
})
7373

7474
// Mock out the meta-data calls to the agent after checkout
75-
agent := tester.MustMock(t, "buildkite-agent")
75+
agent := tester.MockAgent(t)
7676
agent.Expect("meta-data", "exists", "buildkite:git:commit").AndExitWith(1)
7777
agent.Expect("meta-data", "set", "buildkite:git:commit").WithStdin(commitPattern)
7878

@@ -126,7 +126,7 @@ func TestWithResolvingCommitExperiment(t *testing.T) {
126126
}
127127

128128
// Mock out the meta-data calls to the agent after checkout
129-
agent := tester.MustMock(t, "buildkite-agent")
129+
agent := tester.MockAgent(t)
130130
agent.Expect("meta-data", "exists", "buildkite:git:commit").AndExitWith(1)
131131
agent.Expect("meta-data", "set", "buildkite:git:commit").WithStdin(commitPattern)
132132

@@ -177,7 +177,7 @@ func TestCheckingOutLocalGitProject(t *testing.T) {
177177
}
178178

179179
// Mock out the meta-data calls to the agent after checkout
180-
agent := tester.MustMock(t, "buildkite-agent")
180+
agent := tester.MockAgent(t)
181181
agent.Expect("meta-data", "exists", "buildkite:git:commit").AndExitWith(1)
182182
agent.Expect("meta-data", "set", "buildkite:git:commit").WithStdin(commitPattern)
183183

@@ -260,7 +260,7 @@ func TestCheckingOutLocalGitProjectWithSubmodules(t *testing.T) {
260260
}
261261

262262
// Mock out the meta-data calls to the agent after checkout
263-
agent := tester.MustMock(t, "buildkite-agent")
263+
agent := tester.MockAgent(t)
264264
agent.Expect("meta-data", "exists", "buildkite:git:commit").AndExitWith(1)
265265
agent.Expect("meta-data", "set", "buildkite:git:commit").WithStdin(commitPattern)
266266

@@ -334,7 +334,7 @@ func TestCheckingOutLocalGitProjectWithSubmodulesDisabled(t *testing.T) {
334334
}
335335

336336
// Mock out the meta-data calls to the agent after checkout
337-
agent := tester.MustMock(t, "buildkite-agent")
337+
agent := tester.MockAgent(t)
338338
agent.Expect("meta-data", "exists", "buildkite:git:commit").AndExitWith(1)
339339
agent.Expect("meta-data", "set", "buildkite:git:commit").WithStdin(commitPattern)
340340

@@ -385,7 +385,7 @@ func TestCheckingOutShallowCloneOfLocalGitProject(t *testing.T) {
385385
}
386386

387387
// Mock out the meta-data calls to the agent after checkout
388-
agent := tester.MustMock(t, "buildkite-agent")
388+
agent := tester.MockAgent(t)
389389
agent.Expect("meta-data", "exists", "buildkite:git:commit").AndExitWith(1)
390390
agent.Expect("meta-data", "set", "buildkite:git:commit").WithStdin(commitPattern)
391391

@@ -401,7 +401,7 @@ func TestCheckingOutSetsCorrectGitMetadataAndSendsItToBuildkite(t *testing.T) {
401401
}
402402
defer tester.Close()
403403

404-
agent := tester.MustMock(t, "buildkite-agent")
404+
agent := tester.MockAgent(t)
405405
agent.Expect("meta-data", "exists", "buildkite:git:commit").AndExitWith(1)
406406
agent.Expect("meta-data", "set", "buildkite:git:commit").WithStdin(commitPattern)
407407

@@ -514,7 +514,7 @@ func TestCleaningAnExistingCheckout(t *testing.T) {
514514
}
515515

516516
// Mock out the meta-data calls to the agent after checkout
517-
agent := tester.MustMock(t, "buildkite-agent")
517+
agent := tester.MockAgent(t)
518518
agent.
519519
Expect("meta-data", "exists", "buildkite:git:commit").
520520
AndExitWith(0)
@@ -535,7 +535,7 @@ func TestForcingACleanCheckout(t *testing.T) {
535535
defer tester.Close()
536536

537537
// Mock out the meta-data calls to the agent after checkout
538-
agent := tester.MustMock(t, "buildkite-agent")
538+
agent := tester.MockAgent(t)
539539
agent.
540540
Expect("meta-data", "exists", "buildkite:git:commit").
541541
AndExitWith(0)
@@ -564,7 +564,7 @@ func TestCheckoutOnAnExistingRepositoryWithoutAGitFolder(t *testing.T) {
564564
t.Fatal(err)
565565
}
566566

567-
agent := tester.MustMock(t, "buildkite-agent")
567+
agent := tester.MockAgent(t)
568568
agent.
569569
Expect("meta-data", "exists", "buildkite:git:commit").
570570
AndExitWith(0)
@@ -735,7 +735,7 @@ func TestGitMirrorEnv(t *testing.T) {
735735
}
736736

737737
// Mock out the meta-data calls to the agent after checkout
738-
agent := tester.MustMock(t, "buildkite-agent")
738+
agent := tester.MockAgent(t)
739739
agent.Expect("meta-data", "exists", "buildkite:git:commit").AndExitWith(1)
740740
agent.Expect("meta-data", "set", "buildkite:git:commit").WithStdin(commitPattern)
741741

bootstrap/integration/command_integration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestPreExitHooksRunsAfterCommandFails(t *testing.T) {
5050
defer tester.Close()
5151

5252
// Mock out the meta-data calls to the agent after checkout
53-
agent := tester.MustMock(t, "buildkite-agent")
53+
agent := tester.MockAgent(t)
5454
agent.
5555
Expect("meta-data", "exists", "buildkite:git:commit").
5656
AndExitWith(0)

bootstrap/integration/docker_integration_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestRunningCommandWithDocker(t *testing.T) {
2424
defer tester.Close()
2525

2626
// Mock out the meta-data calls to the agent after checkout
27-
agent := tester.MustMock(t, "buildkite-agent")
27+
agent := tester.MockAgent(t)
2828
agent.
2929
Expect("meta-data", "exists", "buildkite:git:commit").
3030
AndExitWith(0)
@@ -57,7 +57,7 @@ func TestRunningCommandWithDockerAndCustomDockerfile(t *testing.T) {
5757
defer tester.Close()
5858

5959
// Mock out the meta-data calls to the agent after checkout
60-
agent := tester.MustMock(t, "buildkite-agent")
60+
agent := tester.MockAgent(t)
6161
agent.
6262
Expect("meta-data", "exists", "buildkite:git:commit").
6363
AndExitWith(0)
@@ -91,7 +91,7 @@ func TestRunningFailingCommandWithDocker(t *testing.T) {
9191
defer tester.Close()
9292

9393
// Mock out the meta-data calls to the agent after checkout
94-
agent := tester.MustMock(t, "buildkite-agent")
94+
agent := tester.MockAgent(t)
9595
agent.
9696
Expect("meta-data", "exists", "buildkite:git:commit").
9797
AndExitWith(0)
@@ -130,7 +130,7 @@ func TestRunningCommandWithDockerCompose(t *testing.T) {
130130
defer tester.Close()
131131

132132
// Mock out the meta-data calls to the agent after checkout
133-
agent := tester.MustMock(t, "buildkite-agent")
133+
agent := tester.MockAgent(t)
134134
agent.
135135
Expect("meta-data", "exists", "buildkite:git:commit").
136136
AndExitWith(0)
@@ -163,7 +163,7 @@ func TestRunningFailingCommandWithDockerCompose(t *testing.T) {
163163
defer tester.Close()
164164

165165
// Mock out the meta-data calls to the agent after checkout
166-
agent := tester.MustMock(t, "buildkite-agent")
166+
agent := tester.MockAgent(t)
167167
agent.
168168
Expect("meta-data", "exists", "buildkite:git:commit").
169169
AndExitWith(0)
@@ -203,7 +203,7 @@ func TestRunningCommandWithDockerComposeAndExtraConfig(t *testing.T) {
203203
defer tester.Close()
204204

205205
// Mock out the meta-data calls to the agent after checkout
206-
agent := tester.MustMock(t, "buildkite-agent")
206+
agent := tester.MockAgent(t)
207207
agent.
208208
Expect("meta-data", "exists", "buildkite:git:commit").
209209
AndExitWith(0)
@@ -237,7 +237,7 @@ func TestRunningCommandWithDockerComposeAndBuildAll(t *testing.T) {
237237
defer tester.Close()
238238

239239
// Mock out the meta-data calls to the agent after checkout
240-
agent := tester.MustMock(t, "buildkite-agent")
240+
agent := tester.MockAgent(t)
241241
agent.
242242
Expect("meta-data", "exists", "buildkite:git:commit").
243243
AndExitWith(0)

bootstrap/integration/hooks_integration_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,6 @@ func TestDirectoryPassesBetweenHooks(t *testing.T) {
171171
}
172172

173173
func TestDirectoryPassesBetweenHooksIgnoredUnderExit(t *testing.T) {
174-
t.Parallel()
175-
176174
tester, err := NewBootstrapTester()
177175
if err != nil {
178176
t.Fatal(err)
@@ -367,7 +365,7 @@ func TestPreExitHooksFireAfterHookFailures(t *testing.T) {
367365
}
368366
defer tester.Close()
369367

370-
agent := tester.MustMock(t, "buildkite-agent")
368+
agent := tester.MockAgent(t)
371369

372370
tester.ExpectGlobalHook(tc.failingHook).
373371
Once().

bootstrap/shell/test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ func NewTestShell(t *testing.T) *Shell {
2121

2222
if os.Getenv(`DEBUG_SHELL`) == "1" {
2323
sh.Logger = TestingLogger{T: t}
24+
sh.Writer = os.Stdout
2425
}
2526

2627
// Windows requires certain env variables to be present

clicommand/env.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package clicommand
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"strings"
8+
9+
"github.com/urfave/cli"
10+
)
11+
12+
const envDescription = `Usage:
13+
buildkite-agent env [options]
14+
15+
Description:
16+
Prints out the environment of the current process as a JSON object, easily parsable by other programs. Used when
17+
executing hooks to discover changes that hooks make to the environment.
18+
19+
Example:
20+
$ buildkite-agent env
21+
22+
Prints the environment passed into the process
23+
`
24+
25+
var EnvCommand = cli.Command{
26+
Name: "env",
27+
Usage: "Prints out the environment of the current process as a JSON object",
28+
Description: envDescription,
29+
Flags: []cli.Flag{
30+
cli.BoolFlag{
31+
Name: "pretty",
32+
Usage: "Pretty print the JSON output",
33+
EnvVar: "BUILDKITE_AGENT_ENV_PRETTY",
34+
},
35+
},
36+
Action: func(c *cli.Context) error {
37+
env := os.Environ()
38+
envMap := make(map[string]string, len(env))
39+
40+
for _, e := range env {
41+
k, v, _ := strings.Cut(e, "=")
42+
envMap[k] = v
43+
}
44+
45+
var (
46+
envJSON []byte
47+
err error
48+
)
49+
50+
if c.Bool("pretty") {
51+
envJSON, err = json.MarshalIndent(envMap, "", " ")
52+
} else {
53+
envJSON, err = json.Marshal(envMap)
54+
}
55+
56+
// let's be polite to interactive shells etc.
57+
envJSON = append(envJSON, '\n')
58+
59+
if err != nil {
60+
fmt.Fprintf(os.Stderr, "Error marshalling JSON: %v\n", err)
61+
os.Exit(1)
62+
}
63+
64+
if _, err := os.Stdout.Write(envJSON); err != nil {
65+
fmt.Fprintf(os.Stderr, "Error writing JSON to stdout: %v\n", err)
66+
os.Exit(1)
67+
}
68+
69+
return nil
70+
},
71+
}

0 commit comments

Comments
 (0)