github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/exec/check_step.go (about) 1 package exec 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "time" 8 9 "code.cloudfoundry.org/lager" 10 "code.cloudfoundry.org/lager/lagerctx" 11 "github.com/pf-qiu/concourse/v6/atc" 12 "github.com/pf-qiu/concourse/v6/atc/creds" 13 "github.com/pf-qiu/concourse/v6/atc/db" 14 "github.com/pf-qiu/concourse/v6/atc/db/lock" 15 "github.com/pf-qiu/concourse/v6/atc/metric" 16 "github.com/pf-qiu/concourse/v6/atc/resource" 17 "github.com/pf-qiu/concourse/v6/atc/runtime" 18 "github.com/pf-qiu/concourse/v6/atc/worker" 19 "github.com/pf-qiu/concourse/v6/tracing" 20 ) 21 22 type CheckStep struct { 23 planID atc.PlanID 24 plan atc.CheckPlan 25 metadata StepMetadata 26 containerMetadata db.ContainerMetadata 27 resourceFactory resource.ResourceFactory 28 resourceConfigFactory db.ResourceConfigFactory 29 strategy worker.ContainerPlacementStrategy 30 pool worker.Pool 31 delegateFactory CheckDelegateFactory 32 workerClient worker.Client 33 defaultCheckTimeout time.Duration 34 } 35 36 //go:generate counterfeiter . CheckDelegateFactory 37 38 type CheckDelegateFactory interface { 39 CheckDelegate(state RunState) CheckDelegate 40 } 41 42 //go:generate counterfeiter . CheckDelegate 43 44 type CheckDelegate interface { 45 BuildStepDelegate 46 47 FindOrCreateScope(db.ResourceConfig) (db.ResourceConfigScope, error) 48 WaitToRun(context.Context, db.ResourceConfigScope) (lock.Lock, bool, error) 49 PointToCheckedConfig(db.ResourceConfigScope) error 50 } 51 52 func NewCheckStep( 53 planID atc.PlanID, 54 plan atc.CheckPlan, 55 metadata StepMetadata, 56 resourceFactory resource.ResourceFactory, 57 resourceConfigFactory db.ResourceConfigFactory, 58 containerMetadata db.ContainerMetadata, 59 strategy worker.ContainerPlacementStrategy, 60 pool worker.Pool, 61 delegateFactory CheckDelegateFactory, 62 client worker.Client, 63 defaultCheckTimeout time.Duration, 64 ) Step { 65 return &CheckStep{ 66 planID: planID, 67 plan: plan, 68 metadata: metadata, 69 resourceFactory: resourceFactory, 70 resourceConfigFactory: resourceConfigFactory, 71 containerMetadata: containerMetadata, 72 pool: pool, 73 strategy: strategy, 74 delegateFactory: delegateFactory, 75 workerClient: client, 76 defaultCheckTimeout: defaultCheckTimeout, 77 } 78 } 79 80 func (step *CheckStep) Run(ctx context.Context, state RunState) (bool, error) { 81 attrs := tracing.Attrs{ 82 "name": step.plan.Name, 83 } 84 85 if step.plan.Resource != "" { 86 attrs["resource"] = step.plan.Resource 87 } 88 89 if step.plan.ResourceType != "" { 90 attrs["resource_type"] = step.plan.ResourceType 91 } 92 93 delegate := step.delegateFactory.CheckDelegate(state) 94 ctx, span := delegate.StartSpan(ctx, "check", attrs) 95 96 ok, err := step.run(ctx, state, delegate) 97 tracing.End(span, err) 98 99 return ok, err 100 } 101 102 func (step *CheckStep) run(ctx context.Context, state RunState, delegate CheckDelegate) (bool, error) { 103 logger := lagerctx.FromContext(ctx) 104 logger = logger.Session("check-step", lager.Data{ 105 "step-name": step.plan.Name, 106 }) 107 108 delegate.Initializing(logger) 109 110 timeout := step.defaultCheckTimeout 111 if step.plan.Timeout != "" { 112 var err error 113 timeout, err = time.ParseDuration(step.plan.Timeout) 114 if err != nil { 115 return false, fmt.Errorf("parse timeout: %w", err) 116 } 117 } 118 119 source, err := creds.NewSource(state, step.plan.Source).Evaluate() 120 if err != nil { 121 return false, fmt.Errorf("resource config creds evaluation: %w", err) 122 } 123 124 resourceTypes, err := creds.NewVersionedResourceTypes(state, step.plan.VersionedResourceTypes).Evaluate() 125 if err != nil { 126 return false, fmt.Errorf("resource types creds evaluation: %w", err) 127 } 128 129 resourceConfig, err := step.resourceConfigFactory.FindOrCreateResourceConfig(step.plan.Type, source, resourceTypes) 130 if err != nil { 131 return false, fmt.Errorf("create resource config: %w", err) 132 } 133 134 // XXX(check-refactor): we should remove scopes as soon as it's safe to do 135 // so, i.e. global resources is on by default. i think this can be done when 136 // time resource becomes time var source (resolving thundering herd problem) 137 // and IAM is handled via var source prototypes (resolving unintentionally 138 // shared history problem) 139 scope, err := delegate.FindOrCreateScope(resourceConfig) 140 if err != nil { 141 return false, fmt.Errorf("create resource config scope: %w", err) 142 } 143 144 lock, run, err := delegate.WaitToRun(ctx, scope) 145 if err != nil { 146 return false, fmt.Errorf("wait: %w", err) 147 } 148 149 if run { 150 defer func() { 151 err := lock.Release() 152 if err != nil { 153 logger.Error("failed-to-release-lock", err) 154 } 155 }() 156 157 fromVersion := step.plan.FromVersion 158 if fromVersion == nil { 159 latestVersion, found, err := scope.LatestVersion() 160 if err != nil { 161 return false, fmt.Errorf("get latest version: %w", err) 162 } 163 164 if found { 165 fromVersion = atc.Version(latestVersion.Version()) 166 } 167 } 168 169 metric.Metrics.ChecksStarted.Inc() 170 171 _, err = scope.UpdateLastCheckStartTime() 172 if err != nil { 173 return false, fmt.Errorf("update check end time: %w", err) 174 } 175 176 result, err := step.runCheck(ctx, logger, delegate, timeout, resourceConfig, source, resourceTypes, fromVersion) 177 if err != nil { 178 metric.Metrics.ChecksFinishedWithError.Inc() 179 180 if _, updateErr := scope.UpdateLastCheckEndTime(); updateErr != nil { 181 return false, fmt.Errorf("update check end time: %w", updateErr) 182 } 183 184 if pointErr := delegate.PointToCheckedConfig(scope); pointErr != nil { 185 return false, fmt.Errorf("update resource config scope: %w", pointErr) 186 } 187 188 var scriptErr runtime.ErrResourceScriptFailed 189 if errors.As(err, &scriptErr) { 190 delegate.Finished(logger, false) 191 return false, nil 192 } 193 194 return false, fmt.Errorf("run check: %w", err) 195 } 196 197 metric.Metrics.ChecksFinishedWithSuccess.Inc() 198 199 err = scope.SaveVersions(db.NewSpanContext(ctx), result.Versions) 200 if err != nil { 201 return false, fmt.Errorf("save versions: %w", err) 202 } 203 204 if len(result.Versions) > 0 { 205 state.StoreResult(step.planID, result.Versions[len(result.Versions)-1]) 206 } 207 208 _, err = scope.UpdateLastCheckEndTime() 209 if err != nil { 210 return false, fmt.Errorf("update check end time: %w", err) 211 } 212 } else { 213 latestVersion, found, err := scope.LatestVersion() 214 if err != nil { 215 return false, fmt.Errorf("get latest version: %w", err) 216 } 217 218 if found { 219 state.StoreResult(step.planID, atc.Version(latestVersion.Version())) 220 } 221 } 222 223 err = delegate.PointToCheckedConfig(scope) 224 if err != nil { 225 return false, fmt.Errorf("update resource config scope: %w", err) 226 } 227 228 delegate.Finished(logger, true) 229 230 return true, nil 231 } 232 233 func (step *CheckStep) runCheck( 234 ctx context.Context, 235 logger lager.Logger, 236 delegate CheckDelegate, 237 timeout time.Duration, 238 resourceConfig db.ResourceConfig, 239 source atc.Source, 240 resourceTypes atc.VersionedResourceTypes, 241 fromVersion atc.Version, 242 ) (worker.CheckResult, error) { 243 workerSpec := worker.WorkerSpec{ 244 Tags: step.plan.Tags, 245 TeamID: step.metadata.TeamID, 246 ResourceType: step.plan.VersionedResourceTypes.Base(step.plan.Type), 247 } 248 249 var imageSpec worker.ImageSpec 250 resourceType, found := step.plan.VersionedResourceTypes.Lookup(step.plan.Type) 251 if found { 252 image := atc.ImageResource{ 253 Name: resourceType.Name, 254 Type: resourceType.Type, 255 Source: resourceType.Source, 256 Params: resourceType.Params, 257 Version: resourceType.Version, 258 Tags: resourceType.Tags, 259 } 260 if len(image.Tags) == 0 { 261 image.Tags = step.plan.Tags 262 } 263 264 types := step.plan.VersionedResourceTypes.Without(step.plan.Type) 265 266 var err error 267 imageSpec, err = delegate.FetchImage(ctx, image, types, resourceType.Privileged) 268 if err != nil { 269 return worker.CheckResult{}, err 270 } 271 } else { 272 imageSpec.ResourceType = step.plan.Type 273 } 274 275 containerSpec := worker.ContainerSpec{ 276 ImageSpec: imageSpec, 277 BindMounts: []worker.BindMountSource{ 278 &worker.CertsVolumeMount{Logger: logger}, 279 }, 280 TeamID: step.metadata.TeamID, 281 Env: step.metadata.Env(), 282 } 283 tracing.Inject(ctx, &containerSpec) 284 285 checkable := step.resourceFactory.NewResource( 286 source, 287 nil, 288 fromVersion, 289 ) 290 291 processSpec := runtime.ProcessSpec{ 292 Path: "/opt/resource/check", 293 StdoutWriter: delegate.Stdout(), 294 StderrWriter: delegate.Stderr(), 295 } 296 297 return step.workerClient.RunCheckStep( 298 ctx, 299 logger, 300 step.containerOwner(resourceConfig), 301 containerSpec, 302 workerSpec, 303 step.strategy, 304 step.containerMetadata, 305 processSpec, 306 delegate, 307 checkable, 308 timeout, 309 ) 310 } 311 312 func (step *CheckStep) containerOwner(resourceConfig db.ResourceConfig) db.ContainerOwner { 313 if step.plan.Resource == "" { 314 return db.NewBuildStepContainerOwner( 315 step.metadata.BuildID, 316 step.planID, 317 step.metadata.TeamID, 318 ) 319 } 320 321 expires := db.ContainerOwnerExpiries{ 322 Min: 5 * time.Minute, 323 Max: 1 * time.Hour, 324 } 325 326 // XXX(check-refactor): this can be turned into NewBuildStepContainerOwner 327 // now, but we should understand the performance implications first - it'll 328 // mean a lot more container churn 329 return db.NewResourceConfigCheckSessionContainerOwner( 330 resourceConfig.ID(), 331 resourceConfig.OriginBaseResourceType().ID, 332 expires, 333 ) 334 }