chore(ax): resolve docker and linuxkit runtime commands

This commit is contained in:
Virgil 2026-03-30 01:19:19 +00:00
parent 1c5e7b77fd
commit 6950a75fa3
6 changed files with 127 additions and 30 deletions

View file

@ -40,13 +40,13 @@ func (b *DockerBuilder) Detect(fs io.Medium, dir string) (bool, error) {
// Build builds Docker images for the specified targets.
// Usage example: call value.Build(...) from integrating code.
func (b *DockerBuilder) Build(ctx context.Context, cfg *build.Config, targets []build.Target) ([]build.Artifact, error) {
// Validate docker CLI is available
if err := b.validateDockerCli(); err != nil {
dockerCommand, err := b.resolveDockerCli()
if err != nil {
return nil, err
}
// Ensure buildx is available
if err := b.ensureBuildx(ctx); err != nil {
if err := b.ensureBuildx(ctx, dockerCommand); err != nil {
return nil, err
}
@ -162,7 +162,7 @@ func (b *DockerBuilder) Build(ctx context.Context, cfg *build.Config, targets []
core.Print(nil, " Platforms: %s", core.Join(", ", platforms...))
core.Print(nil, " Tags: %s", core.Join(", ", imageRefs...))
if err := ax.ExecDir(ctx, cfg.ProjectDir, "docker", args...); err != nil {
if err := ax.ExecDir(ctx, cfg.ProjectDir, dockerCommand, args...); err != nil {
return nil, coreerr.E("DockerBuilder.Build", "buildx build failed", err)
}
@ -179,25 +179,35 @@ func (b *DockerBuilder) Build(ctx context.Context, cfg *build.Config, targets []
return artifacts, nil
}
// validateDockerCli checks if the docker CLI is available.
func (b *DockerBuilder) validateDockerCli() error {
if _, err := ax.LookPath("docker"); err != nil {
return coreerr.E("DockerBuilder.validateDockerCli", "docker CLI not found. Install it from https://docs.docker.com/get-docker/", err)
// resolveDockerCli returns the executable path for the docker CLI.
func (b *DockerBuilder) resolveDockerCli(paths ...string) (string, error) {
if len(paths) == 0 {
paths = []string{
"/usr/local/bin/docker",
"/opt/homebrew/bin/docker",
"/Applications/Docker.app/Contents/Resources/bin/docker",
}
}
return nil
command, err := ax.ResolveCommand("docker", paths...)
if err != nil {
return "", coreerr.E("DockerBuilder.resolveDockerCli", "docker CLI not found. Install it from https://docs.docker.com/get-docker/", err)
}
return command, nil
}
// ensureBuildx ensures docker buildx is available and has a builder.
func (b *DockerBuilder) ensureBuildx(ctx context.Context) error {
func (b *DockerBuilder) ensureBuildx(ctx context.Context, dockerCommand string) error {
// Check if buildx is available
if err := ax.Exec(ctx, "docker", "buildx", "version"); err != nil {
if err := ax.Exec(ctx, dockerCommand, "buildx", "version"); err != nil {
return coreerr.E("DockerBuilder.ensureBuildx", "buildx is not available. Install it from https://docs.docker.com/buildx/working-with-buildx/", err)
}
// Check if we have a builder, create one if not
if err := ax.Exec(ctx, "docker", "buildx", "inspect", "--bootstrap"); err != nil {
if err := ax.Exec(ctx, dockerCommand, "buildx", "inspect", "--bootstrap"); err != nil {
// Try to create a builder
if err := ax.Exec(ctx, "docker", "buildx", "create", "--use", "--bootstrap"); err != nil {
if err := ax.Exec(ctx, dockerCommand, "buildx", "create", "--use", "--bootstrap"); err != nil {
return coreerr.E("DockerBuilder.ensureBuildx", "failed to create buildx builder", err)
}
}

View file

@ -81,3 +81,22 @@ func TestDocker_DockerBuilderInterface_Good(t *testing.T) {
var _ build.Builder = (*DockerBuilder)(nil)
var _ build.Builder = NewDockerBuilder()
}
func TestDocker_DockerBuilderResolveDockerCli_Good(t *testing.T) {
builder := NewDockerBuilder()
fallbackDir := t.TempDir()
fallbackPath := ax.Join(fallbackDir, "docker")
require.NoError(t, ax.WriteFile(fallbackPath, []byte("#!/bin/sh\nexit 0\n"), 0o755))
command, err := builder.resolveDockerCli(fallbackPath)
require.NoError(t, err)
assert.Equal(t, fallbackPath, command)
}
func TestDocker_DockerBuilderResolveDockerCli_Bad(t *testing.T) {
builder := NewDockerBuilder()
_, err := builder.resolveDockerCli(ax.Join(t.TempDir(), "missing-docker"))
require.Error(t, err)
assert.Contains(t, err.Error(), "docker CLI not found")
}

View file

@ -54,7 +54,8 @@ func (p *DockerPublisher) Publish(ctx context.Context, release *Release, pubCfg
}
// Validate docker CLI is available after local config checks.
if err := validateDockerCli(); err != nil {
dockerCommand, err := resolveDockerCli()
if err != nil {
return err
}
@ -62,7 +63,7 @@ func (p *DockerPublisher) Publish(ctx context.Context, release *Release, pubCfg
return p.dryRunPublish(release, dockerCfg)
}
return p.executePublish(ctx, release, dockerCfg)
return p.executePublish(ctx, release, dockerCfg, dockerCommand)
}
// parseConfig extracts Docker-specific configuration.
@ -164,9 +165,9 @@ func (p *DockerPublisher) dryRunPublish(release *Release, cfg DockerConfig) erro
}
// executePublish builds and pushes Docker images.
func (p *DockerPublisher) executePublish(ctx context.Context, release *Release, cfg DockerConfig) error {
func (p *DockerPublisher) executePublish(ctx context.Context, release *Release, cfg DockerConfig, dockerCommand string) error {
// Ensure buildx is available and builder is set up
if err := p.ensureBuildx(ctx); err != nil {
if err := p.ensureBuildx(ctx, dockerCommand); err != nil {
return err
}
@ -177,7 +178,7 @@ func (p *DockerPublisher) executePublish(ctx context.Context, release *Release,
args := p.buildBuildxArgs(cfg, tags, release.Version)
publisherPrint("Building and pushing Docker image: %s", cfg.Image)
if err := publisherRun(ctx, release.ProjectDir, nil, "docker", args...); err != nil {
if err := publisherRun(ctx, release.ProjectDir, nil, dockerCommand, args...); err != nil {
return coreerr.E("docker.Publish", "buildx build failed", err)
}
@ -245,16 +246,16 @@ func (p *DockerPublisher) buildBuildxArgs(cfg DockerConfig, tags []string, versi
}
// ensureBuildx ensures docker buildx is available and has a builder.
func (p *DockerPublisher) ensureBuildx(ctx context.Context) error {
func (p *DockerPublisher) ensureBuildx(ctx context.Context, dockerCommand string) error {
// Check if buildx is available
if err := ax.Exec(ctx, "docker", "buildx", "version"); err != nil {
if err := ax.Exec(ctx, dockerCommand, "buildx", "version"); err != nil {
return coreerr.E("docker.ensureBuildx", "buildx is not available. Install it from https://docs.docker.com/buildx/working-with-buildx/", nil)
}
// Check if we have a builder, create one if not
if err := ax.Exec(ctx, "docker", "buildx", "inspect", "--bootstrap"); err != nil {
if err := ax.Exec(ctx, dockerCommand, "buildx", "inspect", "--bootstrap"); err != nil {
// Try to create a builder
if err := publisherRun(ctx, "", nil, "docker", "buildx", "create", "--use", "--bootstrap"); err != nil {
if err := publisherRun(ctx, "", nil, dockerCommand, "buildx", "create", "--use", "--bootstrap"); err != nil {
return coreerr.E("docker.ensureBuildx", "failed to create buildx builder", err)
}
}
@ -262,10 +263,28 @@ func (p *DockerPublisher) ensureBuildx(ctx context.Context) error {
return nil
}
// resolveDockerCli returns the executable path for the docker CLI.
func resolveDockerCli(paths ...string) (string, error) {
if len(paths) == 0 {
paths = []string{
"/usr/local/bin/docker",
"/opt/homebrew/bin/docker",
"/Applications/Docker.app/Contents/Resources/bin/docker",
}
}
command, err := ax.ResolveCommand("docker", paths...)
if err != nil {
return "", coreerr.E("docker.resolveDockerCli", "docker CLI not found. Install it from https://docs.docker.com/get-docker/", err)
}
return command, nil
}
// validateDockerCli checks if the docker CLI is available.
func validateDockerCli() error {
if _, err := ax.LookPath("docker"); err != nil {
return coreerr.E("docker.validateDockerCli", "docker CLI not found. Install it from https://docs.docker.com/get-docker/", nil)
if _, err := resolveDockerCli(); err != nil {
return coreerr.E("docker.validateDockerCli", "docker CLI not found. Install it from https://docs.docker.com/get-docker/", err)
}
return nil
}

View file

@ -649,6 +649,22 @@ func TestDocker_ValidateDockerCli_Good(t *testing.T) {
})
}
func TestDocker_ResolveDockerCli_Good(t *testing.T) {
fallbackDir := t.TempDir()
fallbackPath := ax.Join(fallbackDir, "docker")
require.NoError(t, ax.WriteFile(fallbackPath, []byte("#!/bin/sh\nexit 0\n"), 0o755))
command, err := resolveDockerCli(fallbackPath)
require.NoError(t, err)
assert.Equal(t, fallbackPath, command)
}
func TestDocker_ResolveDockerCli_Bad(t *testing.T) {
_, err := resolveDockerCli(ax.Join(t.TempDir(), "missing-docker"))
require.Error(t, err)
assert.Contains(t, err.Error(), "docker CLI not found")
}
func TestDocker_DockerPublisherPublishWithCLI_Good(t *testing.T) {
// These tests run only when docker CLI is available
if err := validateDockerCli(); err != nil {

View file

@ -42,8 +42,8 @@ func (p *LinuxKitPublisher) Name() string {
// Publish builds LinuxKit images and uploads them to the GitHub release.
// Usage example: call value.Publish(...) from integrating code.
func (p *LinuxKitPublisher) Publish(ctx context.Context, release *Release, pubCfg PublisherConfig, relCfg ReleaseConfig, dryRun bool) error {
// Validate linuxkit CLI is available
if err := validateLinuxKitCli(); err != nil {
linuxkitCommand, err := resolveLinuxKitCli()
if err != nil {
return err
}
@ -75,7 +75,7 @@ func (p *LinuxKitPublisher) Publish(ctx context.Context, release *Release, pubCf
return p.dryRunPublish(release, lkCfg, repo)
}
return p.executePublish(ctx, release, lkCfg, repo)
return p.executePublish(ctx, release, lkCfg, repo, linuxkitCommand)
}
// parseConfig extracts LinuxKit-specific configuration.
@ -172,7 +172,7 @@ func (p *LinuxKitPublisher) dryRunPublish(release *Release, cfg LinuxKitConfig,
}
// executePublish builds LinuxKit images and uploads them.
func (p *LinuxKitPublisher) executePublish(ctx context.Context, release *Release, cfg LinuxKitConfig, repo string) error {
func (p *LinuxKitPublisher) executePublish(ctx context.Context, release *Release, cfg LinuxKitConfig, repo, linuxkitCommand string) error {
outputDir := ax.Join(release.ProjectDir, "dist", "linuxkit")
// Create output directory
@ -197,7 +197,7 @@ func (p *LinuxKitPublisher) executePublish(ctx context.Context, release *Release
// Build the image
args := p.buildLinuxKitArgs(cfg.Config, format, outputName, outputDir, arch)
publisherPrint("Building LinuxKit image: %s (%s)", outputName, format)
if err := publisherRun(ctx, release.ProjectDir, nil, "linuxkit", args...); err != nil {
if err := publisherRun(ctx, release.ProjectDir, nil, linuxkitCommand, args...); err != nil {
return coreerr.E("linuxkit.Publish", "build failed for "+platform+"/"+format, err)
}
@ -292,9 +292,26 @@ func (p *LinuxKitPublisher) getFormatExtension(format string) string {
}
}
// resolveLinuxKitCli returns the executable path for the linuxkit CLI.
func resolveLinuxKitCli(paths ...string) (string, error) {
if len(paths) == 0 {
paths = []string{
"/usr/local/bin/linuxkit",
"/opt/homebrew/bin/linuxkit",
}
}
command, err := ax.ResolveCommand("linuxkit", paths...)
if err != nil {
return "", coreerr.E("linuxkit.resolveLinuxKitCli", "linuxkit CLI not found. Install it from https://github.com/linuxkit/linuxkit", err)
}
return command, nil
}
// validateLinuxKitCli checks if the linuxkit CLI is available.
func validateLinuxKitCli() error {
if _, err := ax.LookPath("linuxkit"); err != nil {
if _, err := resolveLinuxKitCli(); err != nil {
return coreerr.E("linuxkit.validateLinuxKitCli", "linuxkit CLI not found. Install it from https://github.com/linuxkit/linuxkit", err)
}
return nil

View file

@ -266,6 +266,22 @@ func TestLinuxKit_ValidateLinuxKitCli_Good(t *testing.T) {
})
}
func TestLinuxKit_ResolveLinuxKitCli_Good(t *testing.T) {
fallbackDir := t.TempDir()
fallbackPath := ax.Join(fallbackDir, "linuxkit")
require.NoError(t, ax.WriteFile(fallbackPath, []byte("#!/bin/sh\nexit 0\n"), 0o755))
command, err := resolveLinuxKitCli(fallbackPath)
require.NoError(t, err)
assert.Equal(t, fallbackPath, command)
}
func TestLinuxKit_ResolveLinuxKitCli_Bad(t *testing.T) {
_, err := resolveLinuxKitCli(ax.Join(t.TempDir(), "missing-linuxkit"))
require.Error(t, err)
assert.Contains(t, err.Error(), "linuxkit CLI not found")
}
func TestLinuxKit_LinuxKitPublisherPublishWithCLI_Good(t *testing.T) {
// These tests run only when linuxkit CLI is available
if err := validateLinuxKitCli(); err != nil {