fix(build): emit docker tarball artifacts
This commit is contained in:
parent
e66220f493
commit
ebebfd2155
2 changed files with 113 additions and 19 deletions
|
|
@ -3,6 +3,7 @@ package builders
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"dappco.re/go/core"
|
||||
"dappco.re/go/core/build/internal/ax"
|
||||
|
|
@ -82,6 +83,10 @@ func (b *DockerBuilder) Build(ctx context.Context, cfg *build.Config, targets []
|
|||
}
|
||||
|
||||
// If no targets specified, use current platform
|
||||
buildTargets := targets
|
||||
if len(buildTargets) == 0 {
|
||||
buildTargets = []build.Target{{OS: "linux", Arch: "amd64"}}
|
||||
}
|
||||
if len(platforms) == 0 {
|
||||
platforms = []string{"linux/amd64"}
|
||||
}
|
||||
|
|
@ -141,18 +146,15 @@ func (b *DockerBuilder) Build(ctx context.Context, cfg *build.Config, targets []
|
|||
args = append(args, "--build-arg", core.Sprintf("VERSION=%s", cfg.Version))
|
||||
}
|
||||
|
||||
safeImageName := strings.ReplaceAll(imageName, "/", "_")
|
||||
|
||||
// Output to local docker images or push
|
||||
if cfg.Push {
|
||||
args = append(args, "--push")
|
||||
} else {
|
||||
// For multi-platform builds without push, we need to load or output somewhere
|
||||
if len(platforms) == 1 {
|
||||
args = append(args, "--load")
|
||||
} else {
|
||||
// Multi-platform builds can't use --load, output to tarball
|
||||
outputPath := ax.Join(cfg.OutputDir, core.Sprintf("%s.tar", imageName))
|
||||
args = append(args, "--output", core.Sprintf("type=oci,dest=%s", outputPath))
|
||||
}
|
||||
// Local Docker builds emit an OCI archive so the build output is a file.
|
||||
outputPath := ax.Join(cfg.OutputDir, core.Sprintf("%s.tar", safeImageName))
|
||||
args = append(args, "--output", core.Sprintf("type=oci,dest=%s", outputPath))
|
||||
}
|
||||
|
||||
// Build context (project directory)
|
||||
|
|
@ -167,21 +169,23 @@ 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...))
|
||||
|
||||
// Build once for the full platform set. Docker buildx produces a single
|
||||
// multi-arch image or OCI archive from the combined platform list.
|
||||
if err := ax.ExecDir(ctx, cfg.ProjectDir, dockerCommand, args...); err != nil {
|
||||
return nil, coreerr.E("DockerBuilder.Build", "buildx build failed", err)
|
||||
}
|
||||
|
||||
// Create artifacts for each platform
|
||||
var artifacts []build.Artifact
|
||||
for _, t := range targets {
|
||||
artifacts = append(artifacts, build.Artifact{
|
||||
Path: imageRefs[0], // Primary image reference
|
||||
OS: t.OS,
|
||||
Arch: t.Arch,
|
||||
})
|
||||
artifactPath := imageRefs[0]
|
||||
if !cfg.Push {
|
||||
artifactPath = ax.Join(cfg.OutputDir, core.Sprintf("%s.tar", safeImageName))
|
||||
}
|
||||
|
||||
return artifacts, nil
|
||||
primaryTarget := buildTargets[0]
|
||||
return []build.Artifact{{
|
||||
Path: artifactPath,
|
||||
OS: primaryTarget.OS,
|
||||
Arch: primaryTarget.Arch,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// resolveDockerCli returns the executable path for the docker CLI.
|
||||
|
|
|
|||
|
|
@ -1,23 +1,56 @@
|
|||
package builders
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/build/internal/ax"
|
||||
|
||||
"dappco.re/go/core/build/pkg/build"
|
||||
"dappco.re/go/core/io"
|
||||
coreio "dappco.re/go/core/io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setupFakeDockerToolchain(t *testing.T, binDir string) {
|
||||
t.Helper()
|
||||
|
||||
script := `#!/bin/sh
|
||||
set -eu
|
||||
|
||||
log_file="${DOCKER_BUILD_LOG_FILE:-}"
|
||||
if [ -n "$log_file" ]; then
|
||||
printf '%s\n' "$*" >> "$log_file"
|
||||
fi
|
||||
|
||||
if [ "${1:-}" = "buildx" ] && [ "${2:-}" = "build" ]; then
|
||||
dest=""
|
||||
while [ $# -gt 0 ]; do
|
||||
if [ "$1" = "--output" ]; then
|
||||
shift
|
||||
dest="$(printf '%s' "$1" | sed -n 's#type=oci,dest=##p')"
|
||||
fi
|
||||
shift
|
||||
done
|
||||
if [ -n "$dest" ]; then
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
printf 'oci archive\n' > "$dest"
|
||||
fi
|
||||
fi
|
||||
`
|
||||
|
||||
require.NoError(t, ax.WriteFile(ax.Join(binDir, "docker"), []byte(script), 0o755))
|
||||
}
|
||||
|
||||
func TestDocker_DockerBuilderName_Good(t *testing.T) {
|
||||
builder := NewDockerBuilder()
|
||||
assert.Equal(t, "docker", builder.Name())
|
||||
}
|
||||
|
||||
func TestDocker_DockerBuilderDetect_Good(t *testing.T) {
|
||||
fs := io.Local
|
||||
fs := coreio.Local
|
||||
|
||||
t.Run("detects Dockerfile", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
|
@ -102,3 +135,60 @@ func TestDocker_DockerBuilderResolveDockerCli_Bad(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "docker CLI not found")
|
||||
}
|
||||
|
||||
func TestDocker_DockerBuilderBuild_Good(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test in short mode")
|
||||
}
|
||||
|
||||
binDir := t.TempDir()
|
||||
setupFakeDockerToolchain(t, binDir)
|
||||
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
|
||||
projectDir := t.TempDir()
|
||||
require.NoError(t, ax.WriteFile(ax.Join(projectDir, "Dockerfile"), []byte("FROM alpine:latest\n"), 0o644))
|
||||
|
||||
outputDir := t.TempDir()
|
||||
logDir := t.TempDir()
|
||||
logPath := ax.Join(logDir, "docker.log")
|
||||
t.Setenv("DOCKER_BUILD_LOG_FILE", logPath)
|
||||
|
||||
builder := NewDockerBuilder()
|
||||
cfg := &build.Config{
|
||||
FS: coreio.Local,
|
||||
ProjectDir: projectDir,
|
||||
OutputDir: outputDir,
|
||||
Name: "sample-app",
|
||||
Image: "owner/repo",
|
||||
}
|
||||
targets := []build.Target{
|
||||
{OS: "linux", Arch: "amd64"},
|
||||
{OS: "linux", Arch: "arm64"},
|
||||
}
|
||||
|
||||
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, artifacts, 1)
|
||||
|
||||
expectedPath := ax.Join(outputDir, "owner_repo.tar")
|
||||
assert.Equal(t, expectedPath, artifacts[0].Path)
|
||||
assert.Equal(t, "linux", artifacts[0].OS)
|
||||
assert.Equal(t, "amd64", artifacts[0].Arch)
|
||||
assert.FileExists(t, expectedPath)
|
||||
|
||||
logContent, err := ax.ReadFile(logPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
log := string(logContent)
|
||||
assert.Equal(t, 1, strings.Count(log, "buildx build"))
|
||||
assert.Contains(t, log, "--platform")
|
||||
assert.Contains(t, log, "linux/amd64,linux/arm64")
|
||||
assert.Contains(t, log, "--output")
|
||||
assert.Contains(t, log, "type=oci,dest="+expectedPath)
|
||||
|
||||
artifacts, err = builder.Build(context.Background(), cfg, nil)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, artifacts, 1)
|
||||
assert.Equal(t, "linux", artifacts[0].OS)
|
||||
assert.Equal(t, "amd64", artifacts[0].Arch)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue