github.com/stevenmatthewt/agent@v3.5.4+incompatible/agent/pipeline_parser_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/buildkite/agent/env"
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  func TestPipelineParserParsesYaml(t *testing.T) {
    16  	environ := env.FromSlice([]string{`ENV_VAR_FRIEND="friend"`})
    17  
    18  	result, err := PipelineParser{
    19  		Filename: "awesome.yml",
    20  		Pipeline: []byte("steps:\n  - label: \"hello ${ENV_VAR_FRIEND}\""),
    21  		Env:      environ}.Parse()
    22  
    23  	assert.NoError(t, err)
    24  	j, err := json.Marshal(result)
    25  	assert.Equal(t, `{"steps":[{"label":"hello \"friend\""}]}`, string(j))
    26  }
    27  
    28  func TestPipelineParserParsesYamlWithNoInterpolation(t *testing.T) {
    29  	result, err := PipelineParser{
    30  		Filename:        "awesome.yml",
    31  		Pipeline:        []byte("steps:\n  - label: \"hello ${ENV_VAR_FRIEND}\""),
    32  		NoInterpolation: true,
    33  	}.Parse()
    34  
    35  	assert.NoError(t, err)
    36  	j, err := json.Marshal(result)
    37  	assert.Equal(t, `{"steps":[{"label":"hello ${ENV_VAR_FRIEND}"}]}`, string(j))
    38  }
    39  
    40  func TestPipelineParserSupportsYamlMergesAndAnchors(t *testing.T) {
    41  	complexYAML := `---
    42  base_step: &base_step
    43    type: script
    44    agent_query_rules:
    45      - queue=default
    46  
    47  steps:
    48    - <<: *base_step
    49      name: ':docker: building image'
    50      command: docker build .
    51      agents:
    52        queue: default`
    53  
    54  	result, err := PipelineParser{
    55  		Filename: "awesome.yml",
    56  		Pipeline: []byte(complexYAML)}.Parse()
    57  
    58  	assert.NoError(t, err)
    59  	j, err := json.Marshal(result)
    60  	assert.Equal(t, `{"base_step":{"type":"script","agent_query_rules":["queue=default"]},"steps":[{"type":"script","agent_query_rules":["queue=default"],"name":":docker: building image","command":"docker build .","agents":{"queue":"default"}}]}`, string(j))
    61  }
    62  
    63  func TestPipelineParserReturnsYamlParsingErrors(t *testing.T) {
    64  	_, err := PipelineParser{Filename: "awesome.yml", Pipeline: []byte("steps: %blah%")}.Parse()
    65  	assert.Error(t, err, `Failed to parse awesome.yml: found character that cannot start any token`, fmt.Sprintf("%s", err))
    66  }
    67  
    68  func TestPipelineParserReturnsJsonParsingErrors(t *testing.T) {
    69  	_, err := PipelineParser{Filename: "awesome.json", Pipeline: []byte("{")}.Parse()
    70  	assert.Error(t, err, `Failed to parse awesome.json: line 1: did not find expected node content`, fmt.Sprintf("%s", err))
    71  }
    72  
    73  func TestPipelineParserParsesJson(t *testing.T) {
    74  	environ := env.FromSlice([]string{`ENV_VAR_FRIEND="friend"`})
    75  
    76  	result, err := PipelineParser{
    77  		Filename: "thing.json",
    78  		Pipeline: []byte("\n\n     \n  { \"foo\": \"bye ${ENV_VAR_FRIEND}\" }\n"),
    79  		Env:      environ}.Parse()
    80  
    81  	assert.NoError(t, err)
    82  	j, err := json.Marshal(result)
    83  	assert.Equal(t, `{"foo":"bye \"friend\""}`, string(j))
    84  }
    85  
    86  func TestPipelineParserParsesJsonObjects(t *testing.T) {
    87  	environ := env.FromSlice([]string{`ENV_VAR_FRIEND="friend"`})
    88  
    89  	result, err := PipelineParser{Pipeline: []byte("\n\n     \n  { \"foo\": \"bye ${ENV_VAR_FRIEND}\" }\n"), Env: environ}.Parse()
    90  	assert.NoError(t, err)
    91  	j, err := json.Marshal(result)
    92  	assert.Equal(t, `{"foo":"bye \"friend\""}`, string(j))
    93  }
    94  
    95  func TestPipelineParserParsesJsonArrays(t *testing.T) {
    96  	environ := env.FromSlice([]string{`ENV_VAR_FRIEND="friend"`})
    97  
    98  	result, err := PipelineParser{Pipeline: []byte("\n\n     \n  [ { \"foo\": \"bye ${ENV_VAR_FRIEND}\" } ]\n"), Env: environ}.Parse()
    99  	assert.NoError(t, err)
   100  	j, err := json.Marshal(result)
   101  	assert.Equal(t, `{"steps":[{"foo":"bye \"friend\""}]}`, string(j))
   102  }
   103  
   104  func TestPipelineParserParsesTopLevelSteps(t *testing.T) {
   105  	result, err := PipelineParser{Pipeline: []byte("---\n- name: Build\n  command: echo hello world\n- wait\n"), Env: nil}.Parse()
   106  	assert.NoError(t, err)
   107  	j, err := json.Marshal(result)
   108  	assert.Equal(t, `{"steps":[{"name":"Build","command":"echo hello world"},"wait"]}`, string(j))
   109  }
   110  
   111  func TestPipelineParserPreservesBools(t *testing.T) {
   112  	result, err := PipelineParser{Pipeline: []byte("steps:\n  - trigger: hello\n    async: true")}.Parse()
   113  	assert.Nil(t, err)
   114  	j, err := json.Marshal(result)
   115  	assert.Equal(t, `{"steps":[{"trigger":"hello","async":true}]}`, string(j))
   116  }
   117  
   118  func TestPipelineParserPreservesInts(t *testing.T) {
   119  	result, err := PipelineParser{Pipeline: []byte("steps:\n  - label: hello\n    parallelism: 10")}.Parse()
   120  	assert.Nil(t, err)
   121  	j, err := json.Marshal(result)
   122  	assert.Equal(t, `{"steps":[{"label":"hello","parallelism":10}]}`, string(j))
   123  }
   124  
   125  func TestPipelineParserPreservesNull(t *testing.T) {
   126  	result, err := PipelineParser{Pipeline: []byte("steps:\n  - wait: ~")}.Parse()
   127  	assert.Nil(t, err)
   128  	j, err := json.Marshal(result)
   129  	assert.Equal(t, `{"steps":[{"wait":null}]}`, string(j))
   130  }
   131  
   132  func TestPipelineParserPreservesFloats(t *testing.T) {
   133  	result, err := PipelineParser{Pipeline: []byte("steps:\n  - trigger: hello\n    llamas: 3.142")}.Parse()
   134  	assert.Nil(t, err)
   135  	j, err := json.Marshal(result)
   136  	assert.Equal(t, `{"steps":[{"trigger":"hello","llamas":3.142}]}`, string(j))
   137  }
   138  
   139  func TestPipelineParserHandlesDates(t *testing.T) {
   140  	result, err := PipelineParser{Pipeline: []byte("steps:\n  - trigger: hello\n    llamas: 2002-08-15T17:18:23.18-06:00")}.Parse()
   141  	assert.Nil(t, err)
   142  	j, err := json.Marshal(result)
   143  	assert.Equal(t, `{"steps":[{"trigger":"hello","llamas":"2002-08-15T17:18:23.18-06:00"}]}`, string(j))
   144  }
   145  
   146  func TestPipelineParserInterpolatesKeysAsWellAsValues(t *testing.T) {
   147  	var pipeline = `{
   148  		"env": {
   149  			"${FROM_ENV}TEST1": "MyTest",
   150  			"TEST2": "${FROM_ENV}"
   151  		}
   152  	}`
   153  
   154  	var decoded struct {
   155  		Env map[string]string `json:"env"`
   156  	}
   157  
   158  	environ := env.FromSlice([]string{`FROM_ENV=llamas`})
   159  
   160  	result, err := PipelineParser{Pipeline: []byte(pipeline), Env: environ}.Parse()
   161  	if err != nil {
   162  		t.Fatal(err)
   163  	}
   164  
   165  	err = decodeIntoStruct(&decoded, result)
   166  	if err != nil {
   167  		t.Fatal(err)
   168  	}
   169  
   170  	assert.Equal(t, `MyTest`, decoded.Env["llamasTEST1"])
   171  	assert.Equal(t, `llamas`, decoded.Env["TEST2"])
   172  }
   173  
   174  func TestPipelineParserLoadsGlobalEnvBlockFirst(t *testing.T) {
   175  	var pipeline = `{
   176  		"env": {
   177  			"TEAM1": "England",
   178  			"TEAM2": "Australia",
   179  			"HEADLINE": "${TEAM1} smashes ${TEAM2} to win the ashes in ${YEAR_FROM_SHELL}!!"
   180  		},
   181  		"steps": [{
   182  			"command": "echo ${HEADLINE}"
   183  		}]
   184  	}`
   185  
   186  	var decoded struct {
   187  		Env   map[string]string `json:"env"`
   188  		Steps []struct {
   189  			Command string `json:"command"`
   190  		} `json:"steps"`
   191  	}
   192  
   193  	environ := env.FromSlice([]string{`YEAR_FROM_SHELL=1912`})
   194  
   195  	result, err := PipelineParser{Pipeline: []byte(pipeline), Env: environ}.Parse()
   196  	if err != nil {
   197  		t.Fatal(err)
   198  	}
   199  
   200  	err = decodeIntoStruct(&decoded, result)
   201  	if err != nil {
   202  		t.Fatal(err)
   203  	}
   204  
   205  	assert.Equal(t, `England`, decoded.Env["TEAM1"])
   206  	assert.Equal(t, `England smashes Australia to win the ashes in 1912!!`, decoded.Env["HEADLINE"])
   207  	assert.Equal(t, `echo England smashes Australia to win the ashes in 1912!!`, decoded.Steps[0].Command)
   208  }
   209  
   210  func decodeIntoStruct(into interface{}, from interface{}) error {
   211  	b, err := json.Marshal(from)
   212  	if err != nil {
   213  		return err
   214  	}
   215  	return json.Unmarshal(b, into)
   216  }
   217  
   218  func TestPipelineParserLoadsSystemEnvironment(t *testing.T) {
   219  	var pipeline = `{
   220  		"steps": [{
   221  			"command": "echo ${LLAMAS_ROCK?}"
   222  		}]
   223  	}`
   224  
   225  	var decoded struct {
   226  		Steps []struct {
   227  			Command string `json:"command"`
   228  		} `json:"steps"`
   229  	}
   230  
   231  	_, err := PipelineParser{Pipeline: []byte(pipeline)}.Parse()
   232  	if err == nil {
   233  		t.Fatalf("Expected $LLAMAS_ROCK: not set")
   234  	}
   235  
   236  	os.Setenv("LLAMAS_ROCK", "absolutely")
   237  	defer os.Unsetenv("LLAMAS_ROCK")
   238  
   239  	result2, err := PipelineParser{Pipeline: []byte(pipeline)}.Parse()
   240  	if err != nil {
   241  		t.Fatal(err)
   242  	}
   243  
   244  	err = decodeIntoStruct(&decoded, result2)
   245  	if err != nil {
   246  		t.Fatal(err)
   247  	}
   248  
   249  	if decoded.Steps[0].Command != "echo absolutely" {
   250  		t.Fatalf("Unexpected: %q", decoded.Steps[0].Command)
   251  	}
   252  }
   253  
   254  func TestPipelineParserPreservesOrderOfPlugins(t *testing.T) {
   255  	var pipeline = `---
   256  steps:
   257    - name: ":s3: xxx"
   258      command: "script/buildkite/xxx.sh"
   259      plugins:
   260        xxx/aws-assume-role#v0.1.0:
   261          role: arn:aws:iam::xxx:role/xxx
   262        ecr#v1.1.4:
   263          login: true
   264          account_ids: xxx
   265          registry_region: us-east-1
   266        docker-compose#v2.5.1:
   267          run: xxx
   268          config: .buildkite/docker/docker-compose.yml
   269          env:
   270            - AWS_ACCESS_KEY_ID
   271            - AWS_SECRET_ACCESS_KEY
   272            - AWS_SESSION_TOKEN
   273      agents:
   274        queue: xxx`
   275  
   276  	result, err := PipelineParser{Pipeline: []byte(pipeline), Env: nil}.Parse()
   277  	if err != nil {
   278  		t.Fatal(err)
   279  	}
   280  
   281  	buf := &bytes.Buffer{}
   282  	err = json.NewEncoder(buf).Encode(result)
   283  	if err != nil {
   284  		t.Fatal(err)
   285  	}
   286  
   287  	expected := `{"steps":[{"name":":s3: xxx","command":"script/buildkite/xxx.sh","plugins":{"xxx/aws-assume-role#v0.1.0":{"role":"arn:aws:iam::xxx:role/xxx"},"ecr#v1.1.4":{"login":true,"account_ids":"xxx","registry_region":"us-east-1"},"docker-compose#v2.5.1":{"run":"xxx","config":".buildkite/docker/docker-compose.yml","env":["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_SESSION_TOKEN"]}},"agents":{"queue":"xxx"}}]}`
   288  	assert.Equal(t, expected, strings.TrimSpace(buf.String()))
   289  }