Initial commit

This commit is contained in:
Marc Plano-Lesay 2025-12-09 09:30:47 +11:00
commit 304e531813
Signed by: kernald
GPG key ID: 66A41B08CC62A6CF
12 changed files with 866 additions and 0 deletions

4
.dagger/.gitattributes vendored Normal file
View file

@ -0,0 +1,4 @@
/dagger.gen.go linguist-generated
/internal/dagger/** linguist-generated
/internal/querybuilder/** linguist-generated
/internal/telemetry/** linguist-generated

5
.dagger/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
/dagger.gen.go
/internal/dagger
/internal/querybuilder
/internal/telemetry
/.env

51
.dagger/go.mod Normal file
View file

@ -0,0 +1,51 @@
module dagger/containers
go 1.25.5
require (
github.com/99designs/gqlgen v0.17.81
github.com/Khan/genqlient v0.8.1
github.com/vektah/gqlparser/v2 v2.5.30
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
go.opentelemetry.io/otel/log v0.14.0
go.opentelemetry.io/otel/metric v1.38.0
go.opentelemetry.io/otel/sdk v1.38.0
go.opentelemetry.io/otel/sdk/log v0.14.0
go.opentelemetry.io/otel/sdk/metric v1.38.0
go.opentelemetry.io/otel/trace v1.38.0
go.opentelemetry.io/proto/otlp v1.8.0
golang.org/x/sync v0.17.0
google.golang.org/grpc v1.76.0
)
require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/sosodev/duration v1.3.1 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.29.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0
replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0
replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.14.0
replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.14.0

90
.dagger/go.sum Normal file
View file

@ -0,0 +1,90 @@
github.com/99designs/gqlgen v0.17.81 h1:kCkN/xVyRb5rEQpuwOHRTYq83i0IuTQg9vdIiwEerTs=
github.com/99designs/gqlgen v0.17.81/go.mod h1:vgNcZlLwemsUhYim4dC1pvFP5FX0pr2Y+uYUoHFb1ig=
github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs=
github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE=
github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg=
go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM=
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

315
.dagger/main.go Normal file
View file

@ -0,0 +1,315 @@
// 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
}
}