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 }