315 lines
11 KiB
Go
315 lines
11 KiB
Go
// Container image builder for Ubuntu and Chainguard-based images with extensible tools
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"dagger/containers/internal/dagger"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type Containers struct{}
|
|
|
|
// Configuration structures
|
|
type ToolsConfig struct {
|
|
Core CoreTools `yaml:"core"`
|
|
Tools map[string]ToolDef `yaml:"tools"`
|
|
}
|
|
|
|
type CoreTools struct {
|
|
Git string `yaml:"git"`
|
|
Jq string `yaml:"jq"`
|
|
Yq string `yaml:"yq"`
|
|
Node20 string `yaml:"node20"`
|
|
Node22 string `yaml:"node22"`
|
|
Node24 string `yaml:"node24"`
|
|
}
|
|
|
|
type ToolDef struct {
|
|
Version string `yaml:"version"`
|
|
Description string `yaml:"description"`
|
|
}
|
|
|
|
// Load tools configuration from YAML
|
|
func (m *Containers) loadConfig(ctx context.Context, configFile *dagger.File) (*ToolsConfig, error) {
|
|
content, err := configFile.Contents(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var config ToolsConfig
|
|
if err := yaml.Unmarshal([]byte(content), &config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
// Build Ubuntu base image with core tools
|
|
func (m *Containers) buildUbuntuBase(ctx context.Context, config *ToolsConfig, platform dagger.Platform) *dagger.Container {
|
|
ctr := dag.Container(dagger.ContainerOpts{Platform: platform}).From("ubuntu:24.04")
|
|
|
|
// Determine architecture for downloads
|
|
arch := "amd64"
|
|
if platform == "linux/arm64" {
|
|
arch = "arm64"
|
|
}
|
|
|
|
// Update and install base dependencies
|
|
ctr = ctr.WithExec([]string{"apt-get", "update"}).
|
|
WithExec([]string{"apt-get", "install", "-y",
|
|
"ca-certificates",
|
|
"curl",
|
|
"wget",
|
|
"gnupg",
|
|
"lsb-release",
|
|
})
|
|
|
|
// Install Git
|
|
ctr = ctr.WithExec([]string{"apt-get", "install", "-y", "git"})
|
|
|
|
// Install jq
|
|
ctr = ctr.WithExec([]string{"sh", "-c",
|
|
fmt.Sprintf("curl -sL https://github.com/jqlang/jq/releases/download/jq-%s/jq-linux-%s -o /usr/local/bin/jq && chmod +x /usr/local/bin/jq", config.Core.Jq, arch)})
|
|
|
|
// Install yq
|
|
ctr = ctr.WithExec([]string{"sh", "-c",
|
|
fmt.Sprintf("curl -sL https://github.com/mikefarah/yq/releases/download/v%s/yq_linux_%s -o /usr/local/bin/yq && chmod +x /usr/local/bin/yq", config.Core.Yq, arch)})
|
|
|
|
// Install Node.js versions using nvm approach
|
|
ctr = ctr.WithExec([]string{"sh", "-c",
|
|
"curl -fsSL https://deb.nodesource.com/setup_20.x | bash -"}).
|
|
WithExec([]string{"apt-get", "install", "-y", "nodejs"})
|
|
|
|
// Clean up
|
|
ctr = ctr.WithExec([]string{"apt-get", "clean"}).
|
|
WithExec([]string{"rm", "-rf", "/var/lib/apt/lists/*"})
|
|
|
|
return ctr
|
|
}
|
|
|
|
// Build Chainguard base image with core tools
|
|
func (m *Containers) buildChainguardBase(ctx context.Context, config *ToolsConfig, platform dagger.Platform) *dagger.Container {
|
|
// Using Chainguard's Wolfi-based images which are minimal
|
|
ctr := dag.Container(dagger.ContainerOpts{Platform: platform}).From("cgr.dev/chainguard/wolfi-base:latest")
|
|
|
|
// Determine architecture for downloads
|
|
arch := "amd64"
|
|
if platform == "linux/arm64" {
|
|
arch = "arm64"
|
|
}
|
|
|
|
// Install core tools using apk (Wolfi package manager)
|
|
ctr = ctr.WithExec([]string{"apk", "update"}).
|
|
WithExec([]string{"apk", "add", "git", "curl", "wget", "bash", "ca-certificates"})
|
|
|
|
// Create /usr/local/bin if it doesn't exist
|
|
ctr = ctr.WithExec([]string{"mkdir", "-p", "/usr/local/bin"})
|
|
|
|
// Install jq
|
|
ctr = ctr.WithExec([]string{"sh", "-c",
|
|
fmt.Sprintf("wget -q https://github.com/jqlang/jq/releases/download/jq-%s/jq-linux-%s -O /usr/local/bin/jq && chmod +x /usr/local/bin/jq", config.Core.Jq, arch)})
|
|
|
|
// Install yq
|
|
ctr = ctr.WithExec([]string{"sh", "-c",
|
|
fmt.Sprintf("wget -q https://github.com/mikefarah/yq/releases/download/v%s/yq_linux_%s -O /usr/local/bin/yq && chmod +x /usr/local/bin/yq", config.Core.Yq, arch)})
|
|
|
|
// Install Node.js
|
|
ctr = ctr.WithExec([]string{"apk", "add", "nodejs", "npm"})
|
|
|
|
return ctr
|
|
}
|
|
|
|
// Build Ubuntu image with specific tool
|
|
func (m *Containers) buildUbuntuTool(ctx context.Context, base *dagger.Container, toolName string, toolDef ToolDef, platform dagger.Platform) *dagger.Container {
|
|
ctr := base
|
|
|
|
// Determine architecture for downloads
|
|
arch := "amd64"
|
|
goArch := "x86_64"
|
|
if platform == "linux/arm64" {
|
|
arch = "arm64"
|
|
goArch = "aarch64"
|
|
}
|
|
|
|
switch toolName {
|
|
case "kustomize":
|
|
ctr = ctr.WithExec([]string{"sh", "-c",
|
|
fmt.Sprintf("curl -sL https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%%2Fv%s/kustomize_v%s_linux_%s.tar.gz | tar xz -C /usr/local/bin", toolDef.Version, toolDef.Version, arch)})
|
|
|
|
case "dagger":
|
|
ctr = ctr.WithExec([]string{"sh", "-c",
|
|
fmt.Sprintf("curl -sL https://github.com/dagger/dagger/releases/download/v%s/dagger_v%s_linux_%s.tar.gz | tar xz -C /usr/local/bin", toolDef.Version, toolDef.Version, arch)})
|
|
|
|
case "esphome":
|
|
ctr = ctr.WithExec([]string{"apt-get", "update"}).
|
|
WithExec([]string{"apt-get", "install", "-y", "python3", "python3-pip", "python3-venv"}).
|
|
WithExec([]string{"pip3", "install", "--break-system-packages", fmt.Sprintf("esphome==%s", toolDef.Version)}).
|
|
WithExec([]string{"apt-get", "clean"})
|
|
|
|
case "zola":
|
|
ctr = ctr.WithExec([]string{"sh", "-c",
|
|
fmt.Sprintf("curl -sL https://github.com/getzola/zola/releases/download/v%s/zola-v%s-%s-unknown-linux-gnu.tar.gz | tar xz -C /usr/local/bin", toolDef.Version, toolDef.Version, goArch)})
|
|
|
|
case "yamllint":
|
|
ctr = ctr.WithExec([]string{"apt-get", "update"}).
|
|
WithExec([]string{"apt-get", "install", "-y", "python3", "python3-pip"}).
|
|
WithExec([]string{"pip3", "install", "--break-system-packages", fmt.Sprintf("yamllint==%s", toolDef.Version)}).
|
|
WithExec([]string{"apt-get", "clean"})
|
|
}
|
|
|
|
return ctr
|
|
}
|
|
|
|
// Build Chainguard image with specific tool
|
|
func (m *Containers) buildChainguardTool(ctx context.Context, base *dagger.Container, toolName string, toolDef ToolDef, platform dagger.Platform) *dagger.Container {
|
|
ctr := base
|
|
|
|
// Determine architecture for downloads
|
|
arch := "amd64"
|
|
goArch := "x86_64"
|
|
if platform == "linux/arm64" {
|
|
arch = "arm64"
|
|
goArch = "aarch64"
|
|
}
|
|
|
|
switch toolName {
|
|
case "kustomize":
|
|
ctr = ctr.WithExec([]string{"sh", "-c",
|
|
fmt.Sprintf("wget -q -O - https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%%2Fv%s/kustomize_v%s_linux_%s.tar.gz | tar xz -C /usr/local/bin", toolDef.Version, toolDef.Version, arch)})
|
|
|
|
case "dagger":
|
|
ctr = ctr.WithExec([]string{"sh", "-c",
|
|
fmt.Sprintf("wget -q -O - https://github.com/dagger/dagger/releases/download/v%s/dagger_v%s_linux_%s.tar.gz | tar xz -C /usr/local/bin", toolDef.Version, toolDef.Version, arch)})
|
|
|
|
case "esphome":
|
|
ctr = ctr.WithExec([]string{"apk", "add", "python3", "py3-pip"}).
|
|
WithExec([]string{"pip3", "install", fmt.Sprintf("esphome==%s", toolDef.Version)})
|
|
|
|
case "zola":
|
|
ctr = ctr.WithExec([]string{"sh", "-c",
|
|
fmt.Sprintf("wget -q -O - https://github.com/getzola/zola/releases/download/v%s/zola-v%s-%s-unknown-linux-gnu.tar.gz | tar xz -C /usr/local/bin", toolDef.Version, toolDef.Version, goArch)})
|
|
|
|
case "yamllint":
|
|
ctr = ctr.WithExec([]string{"apk", "add", "python3", "py3-pip"}).
|
|
WithExec([]string{"pip3", "install", fmt.Sprintf("yamllint==%s", toolDef.Version)})
|
|
}
|
|
|
|
return ctr
|
|
}
|
|
|
|
// BuildAll builds all container images for multiple architectures
|
|
func (m *Containers) BuildAll(ctx context.Context,
|
|
// Configuration file
|
|
configFile *dagger.File,
|
|
// Registry to push images to
|
|
// +optional
|
|
registry string,
|
|
) error {
|
|
// Load configuration
|
|
config, err := m.loadConfig(ctx, configFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
platforms := []dagger.Platform{"linux/amd64", "linux/arm64"}
|
|
|
|
// Build and publish Ubuntu base
|
|
ubuntuBaseTag := "ubuntu-runner:24.04"
|
|
if registry != "" {
|
|
ubuntuBaseTag = fmt.Sprintf("%s/%s", registry, ubuntuBaseTag)
|
|
}
|
|
|
|
var ubuntuBaseVariants []*dagger.Container
|
|
for _, platform := range platforms {
|
|
ubuntuBase := m.buildUbuntuBase(ctx, config, platform)
|
|
ubuntuBaseVariants = append(ubuntuBaseVariants, ubuntuBase)
|
|
}
|
|
|
|
if _, err := dag.Container().Publish(ctx, ubuntuBaseTag, dagger.ContainerPublishOpts{
|
|
PlatformVariants: ubuntuBaseVariants,
|
|
}); err != nil {
|
|
return fmt.Errorf("failed to publish ubuntu base: %w", err)
|
|
}
|
|
fmt.Printf("Published (multi-arch): %s\n", ubuntuBaseTag)
|
|
|
|
// Build and publish tool-specific images
|
|
for toolName, toolDef := range config.Tools {
|
|
// Ubuntu variant
|
|
var ubuntuToolVariants []*dagger.Container
|
|
for _, platform := range platforms {
|
|
ubuntuBase := m.buildUbuntuBase(ctx, config, platform)
|
|
ubuntuTool := m.buildUbuntuTool(ctx, ubuntuBase, toolName, toolDef, platform)
|
|
ubuntuToolVariants = append(ubuntuToolVariants, ubuntuTool)
|
|
}
|
|
|
|
ubuntuTag := fmt.Sprintf("ubuntu-%s:%s", toolName, toolDef.Version)
|
|
if registry != "" {
|
|
ubuntuTag = fmt.Sprintf("%s/%s", registry, ubuntuTag)
|
|
}
|
|
if _, err := dag.Container().Publish(ctx, ubuntuTag, dagger.ContainerPublishOpts{
|
|
PlatformVariants: ubuntuToolVariants,
|
|
}); err != nil {
|
|
return fmt.Errorf("failed to publish ubuntu-%s: %w", toolName, err)
|
|
}
|
|
fmt.Printf("Published (multi-arch): %s\n", ubuntuTag)
|
|
|
|
// Chainguard variant
|
|
var chainguardToolVariants []*dagger.Container
|
|
for _, platform := range platforms {
|
|
chainguardBase := m.buildChainguardBase(ctx, config, platform)
|
|
chainguardTool := m.buildChainguardTool(ctx, chainguardBase, toolName, toolDef, platform)
|
|
chainguardToolVariants = append(chainguardToolVariants, chainguardTool)
|
|
}
|
|
|
|
chainguardTag := fmt.Sprintf("%s:%s", toolName, toolDef.Version)
|
|
if registry != "" {
|
|
chainguardTag = fmt.Sprintf("%s/%s", registry, chainguardTag)
|
|
}
|
|
if _, err := dag.Container().Publish(ctx, chainguardTag, dagger.ContainerPublishOpts{
|
|
PlatformVariants: chainguardToolVariants,
|
|
}); err != nil {
|
|
return fmt.Errorf("failed to publish %s: %w", toolName, err)
|
|
}
|
|
fmt.Printf("Published (multi-arch): %s\n", chainguardTag)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Build a single tool image
|
|
func (m *Containers) BuildTool(ctx context.Context,
|
|
// Tool name to build (e.g., "kustomize", "dagger")
|
|
tool string,
|
|
// Configuration file
|
|
configFile *dagger.File,
|
|
// Base image type: "ubuntu" or "chainguard"
|
|
// +optional
|
|
// +default="ubuntu"
|
|
base string,
|
|
// Platform to build for
|
|
// +optional
|
|
// +default="linux/amd64"
|
|
platform dagger.Platform,
|
|
) (*dagger.Container, error) {
|
|
// Load configuration
|
|
config, err := m.loadConfig(ctx, configFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
toolDef, ok := config.Tools[tool]
|
|
if !ok {
|
|
return nil, fmt.Errorf("tool %s not found in config", tool)
|
|
}
|
|
|
|
var baseImage *dagger.Container
|
|
if base == "ubuntu" {
|
|
baseImage = m.buildUbuntuBase(ctx, config, platform)
|
|
return m.buildUbuntuTool(ctx, baseImage, tool, toolDef, platform), nil
|
|
} else {
|
|
baseImage = m.buildChainguardBase(ctx, config, platform)
|
|
return m.buildChainguardTool(ctx, baseImage, tool, toolDef, platform), nil
|
|
}
|
|
}
|