feat: build runtime releases from pinned Git branch commits
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testReleaseArchive(t *testing.T, m string, files map[string]string) []byte {
|
||||
t.Helper()
|
||||
var b bytes.Buffer
|
||||
tw := tar.NewWriter(&b)
|
||||
files["release.json"] = m
|
||||
for name, data := range files {
|
||||
if err := tw.WriteHeader(&tar.Header{Name: name, Size: int64(len(data)), Mode: 0644}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := tw.Write([]byte(data)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
func releaseFixture() releaseInfo {
|
||||
return releaseInfo{releaseManifest: releaseManifest{SchemaVersion: 1, ReleaseID: "runtime-7.1", Version: "0.2.0-rc.3", DeploymentSchema: 7, Platforms: []string{imagePlatform}}, Repository: "https://git.misaka.ren/m1saka/dsh-web.git", Commit: strings.Repeat("a", 40)}
|
||||
}
|
||||
func TestReleaseManifestAndContext(t *testing.T) {
|
||||
info := releaseFixture()
|
||||
data, _ := json.Marshal(info.releaseManifest)
|
||||
for _, tc := range []struct {
|
||||
name, manifest string
|
||||
files map[string]string
|
||||
wantErr bool
|
||||
}{
|
||||
{"valid", string(data), map[string]string{"image/Dockerfile": "FROM scratch", "image/nested/helper": "complete", "image/.git/config": "secret", "image/.env": "secret"}, false},
|
||||
{"malformed", "{", map[string]string{"image/Dockerfile": "FROM scratch"}, true},
|
||||
{"unknown", strings.Replace(string(data), "schema_version", "unknown", 1), map[string]string{"image/Dockerfile": "FROM scratch"}, true},
|
||||
{"incompatible", strings.Replace(string(data), "deployment_schema\":7", "deployment_schema\":8", 1), map[string]string{"image/Dockerfile": "FROM scratch"}, true},
|
||||
{"missing Dockerfile", string(data), map[string]string{"image/helper": "x"}, true},
|
||||
{"unsafe path", string(data), map[string]string{"image/Dockerfile": "FROM scratch", "image/../escape": "x"}, true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, r, err := parseReleaseArchive(info, testReleaseArchive(t, tc.manifest, tc.files))
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tr := tar.NewReader(r)
|
||||
seen := map[string]bool{}
|
||||
for {
|
||||
h, e := tr.Next()
|
||||
if e == io.EOF {
|
||||
break
|
||||
}
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
seen[h.Name] = true
|
||||
}
|
||||
if !seen["nested/helper"] || seen[".env"] || seen[".git/config"] {
|
||||
t.Fatal(seen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestReleaseQueueIdentityRecoveryAndFailure(t *testing.T) {
|
||||
a, _ := imageTestAPI(t, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(404) })
|
||||
info := releaseFixture()
|
||||
b, err := a.enqueueRelease("u", info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
again, err := a.enqueueRelease("u", info)
|
||||
if err != nil || again.ID != b.ID {
|
||||
t.Fatalf("repeat %v %v", again, err)
|
||||
}
|
||||
info.Commit = strings.Repeat("b", 40)
|
||||
info.ReleaseID = "runtime-7.2"
|
||||
next, err := a.enqueueRelease("u", info)
|
||||
if err != nil || next.ID == b.ID || next.Version != b.Version {
|
||||
t.Fatalf("next %v %v", next, err)
|
||||
}
|
||||
buildQueue.Lock()
|
||||
delete(buildQueue.initialized, a.s.DB)
|
||||
err = a.ensureBuilds()
|
||||
buildQueue.Unlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var recovered buildRecord
|
||||
if err = scanBuildSummary(a.s.DB.QueryRow("SELECT "+buildSummaryColumns+" FROM image_builds WHERE id=?", next.ID), &recovered); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if recovered.Status != "queued" || recovered.SourceCommit != info.Commit {
|
||||
t.Fatal(recovered)
|
||||
}
|
||||
if _, err = a.s.DB.Exec("UPDATE image_builds SET status='failed' WHERE id=?", next.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
retry, err := a.enqueueRelease("u", info)
|
||||
if err != nil || retry.ID == next.ID {
|
||||
t.Fatalf("retry %v %v", retry, err)
|
||||
}
|
||||
}
|
||||
func TestReleaseEndpointsAuthorizationFailureAndMismatch(t *testing.T) {
|
||||
a, sid := imageTestAPI(t, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(404) })
|
||||
if w := imageRequest(a.latestRelease, sid, "GET", ""); w.Code != 403 {
|
||||
t.Fatal(w.Code)
|
||||
}
|
||||
if _, err := a.s.DB.Exec("UPDATE users SET admin=1 WHERE id='u'"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
old := fetchRelease
|
||||
t.Cleanup(func() { fetchRelease = old })
|
||||
fetchRelease = func(context.Context, string, string) (releaseInfo, io.Reader, error) {
|
||||
return releaseInfo{}, nil, errors.New("network failure")
|
||||
}
|
||||
if w := imageRequest(a.latestRelease, sid, "GET", ""); w.Code != 502 {
|
||||
t.Fatalf("%d %s", w.Code, w.Body.String())
|
||||
}
|
||||
fetchRelease = func(context.Context, string, string) (releaseInfo, io.Reader, error) {
|
||||
return releaseFixture(), nil, nil
|
||||
}
|
||||
if w := imageRequest(a.releaseBuild, sid, "POST", `{"commit":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}`); w.Code != 409 {
|
||||
t.Fatalf("%d %s", w.Code, w.Body.String())
|
||||
}
|
||||
fetchRelease = func(context.Context, string, string) (releaseInfo, io.Reader, error) {
|
||||
return releaseFixture(), nil, errReleaseIncompatible
|
||||
}
|
||||
if w := imageRequest(a.latestRelease, sid, "GET", ""); w.Code != 200 || !strings.Contains(w.Body.String(), `"compatible":false`) {
|
||||
t.Fatalf("%d %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
func TestReleaseGitEnvironmentAndFailure(t *testing.T) {
|
||||
out, err := gitReleaseCommand(context.Background(), t.TempDir(), "--version")
|
||||
if err != nil || !strings.HasPrefix(string(out), "git version") {
|
||||
t.Fatalf("%q %v", out, err)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
if _, err = gitReleaseCommand(ctx, t.TempDir(), "--version"); err == nil {
|
||||
t.Fatal("cancel ignored")
|
||||
}
|
||||
for _, repository := range []string{"http://example.test/repo", "https://user:[email protected]/repo", "https://example.test/repo?token=secret"} {
|
||||
t.Setenv("DSH_RELEASE_REPOSITORY", repository)
|
||||
if _, _, err = releaseSource(); err == nil {
|
||||
t.Fatal("accepted unsafe source")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPinnedReleaseWorkerAdmissionAndBlock(t *testing.T) {
|
||||
info := releaseFixture()
|
||||
labels := map[string]string{"dsh-mgr.role": "runtime", "dsh-mgr.version": info.Version, "dsh-mgr.recipe": "7", "dsh-mgr.release": info.ReleaseID, "dsh-mgr.source": info.Repository, "dsh-mgr.commit": info.Commit}
|
||||
a, _ := imageTestAPI(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/v1.47/build":
|
||||
var got map[string]string
|
||||
if err := json.Unmarshal([]byte(r.URL.Query().Get("labels")), &got); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if got["dsh-mgr.commit"] != info.Commit || r.URL.Query().Get("t") != "dsh-web:release-"+info.Commit {
|
||||
t.Error("missing provenance", r.URL)
|
||||
}
|
||||
data, _ := io.ReadAll(r.Body)
|
||||
if !bytes.Contains(data, []byte("pinned context")) {
|
||||
t.Error("wrong build context")
|
||||
}
|
||||
io.WriteString(w, `{"aux":{"ID":"sha256:release"}}`)
|
||||
case "/v1.47/images/sha256:release/json":
|
||||
parts := strings.Split(imagePlatform, "/")
|
||||
writeJSON(w, map[string]any{"Id": "sha256:release", "Os": parts[0], "Architecture": parts[1], "Config": map[string]any{"Labels": labels}})
|
||||
case "/v1.47/containers/create":
|
||||
io.WriteString(w, `{"Id":"check"}`)
|
||||
case "/v1.47/containers/check/start", "/v1.47/exec/probe/start", "/v1.47/containers/check":
|
||||
w.WriteHeader(204)
|
||||
case "/v1.47/containers/check/exec":
|
||||
io.WriteString(w, `{"Id":"probe"}`)
|
||||
case "/v1.47/exec/probe/json":
|
||||
io.WriteString(w, `{"Running":false,"ExitCode":0}`)
|
||||
case "/v1.47/containers/check/json":
|
||||
io.WriteString(w, `{"State":{"Running":true}}`)
|
||||
default:
|
||||
t.Errorf("unexpected %s", r.URL)
|
||||
w.WriteHeader(500)
|
||||
}
|
||||
})
|
||||
if _, err := a.s.DB.Exec("UPDATE users SET admin=1 WHERE id='u'"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
npmBuildClient = &http.Client{Transport: imageRoundTrip(func(r *http.Request) (*http.Response, error) {
|
||||
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(`{"version":"0.2.0-rc.3"}`)), Header: make(http.Header)}, nil
|
||||
})}
|
||||
old := fetchRelease
|
||||
t.Cleanup(func() { fetchRelease = old })
|
||||
fetchRelease = func(ctx context.Context, repository, ref string) (releaseInfo, io.Reader, error) {
|
||||
if repository != info.Repository || ref != info.Commit {
|
||||
t.Error("not pinned", repository, ref)
|
||||
}
|
||||
return info, strings.NewReader("pinned context"), nil
|
||||
}
|
||||
b, err := a.enqueueRelease("u", info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.runImageBuild(b)
|
||||
if err = scanBuildSummary(a.s.DB.QueryRow("SELECT "+buildSummaryColumns+" FROM image_builds WHERE id=?", b.ID), &b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if b.Status != "success" {
|
||||
t.Fatalf("%+v", b)
|
||||
}
|
||||
im, err := a.evaluateImage(context.Background(), b.ImageID)
|
||||
if err != nil || !im.Selectable {
|
||||
t.Fatalf("%+v %v", im, err)
|
||||
}
|
||||
if _, err = a.s.DB.Exec("INSERT INTO image_admissions(image_id,version,recipe_rev,platform,state,evidence,admitted_by,admitted_at) VALUES(?,?,?,?,?,?,?,?)", b.ImageID, b.Version, "7", imagePlatform, "blocked", "explicit block", "u", "now"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
im, err = a.evaluateImage(context.Background(), b.ImageID)
|
||||
if err != nil || im.Selectable || im.Admission != "blocked" {
|
||||
t.Fatalf("%+v %v", im, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user