A merge rebuilds what it changed, and what packages it
Three faults in one path. A merge rebuilt every module built from the repository, so one change in a repository holding twenty-six of them meant twenty-six builds. A merge into a repository a module only *packages* source from rebuilt nothing — two modules are built from the control plane's own repository and neither had ever been rebuilt when it moved — because the manifest the mesh keeps carries no build section, so a build now says which repositories it read and the mesh keeps that beside what it stood on. And a module handed over by hand could record a repository with no directory inside it, which is a module nothing can ever rebuild (novox/hq 04-ISSUES/131, /132). A change inside no module's own directory is a change to what they share, and everything built from that repository is rebuilt: rebuilding too much is the safe direction, because the fault this whole path exists for is a mesh that believes it is current and is not.
This commit is contained in:
@@ -36,12 +36,21 @@ type Build struct {
|
||||
// Against is every artifact this build stood on, as references rather than module names —
|
||||
// what makes a build edge derived rather than declared (ADR 0009).
|
||||
Against []string
|
||||
// Read is every repository this build read source from besides the module's own (novox/hq
|
||||
// 04-ISSUES/131), at the ref it read.
|
||||
Read []ReadRepository
|
||||
// Failed is the builder's own words, empty when it worked.
|
||||
Failed string
|
||||
Made []Artifact
|
||||
At time.Time
|
||||
}
|
||||
|
||||
// ReadRepository is a repository a build read source from besides the module's own.
|
||||
type ReadRepository struct {
|
||||
Repository string `json:"repository"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
}
|
||||
|
||||
// Artifact is one thing a build published.
|
||||
type Artifact struct {
|
||||
Name string `json:"name"`
|
||||
@@ -66,17 +75,21 @@ func (i *Inventory) RecordBuild(ctx context.Context, b Build) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
read, err := json.Marshal(b.Read)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var module *string
|
||||
if b.Module != "" {
|
||||
module = &b.Module
|
||||
}
|
||||
_, err = i.store.Pool().Exec(ctx,
|
||||
`insert into build (id, repository, ref, module, commit_hash, built_on, failed, made,
|
||||
source_path, manifest, built_against)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
source_path, manifest, built_against, built_contexts)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
on conflict (id) do nothing`,
|
||||
b.ID, b.Repository, b.Ref, module, b.Commit, b.On, b.Failed, made,
|
||||
b.Path, manifestOrNil(b.Manifest), against)
|
||||
b.Path, manifestOrNil(b.Manifest), against, read)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -199,6 +212,44 @@ func (i *Inventory) BuiltAgainst(ctx context.Context) (map[string][]string, erro
|
||||
return against, rows.Err()
|
||||
}
|
||||
|
||||
// ReadRepositories is what each module's newest successful build read source from besides its own
|
||||
// repository, by module name.
|
||||
//
|
||||
// The mirror of BuiltAgainst, and derived the same way and for the same reason: a merge into a
|
||||
// repository a module only packages is a change to that module, and the manifest the mesh keeps
|
||||
// carries nothing that would say so (novox/hq 04-ISSUES/131).
|
||||
func (i *Inventory) ReadRepositories(ctx context.Context) (map[string][]ReadRepository, error) {
|
||||
rows, err := i.store.Pool().Query(ctx,
|
||||
`select distinct on (module) module, built_contexts
|
||||
from build
|
||||
where module is not null and module <> '' and failed = ''
|
||||
order by module, at desc`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
read := map[string][]ReadRepository{}
|
||||
for rows.Next() {
|
||||
var module string
|
||||
var raw []byte
|
||||
if err := rows.Scan(&module, &raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
continue
|
||||
}
|
||||
var of []ReadRepository
|
||||
if err := json.Unmarshal(raw, &of); err != nil {
|
||||
continue
|
||||
}
|
||||
if len(of) > 0 {
|
||||
read[module] = of
|
||||
}
|
||||
}
|
||||
return read, rows.Err()
|
||||
}
|
||||
|
||||
// manifestOrNil keeps the difference between "declared nothing" and "predates this being kept".
|
||||
//
|
||||
// A build recorded before the mesh kept manifests has no manifest, and that is not the same as one
|
||||
|
||||
@@ -136,3 +136,36 @@ func ids(builds []Build) []string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// What a build read besides its module's own repository comes back for the newest build of each
|
||||
// module, and only for builds that worked. Nothing recorded is absent rather than empty, which is how
|
||||
// a build made before the mesh kept this is told from one that read nothing (novox/hq 04-ISSUES/131).
|
||||
func TestWhatABuildReadComesBackForTheNewestBuildOfEachModule(t *testing.T) {
|
||||
inv := fresh(t)
|
||||
ctx := context.Background()
|
||||
older := aBuild("older", "builder", "")
|
||||
older.Read = []ReadRepository{{Repository: "novox/mesh-controller", Ref: "release"}}
|
||||
newer := aBuild("newer", "builder", "")
|
||||
newer.Read = []ReadRepository{{Repository: "novox/mesh-controller", Ref: "main"}}
|
||||
plain := aBuild("plain", "gitea", "")
|
||||
failed := aBuild("failed", "route-proxy", "cannot clone")
|
||||
failed.Read = []ReadRepository{{Repository: "novox/mesh-controller", Ref: "main"}}
|
||||
for _, b := range []Build{older, newer, plain, failed} {
|
||||
if err := inv.RecordBuild(ctx, b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
read, err := inv.ReadRepositories(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(read["builder"]) != 1 || read["builder"][0].Ref != "main" {
|
||||
t.Fatalf("the newest build's reading is %+v", read["builder"])
|
||||
}
|
||||
if _, has := read["gitea"]; has {
|
||||
t.Fatalf("a build that read nothing but its own repository reads as %+v", read["gitea"])
|
||||
}
|
||||
if _, has := read["route-proxy"]; has {
|
||||
t.Fatal("a failed build's reading was kept as what that module reads")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
-- A build says which repositories it read, so a merge can find everything it affects.
|
||||
--
|
||||
-- A module is built from the one repository the mesh records — where its module.json lives — and some
|
||||
-- modules' recipes reach into a second for the source they package: the packaging and the source are
|
||||
-- allowed to live apart (catalogue's ArtifactContext). That second repository is named in the manifest
|
||||
-- the build read, and the manifest the mesh *keeps* carries no build section, so nothing on the mesh
|
||||
-- could say that a merge into the other repository is a change to this module at all. Two modules are
|
||||
-- built from the control plane's own repository, and neither had ever been rebuilt when it moved
|
||||
-- (novox/hq 04-ISSUES/131).
|
||||
--
|
||||
-- Nullable, like the two derived columns beside it: null is a build recorded before the mesh kept
|
||||
-- this, which is not the same as a build that read nothing but its module's own repository.
|
||||
alter table build add column built_contexts jsonb;
|
||||
Reference in New Issue
Block a user