diff --git a/modules/mesh-build-go/cmd/mesh-build-go/fetch.go b/modules/mesh-build-go/cmd/mesh-build-go/fetch.go index 8644edd9..6679c5e0 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/fetch.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/fetch.go @@ -8,6 +8,7 @@ import ( "context" "crypto/sha256" "encoding/hex" + "encoding/json" "fmt" "os" "path/filepath" @@ -310,15 +311,58 @@ func (m *Module) goEnv() []string { if m.GoModCache != "" { modcache = m.GoModCache } - return append([]string{ + env := []string{ "GOTOOLCHAIN=local", "GOFLAGS=-mod=mod", "GOMODCACHE=" + modcache, "GOCACHE=" + filepath.Join(m.Cache, "gobuild"), - "GOPRIVATE=git.novox.be", - "GIT_TERMINAL_PROMPT=0", - "GIT_SSH_COMMAND=ssh -o BatchMode=yes", - }, m.threadEnv()...) + } + if proxy := m.goSeat(); proxy != "" { + // The Go seat first, the public proxy second, the forge's git last (CAT-BG3, E11: direct goes in the first + // change after mesh-sdk's and mesh-contracts' first numbered versions are in the Go seat); no GOPRIVATE, and + // the checksum database off for the forge's paths, which go.sum and the builder's h1: digest guard instead. + env = append(env, "GOPROXY="+proxy+","+PublicGoProxy+",direct", "GONOSUMDB="+ForgeGoPaths, "GOPRIVATE=") + } else { + env = append(env, "GOPRIVATE="+ForgeGoPaths) + } + return append(append(env, "GIT_TERMINAL_PROMPT=0", "GIT_SSH_COMMAND=ssh -o BatchMode=yes"), m.threadEnv()...) +} + +// PublicGoProxy is Go's public module proxy; ForgeGoPaths the module paths the forge serves. +const ( + PublicGoProxy = "https://proxy.golang.org" + ForgeGoPaths = "git.novox.be" +) + +// goSeat is the Go seat's proxy URL from its binding (`at`, and `serves` scheme, port and go-path), empty when the +// module holds no binding of it or the binding names no address. +func (m *Module) goSeat() string { + if m.GoSeatFile == "" { + return "" + } + raw, err := os.ReadFile(m.GoSeatFile) + if err != nil { + return "" + } + var b struct { + At string `json:"at"` + Serves struct { + Scheme string `json:"scheme"` + Port int `json:"port"` + GoPath string `json:"go-path"` + } `json:"serves"` + } + if json.Unmarshal(raw, &b) != nil || b.At == "" || b.Serves.GoPath == "" { + return "" + } + scheme := b.Serves.Scheme + if scheme == "" { + scheme = "http" + } + if b.Serves.Port == 0 { + return fmt.Sprintf("%s://%s%s", scheme, b.At, b.Serves.GoPath) + } + return fmt.Sprintf("%s://%s:%d%s", scheme, b.At, b.Serves.Port, b.Serves.GoPath) } // goEnvFor is goEnv for a command run in dir: a directory that vendors (vendor/modules.txt) is read from its vendor diff --git a/modules/mesh-build-go/cmd/mesh-build-go/fetch_test.go b/modules/mesh-build-go/cmd/mesh-build-go/fetch_test.go index e86e1559..84ac2c46 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/fetch_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/fetch_test.go @@ -145,6 +145,20 @@ func TestFetchAsksGitForTheSshRemoteAndDownloadsEachModule(t *testing.T) { } } } + // CAT-BG3: with the Go seat bound, the fetch goes through it first, Go's public proxy second and the forge's git + // last, with no GOPRIVATE and the checksum database off for the forge's paths. + seat := filepath.Join(t.TempDir(), "go-package-registry.json") + if err := os.WriteFile(seat, []byte(`{"at":"forge.mesh","serves":{"scheme":"http","port":3000,"go-path":"/api/packages/novox/go"}}`), 0o600); err != nil { + t.Fatal(err) + } + m.GoSeatFile = seat + env := strings.Join(m.goEnv(), " ") + for _, want := range []string{"GOPROXY=http://forge.mesh:3000/api/packages/novox/go,https://proxy.golang.org,direct", "GONOSUMDB=git.novox.be", "GOPRIVATE= "} { + if !strings.Contains(env+" ", want) { + t.Errorf("with the Go seat bound the environment lacks %q: %s", want, env) + } + } + m.GoSeatFile = "" if downloads != 2 || verifies != 2 { t.Errorf("%d downloads and %d verifies, want 2 each (go.mod and sub/dir/go.mod):\n%s", downloads, verifies, lines) } diff --git a/modules/mesh-build-go/cmd/mesh-build-go/forge_test.go b/modules/mesh-build-go/cmd/mesh-build-go/forge_test.go index 89ff15c8..2ca1ba57 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/forge_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/forge_test.go @@ -10,6 +10,7 @@ import ( "encoding/json" "os" "path/filepath" + "slices" "strings" "testing" @@ -145,8 +146,9 @@ func TestManifestRequiresForgeAccountKeepsKeysAndTakesFilesFromTemplates(t *test m := readForgeManifest(t) // artifact-store: the module pushes a done build's archive itself (ADR 0498 ยง2). // node-tools: the module pulls its asks through the tool runner's seat pull (1911). - if len(m.Requires) != 4 || m.Requires[0] != "forge-account" || m.Requires[1] != "go-toolchain" || m.Requires[2] != "artifact-store" || m.Requires[3] != "node-tools" { - t.Errorf("requires = %v, want [forge-account go-toolchain artifact-store node-tools]", m.Requires) + // go-package-registry: the Go seat's read side, the fetch's first source (design 51, CAT-BG3, E2). + if want := []any{"forge-account", "go-toolchain", "go-package-registry", "artifact-store", "node-tools"}; !slices.Equal(m.Requires, want) { + t.Errorf("requires = %v, want %v", m.Requires, want) } keeps := false for _, s := range m.State { diff --git a/modules/mesh-build-go/cmd/mesh-build-go/main.go b/modules/mesh-build-go/cmd/mesh-build-go/main.go index 4e96528b..dabb3df5 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/main.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/main.go @@ -55,6 +55,7 @@ func main() { } m.Run = StepRunner(m.Home) m.RegistryFile = m.Home + "/registry" + m.GoSeatFile = m.Home + "/go-package-registry.json" m.Settings = m.Home + "/settings.json" m.ReadKept = readOf(stdio.State(KeptState)).Get m.ReadManifest = readOf(stdio.State(ManifestsState)).Get diff --git a/modules/mesh-build-go/cmd/mesh-build-go/module.go b/modules/mesh-build-go/cmd/mesh-build-go/module.go index 38e2f843..e4aedaf7 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/module.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/module.go @@ -70,6 +70,10 @@ type Module struct { // RegistryFile holds the artifact store's address (host:port), placed by the manifest; Push puts a blob there // (buildstep.PushBlob when nil; a test's fake store). RegistryFile string + // GoSeatFile is the binding of the provision go-package-registry, the Go seat's read side (design 51 *Go + // packages*): bound, the mesh's Go modules are fetched through it first, Go's public proxy second, and the forge's + // git over the account's ssh last (Go's direct, kept for this milestone only); unbound, as before, directly. + GoSeatFile string Push func(ctx context.Context, registry, repository, digest string, body []byte) error // Executable is this program's path, which the integration level starts again shut off; os.Executable when empty. Executable string diff --git a/modules/mesh-build-go/module.json b/modules/mesh-build-go/module.json index 2c6289c6..9c49aa7d 100644 --- a/modules/mesh-build-go/module.json +++ b/modules/mesh-build-go/module.json @@ -66,9 +66,13 @@ "requires": [ "forge-account", "go-toolchain", + "go-package-registry", "artifact-store", "node-tools" ], + "binds": { + "go-package-registry": "${dir:state}/go-package-registry.json" + }, "own-secrets": { "broker": "${dir:state}/broker" },