Skip to content

Commit

Permalink
buildkite-agent env has --from-env-file (bool) and --print NAME flags
Browse files Browse the repository at this point in the history
This solves the problem of safely parsing BUILDKITE_ENV_FILE, and allows
for usage like this in a pre-command hook or similar:

    local branch
    branch="$(buildkite-agent env --from-env-file --print BUILDKITE_BRANCH)"
    if [[ branch != "main" ]]; then
        echo "This agent only builds branch 'main'"
        exit 42
    fi
  • Loading branch information
pda committed Oct 26, 2022
1 parent e6c28ec commit a5cc6a9
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 5 deletions.
71 changes: 66 additions & 5 deletions clicommand/env.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package clicommand

import (
"bufio"
"encoding/json"
"fmt"
"os"
"strconv"
"strings"

"github.com/urfave/cli"
Expand Down Expand Up @@ -32,14 +34,33 @@ var EnvCommand = cli.Command{
Usage: "Pretty print the JSON output",
EnvVar: "BUILDKITE_AGENT_ENV_PRETTY",
},
cli.BoolFlag{
Name: "from-env-file",
Usage: "Source environment from file described by $BUILDKITE_ENV_FILE",
},
cli.StringFlag{
Name: "print",
Usage: "Print a single environment variable by `NAME` as raw text followed by a newline",
},
},
Action: func(c *cli.Context) error {
env := os.Environ()
envMap := make(map[string]string, len(env))
var envMap map[string]string

if c.Bool("from-env-file") {
envMap = mustLoadEnvFile(os.Getenv("BUILDKITE_ENV_FILE"))
} else {
env := os.Environ()
envMap = make(map[string]string, len(env))

for _, e := range env {
k, v, _ := strings.Cut(e, "=")
envMap[k] = v
}
}

for _, e := range env {
k, v, _ := strings.Cut(e, "=")
envMap[k] = v
if name := c.String("print"); name != "" {
fmt.Println(envMap[name])
return nil
}

var (
Expand Down Expand Up @@ -69,3 +90,43 @@ var EnvCommand = cli.Command{
return nil
},
}

func mustLoadEnvFile(path string) map[string]string {
envMap := make(map[string]string)

if path == "" {
fmt.Fprintln(os.Stderr, "BUILDKITE_ENV_FILE not set")
os.Exit(1)
}

f, err := os.Open(path)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not open BUILDKITE_ENV_FILE: %v\n", err)
os.Exit(1)
}

scanner := bufio.NewScanner(f)

for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}

name, quotedValue, ok := strings.Cut(line, "=")
if !ok {
fmt.Fprintf(os.Stderr, "Unexpected format in BUILDKITE_ENV_FILE %s\n", path)
os.Exit(1)
}

value, err := strconv.Unquote(quotedValue)
if err != nil {
fmt.Fprintf(os.Stderr, "Error unquoting value: %v\n", err)
os.Exit(1)
}

envMap[name] = value
}

return envMap
}
27 changes: 27 additions & 0 deletions clicommand/env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package clicommand

import (
"fmt"
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestMustLoadEnvFile(t *testing.T) {
f, err := os.CreateTemp("", t.Name())
if err != nil {
t.Error(err)
}
data := map[string]string{
"HELLO": "world",
"FOO": "bar\n\"bar\"\n`black hat`\r\n$(have you any root)",
}
for name, value := range data {
fmt.Fprintf(f, "%s=%q\n", name, value)
}

result := mustLoadEnvFile(f.Name())

assert.Equal(t, data, result, "data should round-trip via env file")
}

0 comments on commit a5cc6a9

Please sign in to comment.