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
|
//go:embed deploy/crd/*.yaml
|
||||||
var bootstrapAssets embed.FS
|
var bootstrapAssets embed.FS
|
||||||
|
|
||||||
// gameStackAssets carries everything deploy/bootstrap.sh needs to build the two
|
// gameStackAssets carries everything deploy/bootstrap.sh needs to build the three
|
||||||
// always-on game images (login limbo + lobby) and the Velocity plugin, for the TUI
|
// 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
|
// install path — which pipes the embedded bootstrap.sh into bash and therefore has
|
||||||
// NO source checkout on disk to build from.
|
// 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/limbo/Dockerfile deploy/limbo/entrypoint.sh
|
||||||
//go:embed deploy/lobby/Dockerfile deploy/lobby/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/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/paper/build.gradle plugins/paper/settings.gradle plugins/paper/src
|
||||||
//go:embed plugins/velocity/build.gradle plugins/velocity/settings.gradle plugins/velocity/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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Mode 0644 for everything: entrypoint.sh is invoked as `sh <file>` by both
|
// Mode 0644 for everything: entrypoint.sh is invoked as `sh <file>` by all
|
||||||
// Dockerfiles precisely because the +x bit does not survive a Windows checkout,
|
// three Dockerfiles precisely because the +x bit does not survive a Windows
|
||||||
// so nothing here needs to be executable.
|
// checkout, so nothing here needs to be executable.
|
||||||
if err := tw.WriteHeader(&tar.Header{
|
if err := tw.WriteHeader(&tar.Header{
|
||||||
Name: path,
|
Name: path,
|
||||||
Mode: 0o644,
|
Mode: 0o644,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package felis
|
package felis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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 {
|
func readGameStackFile(t *testing.T, name string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
b, err := gameStackAssets.ReadFile(name)
|
b, err := gameStackAssets.ReadFile(name)
|
||||||
|
|||||||
Reference in new issue
Block a user