From c4274316fa152e19517c6b5c1a296a2596470b26 Mon Sep 17 00:00:00 2001 From: Jared Allard Date: Sun, 9 Mar 2025 16:35:45 -0700 Subject: [PATCH] build(lint): first pass at linter without requiring volume mounts act, much like CircleCI, doesn't allow us to have easily accessible volume mounts between the CI system and the Docker host. So, much like the build of the updater, the linter now no longer requires volume mounts to lint ebuilds. This implementation is decently ineffecient and will be improved over time. --- .../internal/ebuild/embed/verify-manifest.sh | 2 +- .tools/internal/ebuild/manifest.go | 111 ++++++++++++++---- 2 files changed, 90 insertions(+), 23 deletions(-) diff --git a/.tools/internal/ebuild/embed/verify-manifest.sh b/.tools/internal/ebuild/embed/verify-manifest.sh index f5b918d..af67520 100644 --- a/.tools/internal/ebuild/embed/verify-manifest.sh +++ b/.tools/internal/ebuild/embed/verify-manifest.sh @@ -4,7 +4,7 @@ # # Valid exit codes are: # 0 - Manifest is up-to-date and valid -# 1 - General error ocurring during validation +# 1 - General error occurring during validation # 2 - Manifest is invalid or out-of-sync # # Usage: verify-manifest.sh [package-dir] diff --git a/.tools/internal/ebuild/manifest.go b/.tools/internal/ebuild/manifest.go index 3998b4a..ecc593c 100644 --- a/.tools/internal/ebuild/manifest.go +++ b/.tools/internal/ebuild/manifest.go @@ -16,10 +16,17 @@ package ebuild import ( + "context" _ "embed" + "fmt" "io" + "io/fs" "os/exec" + "path" + "path/filepath" + "strings" + "github.com/jaredallard/overlay/.tools/internal/steps/stepshelpers" "github.com/pkg/errors" ) @@ -27,7 +34,7 @@ import ( // Manifest files. // //go:embed embed/verify-manifest.sh -var manifestValidationScript string +var manifestValidationScript []byte // gentooImage is the docker image used for validating Manifest files. var gentooImage = "ghcr.io/jaredallard/overlay:updater" @@ -39,29 +46,89 @@ var ( ErrManifestInvalid = errors.New("manifest is out of date or invalid") ) -// ValidateManifest ensures that the manifest at the provided path is -// valid for the given ebuild. This requires docker to be installed on -// the host and running. -func ValidateManifest(stdout, stderr io.Writer, packageDir, packageName string) error { - cmd := exec.Command( - "docker", "run", - // Ensures we can use the network-sandbox feature. - "--privileged", - // Run bash and mount the ebuild repository at a predictable path. - "--rm", "--entrypoint", "bash", "-v"+packageDir+":/ebuild/src:ro", - gentooImage, "-c", manifestValidationScript, "", packageName, - ) - cmd.Stdout = stdout - cmd.Stderr = stderr - if err := cmd.Run(); err != nil { - var exitErr *exec.ExitError - if errors.As(err, &exitErr) { - if exitErr.ExitCode() == 2 { - return ErrManifestInvalid - } +// copyDirectoryIntoContainer copies all files in the srcPath/ into +// destPath/ in the container. +func copyDirectoryIntoContainer(ctx context.Context, containerID, srcPath, destPath string) error { + directories := make(map[string]struct{}) + + if err := filepath.WalkDir(srcPath, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err } - return errors.Wrap(err, "unknown error while validating manifest") + // We skip directories. + if d.IsDir() { + return nil + } + + relPath, err := filepath.Rel(srcPath, path) + if err != nil { + return fmt.Errorf("could not create relative path for %q: %w", path, err) + } + + dir := filepath.Dir(relPath) + if _, ok := directories[dir]; !ok { + if err := stepshelpers.RunCommandInContainer(ctx, containerID, "mkdir", "-p", filepath.Join(destPath, dir)); err != nil { + return fmt.Errorf("failed to ensure directory %q: %w", dir, err) + } + directories[dir] = struct{}{} + } + + if err := stepshelpers.CopyFileToContainer(ctx, containerID, filepath.Join(srcPath, relPath), filepath.Join(destPath, relPath)); err != nil { + return fmt.Errorf("failed to copy %q into container: %w", relPath, err) + } + + return nil + }); err != nil { + return fmt.Errorf("failed to walk srcDir: %w", err) + } + + return nil +} + +// ValidateManifest ensures that the manifest at the provided path is +// valid for the given ebuild. This requires docker to be installed on +// the host and running. +func ValidateManifest(stdout, stderr io.Writer, overlayDir, packageName string) error { + ctx := context.TODO() + + bid, err := exec.Command( + "docker", "run", "-d", "--rm", "--entrypoint", "sleep", gentooImage, "infinity", + ).Output() + if err != nil { + var execErr *exec.ExitError + if errors.As(err, &execErr) { + return fmt.Errorf("failed to run container: %s", string(execErr.Stderr)) + } + + return fmt.Errorf("failed to run container: %w", err) + } + containerID := strings.TrimSpace(string(bid)) + defer exec.Command("docker", "stop", containerID) //nolint:errcheck // Why: best effort + + lclPkgDir := filepath.Join(overlayDir, packageName) + + containerOverlayDir := "/ebuild/src" + containerPkgDir := path.Join(containerOverlayDir, packageName) + + if err := copyDirectoryIntoContainer(ctx, containerID, lclPkgDir, containerPkgDir); err != nil { + return fmt.Errorf("failed to copy ebuild contents into container: %w", err) + } + + if err := copyDirectoryIntoContainer(ctx, containerID, filepath.Join(overlayDir, "metadata"), "/ebuild/src/metadata"); err != nil { + return fmt.Errorf("failed to copy metadata directory into container: %w", err) + } + + if err := stepshelpers.CopyFileBytesToContainer(ctx, containerID, manifestValidationScript, "/verify-manifest.sh"); err != nil { + return fmt.Errorf("failed to copy validation script into container: %w", err) + } + + if err := stepshelpers.RunCommandInContainer(ctx, containerID, "chmod", "+x", "/verify-manifest.sh"); err != nil { + return fmt.Errorf("failed to mark validation script as executable: %w", err) + } + + if err := stepshelpers.RunCommandInContainer(ctx, containerID, "/verify-manifest.sh", packageName); err != nil { + return fmt.Errorf("ebuild failed to lint: %w", err) } return nil