Test archive artifacts against build-agent's own digests (issue 1589)
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// Issue 1589: the build step packs an archive artifact (novox/hq ADR 0478 §1), as the build agent packs it
|
||||
// (mesh-controller internal/builder: `pack` for a directory, upstreamArchive for a pinned file). The digests below are
|
||||
// the build agent's own, captured from its code (testdata/captured/README).
|
||||
const (
|
||||
buildAgentDirectoryDigest = "sha256:9503d3f91a95be6d8faf8ec696054007cfefe4f5b4713dfc8f4284b026aa6443"
|
||||
buildAgentWithinDigest = "sha256:9bacf4aab06ccfe90100aeb80b1bd94508a4d461b7e5665c599d7c5cb36c790f"
|
||||
buildAgentFileDigest = "sha256:0d605adbdef9e41d8290720296ab13dc820861ab2ef36fafa9de02558d03716b"
|
||||
releaseSHA256 = "2c9e5753c06902072422c4e9424154e8177684cc74af0bcc8dfe2bdf223a7e95"
|
||||
)
|
||||
|
||||
// copyTree copies a directory, keeping modes.
|
||||
func copyTree(t *testing.T, from, to string) {
|
||||
t.Helper()
|
||||
err := filepath.Walk(from, func(p string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rel, _ := filepath.Rel(from, p)
|
||||
// A module.json in the repository would be taken for a module (issue 1527): fixtures keep it as module.json.fixture.
|
||||
if filepath.Base(rel) == "module.json.fixture" {
|
||||
rel = strings.TrimSuffix(rel, ".fixture")
|
||||
}
|
||||
target := filepath.Join(to, rel)
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(target, 0o755)
|
||||
}
|
||||
body, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(target, body, info.Mode().Perm())
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// archiveModule is a module whose workspace ws1 holds the fixture tree of testdata/repos/archive; manifest, when given,
|
||||
// replaces modules/demo/module.json.
|
||||
func archiveModule(t *testing.T, manifest string) (*Module, *keptStates, *[]cmdRecord) {
|
||||
t.Helper()
|
||||
m, states, calls := buildModule(t, manifestWith(goBundle), "x")
|
||||
tree := filepath.Join(m.Work, "ws1", "tree")
|
||||
if err := os.RemoveAll(tree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
copyTree(t, filepath.Join("testdata", "repos", "archive"), tree)
|
||||
if manifest != "" {
|
||||
if err := os.WriteFile(filepath.Join(tree, "modules", "demo", "module.json"), []byte(manifest), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
return m, states, calls
|
||||
}
|
||||
|
||||
// pinnedModule is an archive module whose artifact is pinned to path of a TLS server serving the release, under the
|
||||
// name example.com (a manifest must say https and a named host); the module's client dials the server whatever the name.
|
||||
func pinnedModule(t *testing.T, path, sha, extra string) (*Module, *keptStates) {
|
||||
t.Helper()
|
||||
body, err := os.ReadFile(filepath.Join("testdata", "upstream-release.tar.gz"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/release.tar.gz" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Write(body)
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
m, states, _ := archiveModule(t, pinned("https://example.com"+path, sha, extra))
|
||||
client := srv.Client()
|
||||
client.Transport.(*http.Transport).DialContext = func(ctx context.Context, network, _ string) (net.Conn, error) {
|
||||
return (&net.Dialer{}).DialContext(ctx, network, srv.Listener.Addr().String())
|
||||
}
|
||||
m.HTTP = client
|
||||
return m, states
|
||||
}
|
||||
|
||||
// 1589/AC1: an archive of a directory of the module's tree has the digest the build agent's `pack` gives that tree,
|
||||
// and the module runs no command for it.
|
||||
func TestAnArchiveOfADirectoryHasTheBuildAgentsDigest(t *testing.T) {
|
||||
m, states, calls := archiveModule(t, "")
|
||||
res, st := runBuild(t, m, states, "files")
|
||||
if st.State != delivery.StepPassed {
|
||||
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
|
||||
}
|
||||
if res.Digest != buildAgentDirectoryDigest {
|
||||
t.Errorf("digest %s, the build agent's is %s", res.Digest, buildAgentDirectoryDigest)
|
||||
}
|
||||
if res.Artifact != "files" || res.Size == 0 || res.Binary != "" {
|
||||
t.Errorf("result = %+v", res)
|
||||
}
|
||||
if info, err := os.Stat(res.Path); err != nil || info.Size() != int64(res.Size) {
|
||||
t.Errorf("the archive at %q: %v", res.Path, err)
|
||||
}
|
||||
if len(*calls) != 0 {
|
||||
t.Errorf("an archive ran %d commands", len(*calls))
|
||||
}
|
||||
}
|
||||
|
||||
// An archive does not leave a stray directory in the module's tree: packing the same tree twice is one digest.
|
||||
func TestPackingAnArchiveTwiceIsOneDigest(t *testing.T) {
|
||||
m, states, _ := archiveModule(t, "")
|
||||
a, _ := runBuild(t, m, states, "files")
|
||||
b, _ := runBuild(t, m, states, "files")
|
||||
if a.Digest == "" || a.Digest != b.Digest {
|
||||
t.Errorf("%s and %s", a.Digest, b.Digest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnArchiveFromOutsideTheModuleOrMissingFailsTheStep(t *testing.T) {
|
||||
for from, want := range map[string]string{
|
||||
"../../..": "outside",
|
||||
"nothing": "nothing",
|
||||
} {
|
||||
m, states, _ := archiveModule(t, `{"module":"demo","version":"1","build":{"artifacts":[{"name":"files","kind":"archive","from":"`+from+`"}]}}`)
|
||||
if _, st := runBuild(t, m, states, "files"); st.State != delivery.StepFailed || !strings.Contains(st.Reason, want) {
|
||||
t.Errorf("from %q: %s %q", from, st.State, st.Reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pinned(url, sha, extra string) string {
|
||||
return `{"module":"demo","version":"1","build":{"artifacts":[{"name":"files","kind":"archive","url":"` + url +
|
||||
`","sha256":"` + sha + `","version":"1.0"` + extra + `}]}}`
|
||||
}
|
||||
|
||||
// 1589/AC2: a pinned file is fetched, checked against its sha256 and laid out as the artifact says; the digest is the
|
||||
// build agent's for the same file.
|
||||
func TestAPinnedArchiveIsUnpackedWithinAndHasTheBuildAgentsDigest(t *testing.T) {
|
||||
m, states := pinnedModule(t, "/release.tar.gz", releaseSHA256, `,"within":"release/app"`)
|
||||
res, st := runBuild(t, m, states, "files")
|
||||
if st.State != delivery.StepPassed || res.Digest != buildAgentWithinDigest {
|
||||
t.Fatalf("%s %q: digest %s, the build agent's is %s", st.State, st.Reason, res.Digest, buildAgentWithinDigest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPinnedFileKeptWholeHasTheBuildAgentsDigest(t *testing.T) {
|
||||
m, states := pinnedModule(t, "/release.tar.gz", releaseSHA256, `,"file":"tool.bin"`)
|
||||
res, st := runBuild(t, m, states, "files")
|
||||
if st.State != delivery.StepPassed || res.Digest != buildAgentFileDigest {
|
||||
t.Fatalf("%s %q: digest %s, the build agent's is %s", st.State, st.Reason, res.Digest, buildAgentFileDigest)
|
||||
}
|
||||
}
|
||||
|
||||
// A file that is not the pin's is refused, naming both digests, and nothing is packed.
|
||||
func TestAPinnedFileOfAnotherDigestIsRefused(t *testing.T) {
|
||||
wrong := strings.Repeat("ab", 32)
|
||||
m, states := pinnedModule(t, "/release.tar.gz", wrong, `,"within":"release/app"`)
|
||||
_, st := runBuild(t, m, states, "files")
|
||||
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "sha256:"+wrong) || !strings.Contains(st.Reason, "sha256:"+releaseSHA256) {
|
||||
t.Errorf("the step ended %s: %q", st.State, st.Reason)
|
||||
}
|
||||
if entries, _ := os.ReadDir(filepath.Join(m.Work, "ws1", "artifacts")); len(entries) != 0 {
|
||||
t.Errorf("an archive was left: %v", entries)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPinnedFileTheServerRefusesFailsTheStep(t *testing.T) {
|
||||
m, states := pinnedModule(t, "/gone", releaseSHA256, `,"file":"x"`)
|
||||
if _, st := runBuild(t, m, states, "files"); st.State != delivery.StepFailed || !strings.Contains(st.Reason, "404") {
|
||||
t.Errorf("the step ended %s: %q", st.State, st.Reason)
|
||||
}
|
||||
}
|
||||
|
||||
// Entries outside `within` are refused, as the build agent refuses them.
|
||||
func TestAPinnedArchiveWithEntriesOutsideWithinIsRefused(t *testing.T) {
|
||||
m, states := pinnedModule(t, "/release.tar.gz", releaseSHA256, `,"within":"release/other"`)
|
||||
if _, st := runBuild(t, m, states, "files"); st.State != delivery.StepFailed || !strings.Contains(st.Reason, "release/other") {
|
||||
t.Errorf("the step ended %s: %q", st.State, st.Reason)
|
||||
}
|
||||
}
|
||||
|
||||
// Other kinds are still refused by name.
|
||||
func TestAnImageArtifactIsStillRefusedByName(t *testing.T) {
|
||||
m, states, _ := archiveModule(t, `{"module":"demo","version":"1","build":{"artifacts":[{"name":"files","kind":"image","from":"files"}]}}`)
|
||||
if _, st := runBuild(t, m, states, "files"); st.State != delivery.StepFailed || !strings.Contains(st.Reason, "image") {
|
||||
t.Errorf("the step ended %s: %q", st.State, st.Reason)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user