fix(bootstrap): ship deploy/paper in the embedded game-stack tar
This commit is contained in:
2 files changed
+76
-5
No files matched your search
+6
-5
@@ -13,8 +13,8 @@ var bootstrapScript string
|
||||
//go:embed deploy/crd/*.yaml
|
||||
var bootstrapAssets embed.FS
|
||||
|
||||
// gameStackAssets carries everything deploy/bootstrap.sh needs to build the two
|
||||
// always-on game images (login limbo + lobby) and the Velocity plugin, for the TUI
|
||||
// gameStackAssets carries everything deploy/bootstrap.sh needs to build the three
|
||||
// game images (login limbo, lobby, plain Paper) and the Velocity plugin, for the TUI
|
||||
// install path — which pipes the embedded bootstrap.sh into bash and therefore has
|
||||
// NO source checkout on disk to build from.
|
||||
//
|
||||
@@ -26,6 +26,7 @@ var bootstrapAssets embed.FS
|
||||
//
|
||||
//go:embed deploy/limbo/Dockerfile deploy/limbo/entrypoint.sh
|
||||
//go:embed deploy/lobby/Dockerfile deploy/lobby/entrypoint.sh
|
||||
//go:embed deploy/paper/Dockerfile deploy/paper/entrypoint.sh
|
||||
//go:embed plugins/limbo/build.gradle plugins/limbo/settings.gradle plugins/limbo/src
|
||||
//go:embed plugins/paper/build.gradle plugins/paper/settings.gradle plugins/paper/src
|
||||
//go:embed plugins/velocity/build.gradle plugins/velocity/settings.gradle plugins/velocity/src
|
||||
@@ -46,9 +47,9 @@ func GameStackTar(w io.Writer) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Mode 0644 for everything: entrypoint.sh is invoked as `sh <file>` by both
|
||||
// Dockerfiles precisely because the +x bit does not survive a Windows checkout,
|
||||
// so nothing here needs to be executable.
|
||||
// Mode 0644 for everything: entrypoint.sh is invoked as `sh <file>` by all
|
||||
// three Dockerfiles precisely because the +x bit does not survive a Windows
|
||||
// checkout, so nothing here needs to be executable.
|
||||
if err := tw.WriteHeader(&tar.Header{
|
||||
Name: path,
|
||||
Mode: 0o644,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package felis
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -104,6 +106,74 @@ func TestBootstrapPinsViaBlockConnectionsOff(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The embed list and the images bootstrap.sh builds are two lists nobody reconciles.
|
||||
// deploy/paper shipped an image build without ever being added to gameStackAssets, and
|
||||
// nothing said so: a checkout on disk satisfies the build either way, and the tar is
|
||||
// only the build context on the path that has no checkout — `curl | bash`, where the
|
||||
// third `docker build -f` then names a file that was never unpacked. So derive the
|
||||
// inputs from the script and from each Dockerfile's own COPY lines instead of restating
|
||||
// them here; a fourth image inherits the check for free.
|
||||
func TestGameStackTarCarriesEveryBuildInput(t *testing.T) {
|
||||
// Matches the path only when GAME_STACK_DIR is followed by one, which skips the
|
||||
// build-context arguments (`"$GAME_STACK_DIR"`, `"${GAME_STACK_DIR}:/src:z"`) and
|
||||
// the glob for gradle's output, none of which are inputs this tar has to carry.
|
||||
found := regexp.MustCompile(`\$\{GAME_STACK_DIR\}/(\S+?)"`).FindAllStringSubmatch(BootstrapScript(), -1)
|
||||
var paths []string
|
||||
seen := map[string]bool{}
|
||||
for _, m := range found {
|
||||
if !seen[m[1]] {
|
||||
seen[m[1]] = true
|
||||
paths = append(paths, m[1])
|
||||
}
|
||||
}
|
||||
// Guards the regex itself: a rewrite of how bootstrap.sh spells the build context
|
||||
// would otherwise turn this test into an unconditional pass. It has to come before
|
||||
// the loop — a missing file in there is fatal, and a floor placed after it would
|
||||
// never be reached to say that the regex, not the tar, is what went wrong.
|
||||
if len(paths) < 3 {
|
||||
t.Fatalf("only %d game-stack path(s) resolved out of bootstrap.sh; the limbo, "+
|
||||
"lobby and paper Dockerfiles are all built from ${GAME_STACK_DIR}", len(paths))
|
||||
}
|
||||
for _, path := range paths {
|
||||
requireEmbedded(t, path)
|
||||
// A Dockerfile that arrives without the files it COPYs fails just as late and
|
||||
// just as far from here; the deploy/paper gap was missing its entrypoint too.
|
||||
for _, src := range copySources(t, path) {
|
||||
requireEmbedded(t, src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copySources lists the build-context paths a Dockerfile COPYs in, skipping the
|
||||
// --from=<stage> copies, whose sources are produced by an earlier stage rather than
|
||||
// unpacked from the tar.
|
||||
func copySources(t *testing.T, dockerfile string) []string {
|
||||
t.Helper()
|
||||
var out []string
|
||||
// Continuations are joined first: a COPY split across lines would otherwise be two
|
||||
// fragments, neither of them starting with COPY followed by a source, and its
|
||||
// source would slip past unchecked.
|
||||
body := strings.ReplaceAll(readGameStackFile(t, dockerfile), "\\\n", " ")
|
||||
for line := range strings.SplitSeq(body, "\n") {
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 2 || f[0] != "COPY" || strings.HasPrefix(f[1], "--") {
|
||||
continue
|
||||
}
|
||||
out = append(out, strings.TrimSuffix(f[1], "/"))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// fs.Stat rather than ReadFile: half of these are directories (`COPY plugins/shared/`),
|
||||
// and embed.FS answers for those too.
|
||||
func requireEmbedded(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
if _, err := fs.Stat(gameStackAssets, path); err != nil {
|
||||
t.Errorf("%s is a game-stack build input but is not in gameStackAssets; an "+
|
||||
"install with no source checkout dies on it: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
func readGameStackFile(t *testing.T, name string) string {
|
||||
t.Helper()
|
||||
b, err := gameStackAssets.ReadFile(name)
|
||||
|
||||
Reference in new issue
Block a user