github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/exec/set_pipeline_step_test.go (about) 1 package exec_test 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 9 . "github.com/onsi/ginkgo" 10 . "github.com/onsi/gomega" 11 "go.opentelemetry.io/otel/api/trace" 12 13 "code.cloudfoundry.org/lager/lagerctx" 14 "code.cloudfoundry.org/lager/lagertest" 15 "github.com/pf-qiu/concourse/v6/atc" 16 "github.com/pf-qiu/concourse/v6/atc/db" 17 "github.com/pf-qiu/concourse/v6/atc/db/dbfakes" 18 "github.com/pf-qiu/concourse/v6/atc/exec" 19 "github.com/pf-qiu/concourse/v6/atc/exec/build" 20 "github.com/pf-qiu/concourse/v6/atc/exec/build/buildfakes" 21 "github.com/pf-qiu/concourse/v6/atc/exec/execfakes" 22 "github.com/pf-qiu/concourse/v6/atc/policy" 23 "github.com/pf-qiu/concourse/v6/atc/policy/policyfakes" 24 "github.com/pf-qiu/concourse/v6/atc/worker/workerfakes" 25 "github.com/pf-qiu/concourse/v6/vars" 26 "github.com/onsi/gomega/gbytes" 27 ) 28 29 var _ = Describe("SetPipelineStep", func() { 30 31 const badPipelineContentWithInvalidSyntax = ` 32 --- 33 jobs: 34 - name: 35 ` 36 37 const badPipelineContentWithEmptyContent = ` 38 --- 39 ` 40 41 const pipelineContent = ` 42 --- 43 jobs: 44 - name: some-job 45 plan: 46 - task: some-task 47 config: 48 platform: linux 49 image_resource: 50 type: registry-image 51 source: {repository: busybox} 52 run: 53 path: echo 54 args: 55 - hello 56 ` 57 58 var pipelineObject = atc.Config{ 59 Jobs: atc.JobConfigs{ 60 { 61 Name: "some-job", 62 PlanSequence: []atc.Step{ 63 { 64 Config: &atc.TaskStep{ 65 Name: "some-task", 66 Config: &atc.TaskConfig{ 67 Platform: "linux", 68 ImageResource: &atc.ImageResource{ 69 Type: "registry-image", 70 Source: atc.Source{"repository": "busybox"}, 71 }, 72 Run: atc.TaskRunConfig{ 73 Path: "echo", 74 Args: []string{"hello"}, 75 }, 76 }, 77 }, 78 }, 79 }, 80 }, 81 }, 82 } 83 84 var ( 85 ctx context.Context 86 cancel func() 87 testLogger *lagertest.TestLogger 88 89 fakeTeamFactory *dbfakes.FakeTeamFactory 90 fakeBuildFactory *dbfakes.FakeBuildFactory 91 fakeBuild *dbfakes.FakeBuild 92 fakeTeam *dbfakes.FakeTeam 93 fakePipeline *dbfakes.FakePipeline 94 spanCtx context.Context 95 96 fakeDelegate *execfakes.FakeSetPipelineStepDelegate 97 fakeDelegateFactory *execfakes.FakeSetPipelineStepDelegateFactory 98 99 filter policy.Filter 100 fakeAgent *policyfakes.FakeAgent 101 fakeChecker policy.Checker 102 103 fakeWorkerClient *workerfakes.FakeClient 104 105 spPlan *atc.SetPipelinePlan 106 artifactRepository *build.Repository 107 state *execfakes.FakeRunState 108 fakeSource *buildfakes.FakeRegisterableArtifact 109 110 spStep exec.Step 111 stepOk bool 112 stepErr error 113 114 stepMetadata = exec.StepMetadata{ 115 TeamID: 123, 116 TeamName: "some-team", 117 JobID: 87, 118 JobName: "some-job", 119 BuildID: 42, 120 BuildName: "some-build", 121 PipelineID: 4567, 122 PipelineName: "some-pipeline", 123 PipelineInstanceVars: atc.InstanceVars{"branch": "feature/foo"}, 124 } 125 126 stdout, stderr *gbytes.Buffer 127 128 planID = "56" 129 ) 130 131 BeforeEach(func() { 132 testLogger = lagertest.NewTestLogger("set-pipeline-action-test") 133 ctx, cancel = context.WithCancel(context.Background()) 134 ctx = lagerctx.NewContext(ctx, testLogger) 135 136 artifactRepository = build.NewRepository() 137 state = new(execfakes.FakeRunState) 138 state.ArtifactRepositoryReturns(artifactRepository) 139 140 state.GetStub = vars.StaticVariables{"source-param": "super-secret-source"}.Get 141 142 fakeSource = new(buildfakes.FakeRegisterableArtifact) 143 artifactRepository.RegisterArtifact("some-resource", fakeSource) 144 145 stdout = gbytes.NewBuffer() 146 stderr = gbytes.NewBuffer() 147 148 fakeDelegate = new(execfakes.FakeSetPipelineStepDelegate) 149 fakeDelegate.StdoutReturns(stdout) 150 fakeDelegate.StderrReturns(stderr) 151 152 spanCtx = context.Background() 153 fakeDelegate.StartSpanReturns(spanCtx, trace.NoopSpan{}) 154 155 fakeDelegateFactory = new(execfakes.FakeSetPipelineStepDelegateFactory) 156 fakeDelegateFactory.SetPipelineStepDelegateReturns(fakeDelegate) 157 158 fakeTeamFactory = new(dbfakes.FakeTeamFactory) 159 fakeBuildFactory = new(dbfakes.FakeBuildFactory) 160 fakeBuild = new(dbfakes.FakeBuild) 161 fakeTeam = new(dbfakes.FakeTeam) 162 fakePipeline = new(dbfakes.FakePipeline) 163 164 stepMetadata = exec.StepMetadata{ 165 TeamID: 123, 166 TeamName: "some-team", 167 BuildID: 42, 168 BuildName: "some-build", 169 PipelineID: 4567, 170 PipelineName: "some-pipeline", 171 PipelineInstanceVars: atc.InstanceVars{"branch": "feature/foo"}, 172 } 173 174 fakeTeam.IDReturns(stepMetadata.TeamID) 175 fakeTeam.NameReturns(stepMetadata.TeamName) 176 177 fakePipeline.NameReturns("some-pipeline") 178 fakePipeline.InstanceVarsReturns(atc.InstanceVars{"branch": "feature/foo"}) 179 fakeTeamFactory.GetByIDReturns(fakeTeam) 180 fakeBuildFactory.BuildReturns(fakeBuild, true, nil) 181 182 filter = policy.Filter{ 183 Actions: []string{exec.ActionRunSetPipeline}, 184 } 185 186 fakeAgent = new(policyfakes.FakeAgent) 187 fakeAgent.CheckReturns(policy.PassedPolicyCheck(), nil) 188 fakePolicyAgentFactory.NewAgentReturns(fakeAgent, nil) 189 190 fakeChecker, _ = policy.Initialize(testLogger, "some-cluster", "some-version", filter) 191 192 fakeWorkerClient = new(workerfakes.FakeClient) 193 194 spPlan = &atc.SetPipelinePlan{ 195 Name: "some-pipeline", 196 File: "some-resource/pipeline.yml", 197 InstanceVars: atc.InstanceVars{"branch": "feature/foo"}, 198 } 199 }) 200 201 AfterEach(func() { 202 cancel() 203 }) 204 205 JustBeforeEach(func() { 206 plan := atc.Plan{ 207 ID: atc.PlanID(planID), 208 SetPipeline: spPlan, 209 } 210 211 spStep = exec.NewSetPipelineStep( 212 plan.ID, 213 *plan.SetPipeline, 214 stepMetadata, 215 fakeDelegateFactory, 216 fakeTeamFactory, 217 fakeBuildFactory, 218 fakeWorkerClient, 219 fakeChecker, 220 ) 221 222 stepOk, stepErr = spStep.Run(ctx, state) 223 }) 224 225 Context("when file is not configured", func() { 226 BeforeEach(func() { 227 spPlan = &atc.SetPipelinePlan{ 228 Name: "some-pipeline", 229 } 230 }) 231 232 It("should fail with error of file not configured", func() { 233 Expect(stepErr).To(HaveOccurred()) 234 Expect(stepErr.Error()).To(Equal("file is not specified")) 235 }) 236 }) 237 238 Context("when file is configured", func() { 239 Context("pipeline file not exist", func() { 240 BeforeEach(func() { 241 fakeWorkerClient.StreamFileFromArtifactReturns(nil, errors.New("file not found")) 242 }) 243 244 It("should fail with error of file not configured", func() { 245 Expect(stepErr).To(HaveOccurred()) 246 Expect(stepErr.Error()).To(Equal("file not found")) 247 }) 248 }) 249 250 Context("when pipeline file exists but bad syntax", func() { 251 BeforeEach(func() { 252 fakeWorkerClient.StreamFileFromArtifactReturns(&fakeReadCloser{str: badPipelineContentWithInvalidSyntax}, nil) 253 }) 254 255 It("should not return error", func() { 256 Expect(stepErr).NotTo(HaveOccurred()) 257 }) 258 259 It("should stderr have error message", func() { 260 Expect(stderr).To(gbytes.Say("invalid pipeline:")) 261 Expect(stderr).To(gbytes.Say("- invalid jobs:")) 262 }) 263 264 It("should finish unsuccessfully", func() { 265 Expect(fakeDelegate.FinishedCallCount()).To(Equal(1)) 266 _, succeeded := fakeDelegate.FinishedArgsForCall(0) 267 Expect(succeeded).To(BeFalse()) 268 }) 269 }) 270 271 Context("when pipeline file exists but is empty", func() { 272 BeforeEach(func() { 273 fakeWorkerClient.StreamFileFromArtifactReturns(&fakeReadCloser{str: badPipelineContentWithEmptyContent}, nil) 274 }) 275 276 It("should return an error", func() { 277 Expect(stepErr).NotTo(HaveOccurred()) 278 }) 279 280 It("should log an error message", func() { 281 Expect(stderr).To(gbytes.Say("pipeline must contain at least one job")) 282 }) 283 284 It("should not update the job and build id", func() { 285 Expect(fakePipeline.SetParentIDsCallCount()).To(Equal(0)) 286 }) 287 }) 288 289 Context("when pipeline file is good", func() { 290 BeforeEach(func() { 291 fakeWorkerClient.StreamFileFromArtifactReturns(&fakeReadCloser{str: pipelineContent}, nil) 292 }) 293 294 Context("when get pipeline fails", func() { 295 BeforeEach(func() { 296 fakeTeam.PipelineReturns(nil, false, errors.New("fail to get pipeline")) 297 }) 298 299 It("should return error", func() { 300 Expect(stepErr).To(HaveOccurred()) 301 Expect(stepErr.Error()).To(Equal("fail to get pipeline")) 302 }) 303 }) 304 305 Context("when specified pipeline not found", func() { 306 BeforeEach(func() { 307 fakeTeam.PipelineReturns(nil, false, nil) 308 fakeBuild.SavePipelineReturns(fakePipeline, true, nil) 309 }) 310 311 It("should save the pipeline", func() { 312 Expect(fakeBuild.SavePipelineCallCount()).To(Equal(1)) 313 ref, _, _, _, paused := fakeBuild.SavePipelineArgsForCall(0) 314 Expect(ref).To(Equal(atc.PipelineRef{ 315 Name: "some-pipeline", 316 InstanceVars: atc.InstanceVars{"branch": "feature/foo"}, 317 })) 318 Expect(paused).To(BeFalse()) 319 }) 320 321 It("should stdout have message", func() { 322 Expect(stdout).To(gbytes.Say("done")) 323 }) 324 }) 325 326 Context("when specified pipeline exists already", func() { 327 BeforeEach(func() { 328 fakeTeam.PipelineReturns(fakePipeline, true, nil) 329 fakeBuild.SavePipelineReturns(fakePipeline, false, nil) 330 }) 331 332 Context("when no diff", func() { 333 BeforeEach(func() { 334 fakePipeline.ConfigReturns(pipelineObject, nil) 335 fakePipeline.SetParentIDsReturns(nil) 336 }) 337 338 It("should log 'no changes to apply'", func() { 339 Expect(stdout).To(gbytes.Say("no changes to apply.")) 340 }) 341 342 It("should send a set pipeline changed event", func() { 343 Expect(fakeDelegate.SetPipelineChangedCallCount()).To(Equal(1)) 344 _, changed := fakeDelegate.SetPipelineChangedArgsForCall(0) 345 Expect(changed).To(BeFalse()) 346 }) 347 348 It("should update the job and build id", func() { 349 Expect(fakePipeline.SetParentIDsCallCount()).To(Equal(1)) 350 jobID, buildID := fakePipeline.SetParentIDsArgsForCall(0) 351 Expect(jobID).To(Equal(stepMetadata.JobID)) 352 Expect(buildID).To(Equal(stepMetadata.BuildID)) 353 }) 354 }) 355 356 Context("when there are some diff", func() { 357 BeforeEach(func() { 358 pipelineObject.Jobs[0].PlanSequence[0].Config.(*atc.TaskStep).Config.Run.Args = []string{"hello world"} 359 fakePipeline.ConfigReturns(pipelineObject, nil) 360 }) 361 362 It("should log diff", func() { 363 Expect(stdout).To(gbytes.Say("job some-job has changed:")) 364 }) 365 366 It("should send a set pipeline changed event", func() { 367 Expect(fakeDelegate.SetPipelineChangedCallCount()).To(Equal(1)) 368 _, changed := fakeDelegate.SetPipelineChangedArgsForCall(0) 369 Expect(changed).To(BeTrue()) 370 }) 371 }) 372 373 Context("when SavePipeline fails", func() { 374 BeforeEach(func() { 375 fakeBuild.SavePipelineReturns(nil, false, errors.New("failed to save")) 376 }) 377 378 It("should return error", func() { 379 Expect(stepErr).To(HaveOccurred()) 380 Expect(stepErr.Error()).To(Equal("failed to save")) 381 }) 382 383 Context("due to the pipeline being set by a newer build", func() { 384 BeforeEach(func() { 385 fakeBuild.SavePipelineReturns(nil, false, db.ErrSetByNewerBuild) 386 }) 387 It("logs a warning", func() { 388 Expect(stderr).To(gbytes.Say("WARNING: the pipeline was not saved because it was already saved by a newer build")) 389 }) 390 It("does not fail the step", func() { 391 Expect(stepErr).ToNot(HaveOccurred()) 392 Expect(stepOk).To(BeTrue()) 393 }) 394 }) 395 }) 396 397 It("should save the pipeline un-paused", func() { 398 Expect(fakeBuild.SavePipelineCallCount()).To(Equal(1)) 399 ref, _, _, _, paused := fakeBuild.SavePipelineArgsForCall(0) 400 Expect(ref).To(Equal(atc.PipelineRef{ 401 Name: "some-pipeline", 402 InstanceVars: atc.InstanceVars{"branch": "feature/foo"}, 403 })) 404 Expect(paused).To(BeFalse()) 405 }) 406 407 It("should stdout have message", func() { 408 Expect(stdout).To(gbytes.Say("setting pipeline: some-pipeline")) 409 Expect(stdout).To(gbytes.Say("done")) 410 }) 411 412 It("should finish successfully", func() { 413 Expect(fakeDelegate.FinishedCallCount()).To(Equal(1)) 414 _, succeeded := fakeDelegate.FinishedArgsForCall(0) 415 Expect(succeeded).To(BeTrue()) 416 }) 417 }) 418 419 Context("when set-pipeline self", func() { 420 BeforeEach(func() { 421 spPlan = &atc.SetPipelinePlan{ 422 Name: "self", 423 File: "some-resource/pipeline.yml", 424 Team: "foo-team", 425 InstanceVars: atc.InstanceVars{"branch": "feature/foo"}, 426 } 427 fakeBuild.SavePipelineReturns(fakePipeline, false, nil) 428 }) 429 430 It("should save the pipeline itself", func() { 431 Expect(fakeBuild.SavePipelineCallCount()).To(Equal(1)) 432 pipelineRef, _, _, _, _ := fakeBuild.SavePipelineArgsForCall(0) 433 Expect(pipelineRef).To(Equal(atc.PipelineRef{ 434 Name: "some-pipeline", 435 InstanceVars: atc.InstanceVars{"branch": "feature/foo"}, 436 })) 437 }) 438 439 It("should save to the current team", func() { 440 Expect(fakeBuild.SavePipelineCallCount()).To(Equal(1)) 441 _, teamId, _, _, _ := fakeBuild.SavePipelineArgsForCall(0) 442 Expect(teamId).To(Equal(fakeTeam.ID())) 443 }) 444 445 It("should print an experimental message", func() { 446 Expect(stderr).To(gbytes.Say("WARNING: 'set_pipeline: self' is experimental")) 447 Expect(stderr).To(gbytes.Say("contribute to discussion #5732")) 448 Expect(stderr).To(gbytes.Say("discussions/5732")) 449 }) 450 }) 451 452 Context("when team is configured", func() { 453 var ( 454 fakeUserCurrentTeam *dbfakes.FakeTeam 455 ) 456 457 BeforeEach(func() { 458 fakeUserCurrentTeam = new(dbfakes.FakeTeam) 459 fakeUserCurrentTeam.IDReturns(111) 460 fakeUserCurrentTeam.NameReturns("main") 461 fakeUserCurrentTeam.AdminReturns(false) 462 463 stepMetadata.TeamID = fakeUserCurrentTeam.ID() 464 stepMetadata.TeamName = fakeUserCurrentTeam.Name() 465 fakeTeamFactory.FindTeamReturnsOnCall( 466 0, 467 fakeUserCurrentTeam, true, nil, 468 ) 469 }) 470 471 Context("when team is set to the empty string", func() { 472 BeforeEach(func() { 473 fakeBuild.PipelineReturns(fakePipeline, true, nil) 474 fakeBuild.SavePipelineReturns(fakePipeline, false, nil) 475 spPlan.Team = "" 476 }) 477 478 It("should finish successfully", func() { 479 Expect(fakeDelegate.FinishedCallCount()).To(Equal(1)) 480 _, succeeded := fakeDelegate.FinishedArgsForCall(0) 481 Expect(succeeded).To(BeTrue()) 482 }) 483 }) 484 485 Context("when team does not exist", func() { 486 BeforeEach(func() { 487 spPlan.Team = "not-found" 488 fakeTeamFactory.FindTeamReturnsOnCall( 489 1, 490 nil, false, nil, 491 ) 492 }) 493 494 It("should return error", func() { 495 Expect(stepErr).To(HaveOccurred()) 496 Expect(stepErr.Error()).To(Equal("team not-found not found")) 497 }) 498 }) 499 500 Context("when team exists", func() { 501 Context("when the target team is the current team", func() { 502 BeforeEach(func() { 503 spPlan.Team = fakeUserCurrentTeam.Name() 504 fakeTeamFactory.FindTeamReturnsOnCall( 505 1, 506 fakeUserCurrentTeam, true, nil, 507 ) 508 509 fakeBuild.PipelineReturns(fakePipeline, true, nil) 510 fakeBuild.SavePipelineReturns(fakePipeline, false, nil) 511 }) 512 513 It("should finish successfully", func() { 514 _, teamID, _, _, _ := fakeBuild.SavePipelineArgsForCall(0) 515 Expect(teamID).To(Equal(fakeUserCurrentTeam.ID())) 516 Expect(fakeDelegate.FinishedCallCount()).To(Equal(1)) 517 _, succeeded := fakeDelegate.FinishedArgsForCall(0) 518 Expect(succeeded).To(BeTrue()) 519 }) 520 521 It("should print an experimental message", func() { 522 Expect(stderr).To(gbytes.Say("WARNING: specifying the team")) 523 Expect(stderr).To(gbytes.Say("contribute to discussion #5731")) 524 Expect(stderr).To(gbytes.Say("discussions/5731")) 525 }) 526 }) 527 528 Context("when the team is not the current team", func() { 529 BeforeEach(func() { 530 spPlan.Team = fakeTeam.Name() 531 fakeTeamFactory.FindTeamReturnsOnCall( 532 1, 533 fakeTeam, true, nil, 534 ) 535 }) 536 537 Context("when the current team is an admin team", func() { 538 BeforeEach(func() { 539 fakeUserCurrentTeam.AdminReturns(true) 540 541 fakeBuild.PipelineReturns(fakePipeline, true, nil) 542 fakeBuild.SavePipelineReturns(fakePipeline, false, nil) 543 }) 544 545 It("should finish successfully", func() { 546 _, teamID, _, _, _ := fakeBuild.SavePipelineArgsForCall(0) 547 Expect(teamID).To(Equal(fakeTeam.ID())) 548 Expect(fakeDelegate.FinishedCallCount()).To(Equal(1)) 549 _, succeeded := fakeDelegate.FinishedArgsForCall(0) 550 Expect(succeeded).To(BeTrue()) 551 }) 552 }) 553 554 Context("when the current team is not an admin team", func() { 555 It("should return error", func() { 556 557 Expect(stepErr).To(HaveOccurred()) 558 Expect(stepErr.Error()).To(Equal( 559 "only main team can set another team's pipeline", 560 )) 561 }) 562 }) 563 }) 564 }) 565 }) 566 567 Context("when policy checker enabled", func() { 568 Context("policy check errors", func() { 569 BeforeEach(func() { 570 result := policy.FailedPolicyCheck() 571 fakeAgent.CheckReturns(result, fmt.Errorf("unexpected error")) 572 }) 573 574 It("should return error", func() { 575 Expect(stepErr).To(HaveOccurred()) 576 Expect(stepErr.Error()).To(Equal("error checking policy enforcement")) 577 }) 578 }) 579 580 Context("policy check fails", func() { 581 BeforeEach(func() { 582 result := policy.FailedPolicyCheck() 583 result.Reasons = append(result.Reasons, "foo", "bar") 584 fakeAgent.CheckReturns(result, nil) 585 }) 586 587 It("should return error", func() { 588 Expect(stepErr).To(HaveOccurred()) 589 Expect(stepErr.Error()).To(Equal("policy check failed for set_pipeline: foo, bar")) 590 }) 591 }) 592 593 Context("policy check succeeds", func() { 594 BeforeEach(func() { 595 fakeBuild.PipelineReturns(fakePipeline, true, nil) 596 fakeBuild.SavePipelineReturns(fakePipeline, false, nil) 597 spPlan.Team = "" 598 }) 599 600 It("should finish successfully", func() { 601 _, teamID, _, _, _ := fakeBuild.SavePipelineArgsForCall(0) 602 Expect(teamID).To(Equal(fakeTeam.ID())) 603 Expect(fakeDelegate.FinishedCallCount()).To(Equal(1)) 604 _, succeeded := fakeDelegate.FinishedArgsForCall(0) 605 Expect(succeeded).To(BeTrue()) 606 }) 607 }) 608 }) 609 }) 610 }) 611 }) 612 613 type fakeReadCloser struct { 614 str string 615 index int 616 } 617 618 func (r *fakeReadCloser) Read(p []byte) (int, error) { 619 if r.index >= len(r.str) { 620 return 0, io.EOF 621 } 622 l := copy(p, []byte(r.str)[r.index:]) 623 r.index += l 624 return l, nil 625 } 626 627 func (r *fakeReadCloser) Close() error { 628 return nil 629 }