Skip to content
Open
2 changes: 2 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ jobs:
- run: mkdir test_results
- run:
name: Run tests
environment:
test_env_var: test_env_var_value
command: |
C:\Users\circleci\go\bin\gotestsum.exe --junitfile test_results/windows.xml
- store_test_results:
Expand Down
File renamed without changes.
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.git
Dockerfile*
docker-compose*.yml
.vagrant
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ packrd/
bin/golangci-lint
integration_tests/tmp
*.exe
/issues
/.vagrant
/bash_history
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ GOOS=$(shell go env GOOS)
GOARCH=$(shell go env GOARCH)

build: always
GO111MODULE=on .circleci/pack.sh
.circleci/pack
go build -o build/$(GOOS)/$(GOARCH)/circleci

build-all: build/linux/amd64/circleci build/darwin/amd64/circleci
Expand All @@ -16,15 +16,15 @@ build/%/amd64/circleci: always
clean:
GO111MODULE=off go clean -i
rm -rf build out docs dist
.circleci/pack.sh clean
.circleci/pack clean

.PHONY: test
test:
go test -v ./...
test_env_var=test_env_var_value go test -v ./...

.PHONY: cover
cover:
go test -race -coverprofile=coverage.txt ./...
test_env_var=test_env_var_value go test -race -coverprofile=coverage.txt ./...

.PHONY: lint
lint:
Expand All @@ -40,7 +40,7 @@ install-packr:

.PHONY: pack
pack:
bash .circleci/pack.sh
bash .circleci/pack

.PHONY: install-lint
install-lint:
Expand Down
18 changes: 18 additions & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :

require 'etc'

Vagrant.require_version ">= 2.0.0", "< 3.0.0"

Vagrant.configure("2") do |config|
config.vm.box = "bento/ubuntu-20.04"

config.vm.provider "virtualbox" do |vb|
vb.memory = "2000"
vb.cpus = Etc.nprocessors
end

config.vm.synced_folder "~/.circleci", '/home/vagrant/.circleci'
config.vm.provision "shell", path: "bin/provision"
end
62 changes: 62 additions & 0 deletions bin/provision
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash

apt_update() {
if [[ -z $apt_updated ]]
then
apt-get -qq update &&
apt_updated=true || exit
fi
}

if ! sudo -u vagrant docker run --rm hello-world
then
apt_update &&
apt-get -qq install apt-transport-https ca-certificates curl software-properties-common &&
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - &&
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable" &&
apt-get -qq update &&
apt-get -qq install docker-ce &&
usermod -aG docker vagrant &&
sudo -u vagrant docker run --rm hello-world ||
exit
fi

if ! docker-compose --version
then
curl --silent --show-error --fail --location \
"https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" \
-o /usr/local/bin/docker-compose &&
chmod +x /usr/local/bin/docker-compose &&
docker-compose --version ||
exit
fi

if ! grep '^cd /vagrant$' ~vagrant/.bashrc
then
sudo -u vagrant <<<'cd /vagrant' tee -a ~vagrant/.bashrc ||
exit
fi

if ! grep '^HISTFILE=/vagrant/bash_history$' ~vagrant/.bashrc
then
sudo -u vagrant tee -a <<<'HISTFILE=/vagrant/bash_history' ~vagrant/.bashrc ||
exit
fi

if ! sudo -u vagrant -i go version
then
curl --silent --show-error --fail --location \
"https://golang.org/dl/go1.15.2.linux-amd64.tar.gz" |
tar -C /usr/local -xz &&
sudo -u vagrant tee -a <<<'export PATH=$PATH:/usr/local/go/bin' ~vagrant/.bash_profile &&
sudo -u vagrant -i go version ||
exit 1
fi

if ! dpkg-query -s build-essential
then
apt_update &&
apt-get -qq install build-essential &&
dpkg-query -s build-essential ||
exit
fi
50 changes: 42 additions & 8 deletions local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"path"
"regexp"
"syscall"
"strings"

