github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/engine/build_step_delegate_test.go (about)

     1  package engine_test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"io"
     7  	"time"
     8  
     9  	. "github.com/onsi/ginkgo"
    10  	. "github.com/onsi/gomega"
    11  
    12  	"code.cloudfoundry.org/clock/fakeclock"
    13  	"code.cloudfoundry.org/lager"
    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/engine"
    19  	"github.com/pf-qiu/concourse/v6/atc/event"
    20  	"github.com/pf-qiu/concourse/v6/atc/exec"
    21  	"github.com/pf-qiu/concourse/v6/atc/exec/build"
    22  	"github.com/pf-qiu/concourse/v6/atc/exec/execfakes"
    23  	"github.com/pf-qiu/concourse/v6/atc/policy"
    24  	"github.com/pf-qiu/concourse/v6/atc/policy/policyfakes"
    25  	"github.com/pf-qiu/concourse/v6/atc/runtime/runtimefakes"
    26  	"github.com/pf-qiu/concourse/v6/atc/worker"
    27  	"github.com/pf-qiu/concourse/v6/vars"
    28  )
    29  
    30  var _ = Describe("BuildStepDelegate", func() {
    31  	var (
    32  		logger            *lagertest.TestLogger
    33  		fakeBuild         *dbfakes.FakeBuild
    34  		fakeClock         *fakeclock.FakeClock
    35  		planID            atc.PlanID
    36  		runState          *execfakes.FakeRunState
    37  		fakePolicyChecker *policyfakes.FakeChecker
    38  
    39  		credVars vars.StaticVariables
    40  
    41  		now = time.Date(1991, 6, 3, 5, 30, 0, 0, time.UTC)
    42  
    43  		delegate exec.BuildStepDelegate
    44  	)
    45  
    46  	BeforeEach(func() {
    47  		logger = lagertest.NewTestLogger("test")
    48  
    49  		fakeBuild = new(dbfakes.FakeBuild)
    50  		fakeClock = fakeclock.NewFakeClock(now)
    51  		credVars = vars.StaticVariables{
    52  			"source-param": "super-secret-source",
    53  			"git-key":      "{\n123\n456\n789\n}\n",
    54  		}
    55  		planID = "some-plan-id"
    56  
    57  		runState = new(execfakes.FakeRunState)
    58  		runState.RedactionEnabledReturns(true)
    59  
    60  		repo := build.NewRepository()
    61  		runState.ArtifactRepositoryReturns(repo)
    62  
    63  		fakePolicyChecker = new(policyfakes.FakeChecker)
    64  
    65  		delegate = engine.NewBuildStepDelegate(fakeBuild, planID, runState, fakeClock, fakePolicyChecker)
    66  	})
    67  
    68  	Describe("Initializing", func() {
    69  		JustBeforeEach(func() {
    70  			delegate.Initializing(logger)
    71  		})
    72  
    73  		It("saves an event", func() {
    74  			Expect(fakeBuild.SaveEventCallCount()).To(Equal(1))
    75  			event := fakeBuild.SaveEventArgsForCall(0)
    76  			Expect(event.EventType()).To(Equal(atc.EventType("initialize")))
    77  		})
    78  	})
    79  
    80  	Describe("Finished", func() {
    81  		JustBeforeEach(func() {
    82  			delegate.Finished(logger, true)
    83  		})
    84  
    85  		It("saves an event", func() {
    86  			Expect(fakeBuild.SaveEventCallCount()).To(Equal(1))
    87  			event := fakeBuild.SaveEventArgsForCall(0)
    88  			Expect(event.EventType()).To(Equal(atc.EventType("finish")))
    89  		})
    90  	})
    91  
    92  	Describe("FetchImage", func() {
    93  		var expectedCheckPlan, expectedGetPlan atc.Plan
    94  		var fakeArtifact *runtimefakes.FakeArtifact
    95  		var fakeResourceCache *dbfakes.FakeUsedResourceCache
    96  
    97  		var childState *execfakes.FakeRunState
    98  		var imageResource atc.ImageResource
    99  		var types atc.VersionedResourceTypes
   100  		var privileged bool
   101  
   102  		var imageSpec worker.ImageSpec
   103  		var fetchErr error
   104  
   105  		BeforeEach(func() {
   106  			repo := build.NewRepository()
   107  			runState.ArtifactRepositoryReturns(repo)
   108  
   109  			childState = new(execfakes.FakeRunState)
   110  			runState.NewLocalScopeReturns(childState)
   111  
   112  			fakeArtifact = new(runtimefakes.FakeArtifact)
   113  			childState.ArtifactRepositoryReturns(repo.NewLocalScope())
   114  			childState.ArtifactRepository().RegisterArtifact("image", fakeArtifact)
   115  
   116  			runState.GetStub = vars.StaticVariables{
   117  				"source-var": "super-secret-source",
   118  				"params-var": "super-secret-params",
   119  			}.Get
   120  
   121  			imageResource = atc.ImageResource{
   122  				Type:   "docker",
   123  				Source: atc.Source{"some": "((source-var))"},
   124  				Params: atc.Params{"some": "((params-var))"},
   125  				Tags:   atc.Tags{"some", "tags"},
   126  			}
   127  
   128  			types = atc.VersionedResourceTypes{
   129  				{
   130  					ResourceType: atc.ResourceType{
   131  						Name:   "some-custom-type",
   132  						Type:   "another-custom-type",
   133  						Source: atc.Source{"some-custom": "((source-var))"},
   134  						Params: atc.Params{"some-custom": "((params-var))"},
   135  					},
   136  					Version: atc.Version{"some-custom": "version"},
   137  				},
   138  				{
   139  					ResourceType: atc.ResourceType{
   140  						Name:       "another-custom-type",
   141  						Type:       "registry-image",
   142  						Source:     atc.Source{"another-custom": "((source-var))"},
   143  						Privileged: true,
   144  					},
   145  					Version: atc.Version{"another-custom": "version"},
   146  				},
   147  			}
   148  
   149  			expectedCheckPlan = atc.Plan{
   150  				ID: planID + "/image-check",
   151  				Check: &atc.CheckPlan{
   152  					Name:                   "image",
   153  					Type:                   "docker",
   154  					Source:                 atc.Source{"some": "((source-var))"},
   155  					VersionedResourceTypes: types,
   156  					Tags:                   atc.Tags{"some", "tags"},
   157  				},
   158  			}
   159  
   160  			expectedGetPlan = atc.Plan{
   161  				ID: planID + "/image-get",
   162  				Get: &atc.GetPlan{
   163  					Name:                   "image",
   164  					Type:                   "docker",
   165  					Source:                 atc.Source{"some": "((source-var))"},
   166  					Version:                &atc.Version{"some": "version"},
   167  					Params:                 atc.Params{"some": "((params-var))"},
   168  					VersionedResourceTypes: types,
   169  					Tags:                   atc.Tags{"some", "tags"},
   170  				},
   171  			}
   172  
   173  			fakeResourceCache = new(dbfakes.FakeUsedResourceCache)
   174  
   175  			childState.ResultStub = func(planID atc.PlanID, to interface{}) bool {
   176  				switch planID {
   177  				case expectedCheckPlan.ID:
   178  					switch x := to.(type) {
   179  					case *atc.Version:
   180  						*x = atc.Version{"some": "version"}
   181  					default:
   182  						Fail("unexpected target type")
   183  					}
   184  				case expectedGetPlan.ID:
   185  					switch x := to.(type) {
   186  					case *db.UsedResourceCache:
   187  						*x = fakeResourceCache
   188  					default:
   189  						Fail("unexpected target type")
   190  					}
   191  				default:
   192  					Fail("unknown result key: " + planID.String())
   193  				}
   194  
   195  				return true
   196  			}
   197  
   198  			privileged = false
   199  
   200  			childState.RunReturns(true, nil)
   201  		})
   202  
   203  		JustBeforeEach(func() {
   204  			imageSpec, fetchErr = delegate.FetchImage(context.TODO(), imageResource, types, privileged)
   205  		})
   206  
   207  		It("succeeds", func() {
   208  			Expect(fetchErr).ToNot(HaveOccurred())
   209  		})
   210  
   211  		It("returns an image spec containing the artifact", func() {
   212  			Expect(imageSpec).To(Equal(worker.ImageSpec{
   213  				ImageArtifact: fakeArtifact,
   214  				Privileged:    false,
   215  			}))
   216  		})
   217  
   218  		It("runs a CheckPlan to get the image version", func() {
   219  			Expect(childState.RunCallCount()).To(Equal(2))
   220  
   221  			_, plan := childState.RunArgsForCall(0)
   222  			Expect(plan).To(Equal(expectedCheckPlan))
   223  
   224  			_, plan = childState.RunArgsForCall(1)
   225  			Expect(plan).To(Equal(expectedGetPlan))
   226  		})
   227  
   228  		It("records the resource cache as an image resource for the build", func() {
   229  			Expect(fakeBuild.SaveImageResourceVersionCallCount()).To(Equal(1))
   230  			Expect(fakeBuild.SaveImageResourceVersionArgsForCall(0)).To(Equal(fakeResourceCache))
   231  		})
   232  
   233  		Context("when privileged", func() {
   234  			BeforeEach(func() {
   235  				privileged = true
   236  			})
   237  
   238  			It("returns a privileged image spec", func() {
   239  				Expect(imageSpec).To(Equal(worker.ImageSpec{
   240  					ImageArtifact: fakeArtifact,
   241  					Privileged:    true,
   242  				}))
   243  			})
   244  		})
   245  
   246  		Describe("policy checking", func() {
   247  			BeforeEach(func() {
   248  				fakeBuild.TeamNameReturns("some-team")
   249  				fakeBuild.PipelineNameReturns("some-pipeline")
   250  			})
   251  
   252  			Context("when the action does not need to be checked", func() {
   253  				BeforeEach(func() {
   254  					fakePolicyChecker.ShouldCheckActionReturns(false)
   255  				})
   256  
   257  				It("succeeds", func() {
   258  					Expect(fetchErr).ToNot(HaveOccurred())
   259  				})
   260  
   261  				It("checked if ActionUseImage is enabled", func() {
   262  					Expect(fakePolicyChecker.ShouldCheckActionCallCount()).To(Equal(1))
   263  					action := fakePolicyChecker.ShouldCheckActionArgsForCall(0)
   264  					Expect(action).To(Equal(policy.ActionUseImage))
   265  				})
   266  
   267  				It("does not check", func() {
   268  					Expect(fakePolicyChecker.CheckCallCount()).To(Equal(0))
   269  				})
   270  			})
   271  
   272  			Context("when the action needs to be checked", func() {
   273  				BeforeEach(func() {
   274  					fakePolicyChecker.ShouldCheckActionReturns(true)
   275  				})
   276  
   277  				Context("when the check is allowed", func() {
   278  					BeforeEach(func() {
   279  						fakePolicyChecker.CheckReturns(policy.PolicyCheckOutput{
   280  							Allowed: true,
   281  						}, nil)
   282  					})
   283  
   284  					It("succeeds", func() {
   285  						Expect(fetchErr).ToNot(HaveOccurred())
   286  					})
   287  
   288  					It("checked with the right values", func() {
   289  						Expect(fakePolicyChecker.CheckCallCount()).To(Equal(1))
   290  						input := fakePolicyChecker.CheckArgsForCall(0)
   291  						Expect(input).To(Equal(policy.PolicyCheckInput{
   292  							Action:   policy.ActionUseImage,
   293  							Team:     "some-team",
   294  							Pipeline: "some-pipeline",
   295  							Data: map[string]interface{}{
   296  								"image_type":   "docker",
   297  								"image_source": atc.Source{"some": "((source-var))"},
   298  								"privileged":   false,
   299  							},
   300  						}))
   301  					})
   302  
   303  					Context("when the image source contains credentials", func() {
   304  						BeforeEach(func() {
   305  							imageResource.Source = atc.Source{"some": "super-secret-source"}
   306  
   307  							runState.IterateInterpolatedCredsStub = func(iter vars.TrackedVarsIterator) {
   308  								iter.YieldCred("source-var", "super-secret-source")
   309  							}
   310  						})
   311  
   312  						It("redacts the value prior to checking", func() {
   313  							Expect(fakePolicyChecker.CheckCallCount()).To(Equal(1))
   314  							input := fakePolicyChecker.CheckArgsForCall(0)
   315  							Expect(input).To(Equal(policy.PolicyCheckInput{
   316  								Action:   policy.ActionUseImage,
   317  								Team:     "some-team",
   318  								Pipeline: "some-pipeline",
   319  								Data: map[string]interface{}{
   320  									"image_type":   "docker",
   321  									"image_source": atc.Source{"some": "((redacted))"},
   322  									"privileged":   false,
   323  								},
   324  							}))
   325  						})
   326  					})
   327  
   328  					Context("when privileged", func() {
   329  						BeforeEach(func() {
   330  							privileged = true
   331  						})
   332  
   333  						It("checks with privileged", func() {
   334  							Expect(fakePolicyChecker.CheckCallCount()).To(Equal(1))
   335  							input := fakePolicyChecker.CheckArgsForCall(0)
   336  							Expect(input).To(Equal(policy.PolicyCheckInput{
   337  								Action:   policy.ActionUseImage,
   338  								Team:     "some-team",
   339  								Pipeline: "some-pipeline",
   340  								Data: map[string]interface{}{
   341  									"image_type":   "docker",
   342  									"image_source": atc.Source{"some": "((source-var))"},
   343  									"privileged":   true,
   344  								},
   345  							}))
   346  						})
   347  					})
   348  				})
   349  			})
   350  		})
   351  
   352  		Describe("ordering", func() {
   353  			BeforeEach(func() {
   354  				fakeBuild.SaveEventStub = func(ev atc.Event) error {
   355  					switch ev.(type) {
   356  					case event.ImageCheck:
   357  						Expect(childState.RunCallCount()).To(Equal(0))
   358  					case event.ImageGet:
   359  						Expect(childState.RunCallCount()).To(Equal(1))
   360  					default:
   361  						Fail("unknown event type")
   362  					}
   363  					return nil
   364  				}
   365  			})
   366  
   367  			It("sends events before each run", func() {
   368  				Expect(fakeBuild.SaveEventCallCount()).To(Equal(2))
   369  				e := fakeBuild.SaveEventArgsForCall(0)
   370  				Expect(e).To(Equal(event.ImageCheck{
   371  					Time: 675927000,
   372  					Origin: event.Origin{
   373  						ID: event.OriginID(planID),
   374  					},
   375  					PublicPlan: expectedCheckPlan.Public(),
   376  				}))
   377  
   378  				e = fakeBuild.SaveEventArgsForCall(1)
   379  				Expect(e).To(Equal(event.ImageGet{
   380  					Time: 675927000,
   381  					Origin: event.Origin{
   382  						ID: event.OriginID(planID),
   383  					},
   384  					PublicPlan: expectedGetPlan.Public(),
   385  				}))
   386  			})
   387  		})
   388  
   389  		Context("when a version is already provided", func() {
   390  			BeforeEach(func() {
   391  				imageResource.Version = atc.Version{"some": "version"}
   392  			})
   393  
   394  			It("does not run a CheckPlan", func() {
   395  				Expect(childState.RunCallCount()).To(Equal(1))
   396  				_, plan := childState.RunArgsForCall(0)
   397  				Expect(plan).To(Equal(expectedGetPlan))
   398  
   399  				Expect(childState.ResultCallCount()).To(Equal(1))
   400  				planID, _ := childState.ResultArgsForCall(0)
   401  				Expect(planID).To(Equal(expectedGetPlan.ID))
   402  			})
   403  
   404  			It("only saves an ImageGet event", func() {
   405  				Expect(fakeBuild.SaveEventCallCount()).To(Equal(1))
   406  				e := fakeBuild.SaveEventArgsForCall(0)
   407  				Expect(e).To(Equal(event.ImageGet{
   408  					Time: 675927000,
   409  					Origin: event.Origin{
   410  						ID: event.OriginID(planID),
   411  					},
   412  					PublicPlan: expectedGetPlan.Public(),
   413  				}))
   414  			})
   415  		})
   416  
   417  		Context("when an image name is provided", func() {
   418  			var namedArtifact *runtimefakes.FakeArtifact
   419  
   420  			BeforeEach(func() {
   421  				imageResource.Name = "some-name"
   422  				expectedCheckPlan.Check.Name = "some-name"
   423  				expectedGetPlan.Get.Name = "some-name"
   424  
   425  				namedArtifact = new(runtimefakes.FakeArtifact)
   426  				childState.ArtifactRepositoryReturns(runState.ArtifactRepository().NewLocalScope())
   427  				childState.ArtifactRepository().RegisterArtifact("some-name", namedArtifact)
   428  			})
   429  
   430  			It("uses it for the step names", func() {
   431  				Expect(childState.RunCallCount()).To(Equal(2))
   432  				_, plan := childState.RunArgsForCall(0)
   433  				Expect(plan.Check.Name).To(Equal("some-name"))
   434  				_, plan = childState.RunArgsForCall(1)
   435  				Expect(plan.Get.Name).To(Equal("some-name"))
   436  
   437  				Expect(imageSpec.ImageArtifact).To(Equal(namedArtifact))
   438  			})
   439  		})
   440  
   441  		Context("when checking the image fails", func() {
   442  			BeforeEach(func() {
   443  				childState.RunStub = func(ctx context.Context, plan atc.Plan) (bool, error) {
   444  					if plan.ID == expectedCheckPlan.ID {
   445  						return false, nil
   446  					}
   447  
   448  					return true, nil
   449  				}
   450  			})
   451  
   452  			It("errors", func() {
   453  				Expect(fetchErr).To(MatchError("image check failed"))
   454  			})
   455  		})
   456  
   457  		Context("when no version is returned by the check", func() {
   458  			BeforeEach(func() {
   459  				childState.ResultReturns(false)
   460  			})
   461  
   462  			It("errors", func() {
   463  				Expect(fetchErr).To(MatchError("check did not return a version"))
   464  			})
   465  		})
   466  	})
   467  
   468  	Describe("Stdout", func() {
   469  		var writer io.Writer
   470  
   471  		BeforeEach(func() {
   472  			writer = delegate.Stdout()
   473  		})
   474  
   475  		Describe("writing to the writer", func() {
   476  			var writtenBytes int
   477  			var writeErr error
   478  
   479  			JustBeforeEach(func() {
   480  				writtenBytes, writeErr = writer.Write([]byte("hello\nworld"))
   481  				writer.(io.Closer).Close()
   482  			})
   483  
   484  			Context("when saving the event succeeds", func() {
   485  				BeforeEach(func() {
   486  					fakeBuild.SaveEventReturns(nil)
   487  				})
   488  
   489  				It("returns the length of the string, and no error", func() {
   490  					Expect(writtenBytes).To(Equal(len("hello\nworld")))
   491  					Expect(writeErr).ToNot(HaveOccurred())
   492  				})
   493  
   494  				It("saves a log event", func() {
   495  					Expect(fakeBuild.SaveEventCallCount()).To(Equal(2))
   496  					Expect(fakeBuild.SaveEventArgsForCall(0)).To(Equal(event.Log{
   497  						Time:    now.Unix(),
   498  						Payload: "hello\n",
   499  						Origin: event.Origin{
   500  							Source: event.OriginSourceStdout,
   501  							ID:     "some-plan-id",
   502  						},
   503  					}))
   504  					Expect(fakeBuild.SaveEventArgsForCall(1)).To(Equal(event.Log{
   505  						Time:    now.Unix(),
   506  						Payload: "world",
   507  						Origin: event.Origin{
   508  							Source: event.OriginSourceStdout,
   509  							ID:     "some-plan-id",
   510  						},
   511  					}))
   512  				})
   513  			})
   514  
   515  			Context("when saving the event fails", func() {
   516  				disaster := errors.New("nope")
   517  
   518  				BeforeEach(func() {
   519  					fakeBuild.SaveEventReturns(disaster)
   520  				})
   521  
   522  				It("returns 0 length, and the error", func() {
   523  					Expect(writtenBytes).To(Equal(0))
   524  					Expect(writeErr).To(Equal(disaster))
   525  				})
   526  			})
   527  		})
   528  	})
   529  
   530  	Describe("Stderr", func() {
   531  		var writer io.Writer
   532  
   533  		BeforeEach(func() {
   534  			writer = delegate.Stderr()
   535  		})
   536  
   537  		Describe("writing to the writer", func() {
   538  			var writtenBytes int
   539  			var writeErr error
   540  
   541  			JustBeforeEach(func() {
   542  				writtenBytes, writeErr = writer.Write([]byte("hello\n"))
   543  				writer.(io.Closer).Close()
   544  			})
   545  
   546  			Context("when saving the event succeeds", func() {
   547  				BeforeEach(func() {
   548  					fakeBuild.SaveEventReturns(nil)
   549  				})
   550  
   551  				It("returns the length of the string, and no error", func() {
   552  					Expect(writtenBytes).To(Equal(len("hello\n")))
   553  					Expect(writeErr).ToNot(HaveOccurred())
   554  				})
   555  
   556  				It("saves a log event", func() {
   557  					Expect(fakeBuild.SaveEventCallCount()).To(Equal(1))
   558  					Expect(fakeBuild.SaveEventArgsForCall(0)).To(Equal(event.Log{
   559  						Time:    now.Unix(),
   560  						Payload: "hello\n",
   561  						Origin: event.Origin{
   562  							Source: event.OriginSourceStderr,
   563  							ID:     "some-plan-id",
   564  						},
   565  					}))
   566  				})
   567  			})
   568  
   569  			Context("when saving the event fails", func() {
   570  				disaster := errors.New("nope")
   571  
   572  				BeforeEach(func() {
   573  					fakeBuild.SaveEventReturns(disaster)
   574  				})
   575  
   576  				It("returns 0 length, and the error", func() {
   577  					Expect(writtenBytes).To(Equal(0))
   578  					Expect(writeErr).To(Equal(disaster))
   579  				})
   580  			})
   581  		})
   582  	})
   583  
   584  	Describe("Errored", func() {
   585  		JustBeforeEach(func() {
   586  			delegate.Errored(logger, "fake error message")
   587  		})
   588  
   589  		Context("when saving the event succeeds", func() {
   590  			BeforeEach(func() {
   591  				fakeBuild.SaveEventReturns(nil)
   592  			})
   593  
   594  			It("saves it with the current time", func() {
   595  				Expect(fakeBuild.SaveEventCallCount()).To(Equal(1))
   596  				Expect(fakeBuild.SaveEventArgsForCall(0)).To(Equal(event.Error{
   597  					Time:    now.Unix(),
   598  					Message: "fake error message",
   599  					Origin: event.Origin{
   600  						ID: "some-plan-id",
   601  					},
   602  				}))
   603  			})
   604  		})
   605  
   606  		Context("when saving the event fails", func() {
   607  			disaster := errors.New("nope")
   608  
   609  			BeforeEach(func() {
   610  				fakeBuild.SaveEventReturns(disaster)
   611  			})
   612  
   613  			It("logs an error", func() {
   614  				logs := logger.Logs()
   615  				Expect(len(logs)).To(Equal(1))
   616  				Expect(logs[0].Message).To(Equal("test.failed-to-save-error-event"))
   617  				Expect(logs[0].Data).To(Equal(lager.Data{"error": "nope"}))
   618  			})
   619  		})
   620  	})
   621  
   622  	Describe("No line buffer without secrets redaction", func() {
   623  		var runState exec.RunState
   624  
   625  		BeforeEach(func() {
   626  			credVars := vars.StaticVariables{}
   627  			runState = exec.NewRunState(noopStepper, credVars, false)
   628  			delegate = engine.NewBuildStepDelegate(fakeBuild, "some-plan-id", runState, fakeClock, fakePolicyChecker)
   629  		})
   630  
   631  		Context("Stdout", func() {
   632  			It("should not buffer lines", func() {
   633  				writer := delegate.Stdout()
   634  				writtenBytes, writeErr := writer.Write([]byte("1\r"))
   635  				Expect(writeErr).To(BeNil())
   636  				Expect(writtenBytes).To(Equal(len("1\r")))
   637  				writtenBytes, writeErr = writer.Write([]byte("2\r"))
   638  				Expect(writeErr).To(BeNil())
   639  				Expect(writtenBytes).To(Equal(len("2\r")))
   640  				writtenBytes, writeErr = writer.Write([]byte("3\r"))
   641  				Expect(writeErr).To(BeNil())
   642  				Expect(writtenBytes).To(Equal(len("3\r")))
   643  				writeErr = writer.(io.Closer).Close()
   644  				Expect(writeErr).To(BeNil())
   645  
   646  				Expect(fakeBuild.SaveEventCallCount()).To(Equal(3))
   647  				Expect(fakeBuild.SaveEventArgsForCall(0)).To(Equal(event.Log{
   648  					Time:    now.Unix(),
   649  					Payload: "1\r",
   650  					Origin: event.Origin{
   651  						Source: event.OriginSourceStdout,
   652  						ID:     "some-plan-id",
   653  					},
   654  				}))
   655  				Expect(fakeBuild.SaveEventArgsForCall(1)).To(Equal(event.Log{
   656  					Time:    now.Unix(),
   657  					Payload: "2\r",
   658  					Origin: event.Origin{
   659  						Source: event.OriginSourceStdout,
   660  						ID:     "some-plan-id",
   661  					},
   662  				}))
   663  				Expect(fakeBuild.SaveEventArgsForCall(2)).To(Equal(event.Log{
   664  					Time:    now.Unix(),
   665  					Payload: "3\r",
   666  					Origin: event.Origin{
   667  						Source: event.OriginSourceStdout,
   668  						ID:     "some-plan-id",
   669  					},
   670  				}))
   671  			})
   672  		})
   673  
   674  		Context("Stderr", func() {
   675  			It("should not buffer lines", func() {
   676  				writer := delegate.Stderr()
   677  				writtenBytes, writeErr := writer.Write([]byte("1\r"))
   678  				Expect(writeErr).To(BeNil())
   679  				Expect(writtenBytes).To(Equal(len("1\r")))
   680  				writtenBytes, writeErr = writer.Write([]byte("2\r"))
   681  				Expect(writeErr).To(BeNil())
   682  				Expect(writtenBytes).To(Equal(len("2\r")))
   683  				writtenBytes, writeErr = writer.Write([]byte("3\r"))
   684  				Expect(writeErr).To(BeNil())
   685  				Expect(writtenBytes).To(Equal(len("3\r")))
   686  				writeErr = writer.(io.Closer).Close()
   687  				Expect(writeErr).To(BeNil())
   688  
   689  				Expect(fakeBuild.SaveEventCallCount()).To(Equal(3))
   690  				Expect(fakeBuild.SaveEventArgsForCall(0)).To(Equal(event.Log{
   691  					Time:    now.Unix(),
   692  					Payload: "1\r",
   693  					Origin: event.Origin{
   694  						Source: event.OriginSourceStderr,
   695  						ID:     "some-plan-id",
   696  					},
   697  				}))
   698  				Expect(fakeBuild.SaveEventArgsForCall(1)).To(Equal(event.Log{
   699  					Time:    now.Unix(),
   700  					Payload: "2\r",
   701  					Origin: event.Origin{
   702  						Source: event.OriginSourceStderr,
   703  						ID:     "some-plan-id",
   704  					},
   705  				}))
   706  				Expect(fakeBuild.SaveEventArgsForCall(2)).To(Equal(event.Log{
   707  					Time:    now.Unix(),
   708  					Payload: "3\r",
   709  					Origin: event.Origin{
   710  						Source: event.OriginSourceStderr,
   711  						ID:     "some-plan-id",
   712  					},
   713  				}))
   714  			})
   715  		})
   716  	})
   717  
   718  	Describe("Secrets redaction", func() {
   719  		var (
   720  			runState     exec.RunState
   721  			writer       io.Writer
   722  			writtenBytes int
   723  			writeErr     error
   724  		)
   725  
   726  		BeforeEach(func() {
   727  			runState = exec.NewRunState(noopStepper, credVars, true)
   728  			delegate = engine.NewBuildStepDelegate(fakeBuild, "some-plan-id", runState, fakeClock, fakePolicyChecker)
   729  
   730  			runState.Get(vars.Reference{Path: "source-param"})
   731  			runState.Get(vars.Reference{Path: "git-key"})
   732  		})
   733  
   734  		Context("Stdout", func() {
   735  			Context("single-line secret", func() {
   736  				JustBeforeEach(func() {
   737  					writer = delegate.Stdout()
   738  					writtenBytes, writeErr = writer.Write([]byte("ok super-secret-source ok"))
   739  					writer.(io.Closer).Close()
   740  				})
   741  
   742  				It("should be redacted", func() {
   743  					Expect(writeErr).To(BeNil())
   744  					Expect(writtenBytes).To(Equal(len("ok super-secret-source ok")))
   745  					Expect(fakeBuild.SaveEventCallCount()).To(Equal(1))
   746  					Expect(fakeBuild.SaveEventArgsForCall(0)).To(Equal(event.Log{
   747  						Time:    now.Unix(),
   748  						Payload: "ok ((redacted)) ok",
   749  						Origin: event.Origin{
   750  							Source: event.OriginSourceStdout,
   751  							ID:     "some-plan-id",
   752  						},
   753  					}))
   754  				})
   755  			})
   756  
   757  			Context("multi-line secret", func() {
   758  				var logLines string
   759  
   760  				JustBeforeEach(func() {
   761  					logLines = "ok123ok\nok456ok\nok789ok\n"
   762  					writer = delegate.Stdout()
   763  					writtenBytes, writeErr = writer.Write([]byte(logLines))
   764  					writer.(io.Closer).Close()
   765  				})
   766  
   767  				It("should be redacted", func() {
   768  					Expect(writeErr).To(BeNil())
   769  					Expect(writtenBytes).To(Equal(len(logLines)))
   770  					Expect(fakeBuild.SaveEventCallCount()).To(Equal(1))
   771  					Expect(fakeBuild.SaveEventArgsForCall(0)).To(Equal(event.Log{
   772  						Time:    now.Unix(),
   773  						Payload: "ok((redacted))ok\nok((redacted))ok\nok((redacted))ok\n",
   774  						Origin: event.Origin{
   775  							Source: event.OriginSourceStdout,
   776  							ID:     "some-plan-id",
   777  						},
   778  					}))
   779  				})
   780  			})
   781  
   782  			Context("multi-line secret with random log chunk", func() {
   783  				JustBeforeEach(func() {
   784  					writer = delegate.Stdout()
   785  					writtenBytes, writeErr = writer.Write([]byte("ok123ok\nok4"))
   786  					writtenBytes, writeErr = writer.Write([]byte("56ok\nok789ok\n"))
   787  					writer.(io.Closer).Close()
   788  				})
   789  
   790  				It("should be redacted", func() {
   791  					Expect(fakeBuild.SaveEventCallCount()).To(Equal(2))
   792  					Expect(fakeBuild.SaveEventArgsForCall(0)).To(Equal(event.Log{
   793  						Time:    now.Unix(),
   794  						Payload: "ok((redacted))ok\n",
   795  						Origin: event.Origin{
   796  							Source: event.OriginSourceStdout,
   797  							ID:     "some-plan-id",
   798  						},
   799  					}))
   800  					Expect(fakeBuild.SaveEventArgsForCall(1)).To(Equal(event.Log{
   801  						Time:    now.Unix(),
   802  						Payload: "ok((redacted))ok\nok((redacted))ok\n",
   803  						Origin: event.Origin{
   804  							Source: event.OriginSourceStdout,
   805  							ID:     "some-plan-id",
   806  						},
   807  					}))
   808  				})
   809  			})
   810  		})
   811  
   812  		Context("Stderr", func() {
   813  			Context("single-line secret", func() {
   814  				JustBeforeEach(func() {
   815  					writer = delegate.Stderr()
   816  					writtenBytes, writeErr = writer.Write([]byte("ok super-secret-source ok"))
   817  					writer.(io.Closer).Close()
   818  				})
   819  
   820  				It("should be redacted", func() {
   821  					Expect(writeErr).To(BeNil())
   822  					Expect(writtenBytes).To(Equal(len("ok super-secret-source ok")))
   823  					Expect(fakeBuild.SaveEventCallCount()).To(Equal(1))
   824  					Expect(fakeBuild.SaveEventArgsForCall(0)).To(Equal(event.Log{
   825  						Time:    now.Unix(),
   826  						Payload: "ok ((redacted)) ok",
   827  						Origin: event.Origin{
   828  							Source: event.OriginSourceStderr,
   829  							ID:     "some-plan-id",
   830  						},
   831  					}))
   832  				})
   833  			})
   834  
   835  			Context("multi-line secret", func() {
   836  				var logLines string
   837  
   838  				JustBeforeEach(func() {
   839  					logLines = "{\nok123ok\nok456ok\nok789ok\n}\n"
   840  					writer = delegate.Stderr()
   841  					writtenBytes, writeErr = writer.Write([]byte(logLines))
   842  					writer.(io.Closer).Close()
   843  				})
   844  
   845  				It("should be redacted", func() {
   846  					Expect(writeErr).To(BeNil())
   847  					Expect(writtenBytes).To(Equal(len(logLines)))
   848  					Expect(fakeBuild.SaveEventCallCount()).To(Equal(1))
   849  					Expect(fakeBuild.SaveEventArgsForCall(0)).To(Equal(event.Log{
   850  						Time:    now.Unix(),
   851  						Payload: "{\nok((redacted))ok\nok((redacted))ok\nok((redacted))ok\n}\n",
   852  						Origin: event.Origin{
   853  							Source: event.OriginSourceStderr,
   854  							ID:     "some-plan-id",
   855  						},
   856  					}))
   857  				})
   858  			})
   859  
   860  			Context("multi-line secret with random log chunk", func() {
   861  				JustBeforeEach(func() {
   862  					writer = delegate.Stderr()
   863  					writtenBytes, writeErr = writer.Write([]byte("ok123ok\nok4"))
   864  					writtenBytes, writeErr = writer.Write([]byte("56ok\nok789ok\n"))
   865  					writer.(io.Closer).Close()
   866  				})
   867  
   868  				It("should be redacted", func() {
   869  					Expect(fakeBuild.SaveEventCallCount()).To(Equal(2))
   870  					Expect(fakeBuild.SaveEventArgsForCall(0)).To(Equal(event.Log{
   871  						Time:    now.Unix(),
   872  						Payload: "ok((redacted))ok\n",
   873  						Origin: event.Origin{
   874  							Source: event.OriginSourceStderr,
   875  							ID:     "some-plan-id",
   876  						},
   877  					}))
   878  					Expect(fakeBuild.SaveEventArgsForCall(1)).To(Equal(event.Log{
   879  						Time:    now.Unix(),
   880  						Payload: "ok((redacted))ok\nok((redacted))ok\n",
   881  						Origin: event.Origin{
   882  							Source: event.OriginSourceStderr,
   883  							ID:     "some-plan-id",
   884  						},
   885  					}))
   886  				})
   887  			})
   888  		})
   889  	})
   890  })