build(lint): first pass at linter without requiring volume mounts
All checks were successful
Test / elint (push) Successful in 38s
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:
parent
21e1d47d5f
commit
c4274316fa
2 changed files with 90 additions and 23 deletions
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue