diff --git a/modules/mesh-delivery-pipeline/go.mod b/modules/mesh-delivery-pipeline/go.mod index c6af02f8..a55bcf38 100644 --- a/modules/mesh-delivery-pipeline/go.mod +++ b/modules/mesh-delivery-pipeline/go.mod @@ -42,7 +42,7 @@ require ( git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000 // indirect git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000 // indirect git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000 // indirect - git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk v0.0.0-00010101000000-000000000000 // indirect + git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk v0.0.0-00010101000000-000000000000 github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op // indirect github.com/minio/highwayhash v1.0.4 // indirect github.com/nats-io/jwt/v2 v2.8.2 // indirect diff --git a/modules/mesh-delivery-pipeline/internal/pipeline/place.go b/modules/mesh-delivery-pipeline/internal/pipeline/place.go index 68f5864d..2037f55a 100644 --- a/modules/mesh-delivery-pipeline/internal/pipeline/place.go +++ b/modules/mesh-delivery-pipeline/internal/pipeline/place.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "fmt" "sort" + "strconv" "strings" "time" @@ -53,10 +54,21 @@ var moduleSteps = []string{seat.StepResultStepComposition, seat.StepResultStepRe // kindImage is the form of an acting rule's `on` naming an artifact kind (§324). const kindImage = "kind: image" -// AskID is a step's ask id, the same for the same pipeline, artifact and step, so a start asked again asks nothing new. -func AskID(pipeline, artifact, step string) string { +// AskID is a step's ask id in the usual ask id form the holders accept (buildstep.CheckAskID): base 36 of started in +// milliseconds, then six hex digits of the sha256 of the pipeline, the artifact and the step, so the same placement makes +// the same id at every start and the bus dedupes on it; started is the pipeline's start time, or a rerun's own (design 47 +// G A-297). +func AskID(started time.Time, pipeline, artifact, step string) string { sum := sha256.Sum256([]byte(pipeline + "\n" + artifact + "\n" + step)) - return "step-" + hex.EncodeToString(sum[:12]) + return strconv.FormatInt(started.UnixMilli(), 36) + hex.EncodeToString(sum[:3]) +} + +// startOf is a pipeline's start time, the time its step ask ids open with (A-297); now for a pipeline that records none. +func startOf(p seat.Pipeline, now time.Time) time.Time { + if p.Started != nil { + return *p.Started + } + return now } // Place places a pipeline's steps (design 47 G §21, §23, §323; A-91): only the steps the module's registration policy @@ -74,7 +86,7 @@ func Place(p seat.Pipeline, reg catalog.Registration, entries map[string]catalog // The build step is every pipeline's first, required whatever the policy names, asked of the seat mesh-builds for the // module's own entry only (A-99, A-97): a group's other members are built by their own pipelines. build := seat.StepResult{Step: seat.StepResultStepBuild, Status: seat.StepResultStatusRequired, Fingerprint: p.Fingerprint, - Seat: builds.Seat, Outcome: seat.StepResultOutcomeRunning, Started: &now, Ask: AskID(p.Pipeline, "", seat.StepResultStepBuild)} + Seat: builds.Seat, Outcome: seat.StepResultOutcomeRunning, Started: &now, Ask: AskID(startOf(p, now), p.Pipeline, "", seat.StepResultStepBuild)} steps = append(steps, build) asks = append(asks, Asked{Seat: builds.Seat, ID: build.Ask, Ask: builds.StepRun{Pipeline: p.Pipeline, Step: builds.StepRunStepBuild, Branch: p.Branch, Revision: p.Revision, Source: p.Source, Entries: []builds.BuildEntry{{Module: p.Module, Commit: p.Commit, Fingerprint: p.Fingerprint}}}}) @@ -102,7 +114,7 @@ func Place(p seat.Pipeline, reg catalog.Registration, entries map[string]catalog continue } s.Seat, s.Outcome, s.Started = bs, seat.StepResultOutcomeRunning, &now - s.Ask = AskID(p.Pipeline, a.Ref, step) + s.Ask = AskID(startOf(p, now), p.Pipeline, a.Ref, step) steps = append(steps, s) ask := Asked{Seat: bs, ID: s.Ask, Ask: stepAsk(p, entry, a, step, s.Ask, asker, now)} if a.Package != nil { @@ -124,7 +136,7 @@ func Place(p seat.Pipeline, reg catalog.Registration, entries map[string]catalog // running at once, and its end is heard from that seat as the build seats' are. Review and approval wait. if step == seat.StepResultStepComposition && status != seat.StepResultStatusBypass { s.Seat, s.Outcome, s.Started, s.Fingerprint = wants.Seat, seat.StepResultOutcomeRunning, &now, p.Fingerprint - s.Ask = AskID(p.Pipeline, "", step) + s.Ask = AskID(startOf(p, now), p.Pipeline, "", step) asks = append(asks, Asked{Seat: wants.Seat, ID: s.Ask, Ask: wants.StepRun{Branch: p.Branch, Commit: p.Commit, Fingerprint: p.Fingerprint, Module: p.Module, Pipeline: p.Pipeline, Revision: p.Revision, Source: p.Source, Step: wants.StepRunStepComposition}}) } diff --git a/modules/mesh-delivery-pipeline/internal/pipeline/place_test.go b/modules/mesh-delivery-pipeline/internal/pipeline/place_test.go index 1c26872d..e6c234dc 100644 --- a/modules/mesh-delivery-pipeline/internal/pipeline/place_test.go +++ b/modules/mesh-delivery-pipeline/internal/pipeline/place_test.go @@ -2,6 +2,7 @@ package pipeline import ( "encoding/json" + "strconv" "strings" "testing" "time" @@ -11,6 +12,7 @@ import ( seat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshpipelinerunner" wants "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants" nodebuildgo "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildgo" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep" ) func stepSeatOf(t *testing.T, name string) StepSeat { @@ -81,6 +83,12 @@ func TestStartPlacesTheStepsThePolicyNamesAndAsksEachBuildSeat(t *testing.T) { if len(asks.asks) != 7 { t.Fatalf("asked %d steps, want the build, the five running build steps and the composition", len(asks.asks)) } + // A-297: every step's ask id is the usual ask id form the holders accept, opened by the pipeline's start time. + for i, id := range asks.ids { + if err := buildstep.CheckAskID(id); err != nil || !strings.HasPrefix(id, strconv.FormatInt(p.Started.UnixMilli(), 36)) { + t.Errorf("ask %d's id %q is not the usual form opened by the pipeline's start: %v", i, id, err) + } + } // A-99: the build is every pipeline's first step, required whatever the policy names, asked of mesh-builds for the // module's own entry only. b, ok := asks.asks[0].(builds.StepRun) @@ -181,10 +189,11 @@ func TestCompositionIsAskedOfTheAssignerAndItsEndIsTaken(t *testing.T) { // placed under; an ended step, an ended pipeline and another branch's pipeline are asked of no one (design 47 G §88). func TestResumeAsksTheStepsPlacedAndNotEnded(t *testing.T) { reg, entries := placeFixture() + started := time.Unix(1, 0) // A-297: a step's ask id opens with its pipeline's start, so resuming later places it under the same id placed := func(branch string, source int64) seat.Pipeline { p := seat.Pipeline{Pipeline: Key(branch, source, "app"), Branch: branch, Source: source, Module: "app", Commit: "c1", Fingerprint: "src2:aa", - Repository: "o/app", Outcome: seat.PipelineOutcomeRunning} - p.Steps, _ = Place(p, reg, entries, time.Unix(1, 0), "the-runner") + Repository: "o/app", Outcome: seat.PipelineOutcomeRunning, Started: &started} + p.Steps, _ = Place(p, reg, entries, started, "the-runner") return p } main4, main5, beta6 := placed("main", 4), placed("main", 5), placed("beta", 6) diff --git a/modules/mesh-delivery-pipeline/internal/pipeline/verbs.go b/modules/mesh-delivery-pipeline/internal/pipeline/verbs.go index 6ff1d86c..a0118b68 100644 --- a/modules/mesh-delivery-pipeline/internal/pipeline/verbs.go +++ b/modules/mesh-delivery-pipeline/internal/pipeline/verbs.go @@ -325,12 +325,12 @@ func (v Verbs) askOf(p seat.Pipeline, s seat.StepResult, caller string, now time return nil, fmt.Errorf("the catalog half of %s is not held yet: the step is asked when it is", p.Branch) } _, placed := Place(p, reg, half.Entries, now, v.Starter.Name) - base := AskID(p.Pipeline, s.Artifact, s.Step) + base := AskID(startOf(p, now), p.Pipeline, s.Artifact, s.Step) for _, a := range placed { if a.ID != base { continue } - a.ID = AskID(p.Pipeline, s.Artifact, s.Step+"\nrerun\n"+now.Format(time.RFC3339Nano)) + a.ID = AskID(now, p.Pipeline, s.Artifact, s.Step) switch b := a.Ask.(type) { case nodebuildgo.StepAsk: b.ID = a.ID