diff --git a/Dockerfile b/Dockerfile index 4b227f1..3aad482 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,15 @@ # Build both binaries +# Use BUILDPLATFORM so Go runs natively, cross-compile for target arch FROM --platform=$BUILDPLATFORM golang:1.24 AS builder ARG TARGETOS ARG TARGETARCH +ARG BUILDARCH + +# Install C cross-compiler for CGO when building arm64 from amd64 +RUN if [ "$BUILDARCH" = "amd64" ] && [ "$TARGETARCH" = "arm64" ]; then \ + apt-get update && apt-get install -y gcc-aarch64-linux-gnu && rm -rf /var/lib/apt/lists/*; \ + fi WORKDIR /workspace @@ -27,7 +34,9 @@ RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \ # Build controller (requires CGO for sqlite3) # Use -s -w to strip debug symbols and reduce binary size -RUN CGO_ENABLED=1 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \ +# Use cross-compiler for arm64 when building from amd64 +RUN CC=$(if [ "$TARGETARCH" = "arm64" ] && [ "$BUILDARCH" = "amd64" ]; then echo "aarch64-linux-gnu-gcc"; else echo "gcc"; fi) && \ + CGO_ENABLED=1 CC=$CC GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \ go build -a -ldflags='-s -w' -o manager ./cmd/controller # Unified image with both binaries diff --git a/Makefile b/Makefile index f0c113f..8738e0e 100644 --- a/Makefile +++ b/Makefile @@ -163,24 +163,23 @@ build: manifests generate fmt vet ## Build manager binary. run: manifests generate fmt vet ## Run a controller from your host. go run ./cmd/controller/main.go -# If you wish to build the manager image targeting other platforms you can use the --platform flag. -# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. +# Build multi-arch docker image using buildx (native builds per arch due to CGO) # More info: https://docs.docker.com/develop/develop-images/build_enhancements/ .PHONY: docker-build -docker-build: ## Build docker image with the manager. - cd .. && $(CONTAINER_TOOL) build -f loom/Dockerfile -t ${IMG} . - -.PHONY: docker-push -docker-push: ## Push docker image with the manager. - $(CONTAINER_TOOL) push ${IMG} +docker-build: setup-buildx ## Build and push multi-arch docker image. + cd .. && $(CONTAINER_TOOL) buildx build \ + --builder loom-builder \ + --platform=linux/amd64,linux/arm64 \ + --push \ + --tag ${IMG} \ + -f loom/Dockerfile . -# Runner image configuration -RUNNER_IMG ?= atcr.io/evan.jarrett.net/loom-runner:$(VERSION) -RUNNER_IMG_LATEST ?= atcr.io/evan.jarrett.net/loom-runner:latest -RUNNER_PLATFORMS ?= linux/amd64,linux/arm64 +.PHONY: docker-build-local +docker-build-local: ## Build docker image for local arch only (no push). + cd .. && $(CONTAINER_TOOL) build -f loom/Dockerfile -t ${IMG} . -.PHONY: setup-buildx-runner -setup-buildx-runner: ## Set up buildx builder with credential access for runner builds +.PHONY: setup-buildx +setup-buildx: ## Set up buildx builder with credential access for multi-arch builds @echo "Setting up loom-builder with credential access..." # Remove existing builder if present - $(CONTAINER_TOOL) buildx rm loom-builder 2>/dev/null || true @@ -209,31 +208,6 @@ setup-buildx-runner: ## Set up buildx builder with credential access for runner fi @echo "✓ Builder setup complete!" -.PHONY: docker-build-runner -docker-build-runner: setup-buildx-runner ## Build multi-arch docker image for the loom runner binary. - @if [ -n "$(DOCKER_USERNAME)" ] && [ -n "$(DOCKER_PASSWORD)" ]; then \ - echo "Authenticating to atcr.io with username/password..."; \ - echo "$(DOCKER_PASSWORD)" | $(CONTAINER_TOOL) login atcr.io -u "$(DOCKER_USERNAME)" --password-stdin; \ - echo "Copying updated credentials to builder..."; \ - $(CONTAINER_TOOL) cp $(HOME)/.docker/config.json buildx_buildkit_loom-builder0:/root/.docker/config.json; \ - else \ - echo "⚠ No DOCKER_USERNAME/DOCKER_PASSWORD set. Will try credential helper (may not work in container)."; \ - fi - cd .. && $(CONTAINER_TOOL) buildx build \ - --builder loom-builder \ - --platform=$(RUNNER_PLATFORMS) \ - --tag $(RUNNER_IMG) \ - --tag $(RUNNER_IMG_LATEST) \ - --push \ - --file Dockerfile.runner \ - . - -.PHONY: docker-build-runner-local -docker-build-runner-local: ## Build local runner image (single arch) for testing. - cd .. && $(CONTAINER_TOOL) build \ - --tag $(RUNNER_IMG_LATEST) \ - --file Dockerfile.runner \ - . .PHONY: test-registry-auth test-registry-auth: ## Test registry authentication before building diff --git a/cmd/runner/main.go b/cmd/runner/main.go index 98e8270..cc859b3 100644 --- a/cmd/runner/main.go +++ b/cmd/runner/main.go @@ -32,12 +32,52 @@ func (s *simpleStep) Kind() models.StepKind { } func main() { + // Handle --install flag for self-copying (used by init container in distroless image) + if len(os.Args) >= 3 && os.Args[1] == "--install" { + if err := installSelf(os.Args[2]); err != nil { + fmt.Fprintf(os.Stderr, "install failed: %v\n", err) + os.Exit(1) + } + os.Exit(0) + } + if err := run(); err != nil { fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) os.Exit(1) } } +// installSelf copies this executable to the destination path +func installSelf(dst string) error { + src, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to get executable path: %w", err) + } + + srcFile, err := os.Open(src) + if err != nil { + return fmt.Errorf("failed to open source: %w", err) + } + defer srcFile.Close() + + dstFile, err := os.Create(dst) + if err != nil { + return fmt.Errorf("failed to create destination: %w", err) + } + defer dstFile.Close() + + if _, err := io.Copy(dstFile, srcFile); err != nil { + return fmt.Errorf("failed to copy: %w", err) + } + + // Make executable + if err := os.Chmod(dst, 0755); err != nil { + return fmt.Errorf("failed to chmod: %w", err) + } + + return nil +} + func run() error { // Read workflow spec from environment workflowJSON := os.Getenv("LOOM_WORKFLOW_SPEC") diff --git a/internal/jobbuilder/job_template.go b/internal/jobbuilder/job_template.go index 09672ef..dde0caa 100644 --- a/internal/jobbuilder/job_template.go +++ b/internal/jobbuilder/job_template.go @@ -268,7 +268,7 @@ echo "User setup complete" { Name: "install-runner", Image: config.LoomImage, - Command: []string{"cp", "/loom-runner", "/runner-bin/loom-runner"}, + Command: []string{"/loom-runner", "--install", "/runner-bin/loom-runner"}, SecurityContext: &corev1.SecurityContext{ AllowPrivilegeEscalation: &[]bool{false}[0], RunAsNonRoot: &[]bool{true}[0],