build(lint): first pass at linter without requiring volume mounts
All checks were successful
Test / elint (push) Successful in 38s

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.
This commit is contained in:
Jared Allard 2025-03-09 16:35:45 -07:00
parent 21e1d47d5f
commit c4274316fa
Signed by: jaredallard
SSH key fingerprint: SHA256:wyRyyv28jBYw8Yp/oABNPUYvbGd6hyZj23XVXEm5G/U
2 changed files with 90 additions and 23 deletions

View file

@ -4,7 +4,7 @@
# #
# Valid exit codes are: # Valid exit codes are:
# 0 - Manifest is up-to-date and valid # 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 # 2 - Manifest is invalid or out-of-sync
# #
# Usage: verify-manifest.sh [package-dir] # Usage: verify-manifest.sh [package-dir]

View file

@ -16,10 +16,17 @@
package ebuild package ebuild
import ( import (
"context"
_ "embed" _ "embed"
"fmt"
"io" "io"
"io/fs"
"os/exec" "os/exec"
"path"
"path/filepath"
"strings"
"github.com/jaredallard/overlay/.tools/internal/steps/stepshelpers"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -27,7 +34,7 @@ import (
// Manifest files. // Manifest files.
// //
//go:embed embed/verify-manifest.sh //go:embed embed/verify-manifest.sh
var manifestValidationScript string var manifestValidationScript []byte
// gentooImage is the docker image used for validating Manifest files. // gentooImage is the docker image used for validating Manifest files.
var gentooImage = "ghcr.io/jaredallard/overlay:updater" var gentooImage = "ghcr.io/jaredallard/overlay:updater"
@ -39,29 +46,89 @@ var (
ErrManifestInvalid = errors.New("manifest is out of date or invalid") ErrManifestInvalid = errors.New("manifest is out of date or invalid")
) )
// ValidateManifest ensures that the manifest at the provided path is // copyDirectoryIntoContainer copies all files in the srcPath/ into
// valid for the given ebuild. This requires docker to be installed on // destPath/ in the container.
// the host and running. func copyDirectoryIntoContainer(ctx context.Context, containerID, srcPath, destPath string) error {
func ValidateManifest(stdout, stderr io.Writer, packageDir, packageName string) error { directories := make(map[string]struct{})
cmd := exec.Command(
"docker", "run", if err := filepath.WalkDir(srcPath, func(path string, d fs.DirEntry, err error) error {
// Ensures we can use the network-sandbox feature. if err != nil {
"--privileged", return err
// 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
}
} }
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 return nil