github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/build.go (about) 1 package db 2 3 import ( 4 "database/sql" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "strconv" 9 "strings" 10 "time" 11 12 "code.cloudfoundry.org/lager" 13 sq "github.com/Masterminds/squirrel" 14 "github.com/pf-qiu/concourse/v6/atc/creds" 15 "github.com/pf-qiu/concourse/v6/vars" 16 "github.com/lib/pq" 17 "go.opentelemetry.io/otel/api/propagation" 18 19 "github.com/pf-qiu/concourse/v6/atc" 20 "github.com/pf-qiu/concourse/v6/atc/db/encryption" 21 "github.com/pf-qiu/concourse/v6/atc/db/lock" 22 "github.com/pf-qiu/concourse/v6/atc/event" 23 "github.com/pf-qiu/concourse/v6/tracing" 24 ) 25 26 const schema = "exec.v2" 27 28 var ErrAdoptRerunBuildHasNoInputs = errors.New("inputs not ready for build to rerun") 29 var ErrSetByNewerBuild = errors.New("pipeline set by a newer build") 30 31 type BuildInput struct { 32 Name string 33 Version atc.Version 34 ResourceID int 35 36 FirstOccurrence bool 37 ResolveError string 38 39 Context SpanContext 40 } 41 42 func (bi BuildInput) SpanContext() propagation.HTTPSupplier { 43 return bi.Context 44 } 45 46 type BuildOutput struct { 47 Name string 48 Version atc.Version 49 } 50 51 type BuildStatus string 52 53 const ( 54 BuildStatusPending BuildStatus = "pending" 55 BuildStatusStarted BuildStatus = "started" 56 BuildStatusAborted BuildStatus = "aborted" 57 BuildStatusSucceeded BuildStatus = "succeeded" 58 BuildStatusFailed BuildStatus = "failed" 59 BuildStatusErrored BuildStatus = "errored" 60 ) 61 62 func (status BuildStatus) String() string { 63 return string(status) 64 } 65 66 var buildsQuery = psql.Select(` 67 b.id, 68 b.name, 69 b.job_id, 70 b.resource_id, 71 b.resource_type_id, 72 b.team_id, 73 b.status, 74 b.manually_triggered, 75 b.scheduled, 76 b.schema, 77 b.private_plan, 78 b.public_plan, 79 b.create_time, 80 b.start_time, 81 b.end_time, 82 b.reap_time, 83 j.name, 84 r.name, 85 rt.name, 86 b.pipeline_id, 87 p.name, 88 p.instance_vars, 89 t.name, 90 b.nonce, 91 b.drained, 92 b.aborted, 93 b.completed, 94 b.inputs_ready, 95 b.rerun_of, 96 rb.name, 97 b.rerun_number, 98 b.span_context 99 `). 100 From("builds b"). 101 JoinClause("LEFT OUTER JOIN jobs j ON b.job_id = j.id"). 102 JoinClause("LEFT OUTER JOIN resources r ON b.resource_id = r.id"). 103 JoinClause("LEFT OUTER JOIN resource_types rt ON b.resource_type_id = rt.id"). 104 JoinClause("LEFT OUTER JOIN pipelines p ON b.pipeline_id = p.id"). 105 JoinClause("LEFT OUTER JOIN teams t ON b.team_id = t.id"). 106 JoinClause("LEFT OUTER JOIN builds rb ON rb.id = b.rerun_of") 107 108 var minMaxIdQuery = psql.Select("COALESCE(MAX(b.id), 0)", "COALESCE(MIN(b.id), 0)"). 109 From("builds as b") 110 111 var latestCompletedBuildQuery = psql.Select("max(id)"). 112 From("builds"). 113 Where(sq.Expr(`status NOT IN ('pending', 'started')`)) 114 115 //go:generate counterfeiter . Build 116 117 type Build interface { 118 PipelineRef 119 120 ID() int 121 Name() string 122 123 TeamID() int 124 TeamName() string 125 126 JobID() int 127 JobName() string 128 129 ResourceID() int 130 ResourceName() string 131 132 ResourceTypeID() int 133 ResourceTypeName() string 134 135 Schema() string 136 PrivatePlan() atc.Plan 137 PublicPlan() *json.RawMessage 138 HasPlan() bool 139 Status() BuildStatus 140 StartTime() time.Time 141 IsNewerThanLastCheckOf(input Resource) bool 142 EndTime() time.Time 143 ReapTime() time.Time 144 IsManuallyTriggered() bool 145 IsScheduled() bool 146 IsRunning() bool 147 IsCompleted() bool 148 InputsReady() bool 149 RerunOf() int 150 RerunOfName() string 151 RerunNumber() int 152 153 LagerData() lager.Data 154 TracingAttrs() tracing.Attrs 155 156 SyslogTag(event.OriginID) string 157 158 Reload() (bool, error) 159 160 ResourcesChecked() (bool, error) 161 162 AcquireTrackingLock(logger lager.Logger, interval time.Duration) (lock.Lock, bool, error) 163 164 Interceptible() (bool, error) 165 Preparation() (BuildPreparation, bool, error) 166 167 Start(atc.Plan) (bool, error) 168 Finish(BuildStatus) error 169 170 Variables(lager.Logger, creds.Secrets, creds.VarSourcePool) (vars.Variables, error) 171 172 SetInterceptible(bool) error 173 174 Events(uint) (EventSource, error) 175 SaveEvent(event atc.Event) error 176 177 Artifacts() ([]WorkerArtifact, error) 178 Artifact(artifactID int) (WorkerArtifact, error) 179 180 SaveOutput(string, atc.Source, atc.VersionedResourceTypes, atc.Version, ResourceConfigMetadataFields, string, string) error 181 AdoptInputsAndPipes() ([]BuildInput, bool, error) 182 AdoptRerunInputsAndPipes() ([]BuildInput, bool, error) 183 184 Resources() ([]BuildInput, []BuildOutput, error) 185 SaveImageResourceVersion(UsedResourceCache) error 186 187 Delete() (bool, error) 188 MarkAsAborted() error 189 IsAborted() bool 190 AbortNotifier() (Notifier, error) 191 192 IsDrained() bool 193 SetDrained(bool) error 194 195 SpanContext() propagation.HTTPSupplier 196 197 SavePipeline( 198 pipelineRef atc.PipelineRef, 199 teamId int, 200 config atc.Config, 201 from ConfigVersion, 202 initiallyPaused bool, 203 ) (Pipeline, bool, error) 204 } 205 206 type build struct { 207 pipelineRef 208 209 id int 210 name string 211 status BuildStatus 212 scheduled bool 213 inputsReady bool 214 215 teamID int 216 teamName string 217 218 jobID int 219 jobName string 220 221 resourceID int 222 resourceName string 223 224 resourceTypeID int 225 resourceTypeName string 226 227 isManuallyTriggered bool 228 229 rerunOf int 230 rerunOfName string 231 rerunNumber int 232 233 schema string 234 privatePlan atc.Plan 235 publicPlan *json.RawMessage 236 237 createTime time.Time 238 startTime time.Time 239 endTime time.Time 240 reapTime time.Time 241 242 drained bool 243 aborted bool 244 completed bool 245 246 spanContext SpanContext 247 } 248 249 func newEmptyBuild(conn Conn, lockFactory lock.LockFactory) *build { 250 return &build{pipelineRef: pipelineRef{conn: conn, lockFactory: lockFactory}} 251 } 252 253 var ErrBuildDisappeared = errors.New("build disappeared from db") 254 var ErrBuildHasNoPipeline = errors.New("build has no pipeline") 255 var ErrBuildArtifactNotFound = errors.New("build artifact not found") 256 257 type ResourceNotFoundInPipeline struct { 258 Resource string 259 Pipeline string 260 } 261 262 func (r ResourceNotFoundInPipeline) Error() string { 263 return fmt.Sprintf("resource %s not found in pipeline %s", r.Resource, r.Pipeline) 264 } 265 266 // SyslogTag returns a string to be set as a tag on syslog events pertaining to 267 // the build. 268 func (b *build) SyslogTag(origin event.OriginID) string { 269 segments := []string{b.teamName} 270 271 if b.pipelineID != 0 { 272 segments = append(segments, b.pipelineName) 273 } 274 275 if b.jobID != 0 { 276 segments = append(segments, b.jobName, b.name) 277 } else if b.resourceID != 0 { 278 segments = append(segments, b.resourceName, strconv.Itoa(b.id)) 279 } else if b.resourceTypeID != 0 { 280 segments = append(segments, b.resourceTypeName, strconv.Itoa(b.id)) 281 } else { 282 segments = append(segments, strconv.Itoa(b.id)) 283 } 284 285 segments = append(segments, origin.String()) 286 287 return strings.Join(segments, "/") 288 } 289 290 // LagerData returns attributes which are to be emitted in logs pertaining to 291 // the build. 292 func (b *build) LagerData() lager.Data { 293 data := lager.Data{ 294 "build_id": b.id, 295 "build": b.name, 296 "team": b.teamName, 297 } 298 299 if b.pipelineID != 0 { 300 data["pipeline"] = b.pipelineName 301 } 302 303 if b.jobID != 0 { 304 data["job"] = b.jobName 305 } 306 307 if b.resourceID != 0 { 308 data["resource"] = b.resourceName 309 } 310 311 if b.resourceTypeID != 0 { 312 data["resource_type"] = b.resourceTypeName 313 } 314 315 return data 316 } 317 318 // TracingAttrs returns attributes which are to be emitted in spans and 319 // metrics pertaining to the build. 320 func (b *build) TracingAttrs() tracing.Attrs { 321 data := tracing.Attrs{ 322 "build_id": strconv.Itoa(b.id), 323 "build": b.name, 324 "team": b.teamName, 325 } 326 327 if b.pipelineID != 0 { 328 data["pipeline"] = b.pipelineName 329 } 330 331 if b.jobID != 0 { 332 data["job"] = b.jobName 333 } 334 335 if b.resourceID != 0 { 336 data["resource"] = b.resourceName 337 } 338 339 if b.resourceTypeID != 0 { 340 data["resource_type"] = b.resourceTypeName 341 } 342 343 return data 344 } 345 346 func (b *build) ID() int { return b.id } 347 func (b *build) Name() string { return b.name } 348 func (b *build) JobID() int { return b.jobID } 349 func (b *build) JobName() string { return b.jobName } 350 func (b *build) ResourceID() int { return b.resourceID } 351 func (b *build) ResourceName() string { return b.resourceName } 352 func (b *build) ResourceTypeID() int { return b.resourceTypeID } 353 func (b *build) ResourceTypeName() string { return b.resourceTypeName } 354 func (b *build) TeamID() int { return b.teamID } 355 func (b *build) TeamName() string { return b.teamName } 356 func (b *build) IsManuallyTriggered() bool { return b.isManuallyTriggered } 357 func (b *build) Schema() string { return b.schema } 358 func (b *build) PrivatePlan() atc.Plan { return b.privatePlan } 359 func (b *build) PublicPlan() *json.RawMessage { return b.publicPlan } 360 func (b *build) HasPlan() bool { return string(*b.publicPlan) != "{}" } 361 func (b *build) IsNewerThanLastCheckOf(input Resource) bool { 362 return b.createTime.After(input.LastCheckEndTime()) 363 } 364 func (b *build) StartTime() time.Time { return b.startTime } 365 func (b *build) EndTime() time.Time { return b.endTime } 366 func (b *build) ReapTime() time.Time { return b.reapTime } 367 func (b *build) Status() BuildStatus { return b.status } 368 func (b *build) IsScheduled() bool { return b.scheduled } 369 func (b *build) IsDrained() bool { return b.drained } 370 func (b *build) IsRunning() bool { return !b.completed } 371 func (b *build) IsAborted() bool { return b.aborted } 372 func (b *build) IsCompleted() bool { return b.completed } 373 func (b *build) InputsReady() bool { return b.inputsReady } 374 func (b *build) RerunOf() int { return b.rerunOf } 375 func (b *build) RerunOfName() string { return b.rerunOfName } 376 func (b *build) RerunNumber() int { return b.rerunNumber } 377 378 func (b *build) Reload() (bool, error) { 379 row := buildsQuery.Where(sq.Eq{"b.id": b.id}). 380 RunWith(b.conn). 381 QueryRow() 382 383 err := scanBuild(b, row, b.conn.EncryptionStrategy()) 384 if err != nil { 385 if err == sql.ErrNoRows { 386 return false, nil 387 } 388 return false, err 389 } 390 391 return true, nil 392 } 393 394 func (b *build) Interceptible() (bool, error) { 395 var interceptible bool 396 397 err := psql.Select("interceptible"). 398 From("builds"). 399 Where(sq.Eq{ 400 "id": b.id, 401 }). 402 RunWith(b.conn). 403 QueryRow().Scan(&interceptible) 404 405 if err != nil { 406 return true, err 407 } 408 409 return interceptible, nil 410 } 411 412 func (b *build) SetInterceptible(i bool) error { 413 rows, err := psql.Update("builds"). 414 Set("interceptible", i). 415 Where(sq.Eq{ 416 "id": b.id, 417 }). 418 RunWith(b.conn). 419 Exec() 420 if err != nil { 421 return err 422 } 423 424 affected, err := rows.RowsAffected() 425 if err != nil { 426 return err 427 } 428 429 if affected == 0 { 430 return ErrBuildDisappeared 431 } 432 433 return nil 434 } 435 436 func (b *build) ResourcesChecked() (bool, error) { 437 var notChecked bool 438 err := b.conn.QueryRow(` 439 SELECT EXISTS ( 440 SELECT 1 441 FROM resources r 442 JOIN job_inputs ji ON ji.resource_id = r.id 443 JOIN resource_config_scopes rs ON r.resource_config_scope_id = rs.id 444 WHERE ji.job_id = $1 445 AND rs.last_check_end_time < $2 446 AND NOT EXISTS ( 447 SELECT 448 FROM resource_pins 449 WHERE resource_id = r.id 450 ) 451 )`, b.jobID, b.createTime).Scan(¬Checked) 452 if err != nil { 453 return false, err 454 } 455 456 return !notChecked, nil 457 } 458 459 func (b *build) Start(plan atc.Plan) (bool, error) { 460 tx, err := b.conn.Begin() 461 if err != nil { 462 return false, err 463 } 464 465 defer Rollback(tx) 466 467 started, err := b.start(tx, plan) 468 if err != nil { 469 return false, err 470 } 471 472 err = tx.Commit() 473 if err != nil { 474 return false, err 475 } 476 477 if !started { 478 return false, nil 479 } 480 481 err = b.conn.Bus().Notify(buildEventsChannel(b.id)) 482 if err != nil { 483 return false, err 484 } 485 486 err = b.conn.Bus().Notify(atc.ComponentBuildTracker) 487 if err != nil { 488 return false, err 489 } 490 491 return true, nil 492 } 493 494 func (b *build) start(tx Tx, plan atc.Plan) (bool, error) { 495 metadata, err := json.Marshal(plan) 496 if err != nil { 497 return false, err 498 } 499 500 encryptedPlan, nonce, err := b.conn.EncryptionStrategy().Encrypt([]byte(metadata)) 501 if err != nil { 502 return false, err 503 } 504 505 var startTime time.Time 506 err = psql.Update("builds"). 507 Set("status", BuildStatusStarted). 508 Set("start_time", sq.Expr("now()")). 509 Set("schema", schema). 510 Set("private_plan", encryptedPlan). 511 Set("public_plan", plan.Public()). 512 Set("nonce", nonce). 513 Where(sq.Eq{ 514 "id": b.id, 515 "status": "pending", 516 "aborted": false, 517 }). 518 Suffix("RETURNING start_time"). 519 RunWith(tx). 520 QueryRow(). 521 Scan(&startTime) 522 if err != nil { 523 if err == sql.ErrNoRows { 524 return false, nil 525 } 526 return false, err 527 } 528 529 err = b.saveEvent(tx, event.Status{ 530 Status: atc.StatusStarted, 531 Time: startTime.Unix(), 532 }) 533 if err != nil { 534 return false, err 535 } 536 537 return true, nil 538 } 539 540 func (b *build) Finish(status BuildStatus) error { 541 tx, err := b.conn.Begin() 542 if err != nil { 543 return err 544 } 545 546 defer Rollback(tx) 547 548 var endTime time.Time 549 550 err = psql.Update("builds"). 551 Set("status", status). 552 Set("end_time", sq.Expr("now()")). 553 Set("completed", true). 554 Set("private_plan", nil). 555 Set("nonce", nil). 556 Where(sq.Eq{"id": b.id}). 557 Suffix("RETURNING end_time"). 558 RunWith(tx). 559 QueryRow(). 560 Scan(&endTime) 561 if err != nil { 562 return err 563 } 564 565 err = b.saveEvent(tx, event.Status{ 566 Status: atc.BuildStatus(status), 567 Time: endTime.Unix(), 568 }) 569 if err != nil { 570 return err 571 } 572 573 _, err = tx.Exec(fmt.Sprintf(` 574 DROP SEQUENCE %s 575 `, buildEventSeq(b.id))) 576 if err != nil { 577 return err 578 } 579 580 if b.jobID != 0 && status == BuildStatusSucceeded { 581 _, err = tx.Exec(`WITH caches AS ( 582 SELECT resource_cache_id, build_id 583 FROM build_image_resource_caches brc 584 JOIN builds b ON b.id = brc.build_id 585 WHERE b.job_id = $1 586 ) 587 DELETE FROM build_image_resource_caches birc 588 USING caches c 589 WHERE c.build_id = birc.build_id AND birc.build_id < $2`, 590 b.jobID, b.id) 591 if err != nil { 592 return err 593 } 594 595 rows, err := psql.Select("o.resource_id", "o.version_md5"). 596 From("build_resource_config_version_outputs o"). 597 Where(sq.Eq{ 598 "o.build_id": b.id, 599 }). 600 RunWith(tx). 601 Query() 602 if err != nil { 603 return err 604 } 605 606 defer Close(rows) 607 608 uniqueVersions := map[AlgorithmVersion]bool{} 609 outputVersions := map[string][]string{} 610 for rows.Next() { 611 var resourceID int 612 var version string 613 614 err = rows.Scan(&resourceID, &version) 615 if err != nil { 616 return err 617 } 618 619 resourceVersion := AlgorithmVersion{ 620 ResourceID: resourceID, 621 Version: ResourceVersion(version), 622 } 623 624 if !uniqueVersions[resourceVersion] { 625 resID := strconv.Itoa(resourceID) 626 outputVersions[resID] = append(outputVersions[resID], version) 627 628 uniqueVersions[resourceVersion] = true 629 } 630 } 631 632 rows, err = psql.Select("i.resource_id", "i.version_md5"). 633 From("build_resource_config_version_inputs i"). 634 Where(sq.Eq{ 635 "i.build_id": b.id, 636 }). 637 RunWith(tx). 638 Query() 639 if err != nil { 640 return err 641 } 642 643 defer Close(rows) 644 645 for rows.Next() { 646 var resourceID int 647 var version string 648 649 err = rows.Scan(&resourceID, &version) 650 if err != nil { 651 return err 652 } 653 654 resourceVersion := AlgorithmVersion{ 655 ResourceID: resourceID, 656 Version: ResourceVersion(version), 657 } 658 659 if !uniqueVersions[resourceVersion] { 660 resID := strconv.Itoa(resourceID) 661 outputVersions[resID] = append(outputVersions[resID], version) 662 663 uniqueVersions[resourceVersion] = true 664 } 665 } 666 667 outputsJSON, err := json.Marshal(outputVersions) 668 if err != nil { 669 return err 670 } 671 672 var rerunOf sql.NullInt64 673 if b.rerunOf != 0 { 674 rerunOf = sql.NullInt64{Int64: int64(b.rerunOf), Valid: true} 675 } 676 677 _, err = psql.Insert("successful_build_outputs"). 678 Columns("build_id", "job_id", "rerun_of", "outputs"). 679 Values(b.id, b.jobID, rerunOf, outputsJSON). 680 RunWith(tx). 681 Exec() 682 if err != nil { 683 return err 684 } 685 686 // recursively archive any child pipelines. This is likely the most common case for 687 // automatic archiving so it's worth it to make the feedback more instantenous rather 688 // than relying on GC 689 pipelineRows, err := pipelinesQuery. 690 Prefix(` 691 WITH RECURSIVE pipelines_to_archive AS ( 692 SELECT id from pipelines where archived = false AND parent_job_id = $1 AND parent_build_id < $2 693 UNION 694 SELECT p.id from pipelines p join jobs j on p.parent_job_id = j.id join pipelines_to_archive on j.pipeline_id = pipelines_to_archive.id 695 )`, 696 b.jobID, b.id, 697 ). 698 Where("EXISTS(SELECT 1 FROM pipelines_to_archive pa WHERE pa.id = p.id)"). 699 RunWith(tx). 700 Query() 701 702 if err != nil { 703 return err 704 } 705 defer pipelineRows.Close() 706 707 err = archivePipelines(tx, b.conn, b.lockFactory, pipelineRows) 708 if err != nil { 709 return err 710 } 711 } 712 713 if b.jobID != 0 { 714 err = requestScheduleOnDownstreamJobs(tx, b.jobID) 715 if err != nil { 716 return err 717 } 718 719 err = updateTransitionBuildForJob(tx, b.jobID, b.id, status, b.rerunOf) 720 if err != nil { 721 return err 722 } 723 724 latestNonRerunID, err := latestCompletedNonRerunBuild(tx, b.jobID) 725 if err != nil { 726 return err 727 } 728 729 err = updateLatestCompletedBuildForJob(tx, b.jobID, latestNonRerunID) 730 if err != nil { 731 return err 732 } 733 734 err = updateNextBuildForJob(tx, b.jobID, latestNonRerunID) 735 if err != nil { 736 return err 737 } 738 } 739 740 err = tx.Commit() 741 if err != nil { 742 return err 743 } 744 745 err = b.conn.Bus().Notify(buildEventsChannel(b.id)) 746 if err != nil { 747 return err 748 } 749 750 return nil 751 } 752 753 // Variables creates variables for this build. If the build is a one-off build, it 754 // just uses the global secrets manager. If it belongs to a pipeline, it combines 755 // the global secrets manager with the pipeline's var_sources. 756 func (b *build) Variables(logger lager.Logger, globalSecrets creds.Secrets, varSourcePool creds.VarSourcePool) (vars.Variables, error) { 757 // "fly execute" generated build will have no pipeline. 758 if b.pipelineID == 0 { 759 return creds.NewVariables(globalSecrets, b.teamName, b.pipelineName, false), nil 760 } 761 pipeline, found, err := b.Pipeline() 762 if err != nil { 763 return nil, fmt.Errorf("failed to find pipeline: %w", err) 764 } 765 if !found { 766 return nil, errors.New("pipeline not found") 767 } 768 769 return pipeline.Variables(logger, globalSecrets, varSourcePool) 770 } 771 772 func (b *build) SetDrained(drained bool) error { 773 _, err := psql.Update("builds"). 774 Set("drained", drained). 775 Where(sq.Eq{"id": b.id}). 776 RunWith(b.conn). 777 Exec() 778 779 if err == nil { 780 b.drained = drained 781 } 782 return err 783 } 784 785 func (b *build) Delete() (bool, error) { 786 rows, err := psql.Delete("builds"). 787 Where(sq.Eq{ 788 "id": b.id, 789 }). 790 RunWith(b.conn). 791 Exec() 792 if err != nil { 793 return false, err 794 } 795 796 affected, err := rows.RowsAffected() 797 if err != nil { 798 return false, err 799 } 800 801 if affected == 0 { 802 return false, ErrBuildDisappeared 803 } 804 805 return true, nil 806 } 807 808 // MarkAsAborted will send the abort notification to all build abort 809 // channel listeners. It will set the status to aborted that will make 810 // AbortNotifier send notification in case if tracking ATC misses the first 811 // notification on abort channel. 812 // Setting status as aborted will also make Start() return false in case where 813 // build was aborted before it was started. 814 func (b *build) MarkAsAborted() error { 815 tx, err := b.conn.Begin() 816 if err != nil { 817 return err 818 } 819 820 defer Rollback(tx) 821 822 _, err = psql.Update("builds"). 823 Set("aborted", true). 824 Where(sq.Eq{"id": b.id}). 825 RunWith(tx). 826 Exec() 827 if err != nil { 828 return err 829 } 830 831 if b.status == BuildStatusPending { 832 err = requestSchedule(tx, b.jobID) 833 if err != nil { 834 return err 835 } 836 } 837 838 err = tx.Commit() 839 if err != nil { 840 return err 841 } 842 843 return b.conn.Bus().Notify(buildAbortChannel(b.id)) 844 } 845 846 // AbortNotifier returns a Notifier that can be watched for when the build 847 // is marked as aborted. Once the build is marked as aborted it will send a 848 // notification to finish the build to ATC that is tracking this build. 849 func (b *build) AbortNotifier() (Notifier, error) { 850 return newConditionNotifier(b.conn.Bus(), buildAbortChannel(b.id), func() (bool, error) { 851 var aborted bool 852 err := psql.Select("aborted = true"). 853 From("builds"). 854 Where(sq.Eq{"id": b.id}). 855 RunWith(b.conn). 856 QueryRow(). 857 Scan(&aborted) 858 859 return aborted, err 860 }) 861 } 862 863 func (b *build) SaveImageResourceVersion(rc UsedResourceCache) error { 864 _, err := psql.Insert("build_image_resource_caches"). 865 Columns("resource_cache_id", "build_id"). 866 Values(rc.ID(), b.id). 867 RunWith(b.conn). 868 Exec() 869 if err != nil { 870 if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == pqUniqueViolationErrCode { 871 return nil 872 } 873 874 return err 875 } 876 877 return nil 878 } 879 880 func (b *build) AcquireTrackingLock(logger lager.Logger, interval time.Duration) (lock.Lock, bool, error) { 881 lock, acquired, err := b.lockFactory.Acquire( 882 logger.Session("lock", lager.Data{ 883 "build_id": b.id, 884 }), 885 lock.NewBuildTrackingLockID(b.id), 886 ) 887 if err != nil { 888 return nil, false, err 889 } 890 891 if !acquired { 892 return nil, false, nil 893 } 894 895 return lock, true, nil 896 } 897 898 func (b *build) Preparation() (BuildPreparation, bool, error) { 899 if b.jobID == 0 || b.status != BuildStatusPending { 900 return BuildPreparation{ 901 BuildID: b.id, 902 PausedPipeline: BuildPreparationStatusNotBlocking, 903 PausedJob: BuildPreparationStatusNotBlocking, 904 MaxRunningBuilds: BuildPreparationStatusNotBlocking, 905 Inputs: map[string]BuildPreparationStatus{}, 906 InputsSatisfied: BuildPreparationStatusNotBlocking, 907 MissingInputReasons: MissingInputReasons{}, 908 }, true, nil 909 } 910 911 var ( 912 pausedPipeline bool 913 pausedJob bool 914 maxInFlightReached bool 915 pipelineID int 916 jobName string 917 ) 918 err := psql.Select("p.paused, j.paused, j.max_in_flight_reached, j.pipeline_id, j.name"). 919 From("builds b"). 920 Join("jobs j ON b.job_id = j.id"). 921 Join("pipelines p ON j.pipeline_id = p.id"). 922 Where(sq.Eq{"b.id": b.id}). 923 RunWith(b.conn). 924 QueryRow(). 925 Scan(&pausedPipeline, &pausedJob, &maxInFlightReached, &pipelineID, &jobName) 926 if err != nil { 927 if err == sql.ErrNoRows { 928 return BuildPreparation{}, false, nil 929 } 930 return BuildPreparation{}, false, err 931 } 932 933 pausedPipelineStatus := BuildPreparationStatusNotBlocking 934 if pausedPipeline { 935 pausedPipelineStatus = BuildPreparationStatusBlocking 936 } 937 938 pausedJobStatus := BuildPreparationStatusNotBlocking 939 if pausedJob { 940 pausedJobStatus = BuildPreparationStatusBlocking 941 } 942 943 maxInFlightReachedStatus := BuildPreparationStatusNotBlocking 944 if maxInFlightReached { 945 maxInFlightReachedStatus = BuildPreparationStatusBlocking 946 } 947 948 tf := NewTeamFactory(b.conn, b.lockFactory) 949 t, found, err := tf.FindTeam(b.teamName) 950 if err != nil { 951 return BuildPreparation{}, false, err 952 } 953 954 if !found { 955 return BuildPreparation{}, false, nil 956 } 957 958 pipeline, found, err := t.Pipeline(b.PipelineRef()) 959 if err != nil { 960 return BuildPreparation{}, false, err 961 } 962 963 if !found { 964 return BuildPreparation{}, false, nil 965 } 966 967 job, found, err := pipeline.Job(jobName) 968 if err != nil { 969 return BuildPreparation{}, false, err 970 } 971 972 if !found { 973 return BuildPreparation{}, false, nil 974 } 975 976 config, err := job.Config() 977 if err != nil { 978 return BuildPreparation{}, false, err 979 } 980 981 configInputs := config.Inputs() 982 983 buildInputs, err := job.GetNextBuildInputs() 984 if err != nil { 985 return BuildPreparation{}, false, err 986 } 987 988 resolved := true 989 for _, input := range buildInputs { 990 if input.ResolveError != "" { 991 resolved = false 992 break 993 } 994 } 995 996 inputsSatisfiedStatus := BuildPreparationStatusNotBlocking 997 inputs := map[string]BuildPreparationStatus{} 998 missingInputReasons := MissingInputReasons{} 999 1000 for _, configInput := range configInputs { 1001 buildInput := BuildInput{} 1002 found := false 1003 for _, b := range buildInputs { 1004 if b.Name == configInput.Name { 1005 found = true 1006 buildInput = b 1007 break 1008 } 1009 } 1010 1011 if found { 1012 if buildInput.ResolveError == "" { 1013 if b.IsManuallyTriggered() { 1014 resource, _, err := pipeline.ResourceByID(buildInput.ResourceID) 1015 if err != nil { 1016 return BuildPreparation{}, false, err 1017 } 1018 1019 // input is blocking if its last check time is before build create time 1020 if b.IsNewerThanLastCheckOf(resource) { 1021 inputs[buildInput.Name] = BuildPreparationStatusBlocking 1022 missingInputReasons.RegisterNoResourceCheckFinished(buildInput.Name) 1023 inputsSatisfiedStatus = BuildPreparationStatusBlocking 1024 } else { 1025 inputs[buildInput.Name] = BuildPreparationStatusNotBlocking 1026 } 1027 } else { 1028 inputs[buildInput.Name] = BuildPreparationStatusNotBlocking 1029 } 1030 } else { 1031 inputs[configInput.Name] = BuildPreparationStatusBlocking 1032 missingInputReasons.RegisterResolveError(configInput.Name, buildInput.ResolveError) 1033 inputsSatisfiedStatus = BuildPreparationStatusBlocking 1034 } 1035 } else { 1036 if resolved { 1037 inputs[configInput.Name] = BuildPreparationStatusBlocking 1038 missingInputReasons.RegisterMissingInput(configInput.Name) 1039 inputsSatisfiedStatus = BuildPreparationStatusBlocking 1040 } 1041 } 1042 } 1043 1044 buildPreparation := BuildPreparation{ 1045 BuildID: b.id, 1046 PausedPipeline: pausedPipelineStatus, 1047 PausedJob: pausedJobStatus, 1048 MaxRunningBuilds: maxInFlightReachedStatus, 1049 Inputs: inputs, 1050 InputsSatisfied: inputsSatisfiedStatus, 1051 MissingInputReasons: missingInputReasons, 1052 } 1053 1054 return buildPreparation, true, nil 1055 } 1056 1057 func (b *build) Events(from uint) (EventSource, error) { 1058 notifier, err := newConditionNotifier(b.conn.Bus(), buildEventsChannel(b.id), func() (bool, error) { 1059 return true, nil 1060 }) 1061 if err != nil { 1062 return nil, err 1063 } 1064 1065 return newBuildEventSource( 1066 b.id, 1067 b.eventsTable(), 1068 b.conn, 1069 notifier, 1070 from, 1071 ), nil 1072 } 1073 1074 func (b *build) SaveEvent(event atc.Event) error { 1075 tx, err := b.conn.Begin() 1076 if err != nil { 1077 return err 1078 } 1079 1080 defer Rollback(tx) 1081 1082 err = b.saveEvent(tx, event) 1083 if err != nil { 1084 return err 1085 } 1086 1087 err = tx.Commit() 1088 if err != nil { 1089 return err 1090 } 1091 1092 return b.conn.Bus().Notify(buildEventsChannel(b.id)) 1093 } 1094 1095 func (b *build) Artifact(artifactID int) (WorkerArtifact, error) { 1096 1097 artifact := artifact{ 1098 conn: b.conn, 1099 } 1100 1101 err := psql.Select("id", "name", "created_at"). 1102 From("worker_artifacts"). 1103 Where(sq.Eq{ 1104 "id": artifactID, 1105 }). 1106 RunWith(b.conn). 1107 Scan(&artifact.id, &artifact.name, &artifact.createdAt) 1108 1109 return &artifact, err 1110 } 1111 1112 func (b *build) Artifacts() ([]WorkerArtifact, error) { 1113 artifacts := []WorkerArtifact{} 1114 1115 rows, err := psql.Select("id", "name", "created_at"). 1116 From("worker_artifacts"). 1117 Where(sq.Eq{ 1118 "build_id": b.id, 1119 }). 1120 RunWith(b.conn). 1121 Query() 1122 if err != nil { 1123 return nil, err 1124 } 1125 1126 defer Close(rows) 1127 1128 for rows.Next() { 1129 wa := artifact{ 1130 conn: b.conn, 1131 buildID: b.id, 1132 } 1133 1134 err = rows.Scan(&wa.id, &wa.name, &wa.createdAt) 1135 if err != nil { 1136 return nil, err 1137 } 1138 1139 artifacts = append(artifacts, &wa) 1140 } 1141 1142 return artifacts, nil 1143 } 1144 1145 func (b *build) SaveOutput( 1146 resourceType string, 1147 source atc.Source, 1148 resourceTypes atc.VersionedResourceTypes, 1149 version atc.Version, 1150 metadata ResourceConfigMetadataFields, 1151 outputName string, 1152 resourceName string, 1153 ) error { 1154 // We should never save outputs for builds without a Pipeline ID because 1155 // One-off Builds will never have Put steps. This shouldn't happen, but 1156 // its best to return an error just in case 1157 if b.pipelineID == 0 { 1158 return ErrBuildHasNoPipeline 1159 } 1160 1161 pipeline, found, err := b.Pipeline() 1162 if err != nil { 1163 return err 1164 } 1165 1166 if !found { 1167 return ErrBuildHasNoPipeline 1168 } 1169 1170 theResource, found, err := pipeline.Resource(resourceName) 1171 if err != nil { 1172 return err 1173 } 1174 1175 if !found { 1176 return ResourceNotFoundInPipeline{resourceName, b.pipelineName} 1177 } 1178 1179 tx, err := b.conn.Begin() 1180 if err != nil { 1181 return err 1182 } 1183 1184 defer Rollback(tx) 1185 1186 resourceConfigDescriptor, err := constructResourceConfigDescriptor(resourceType, source, resourceTypes) 1187 if err != nil { 1188 return err 1189 } 1190 1191 resourceConfig, err := resourceConfigDescriptor.findOrCreate(tx, b.lockFactory, b.conn) 1192 if err != nil { 1193 return err 1194 } 1195 1196 resourceConfigScope, err := findOrCreateResourceConfigScope(tx, b.conn, b.lockFactory, resourceConfig, theResource) 1197 if err != nil { 1198 return err 1199 } 1200 1201 newVersion, err := saveResourceVersion(tx, resourceConfigScope.ID(), version, metadata, nil) 1202 if err != nil { 1203 return err 1204 } 1205 1206 versionBytes, err := json.Marshal(version) 1207 if err != nil { 1208 return err 1209 } 1210 1211 versionJSON := string(versionBytes) 1212 1213 if newVersion { 1214 err = incrementCheckOrder(tx, resourceConfigScope.ID(), versionJSON) 1215 if err != nil { 1216 return err 1217 } 1218 } 1219 1220 err = theResource.(*resource).setResourceConfigScopeInTransaction(tx, resourceConfigScope) 1221 if err != nil { 1222 return err 1223 } 1224 1225 _, err = psql.Insert("build_resource_config_version_outputs"). 1226 Columns("resource_id", "build_id", "version_md5", "name"). 1227 Values(theResource.ID(), strconv.Itoa(b.id), sq.Expr("md5(?)", versionJSON), outputName). 1228 Suffix("ON CONFLICT DO NOTHING"). 1229 RunWith(tx). 1230 Exec() 1231 if err != nil { 1232 return err 1233 } 1234 1235 if newVersion { 1236 err = requestScheduleForJobsUsingResourceConfigScope(tx, resourceConfigScope.ID()) 1237 if err != nil { 1238 return err 1239 } 1240 } 1241 1242 err = tx.Commit() 1243 if err != nil { 1244 return err 1245 } 1246 1247 return nil 1248 } 1249 1250 func (b *build) AdoptInputsAndPipes() ([]BuildInput, bool, error) { 1251 tx, err := b.conn.Begin() 1252 if err != nil { 1253 return nil, false, err 1254 } 1255 1256 defer tx.Rollback() 1257 1258 var determined bool 1259 err = psql.Select("inputs_determined"). 1260 From("jobs"). 1261 Where(sq.Eq{ 1262 "id": b.jobID, 1263 }). 1264 RunWith(tx). 1265 QueryRow(). 1266 Scan(&determined) 1267 if err != nil { 1268 return nil, false, err 1269 } 1270 1271 if !determined { 1272 return nil, false, nil 1273 } 1274 1275 _, err = psql.Delete("build_resource_config_version_inputs"). 1276 Where(sq.Eq{"build_id": b.id}). 1277 RunWith(tx). 1278 Exec() 1279 if err != nil { 1280 return nil, false, err 1281 } 1282 1283 rows, err := psql.Insert("build_resource_config_version_inputs"). 1284 Columns("resource_id", "version_md5", "name", "first_occurrence", "build_id"). 1285 Select(psql.Select("i.resource_id", "i.version_md5", "i.input_name", "i.first_occurrence"). 1286 Column("?", b.id). 1287 From("next_build_inputs i"). 1288 Where(sq.Eq{"i.job_id": b.jobID})). 1289 Suffix("ON CONFLICT (build_id, resource_id, version_md5, name) DO UPDATE SET first_occurrence = EXCLUDED.first_occurrence"). 1290 Suffix("RETURNING name, resource_id, version_md5, first_occurrence"). 1291 RunWith(tx). 1292 Query() 1293 if err != nil { 1294 return nil, false, err 1295 } 1296 1297 inputs := InputMapping{} 1298 for rows.Next() { 1299 var ( 1300 inputName string 1301 firstOccurrence bool 1302 versionMD5 string 1303 resourceID int 1304 ) 1305 1306 err := rows.Scan(&inputName, &resourceID, &versionMD5, &firstOccurrence) 1307 if err != nil { 1308 return nil, false, err 1309 } 1310 1311 inputs[inputName] = InputResult{ 1312 Input: &AlgorithmInput{ 1313 AlgorithmVersion: AlgorithmVersion{ 1314 ResourceID: resourceID, 1315 Version: ResourceVersion(versionMD5), 1316 }, 1317 FirstOccurrence: firstOccurrence, 1318 }, 1319 } 1320 } 1321 1322 buildInputs := []BuildInput{} 1323 1324 for inputName, input := range inputs { 1325 var versionBlob string 1326 1327 err = psql.Select("v.version"). 1328 From("resource_config_versions v"). 1329 Join("resources r ON r.resource_config_scope_id = v.resource_config_scope_id"). 1330 Where(sq.Eq{ 1331 "v.version_md5": input.Input.Version, 1332 "r.id": input.Input.ResourceID, 1333 }). 1334 RunWith(tx). 1335 QueryRow(). 1336 Scan(&versionBlob) 1337 if err != nil { 1338 if err == sql.ErrNoRows { 1339 tx.Rollback() 1340 1341 _, err = psql.Update("next_build_inputs"). 1342 Set("resolve_error", fmt.Sprintf("chosen version of input %s not available", inputName)). 1343 Where(sq.Eq{ 1344 "job_id": b.jobID, 1345 "input_name": inputName, 1346 }). 1347 RunWith(b.conn). 1348 Exec() 1349 } 1350 1351 return nil, false, err 1352 } 1353 1354 var version atc.Version 1355 err = json.Unmarshal([]byte(versionBlob), &version) 1356 if err != nil { 1357 return nil, false, err 1358 } 1359 1360 buildInputs = append(buildInputs, BuildInput{ 1361 Name: inputName, 1362 ResourceID: input.Input.ResourceID, 1363 Version: version, 1364 FirstOccurrence: input.Input.FirstOccurrence, 1365 }) 1366 } 1367 1368 _, err = psql.Delete("build_pipes"). 1369 Where(sq.Eq{"to_build_id": b.id}). 1370 RunWith(tx). 1371 Exec() 1372 if err != nil { 1373 return nil, false, err 1374 } 1375 1376 _, err = psql.Insert("build_pipes"). 1377 Columns("from_build_id", "to_build_id"). 1378 Select(psql.Select("nbp.from_build_id"). 1379 Column("?", b.id). 1380 From("next_build_pipes nbp"). 1381 Where(sq.Eq{"nbp.to_job_id": b.jobID})). 1382 Suffix("ON CONFLICT DO NOTHING"). 1383 RunWith(tx). 1384 Exec() 1385 if err != nil { 1386 return nil, false, err 1387 } 1388 1389 _, err = psql.Update("builds"). 1390 Set("inputs_ready", true). 1391 Where(sq.Eq{ 1392 "id": b.id, 1393 }). 1394 RunWith(tx). 1395 Exec() 1396 if err != nil { 1397 return nil, false, err 1398 } 1399 1400 err = tx.Commit() 1401 if err != nil { 1402 return nil, false, err 1403 } 1404 1405 return buildInputs, true, nil 1406 } 1407 1408 func (b *build) AdoptRerunInputsAndPipes() ([]BuildInput, bool, error) { 1409 tx, err := b.conn.Begin() 1410 if err != nil { 1411 return nil, false, err 1412 } 1413 1414 defer tx.Rollback() 1415 1416 var ready bool 1417 err = psql.Select("inputs_ready"). 1418 From("builds"). 1419 Where(sq.Eq{ 1420 "id": b.rerunOf, 1421 }). 1422 RunWith(tx). 1423 QueryRow(). 1424 Scan(&ready) 1425 if err != nil { 1426 return nil, false, err 1427 } 1428 1429 if !ready { 1430 return nil, false, nil 1431 } 1432 1433 _, err = psql.Delete("build_resource_config_version_inputs"). 1434 Where(sq.Eq{"build_id": b.id}). 1435 RunWith(tx). 1436 Exec() 1437 if err != nil { 1438 return nil, false, err 1439 } 1440 1441 rows, err := psql.Insert("build_resource_config_version_inputs"). 1442 Columns("resource_id", "version_md5", "name", "first_occurrence", "build_id"). 1443 Select(psql.Select("i.resource_id", "i.version_md5", "i.name", "false"). 1444 Column("?", b.id). 1445 From("build_resource_config_version_inputs i"). 1446 Where(sq.Eq{"i.build_id": b.rerunOf})). 1447 Suffix("ON CONFLICT (build_id, resource_id, version_md5, name) DO NOTHING"). 1448 Suffix("RETURNING name, resource_id, version_md5, first_occurrence"). 1449 RunWith(tx). 1450 Query() 1451 if err != nil { 1452 return nil, false, err 1453 } 1454 1455 inputs := InputMapping{} 1456 for rows.Next() { 1457 var ( 1458 inputName string 1459 firstOccurrence bool 1460 versionMD5 string 1461 resourceID int 1462 ) 1463 1464 err := rows.Scan(&inputName, &resourceID, &versionMD5, &firstOccurrence) 1465 if err != nil { 1466 return nil, false, err 1467 } 1468 1469 inputs[inputName] = InputResult{ 1470 Input: &AlgorithmInput{ 1471 AlgorithmVersion: AlgorithmVersion{ 1472 ResourceID: resourceID, 1473 Version: ResourceVersion(versionMD5), 1474 }, 1475 FirstOccurrence: firstOccurrence, 1476 }, 1477 } 1478 } 1479 1480 buildInputs := []BuildInput{} 1481 for inputName, input := range inputs { 1482 var versionBlob string 1483 1484 err = psql.Select("v.version"). 1485 From("resource_config_versions v"). 1486 Join("resources r ON r.resource_config_scope_id = v.resource_config_scope_id"). 1487 Where(sq.Eq{ 1488 "v.version_md5": input.Input.Version, 1489 "r.id": input.Input.ResourceID, 1490 }). 1491 RunWith(tx). 1492 QueryRow(). 1493 Scan(&versionBlob) 1494 if err != nil { 1495 if err == sql.ErrNoRows { 1496 tx.Rollback() 1497 1498 _, err = psql.Update("next_build_inputs"). 1499 Set("resolve_error", fmt.Sprintf("chosen version of input %s not available", inputName)). 1500 Where(sq.Eq{ 1501 "job_id": b.jobID, 1502 "input_name": inputName, 1503 }). 1504 RunWith(b.conn). 1505 Exec() 1506 1507 err = b.MarkAsAborted() 1508 if err != nil { 1509 return nil, false, err 1510 } 1511 } 1512 1513 return nil, false, err 1514 } 1515 1516 var version atc.Version 1517 err = json.Unmarshal([]byte(versionBlob), &version) 1518 if err != nil { 1519 return nil, false, err 1520 } 1521 1522 buildInputs = append(buildInputs, BuildInput{ 1523 Name: inputName, 1524 ResourceID: input.Input.ResourceID, 1525 Version: version, 1526 FirstOccurrence: input.Input.FirstOccurrence, 1527 }) 1528 } 1529 1530 _, err = psql.Delete("build_pipes"). 1531 Where(sq.Eq{"to_build_id": b.id}). 1532 RunWith(tx). 1533 Exec() 1534 if err != nil { 1535 return nil, false, err 1536 } 1537 1538 _, err = psql.Insert("build_pipes"). 1539 Columns("from_build_id", "to_build_id"). 1540 Select(psql.Select("bp.from_build_id"). 1541 Column("?", b.id). 1542 From("build_pipes bp"). 1543 Where(sq.Eq{"bp.to_build_id": b.rerunOf})). 1544 Suffix("ON CONFLICT DO NOTHING"). 1545 RunWith(tx). 1546 Exec() 1547 if err != nil { 1548 return nil, false, err 1549 } 1550 1551 _, err = psql.Update("builds"). 1552 Set("inputs_ready", true). 1553 Where(sq.Eq{ 1554 "id": b.id, 1555 }). 1556 RunWith(tx). 1557 Exec() 1558 if err != nil { 1559 return nil, false, err 1560 } 1561 1562 err = tx.Commit() 1563 if err != nil { 1564 return nil, false, err 1565 } 1566 1567 return buildInputs, true, nil 1568 } 1569 1570 func (b *build) Resources() ([]BuildInput, []BuildOutput, error) { 1571 inputs := []BuildInput{} 1572 outputs := []BuildOutput{} 1573 1574 tx, err := b.conn.Begin() 1575 if err != nil { 1576 return nil, nil, err 1577 } 1578 1579 defer Rollback(tx) 1580 1581 rows, err := psql.Select("inputs.name", "resources.id", "versions.version", `COALESCE(inputs.first_occurrence, NOT EXISTS ( 1582 SELECT 1 1583 FROM build_resource_config_version_inputs i, builds b 1584 WHERE versions.version_md5 = i.version_md5 1585 AND resources.resource_config_scope_id = versions.resource_config_scope_id 1586 AND resources.id = i.resource_id 1587 AND b.job_id = builds.job_id 1588 AND i.build_id = b.id 1589 AND i.build_id < builds.id 1590 ))`). 1591 From("resource_config_versions versions, build_resource_config_version_inputs inputs, builds, resources"). 1592 Where(sq.Eq{"builds.id": b.id}). 1593 Where(sq.Expr("inputs.build_id = builds.id")). 1594 Where(sq.Expr("inputs.version_md5 = versions.version_md5")). 1595 Where(sq.Expr("resources.resource_config_scope_id = versions.resource_config_scope_id")). 1596 Where(sq.Expr("resources.id = inputs.resource_id")). 1597 Where(sq.Expr(`NOT EXISTS ( 1598 SELECT 1 1599 FROM build_resource_config_version_outputs outputs 1600 WHERE outputs.version_md5 = versions.version_md5 1601 AND versions.resource_config_scope_id = resources.resource_config_scope_id 1602 AND outputs.resource_id = resources.id 1603 AND outputs.build_id = inputs.build_id 1604 )`)). 1605 RunWith(tx). 1606 Query() 1607 if err != nil { 1608 return nil, nil, err 1609 } 1610 1611 defer Close(rows) 1612 1613 for rows.Next() { 1614 var ( 1615 inputName string 1616 firstOcc bool 1617 versionBlob string 1618 version atc.Version 1619 resourceID int 1620 ) 1621 1622 err = rows.Scan(&inputName, &resourceID, &versionBlob, &firstOcc) 1623 if err != nil { 1624 return nil, nil, err 1625 } 1626 1627 err = json.Unmarshal([]byte(versionBlob), &version) 1628 if err != nil { 1629 return nil, nil, err 1630 } 1631 1632 inputs = append(inputs, BuildInput{ 1633 Name: inputName, 1634 Version: version, 1635 ResourceID: resourceID, 1636 FirstOccurrence: firstOcc, 1637 }) 1638 } 1639 1640 rows, err = psql.Select("outputs.name", "versions.version"). 1641 From("resource_config_versions versions, build_resource_config_version_outputs outputs, builds, resources"). 1642 Where(sq.Eq{"builds.id": b.id}). 1643 Where(sq.Expr("outputs.build_id = builds.id")). 1644 Where(sq.Expr("outputs.version_md5 = versions.version_md5")). 1645 Where(sq.Expr("outputs.resource_id = resources.id")). 1646 Where(sq.Expr("resources.resource_config_scope_id = versions.resource_config_scope_id")). 1647 RunWith(tx). 1648 Query() 1649 1650 if err != nil { 1651 return nil, nil, err 1652 } 1653 1654 defer Close(rows) 1655 1656 for rows.Next() { 1657 var ( 1658 outputName string 1659 versionBlob string 1660 version atc.Version 1661 ) 1662 1663 err := rows.Scan(&outputName, &versionBlob) 1664 if err != nil { 1665 return nil, nil, err 1666 } 1667 1668 err = json.Unmarshal([]byte(versionBlob), &version) 1669 if err != nil { 1670 return nil, nil, err 1671 } 1672 1673 outputs = append(outputs, BuildOutput{ 1674 Name: outputName, 1675 Version: version, 1676 }) 1677 } 1678 1679 err = tx.Commit() 1680 if err != nil { 1681 return nil, nil, err 1682 } 1683 1684 return inputs, outputs, nil 1685 } 1686 1687 func (b *build) SpanContext() propagation.HTTPSupplier { 1688 return b.spanContext 1689 } 1690 1691 func (b *build) SavePipeline( 1692 pipelineRef atc.PipelineRef, 1693 teamID int, 1694 config atc.Config, 1695 from ConfigVersion, 1696 initiallyPaused bool, 1697 ) (Pipeline, bool, error) { 1698 tx, err := b.conn.Begin() 1699 if err != nil { 1700 return nil, false, err 1701 } 1702 1703 defer Rollback(tx) 1704 1705 jobID := newNullInt64(b.jobID) 1706 buildID := newNullInt64(b.id) 1707 pipelineID, isNewPipeline, err := savePipeline(tx, pipelineRef, config, from, initiallyPaused, teamID, jobID, buildID) 1708 if err != nil { 1709 return nil, false, err 1710 } 1711 1712 pipeline := newPipeline(b.conn, b.lockFactory) 1713 err = scanPipeline( 1714 pipeline, 1715 pipelinesQuery. 1716 Where(sq.Eq{"p.id": pipelineID}). 1717 RunWith(tx). 1718 QueryRow(), 1719 ) 1720 if err != nil { 1721 return nil, false, err 1722 } 1723 1724 err = tx.Commit() 1725 if err != nil { 1726 return nil, false, err 1727 } 1728 1729 return pipeline, isNewPipeline, nil 1730 } 1731 1732 func newNullInt64(i int) sql.NullInt64 { 1733 return sql.NullInt64{ 1734 Valid: true, 1735 Int64: int64(i), 1736 } 1737 } 1738 1739 func createBuildEventSeq(tx Tx, buildid int) error { 1740 _, err := tx.Exec(fmt.Sprintf(` 1741 CREATE SEQUENCE %s MINVALUE 0 1742 `, buildEventSeq(buildid))) 1743 return err 1744 } 1745 1746 func buildEventSeq(buildid int) string { 1747 return fmt.Sprintf("build_event_id_seq_%d", buildid) 1748 } 1749 1750 func scanBuild(b *build, row scannable, encryptionStrategy encryption.Strategy) error { 1751 var ( 1752 jobID, resourceID, resourceTypeID, pipelineID, rerunOf, rerunNumber sql.NullInt64 1753 schema, privatePlan, jobName, resourceName, resourceTypeName, pipelineName, publicPlan, rerunOfName sql.NullString 1754 createTime, startTime, endTime, reapTime pq.NullTime 1755 nonce, spanContext sql.NullString 1756 drained, aborted, completed bool 1757 status string 1758 pipelineInstanceVars sql.NullString 1759 ) 1760 1761 err := row.Scan( 1762 &b.id, 1763 &b.name, 1764 &jobID, 1765 &resourceID, 1766 &resourceTypeID, 1767 &b.teamID, 1768 &status, 1769 &b.isManuallyTriggered, 1770 &b.scheduled, 1771 &schema, 1772 &privatePlan, 1773 &publicPlan, 1774 &createTime, 1775 &startTime, 1776 &endTime, 1777 &reapTime, 1778 &jobName, 1779 &resourceName, 1780 &resourceTypeName, 1781 &pipelineID, 1782 &pipelineName, 1783 &pipelineInstanceVars, 1784 &b.teamName, 1785 &nonce, 1786 &drained, 1787 &aborted, 1788 &completed, 1789 &b.inputsReady, 1790 &rerunOf, 1791 &rerunOfName, 1792 &rerunNumber, 1793 &spanContext, 1794 ) 1795 if err != nil { 1796 return err 1797 } 1798 1799 b.status = BuildStatus(status) 1800 b.jobID = int(jobID.Int64) 1801 b.jobName = jobName.String 1802 b.resourceID = int(resourceID.Int64) 1803 b.resourceName = resourceName.String 1804 b.resourceTypeID = int(resourceTypeID.Int64) 1805 b.resourceTypeName = resourceTypeName.String 1806 b.pipelineID = int(pipelineID.Int64) 1807 b.pipelineName = pipelineName.String 1808 b.schema = schema.String 1809 b.createTime = createTime.Time 1810 b.startTime = startTime.Time 1811 b.endTime = endTime.Time 1812 b.reapTime = reapTime.Time 1813 b.drained = drained 1814 b.aborted = aborted 1815 b.completed = completed 1816 b.rerunOf = int(rerunOf.Int64) 1817 b.rerunOfName = rerunOfName.String 1818 b.rerunNumber = int(rerunNumber.Int64) 1819 1820 var ( 1821 noncense *string 1822 decryptedPlan []byte 1823 ) 1824 1825 if nonce.Valid { 1826 noncense = &nonce.String 1827 decryptedPlan, err = encryptionStrategy.Decrypt(string(privatePlan.String), noncense) 1828 if err != nil { 1829 return err 1830 } 1831 } else { 1832 decryptedPlan = []byte(privatePlan.String) 1833 } 1834 1835 if len(decryptedPlan) > 0 { 1836 err = json.Unmarshal(decryptedPlan, &b.privatePlan) 1837 if err != nil { 1838 return err 1839 } 1840 } 1841 1842 if publicPlan.Valid { 1843 err = json.Unmarshal([]byte(publicPlan.String), &b.publicPlan) 1844 if err != nil { 1845 return err 1846 } 1847 } 1848 1849 if spanContext.Valid { 1850 err = json.Unmarshal([]byte(spanContext.String), &b.spanContext) 1851 if err != nil { 1852 return err 1853 } 1854 } 1855 1856 if pipelineInstanceVars.Valid { 1857 err = json.Unmarshal([]byte(pipelineInstanceVars.String), &b.pipelineInstanceVars) 1858 if err != nil { 1859 return err 1860 } 1861 } 1862 1863 return nil 1864 } 1865 1866 func (b *build) saveEvent(tx Tx, event atc.Event) error { 1867 payload, err := json.Marshal(event) 1868 if err != nil { 1869 return err 1870 } 1871 1872 _, err = psql.Insert(b.eventsTable()). 1873 Columns("event_id", "build_id", "type", "version", "payload"). 1874 Values(sq.Expr("nextval('"+buildEventSeq(b.id)+"')"), b.id, string(event.EventType()), string(event.Version()), payload). 1875 RunWith(tx). 1876 Exec() 1877 return err 1878 } 1879 1880 func (b *build) eventsTable() string { 1881 if b.pipelineID != 0 { 1882 return fmt.Sprintf("pipeline_build_events_%d", b.pipelineID) 1883 } else { 1884 return fmt.Sprintf("team_build_events_%d", b.teamID) 1885 } 1886 } 1887 1888 func createBuild(tx Tx, build *build, vals map[string]interface{}) error { 1889 var buildID int 1890 1891 buildVals := make(map[string]interface{}) 1892 for name, value := range vals { 1893 buildVals[name] = value 1894 } 1895 1896 buildVals["needs_v6_migration"] = false 1897 1898 err := psql.Insert("builds"). 1899 SetMap(buildVals). 1900 Suffix("RETURNING id"). 1901 RunWith(tx). 1902 QueryRow(). 1903 Scan(&buildID) 1904 if err != nil { 1905 return err 1906 } 1907 1908 err = scanBuild(build, buildsQuery. 1909 Where(sq.Eq{"b.id": buildID}). 1910 RunWith(tx). 1911 QueryRow(), 1912 build.conn.EncryptionStrategy(), 1913 ) 1914 if err != nil { 1915 return err 1916 } 1917 1918 return createBuildEventSeq(tx, buildID) 1919 } 1920 1921 func buildStartedChannel() string { 1922 return atc.ComponentBuildTracker 1923 } 1924 1925 func buildEventsChannel(buildID int) string { 1926 return fmt.Sprintf("build_events_%d", buildID) 1927 } 1928 1929 func buildAbortChannel(buildID int) string { 1930 return fmt.Sprintf("build_abort_%d", buildID) 1931 } 1932 1933 func latestCompletedNonRerunBuild(tx Tx, jobID int) (int, error) { 1934 var latestNonRerunId int 1935 err := latestCompletedBuildQuery. 1936 Where(sq.Eq{"job_id": jobID}). 1937 Where(sq.Eq{"rerun_of": nil}). 1938 RunWith(tx). 1939 QueryRow(). 1940 Scan(&latestNonRerunId) 1941 if err != nil && err == sql.ErrNoRows { 1942 return 0, nil 1943 } 1944 1945 return latestNonRerunId, nil 1946 } 1947 1948 func updateNextBuildForJob(tx Tx, jobID int, latestNonRerunId int) error { 1949 _, err := tx.Exec(` 1950 UPDATE jobs AS j 1951 SET next_build_id = ( 1952 SELECT min(b.id) 1953 FROM builds b 1954 INNER JOIN jobs j ON j.id = b.job_id 1955 WHERE b.job_id = $1 1956 AND b.status IN ('pending', 'started') 1957 AND (b.rerun_of IS NULL OR b.rerun_of = $2) 1958 ) 1959 WHERE j.id = $1 1960 `, jobID, latestNonRerunId) 1961 if err != nil { 1962 return err 1963 } 1964 return nil 1965 } 1966 1967 func updateLatestCompletedBuildForJob(tx Tx, jobID int, latestNonRerunId int) error { 1968 var latestRerunId sql.NullString 1969 err := latestCompletedBuildQuery. 1970 Where(sq.Eq{"job_id": jobID}). 1971 Where(sq.Eq{"rerun_of": latestNonRerunId}). 1972 RunWith(tx). 1973 QueryRow(). 1974 Scan(&latestRerunId) 1975 if err != nil { 1976 return err 1977 } 1978 1979 var id int 1980 if latestRerunId.Valid { 1981 id, err = strconv.Atoi(latestRerunId.String) 1982 if err != nil { 1983 return err 1984 } 1985 } else { 1986 id = latestNonRerunId 1987 } 1988 1989 _, err = tx.Exec(` 1990 UPDATE jobs AS j 1991 SET latest_completed_build_id = $1 1992 WHERE j.id = $2 1993 `, id, jobID) 1994 if err != nil { 1995 return err 1996 } 1997 1998 return nil 1999 } 2000 2001 func updateTransitionBuildForJob(tx Tx, jobID int, buildID int, buildStatus BuildStatus, rerunID int) error { 2002 var shouldUpdateTransition bool 2003 2004 var latestID int 2005 var latestStatus BuildStatus 2006 err := psql.Select("b.id", "b.status"). 2007 From("builds b"). 2008 JoinClause("INNER JOIN jobs j ON j.latest_completed_build_id = b.id"). 2009 Where(sq.Eq{"j.id": jobID}). 2010 RunWith(tx). 2011 QueryRow(). 2012 Scan(&latestID, &latestStatus) 2013 if err != nil { 2014 if err == sql.ErrNoRows { 2015 // this is the first completed build; initiate transition 2016 shouldUpdateTransition = true 2017 } else { 2018 return err 2019 } 2020 } 2021 2022 if buildID < latestID { 2023 // latest completed build is actually after this one, so this build 2024 // has no influence on the job's overall state 2025 // 2026 // this can happen when multiple builds are running at a time and the 2027 // later-queued ones finish earlier 2028 return nil 2029 } 2030 2031 if latestStatus != buildStatus && (isNotRerunBuild(rerunID) || rerunID == latestID) { 2032 // status has changed; transitioned! 2033 shouldUpdateTransition = true 2034 } 2035 2036 if shouldUpdateTransition { 2037 _, err := psql.Update("jobs"). 2038 Set("transition_build_id", buildID). 2039 Where(sq.Eq{"id": jobID}). 2040 RunWith(tx). 2041 Exec() 2042 if err != nil { 2043 return err 2044 } 2045 } 2046 2047 return nil 2048 } 2049 2050 func isNotRerunBuild(rerunID int) bool { 2051 return rerunID == 0 2052 }