"github.com/CircleCI-Public/circleci-cli/api"
"github.com/CircleCI-Public/circleci-cli/api/graphql"
Expand Down Expand Up @@ -41,7 +42,11 @@ func UpdateBuildAgent() error {

func Execute(flags *pflag.FlagSet, cfg *settings.Config) error {

processedArgs, configPath := buildAgentArguments(flags)
processedArgs, configPath, err := buildAgentArguments(flags)
if err != nil {
return err
}

cl := graphql.NewClient(cfg.Host, cfg.Endpoint, cfg.Token, cfg.Debug)
configResponse, err := api.ConfigQuery(cl, configPath, pipeline.FabricatedValues())

Expand Down Expand Up @@ -87,7 +92,7 @@ func Execute(flags *pflag.FlagSet, cfg *settings.Config) error {
arguments := generateDockerCommand(processedConfigPath, image, pwd, processedArgs...)

if cfg.Debug {
_, err = fmt.Fprintf(os.Stderr, "Starting docker with args: %s", arguments)
_, err = fmt.Fprintf(os.Stderr, "Starting docker with args: %s\n", arguments)
if err != nil {
return err
}
Expand All @@ -97,6 +102,11 @@ func Execute(flags *pflag.FlagSet, cfg *settings.Config) error {
return errors.Wrap(err, "Could not find a `docker` executable on $PATH; please ensure that docker installed")
}

for _, e := range os.Environ() {
pair := strings.SplitN(e, "=", 2)
fmt.Fprintf(os.Stderr, "%s=%s\n", pair[0], pair[1])
}

err = syscall.Exec(dockerPath, arguments, os.Environ()) // #nosec
return errors.Wrap(err, "failed to execute docker")
}
Expand All @@ -118,7 +128,7 @@ func AddFlagsForDocumentation(flags *pflag.FlagSet) {
flags.String("revision", "", "Git Revision")
flags.String("branch", "", "Git branch")
flags.String("repo-url", "", "Git Url")
flags.StringArrayP("env", "e", nil, "Set environment variables, e.g. `-e VAR=VAL`")
flags.StringArrayP("env", "e", nil, "Set environment variables, e.g. `-e VAR=VAL` or `-e VAR`")
}

// Given the full set of flags that were passed to this command, return the path
Expand All @@ -129,21 +139,33 @@ func AddFlagsForDocumentation(flags *pflag.FlagSet) {
// GraphQL API, and feed the result of that into `build-agent`. The first step of
// that process is to find the local path to the config file. This is supplied with
// the `config` flag.
func buildAgentArguments(flags *pflag.FlagSet) ([]string, string) {
func buildAgentArguments(flags *pflag.FlagSet) ([]string, string, error) {

var result []string = []string{}
var outerErr error

// build a list of all supplied flags, that we will pass on to build-agent
flags.Visit(func(flag *pflag.Flag) {
if flag.Name != "config" && flag.Name != "debug" {
result = append(result, unparseFlag(flags, flag)...)
unparsedFlag, err := unparseFlag(flags, flag)

if err != nil {
outerErr = errors.Wrap(err, "Failed to build agent arguments")
}

result = append(result, unparsedFlag...)
}
})

if outerErr != nil {
return nil, "", outerErr
}

result = append(result, flags.Args()...)

configPath, _ := flags.GetString("config")

return result, configPath
return result, configPath, nil
}

func picardImage(output io.Writer) (string, error) {
Expand Down Expand Up @@ -302,18 +324,30 @@ func generateDockerCommand(configPath, image, pwd string, arguments ...string) [
// Convert the given flag back into a list of strings suitable to be passed on
// the command line to run docker.
// https://github.com/CircleCI-Public/circleci-cli/issues/391
func unparseFlag(flags *pflag.FlagSet, flag *pflag.Flag) []string {
func unparseFlag(flags *pflag.FlagSet, flag *pflag.Flag) ([]string, error) {
flagName := "--" + flag.Name
result := []string{}
switch flag.Value.Type() {
// A stringArray type argument is collapsed into a single flag:
// `--foo 1 --foo 2` will result in a single `foo` flag with an array of values.
case "stringArray":
for _, value := range flag.Value.(pflag.SliceValue).GetSlice() {
if flag.Name == "env" && ! strings.Contains(value, "=") {
// Bare env arg passed. Resolve from environment.
variableValue, ok := os.LookupEnv(value)
if !ok {
return nil, errors.New(fmt.Sprintf(
"Failed to resolve environment variable ‘%s’ in the environment.\n",
value,
))
} else {
value = fmt.Sprintf("%s=%s", value, variableValue)
}
}
result = append(result, flagName, value)
}
default:
result = append(result, flagName, flag.Value.String())
}
return result
return result, nil
}
16 changes: 11 additions & 5 deletions local/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ var _ = Describe("build", func() {
if testCase.expectedError != "" {
Expect(err).To(MatchError(testCase.expectedError))
}
args, configPath := buildAgentArguments(flags)
args, configPath, _ := buildAgentArguments(flags)
Expect(args).To(Equal(testCase.expectedArgs))
Expect(configPath).To(Equal(testCase.expectedConfigPath))

Expand Down Expand Up @@ -98,15 +98,21 @@ var _ = Describe("build", func() {
}),

Entry("many args, multiple envs", TestCase{
input: []string{"--env", "foo", "--env", "bar", "--env", "baz"},
input: []string{"--env", "foo=foo", "--env", "bar=bar", "--env", "baz=baz"},
expectedConfigPath: ".circleci/config.yml",
expectedArgs: []string{"--env", "foo", "--env", "bar", "--env", "baz"},
expectedArgs: []string{"--env", "foo=foo", "--env", "bar=bar", "--env", "baz=baz"},
}),

Entry("comma in env value (issue #440)", TestCase{
input: []string{"--env", "{\"json\":[\"like\",\"value\"]}"},
input: []string{"--env", "JSON={\"json\":[\"like\",\"value\"]}"},
expectedConfigPath: ".circleci/config.yml",
expectedArgs: []string{"--env", "{\"json\":[\"like\",\"value\"]}"},
expectedArgs: []string{"--env", "JSON={\"json\":[\"like\",\"value\"]}"},
}),

Entry("bare env value (issue #440)", TestCase{
input: []string{"--env", "test_env_var"},
expectedConfigPath: ".circleci/config.yml",
expectedArgs: []string{"--env", "test_env_var=test_env_var_value"},
}),

Entry("args that are not flags", TestCase{
Expand Down