package buildstep import ( "encoding/json" "fmt" "slices" "strings" "time" "git.novox.be/novox/mesh-sdk/go/buscalls" "git.novox.be/novox/mesh-sdk/go/catalogue" ) // **The artifact store's holder takes its own new build even when its seat is the one broken** (novox/hq design 47, // lines 1888-1906 at b33ac325; test 60): when the seat oci-artifact-registry refuses or does not answer the store of a // trunk build of a module that claims that seat, the build module keeps that build on the bus, one object per build // named by its digest, and the node-engine composing the holder asks the build seats' verb kept-artifact where it is. // The judgement the three build modules share is here (ADR 0634 ยง2): what may be kept, the kept builds a node holds, // and the verb's answer with its three refusals. The store and the verb's serving are each build module's own. // StoreSeat is the seat whose holder's builds a build node keeps. const StoreSeat = "oci-artifact-registry" // The refusals of kept-artifact, the words of the part build-step's enum. const ( KeptRefusedClaimsNoStore = "claims no " + StoreSeat KeptRefusedNotKept = "not kept" KeptRefusedDigestDiffers = "digest differs" ) // CatalogModuleVerb is the seat mesh-catalog's verb that answers one module's manifest, as a manifest invokes it. const CatalogModuleVerb = "seat:mesh-catalog.module" // KeptFor is what a build keeps a refused artifact for: the module, which claims the store's seat, and its build's // fingerprint, which kept-artifact is asked by. type KeptFor struct{ Module, Fp string } // KeepOf is the keep of a build, or nil when nothing may be kept: only a trunk build (publish) of a module whose manifest // at the built commit claims the store's seat, named by its fingerprint. func KeepOf(publish bool, manifest catalogue.Manifest, fp string) *KeptFor { if !publish || fp == "" || !manifest.ClaimsSeat(StoreSeat) { return nil } return &KeptFor{Module: manifest.Module, Fp: fp} } // KeptBuild is one kept build: the object's description names its module, fingerprint and the node that kept it. type KeptBuild struct { Module string `json:"module"` Fp string `json:"fp"` Node string `json:"node"` Digest string `json:"digest"` Bytes uint64 `json:"bytes"` LastUsed time.Time `json:"last-used"` } // KeptObject is the object name (the hex digest of "sha256:") and description a build is kept under on node. func KeptObject(k KeptFor, node, digest string) (name, description string) { desc, _ := json.Marshal(KeptBuild{Module: k.Module, Fp: k.Fp, Node: node}) return strings.TrimPrefix(digest, "sha256:"), string(desc) } // KeptBuildsOf are the builds node keeps among a store's objects, by digest. func KeptBuildsOf(infos []buscalls.ObjectInfo, node string) []KeptBuild { out := []KeptBuild{} for _, i := range infos { var k KeptBuild if i.Deleted || json.Unmarshal([]byte(i.Description), &k) != nil || k.Node != node { continue } k.Digest, k.Bytes, k.LastUsed = i.Name, i.Size, i.Modified.UTC() out = append(out, k) } slices.SortFunc(out, func(a, b KeptBuild) int { return strings.Compare(a.Digest, b.Digest) }) return out } // ClaimsStoreOf reads the seat mesh-catalog's answer of one module (CatalogModuleVerb): whether its manifest claims the // store's seat; a module the catalog holds no manifest of claims nothing. func ClaimsStoreOf(module string, raw []byte) (bool, error) { var a struct { Module struct { Manifest json.RawMessage `json:"manifest"` } `json:"module"` } if err := json.Unmarshal(raw, &a); err != nil { return false, fmt.Errorf("%s answered what is no module: %w", CatalogModuleVerb, err) } if len(a.Module.Manifest) == 0 { return false, nil } man, err := catalogue.Decode(a.Module.Manifest) if err != nil { return false, fmt.Errorf("the manifest of %s: %w", module, err) } return man.ClaimsSeat(StoreSeat), nil } // KeptArtifactAnswer is the verb kept-artifact's answer, the part build-step's layout. type KeptArtifactAnswer struct { Module string `json:"module"` Fp string `json:"fp"` Digest string `json:"digest,omitempty"` Size int64 `json:"size,omitempty"` Store string `json:"store,omitempty"` Object string `json:"object,omitempty"` Refused string `json:"refused,omitempty"` Words string `json:"words,omitempty"` Error string `json:"error,omitempty"` } // KeptArtifact answers kept-artifact for module, fp and the hex digest from the builds this node keeps, in store (the // store's name on the bus); claims asks the seat mesh-catalog whether a module claims the store's seat, only when no // build of it is kept here at that digest. func KeptArtifact(module, fp, digest string, kept []KeptBuild, store string, claims func(module string) (bool, error)) KeptArtifactAnswer { a := KeptArtifactAnswer{Module: module, Fp: fp} var other *KeptBuild for i, k := range kept { if k.Module != module || k.Fp != fp { continue } if k.Digest == digest { a.Digest, a.Size, a.Object = k.Digest, int64(k.Bytes), k.Digest if store == "" { a.Error = "this module runs with no name of its own (MESH_MODULE), so its store's name on the bus is unknown" return a } a.Store = store return a } other = &kept[i] } ok, err := claims(module) switch { case err != nil: a.Error = "asking whether " + module + " claims " + StoreSeat + ": " + err.Error() case !ok: a.Refused = KeptRefusedClaimsNoStore a.Words = fmt.Sprintf("%s claims no %s: a build node keeps builds for that seat's holder alone", module, StoreSeat) case other != nil: a.Refused, a.Digest = KeptRefusedDigestDiffers, other.Digest a.Words = fmt.Sprintf("this node keeps the build of %s at %s under the digest %s, not %s", module, fp, other.Digest, digest) default: a.Refused = KeptRefusedNotKept a.Words = fmt.Sprintf("this node keeps no build of %s at %s (never kept, or swept)", module, fp) } return a }