diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index ba26e66..7f3f5aa 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -16,6 +16,25 @@ concurrency: cancel-in-progress: true jobs: + check-dockerimage: + name: Docker image + runs-on: depot-ubuntu-24.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/determinate-nix-action@d96678350ffd6a456235832eb11e1c491589b7bb # v3.21.8 + + - name: Cache Nix store + uses: DeterminateSystems/magic-nix-cache-action@908b263ff629f4cc17666315b7fd3ec127c6244d # v14 + + - name: Test image builds + run: nix develop --command make -C dockerimage test + env: + DAGGER_CLOUD_TOKEN: ${{ secrets.DAGGER_CLOUD_TOKEN }} + check-pr-title: name: Check PR Title runs-on: depot-ubuntu-24.04 diff --git a/README.md b/README.md index f7d5bef..83d0833 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ this includes Go tooling, various CLIs, checks, utilities, and AI tools. |--------|-------------| | [`github.com/papercomputeco/daggerverse/bucketupload`](./bucketupload) | S3-compat bucket uploading | | [`github.com/papercomputeco/daggerverse/checksum`](./checksum) | Recursively generate checksums for files in a directory | +| [`github.com/papercomputeco/daggerverse/dockerimage`](./dockerimage) | Build and publish multi-platform Dockerfile images | | [`github.com/papercomputeco/daggerverse/ghrelease`](./ghrelease) | Flatten and upload build artifacts to GitHub releases | | [`github.com/papercomputeco/daggerverse/golangcilint`](./golangcilint/) | Golang CI linting and checking | | [`github.com/papercomputeco/daggerverse/utils`](./utils) | Catch-all utilities (flatten build artifacts, etc.) | diff --git a/dockerimage/.gitattributes b/dockerimage/.gitattributes new file mode 100644 index 0000000..d3384f5 --- /dev/null +++ b/dockerimage/.gitattributes @@ -0,0 +1,3 @@ +/dagger.gen.go linguist-generated +/internal/dagger/** linguist-generated +/internal/telemetry/** linguist-generated diff --git a/dockerimage/.gitignore b/dockerimage/.gitignore new file mode 100644 index 0000000..3342715 --- /dev/null +++ b/dockerimage/.gitignore @@ -0,0 +1,4 @@ +/dagger.gen.go +/internal/dagger +/internal/telemetry +/.env diff --git a/dockerimage/Makefile b/dockerimage/Makefile new file mode 100644 index 0000000..7ed623e --- /dev/null +++ b/dockerimage/Makefile @@ -0,0 +1,11 @@ +PLATFORMS ?= linux/amd64 linux/arm64 + +.PHONY: test +test: + @for platform in $(PLATFORMS); do \ + dagger call \ + --source=./fixtures \ + with-build-arg --name=MESSAGE --value=expected \ + build --platform=$$platform \ + sync >/dev/null || exit 1; \ + done diff --git a/dockerimage/README.md b/dockerimage/README.md new file mode 100644 index 0000000..2374172 --- /dev/null +++ b/dockerimage/README.md @@ -0,0 +1,51 @@ +# github.com/papercomputeco/daggerverse/dockerimage + +Builds a Dockerfile locally or publishes a multi-platform OCI image. + +Publishing defaults to `linux/amd64` and `linux/arm64`. Registry authentication +is supplied by the caller's Dagger environment. + +Run the image fixtures for both platforms with `make test`. + +## Usage + +### Build locally + +```sh +dagger call \ + -m github.com/papercomputeco/daggerverse/dockerimage \ + --source=. \ + build \ + export-image --name=my-app:dev +``` + +Build a specific platform with a build argument, without loading it: + +```sh +dagger call \ + -m github.com/papercomputeco/daggerverse/dockerimage \ + --source=. \ + with-build-arg --name=LDFLAGS --value="-s -w" \ + build --platform=linux/arm64 \ + sync +``` + +Repeat `with-build-arg` to pass more than one argument. + +### Publish + +```sh +dagger call \ + -m github.com/papercomputeco/daggerverse/dockerimage \ + --source=. \ + publish \ + --repository=registry.example.com/team/my-app \ + --tags=v1.0.0 \ + --tags=latest +``` + +Use constructor options to select another Dockerfile or replace the default +publishing platforms. + +For release workflows, pin the module to a commit instead of floating on the +default branch. diff --git a/dockerimage/dagger.json b/dockerimage/dagger.json new file mode 100644 index 0000000..6e36f0d --- /dev/null +++ b/dockerimage/dagger.json @@ -0,0 +1,7 @@ +{ + "name": "dockerimage", + "engineVersion": "v0.21.7", + "sdk": { + "source": "go" + } +} diff --git a/dockerimage/fixtures/Dockerfile b/dockerimage/fixtures/Dockerfile new file mode 100644 index 0000000..a087406 --- /dev/null +++ b/dockerimage/fixtures/Dockerfile @@ -0,0 +1,4 @@ +FROM alpine:3.20 +ARG MESSAGE=default +RUN test "$MESSAGE" = expected +CMD ["cat", "/etc/alpine-release"] diff --git a/dockerimage/go.mod b/dockerimage/go.mod new file mode 100644 index 0000000..7d48b06 --- /dev/null +++ b/dockerimage/go.mod @@ -0,0 +1,53 @@ +module dagger/dockerimage + +go 1.26.4 + +require ( + github.com/Khan/genqlient v0.8.1 + github.com/dagger/otel-go v1.43.0 + github.com/dagger/querybuilder v0.0.0-20260402040506-574a5e81cb59 + github.com/vektah/gqlparser/v2 v2.5.32 + go.opentelemetry.io/otel v1.43.0 + go.opentelemetry.io/otel/trace v1.43.0 +) + +require ( + github.com/99designs/gqlgen v0.17.89 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // 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.28.0 // indirect + github.com/sosodev/duration v1.4.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 // indirect + go.opentelemetry.io/otel/log v0.17.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 + go.opentelemetry.io/otel/sdk/log v0.17.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.34.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/grpc v1.79.3 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 + +replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.16.0 + +replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.16.0 diff --git a/dockerimage/go.sum b/dockerimage/go.sum new file mode 100644 index 0000000..4bb39e6 --- /dev/null +++ b/dockerimage/go.sum @@ -0,0 +1,97 @@ +github.com/99designs/gqlgen v0.17.89 h1:KzEcxPiMgQoMw3m/E85atUEHyZyt0PbAflMia5Kw8z8= +github.com/99designs/gqlgen v0.17.89/go.mod h1:GFqruTVGB7ZTdrf1uzOagpXbY7DrEt1pIxnTdhIbWvQ= +github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= +github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= +github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= +github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dagger/otel-go v1.43.0 h1:AYCnAamWmxtSxigWPTgC+8EWqiWPcDZEegh8y05gdJ8= +github.com/dagger/otel-go v1.43.0/go.mod h1:83CTuXi70zcx1kaym5buqmb7RNzg1E9dEiQSFyLbLdU= +github.com/dagger/querybuilder v0.0.0-20260402040506-574a5e81cb59 h1:g6vfdGRyz6fAjfHz5FyYPZgHy8qcQ31fHrBl1iCOzxw= +github.com/dagger/querybuilder v0.0.0-20260402040506-574a5e81cb59/go.mod h1:jsdUJeYzcbyK1j/EqMGPrQgNYxl/Zfg06vvM9C/xXxs= +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.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= +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.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE= +github.com/sosodev/duration v1.4.0/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.32 h1:k9QPJd4sEDTL+qB4ncPLflqTJ3MmjB9SrVzJrawpFSc= +github.com/vektah/gqlparser/v2 v2.5.32/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.41.0 h1:VO3BL6OZXRQ1yQc8W6EVfJzINeJ35BkiHx4MYfoQf44= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.41.0/go.mod h1:qRDnJ2nv3CQXMK2HUd9K9VtvedsPAce3S+/4LZHjX/s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.41.0 h1:MMrOAN8H1FrvDyq9UJ4lu5/+ss49Qgfgb7Zpm0m8ABo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.41.0/go.mod h1:Na+2NNASJtF+uT4NxDe0G+NQb+bUgdPDfwxY/6JmS/c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0/go.mod h1:u3T6vz0gh/NVzgDgiwkgLxpsSF6PaPmo2il0apGJbls= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 h1:mq/Qcf28TWz719lE3/hMB4KkyDuLJIvgJnFGcd0kEUI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0/go.mod h1:yk5LXEYhsL2htyDNJbEq7fWzNEigeEdV5xBF/Y+kAv0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 h1:inYW9ZhgqiDqh6BioM7DVHHzEGVq76Db5897WLGZ5Go= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0/go.mod h1:Izur+Wt8gClgMJqO/cZ8wdeeMryJ/xxiOVgFSSfpDTY= +go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= +go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI= +go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4= +go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4= +go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +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.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +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-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/dockerimage/main.go b/dockerimage/main.go new file mode 100644 index 0000000..5633e65 --- /dev/null +++ b/dockerimage/main.go @@ -0,0 +1,125 @@ +// Build and publish multi-platform images from Dockerfiles. +package main + +import ( + "context" + "fmt" + + "dagger/dockerimage/internal/dagger" +) + +var defaultPlatforms = []dagger.Platform{ + "linux/amd64", + "linux/arm64", +} + +type buildArg struct { + Name string + Value string +} + +// Dockerimage builds and publishes a Dockerfile from a source directory. +type Dockerimage struct { + // +private + Source *dagger.Directory + + // +private + Dockerfile string + + // +private + BuildArgs []buildArg + + // +private + Platforms []dagger.Platform +} + +// New configures a Dockerfile image workflow. +func New( + // Build context. + // +defaultPath="/" + source *dagger.Directory, + + // Dockerfile path relative to the build context. + // +optional + // +default="Dockerfile" + dockerfile string, + + // Platforms included when publishing. + // +optional + platforms []dagger.Platform, +) *Dockerimage { + if len(platforms) == 0 { + platforms = defaultPlatforms + } + + return &Dockerimage{ + Source: source, + Dockerfile: dockerfile, + Platforms: platforms, + } +} + +// WithBuildArg adds a Docker build argument. +func (m *Dockerimage) WithBuildArg(name, value string) *Dockerimage { + m.BuildArgs = append(m.BuildArgs, buildArg{Name: name, Value: value}) + return m +} + +// Build builds the image for the local platform, or for platform when provided. +func (m *Dockerimage) Build( + // Target platform, for example linux/arm64. + // +optional + platform dagger.Platform, +) *dagger.Container { + buildArgs := make([]dagger.BuildArg, 0, len(m.BuildArgs)) + for _, arg := range m.BuildArgs { + buildArgs = append(buildArgs, dagger.BuildArg{Name: arg.Name, Value: arg.Value}) + } + + return m.Source.DockerBuild(dagger.DirectoryDockerBuildOpts{ + Dockerfile: m.Dockerfile, + BuildArgs: buildArgs, + Platform: platform, + }) +} + +// Publish builds all configured platform variants and publishes each tag. +func (m *Dockerimage) Publish( + ctx context.Context, + + // Complete untagged repository, for example registry.example.com/team/app. + repository string, + + // Tags to publish, for example ["v1.0.0", "latest"]. + tags []string, +) ([]string, error) { + if repository == "" { + return nil, fmt.Errorf("repository is required") + } + if len(tags) == 0 { + return nil, fmt.Errorf("at least one tag is required") + } + + variants := make([]*dagger.Container, 0, len(m.Platforms)) + for _, platform := range m.Platforms { + variants = append(variants, m.Build(platform)) + } + + published := make([]string, 0, len(tags)) + for _, tag := range tags { + if tag == "" { + return published, fmt.Errorf("tag must not be empty") + } + + ref := fmt.Sprintf("%s:%s", repository, tag) + address, err := dag.Container().Publish(ctx, ref, dagger.ContainerPublishOpts{ + PlatformVariants: variants, + }) + if err != nil { + return published, fmt.Errorf("failed to publish %s: %w", ref, err) + } + published = append(published, address) + } + + return published, nil +} diff --git a/flake.lock b/flake.lock index 0f00751..113d4cd 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1778107833, - "narHash": "sha256-q5XQep2mpgTPiWwuYB1+L2dsFeACT6sHx8J939iM+HE=", + "lastModified": 1781718180, + "narHash": "sha256-olV0eG2YQlocEIgYl/zoeXn9BYVog3oq13dwhQi3BXw=", "owner": "dagger", "repo": "nix", - "rev": "873cc22ba46b73d4a6c1aa6c102ef3aabc736496", + "rev": "c93d69f685b8a58eaa767eaaf189b0ac6f4f741a", "type": "github" }, "original": {