diff --git a/modules/mesh-build-image/cmd/mesh-build-image/build.go b/modules/mesh-build-image/cmd/mesh-build-image/build.go index 307446b48..70fa9e083 100644 --- a/modules/mesh-build-image/cmd/mesh-build-image/build.go +++ b/modules/mesh-build-image/cmd/mesh-build-image/build.go @@ -173,7 +173,7 @@ func (m *Module) build(ctx context.Context, args map[string]any) (any, error) { argv = append(argv, contextDir) if r := m.Run(ctx, m.env(), "buildah", argv...); r.Status != 0 || r.Err != "" { m.sayFailure("buildah bud of "+art.Name+" of "+manifest.Module, r) - return nil, fmt.Errorf("buildah bud of %s: %s", art.Name, cause(r)) + return nil, buildstep.Transport(fmt.Errorf("buildah bud of %s: %s", art.Name, cause(r))) } defer m.remove(local) digest, err := m.localDigest(ctx, local) diff --git a/modules/mesh-build-image/cmd/mesh-build-image/build_test.go b/modules/mesh-build-image/cmd/mesh-build-image/build_test.go index 8430c34a9..b886de329 100644 --- a/modules/mesh-build-image/cmd/mesh-build-image/build_test.go +++ b/modules/mesh-build-image/cmd/mesh-build-image/build_test.go @@ -178,6 +178,8 @@ func TestBuildFailsWithBuildahsError(t *testing.T) { []string{"Error: the end"}, []string{"early noise", long}}, "nothing on stderr": {forgekey.Ran{Status: 2, Stdout: steps}, []string{"exit status 2", "nothing on stderr", "dl-cdn"}, nil}, "never ran": {forgekey.Ran{Err: "ENOENT"}, []string{"ENOENT", "wrote nothing"}, nil}, + "a pull the network cut": {forgekey.Ran{Status: 125, Stdout: steps, Stderr: "Error: creating build container: initializing source docker://alpine: pinging container registry registry-1.docker.io: Get \"https://registry-1.docker.io/v2/\": dial tcp: lookup registry-1.docker.io on 10.10.0.3:53: i/o timeout\n"}, + []string{"i/o timeout"}, nil}, } for name, c := range cases { m, f, states := setup(t, same) @@ -200,6 +202,10 @@ func TestBuildFailsWithBuildahsError(t *testing.T) { if len(st.Reason) > CauseBytes+200 { t.Errorf("%s: the reason is %d bytes, more than its bound", name, len(st.Reason)) } + // ADR 0664 §1: a pull the network cut ends passing, for the bus to give the ask again; no other failure does. + if st.Passing != strings.Contains(name, "the network cut") { + t.Errorf("%s: passing = %v", name, st.Passing) + } if len(f.called("push")) != 0 { t.Errorf("%s: a failed build pushed", name) } diff --git a/modules/mesh-build-image/cmd/mesh-build-image/copy.go b/modules/mesh-build-image/cmd/mesh-build-image/copy.go index 28655df7e..bd84c8ceb 100644 --- a/modules/mesh-build-image/cmd/mesh-build-image/copy.go +++ b/modules/mesh-build-image/cmd/mesh-build-image/copy.go @@ -112,7 +112,9 @@ func (m *Module) copy(ctx context.Context, args map[string]any) (any, error) { raw, err := m.call()("seat:"+buildstep.ImageSeat+"."+reg.VerbMirror, reg.MirrorInput{Name: module + "/" + name, Source: from, Digest: digest}) if err != nil { said.Refused(err) - return nil, fmt.Errorf("%s%s: %s's mirror: %v", builds.NotStored, name, buildstep.ImageSeat, err) + // A seat verb's "no responders" with a holder named is a transport fault (ADR 0664 §1): the seat's tool runner is replaced. + err = buildstep.HolderSilentOr(m.call(), buildstep.ImageSeat, err) + return nil, fmt.Errorf("%s%s: %s's mirror: %w", builds.NotStored, name, buildstep.ImageSeat, err) } var answer reg.MirrorAnswer _ = json.Unmarshal(raw, &answer) diff --git a/modules/mesh-build-node/cmd/mesh-build-node/fetch.go b/modules/mesh-build-node/cmd/mesh-build-node/fetch.go index 1f5ebc28c..8d96b6641 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/fetch.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/fetch.go @@ -353,7 +353,7 @@ func (m *Module) installOwn(ctx context.Context, root, dir, rel string) (install "--no-audit", "--no-fund", "--prefix", scratch, "--userconfig", npmrc) os.Remove(npmrc) if why := ranFailed(r); why != "" { - return false, "", fmt.Errorf("installing the packages of %s (%s): %s", rel, strings.Join(names, ", "), why) + return false, "", buildstep.Transport(fmt.Errorf("installing the packages of %s (%s): %s", rel, strings.Join(names, ", "), why)) } nm := filepath.Join(scratch, "node_modules") if err := removeSDKCopies(nm); err != nil { diff --git a/modules/mesh-build-node/cmd/mesh-build-node/package.go b/modules/mesh-build-node/cmd/mesh-build-node/package.go index ab61e2e1b..9da43efab 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/package.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/package.go @@ -93,7 +93,7 @@ func (m *Module) installDev(ctx context.Context, root, dir, rel string) error { env := append(m.baseEnv(), "npm_config_cache="+m.npmCache()) r := m.Run(ctx, env, "npm", "ci", "--ignore-scripts", "--no-bin-links", "--no-audit", "--no-fund", "--prefix", dir, "--userconfig", npmrc) if why := ranFailed(r); why != "" { - return fmt.Errorf("installing the dependencies of the package in %s: %s", rel, why) + return buildstep.Transport(fmt.Errorf("installing the dependencies of the package in %s: %s", rel, why)) } return writeShims(filepath.Join(dir, "node_modules"), shimDir(root, rel)) } diff --git a/modules/mesh-build-node/cmd/mesh-build-node/toolchain.go b/modules/mesh-build-node/cmd/mesh-build-node/toolchain.go index 08091c421..db3f8e091 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/toolchain.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/toolchain.go @@ -10,6 +10,7 @@ package main import ( "context" "fmt" + "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep" "io/fs" "os" "path/filepath" @@ -99,8 +100,8 @@ func (t Toolchain) Ensure(ctx context.Context, version string) error { "npm", "ci", "--ignore-scripts", "--no-bin-links", "--no-audit", "--no-fund", "--prefix", dir, "--userconfig", npmrc) os.Remove(npmrc) if ran.Err != "" || ran.Status != 0 { - return fmt.Errorf("npm ci --ignore-scripts for the toolchain %s failed (status %d %s): %s", - version, ran.Status, ran.Err, tail(ran.Stderr)) + return buildstep.Transport(fmt.Errorf("npm ci --ignore-scripts for the toolchain %s failed (status %d %s): %s", + version, ran.Status, ran.Err, tail(ran.Stderr))) } if err := os.WriteFile(filepath.Join(dir, installedMark), []byte(version+"\n"), 0o600); err != nil { return err diff --git a/modules/mesh-build-node/cmd/mesh-build-node/toolchain_test.go b/modules/mesh-build-node/cmd/mesh-build-node/toolchain_test.go index 8b208b60d..0c1a02038 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/toolchain_test.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/toolchain_test.go @@ -3,6 +3,7 @@ package main import ( "context" "errors" + sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep" "os" "path/filepath" "strings" @@ -86,6 +87,18 @@ func TestAMissingToolchainDirectoryIsInstalledOnceFromTheLock(t *testing.T) { if err := tc.Ensure(context.Background(), version); err != nil || len(fake.calls) != 1 { t.Errorf("a present directory ran npm again: %d calls, %v", len(fake.calls), err) } + // ADR 0664 §1 (issue 1748): an install the network cut is a transport fault, the bus gives the ask again; a package the + // registry lacks is not. + for _, c := range []struct { + stderr string + passing bool + }{{"npm error code ECONNRESET\nnpm error network aborted", true}, {"npm error code EAI_AGAIN", true}, {"npm error code E404\nnpm error 404 Not Found", false}} { + bad := &fakeNpm{ran: forgekey.Ran{Status: 1, Stderr: c.stderr}} + err := toolchainIn(t, bad).Ensure(context.Background(), Recipe{Steps: "2", Lock: "y"}.Version()) + if err == nil || sdkstep.IsPassing(err) != c.passing { + t.Errorf("%q: %v, passing %v, want %v", c.stderr, err, err != nil && sdkstep.IsPassing(err), c.passing) + } + } } // 1488/AC4: the install is told the mesh's registry for @novox with the account's credential, and only through a