Exclude build proxy artifacts from native provider module archives
Private module tooling / contract (pull_request) Failing after 4s
Private module tooling / contract (pull_request) Failing after 4s
Authored-By: Codex (GPT-6) <noreply@openai.com>
This commit is contained in:
@@ -35,6 +35,11 @@ client once, then run the generated command with `-write` to copy unchanged
|
|||||||
generator itself enforces offline resolution and a local toolchain. It preserves
|
generator itself enforces offline resolution and a local toolchain. It preserves
|
||||||
the complete original archives, including any licenses and notices. Normal Go
|
the complete original archives, including any licenses and notices. Normal Go
|
||||||
downloads still validate their content against the consumer's `go.sum`.
|
downloads still validate their content against the consumer's `go.sum`.
|
||||||
|
The generated proxy directory has an exact build-only nested `go.mod` marker.
|
||||||
|
Native Go module packaging excludes that directory from provider releases, so
|
||||||
|
downstream consumers never recursively bundle another provider's build inputs.
|
||||||
|
The checker requires the marker, and a native local-Git archive test proves the
|
||||||
|
exclusion without permitting Git network protocols.
|
||||||
|
|
||||||
Consumer resolution uses `GONOPROXY=none`, `GONOSUMDB=code.fritzlab.net`,
|
Consumer resolution uses `GONOPROXY=none`, `GONOSUMDB=code.fritzlab.net`,
|
||||||
`GOFLAGS=-mod=readonly`, `GOTOOLCHAIN=local`, and
|
`GOFLAGS=-mod=readonly`, `GOTOOLCHAIN=local`, and
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import importlib.util
|
import importlib.util
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import os
|
||||||
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
import zipfile
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||||
SPEC = importlib.util.spec_from_file_location("exporter", ROOT / "tools/export-private-modules.py")
|
SPEC = importlib.util.spec_from_file_location("exporter", ROOT / "tools/export-private-modules.py")
|
||||||
@@ -11,6 +14,46 @@ SPEC.loader.exec_module(MODULE)
|
|||||||
|
|
||||||
|
|
||||||
class ExportTest(unittest.TestCase):
|
class ExportTest(unittest.TestCase):
|
||||||
|
def test_native_go_archive_excludes_nested_build_artifacts(self):
|
||||||
|
with tempfile.TemporaryDirectory() as temporary:
|
||||||
|
root = pathlib.Path(temporary)
|
||||||
|
source = root / "source"
|
||||||
|
source.mkdir()
|
||||||
|
module = "example.invalid/fixture.git"
|
||||||
|
(source / "go.mod").write_text("module " + module + "\n\ngo 1.27.0\n")
|
||||||
|
(source / "provider.go").write_text("package fixture\n")
|
||||||
|
boundary = source / "third_party/go-proxy"
|
||||||
|
boundary.mkdir(parents=True)
|
||||||
|
(boundary / "go.mod").write_text("module example.invalid/build-artifacts\n\ngo 1.27.0\n")
|
||||||
|
(boundary / "provider.zip").write_bytes(b"must not enter the provider module archive")
|
||||||
|
subprocess.run(["git", "init", "-q", str(source)], check=True)
|
||||||
|
subprocess.run(["git", "-C", str(source), "add", "."], check=True)
|
||||||
|
subprocess.run(["git", "-C", str(source), "-c", "user.name=Fixture",
|
||||||
|
"-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"], check=True)
|
||||||
|
subprocess.run(["git", "-C", str(source), "tag", "v0.1.0"], check=True)
|
||||||
|
config = root / "gitconfig"
|
||||||
|
config.write_text('[url "' + source.as_uri() + '"]\n'
|
||||||
|
'\tinsteadOf = https://example.invalid/fixture\n')
|
||||||
|
# Let Go select the synthetic VCS URL; Git still permits only the
|
||||||
|
# rewritten local file protocol, with no network scheme allowed.
|
||||||
|
env = dict(os.environ, GO111MODULE="on", GOTOOLCHAIN="local", GOFLAGS="", GOWORK="off",
|
||||||
|
GOPROXY="direct", GONOPROXY="none", GOSUMDB="off", GOVCS="*:git",
|
||||||
|
GOINSECURE="example.invalid",
|
||||||
|
GOMODCACHE=str(root / "cache"), GIT_CONFIG_GLOBAL=str(config),
|
||||||
|
GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_COUNT="0", GIT_ALLOW_PROTOCOL="file")
|
||||||
|
probe = subprocess.run(["git", "ls-remote", "https://example.invalid/fixture"], env=env, capture_output=True)
|
||||||
|
self.assertEqual(probe.returncode, 0, probe.stderr.decode())
|
||||||
|
# Native Git accepts only the local file protocol: a missed rewrite
|
||||||
|
# cannot turn this proof into a network or credential lookup.
|
||||||
|
result = subprocess.run(["go", "mod", "download", "-json", module + "@v0.1.0"],
|
||||||
|
cwd=root, env=env, capture_output=True)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stdout.decode() + result.stderr.decode())
|
||||||
|
archive = json.loads(result.stdout)["Zip"]
|
||||||
|
with zipfile.ZipFile(archive) as zipped:
|
||||||
|
names = zipped.namelist()
|
||||||
|
self.assertIn(module + "@v0.1.0/provider.go", names)
|
||||||
|
self.assertFalse(any("third_party/go-proxy" in name for name in names))
|
||||||
|
|
||||||
def test_committed_bytes_and_provenance_are_exact(self):
|
def test_committed_bytes_and_provenance_are_exact(self):
|
||||||
with tempfile.TemporaryDirectory() as temporary:
|
with tempfile.TemporaryDirectory() as temporary:
|
||||||
base = pathlib.Path(temporary)
|
base = pathlib.Path(temporary)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
const bundleRoot = "third_party/go-proxy"
|
const bundleRoot = "third_party/go-proxy"
|
||||||
const privatePrefix = "code.fritzlab.net/"
|
const privatePrefix = "code.fritzlab.net/"
|
||||||
|
const archiveBoundary = "module code.fritzlab.net/action/image-build/private-module-artifacts\n\ngo 1.27.0\n"
|
||||||
|
|
||||||
type artifact struct {
|
type artifact struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
@@ -79,6 +80,9 @@ func generate() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(temp)
|
defer os.RemoveAll(temp)
|
||||||
|
if err := os.WriteFile(filepath.Join(temp, "go.mod"), []byte(archiveBoundary), 0444); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
decoder := json.NewDecoder(bytes.NewReader(raw))
|
decoder := json.NewDecoder(bytes.NewReader(raw))
|
||||||
var total int64
|
var total int64
|
||||||
for {
|
for {
|
||||||
@@ -226,7 +230,11 @@ func check() error {
|
|||||||
if mod != m.GoModSHA256 || sum != m.GoSumSHA256 {
|
if mod != m.GoModSHA256 || sum != m.GoSumSHA256 {
|
||||||
return errors.New("private module closure stale; regenerate after go.mod/go.sum changes")
|
return errors.New("private module closure stale; regenerate after go.mod/go.sum changes")
|
||||||
}
|
}
|
||||||
expected := map[string]bool{"manifest.json": true}
|
boundary, err := os.ReadFile(filepath.Join(bundleRoot, "go.mod"))
|
||||||
|
if err != nil || string(boundary) != archiveBoundary {
|
||||||
|
return errors.New("private module archive boundary missing or modified")
|
||||||
|
}
|
||||||
|
expected := map[string]bool{"manifest.json": true, "go.mod": true}
|
||||||
last := ""
|
last := ""
|
||||||
var total int64
|
var total int64
|
||||||
for _, m := range m.Modules {
|
for _, m := range m.Modules {
|
||||||
|
|||||||
@@ -27,9 +27,17 @@ func TestBundleRejectsStaleMissingModifiedAndUnexpectedArtifacts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
b, _ := json.Marshal(m)
|
b, _ := json.Marshal(m)
|
||||||
os.WriteFile(filepath.Join(bundleRoot, "manifest.json"), b, 0600)
|
os.WriteFile(filepath.Join(bundleRoot, "manifest.json"), b, 0600)
|
||||||
|
os.WriteFile(filepath.Join(bundleRoot, "go.mod"), []byte(archiveBoundary), 0600)
|
||||||
if err := check(); err != nil {
|
if err := check(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
for _, boundary := range []string{"", "module unexpected.invalid/nested\n"} {
|
||||||
|
os.WriteFile(filepath.Join(bundleRoot, "go.mod"), []byte(boundary), 0600)
|
||||||
|
if check() == nil {
|
||||||
|
t.Fatal("missing or changed archive boundary accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.WriteFile(filepath.Join(bundleRoot, "go.mod"), []byte(archiveBoundary), 0600)
|
||||||
originalPath, originalVersion := m.Modules[0].Path, m.Modules[0].Version
|
originalPath, originalVersion := m.Modules[0].Path, m.Modules[0].Version
|
||||||
for _, change := range []string{"path", "version"} {
|
for _, change := range []string{"path", "version"} {
|
||||||
if change == "path" {
|
if change == "path" {
|
||||||
|
|||||||
Reference in New Issue
Block a user