mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-18 14:56:17 -05:00
feat: Implement using of buildx for docker_image (#717)
* feat: Implement using of buildx for docker_image * fix: secrets in docker_image * fix: fmt issues * test: Add test for build_log_file * chore: Disable flaky test and update goreleaser to exclude not possible build * feat: Small refactoring and documentation improvements
This commit is contained in:
parent
46a6a72595
commit
b9181a75e4
13 changed files with 1492 additions and 610 deletions
|
|
@ -24,6 +24,8 @@ builds:
|
|||
ignore:
|
||||
- goos: darwin
|
||||
goarch: "386"
|
||||
- goos: openbsd
|
||||
goarch: arm
|
||||
binary: "{{ .ProjectName }}_v{{ .Version }}"
|
||||
archives:
|
||||
- format: zip
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ resource "docker_image" "ubuntu" {
|
|||
|
||||
### Build
|
||||
|
||||
You can also use the resource to build an image.
|
||||
You can also use the resource to build an image. By default the build block is using the old legacy docker build. In order to use a buildx builder, please read the section below
|
||||
|
||||
-> **Note**: The default timeout for the building is 20 minutes. If you need to increase this, you can use [operation timeouts](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts).
|
||||
|
||||
|
|
@ -81,6 +81,14 @@ resource "docker_image" "zoo" {
|
|||
}
|
||||
```
|
||||
|
||||
### Buildx
|
||||
|
||||
-> **Note**: The buildx feature is currently in preview and may have some quirks. Known issues: Setting `ulimits` will not work.
|
||||
|
||||
The `build` argument uses the legacy docker builder. If you want to use a buildx builder, you need to set the `builder` argument. For the default buildx builder, you can set the `builder` argument to `default`. For a custom buildx builder, you can set the `builder` argument to the name of the builder. You can find the name of the builder by running `docker buildx ls`.
|
||||
|
||||
The single platform build result is automatically loaded to `docker images`.
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
|
|
@ -116,6 +124,8 @@ Optional:
|
|||
- `auth_config` (Block List) The configuration for the authentication (see [below for nested schema](#nestedblock--build--auth_config))
|
||||
- `build_args` (Map of String) Pairs for build-time variables in the form of `ENDPOINT : "https://example.com"`
|
||||
- `build_id` (String) BuildID is an optional identifier that can be passed together with the build request. The same identifier can be used to gracefully cancel the build with the cancel request.
|
||||
- `build_log_file` (String) Path to a file where the buildx log are written to. Only available when `builder` is set. If not set, no logs are available. The path is taken as is, so make sure to use a path that is available.
|
||||
- `builder` (String) Set the name of the buildx builder to use. If not set or empty, the legacy builder will be used.
|
||||
- `cache_from` (List of String) Images to consider as cache sources
|
||||
- `cgroup_parent` (String) Optional parent cgroup for the container
|
||||
- `cpu_period` (Number) The length of a CPU period in microseconds
|
||||
|
|
@ -135,9 +145,9 @@ Optional:
|
|||
- `no_cache` (Boolean) Do not use the cache when building the image
|
||||
- `platform` (String) Set platform if server is multi-platform capable
|
||||
- `pull_parent` (Boolean) Attempt to pull the image even if an older image exists locally
|
||||
- `remote_context` (String) A Git repository URI or HTTP/HTTPS context URI
|
||||
- `remote_context` (String) A Git repository URI or HTTP/HTTPS context URI. Will be ignored if `builder` is set.
|
||||
- `remove` (Boolean) Remove intermediate containers after a successful build. Defaults to `true`.
|
||||
- `secrets` (Block List) Set build-time secrets (see [below for nested schema](#nestedblock--build--secrets))
|
||||
- `secrets` (Block List) Set build-time secrets. Only available when you use a buildx builder. (see [below for nested schema](#nestedblock--build--secrets))
|
||||
- `security_opt` (List of String) The security options
|
||||
- `session_id` (String) Set an ID for the build session
|
||||
- `shm_size` (Number) Size of /dev/shm in bytes. The size must be greater than 0
|
||||
|
|
@ -176,7 +186,7 @@ Required:
|
|||
Optional:
|
||||
|
||||
- `env` (String) Environment variable source of the secret
|
||||
- `src` (String) File source of the secret
|
||||
- `src` (String) File source of the secret. Takes precedence over `env`
|
||||
|
||||
|
||||
<a id="nestedblock--build--ulimit"></a>
|
||||
|
|
|
|||
155
go.mod
155
go.mod
|
|
@ -6,19 +6,25 @@ toolchain go1.24.2
|
|||
|
||||
require (
|
||||
github.com/client9/misspell v0.3.4
|
||||
github.com/containerd/console v1.0.4
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/buildx v0.23.0
|
||||
github.com/docker/cli v28.0.4+incompatible
|
||||
github.com/docker/distribution v2.8.1+incompatible
|
||||
github.com/docker/docker v28.0.4+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/golangci/golangci-lint v1.64.8
|
||||
github.com/hashicorp/go-cty v1.5.0
|
||||
github.com/hashicorp/terraform-plugin-docs v0.14.1
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1
|
||||
github.com/hashicorp/terraform-plugin-log v0.9.0
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0
|
||||
github.com/katbyte/terrafmt v0.5.4
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/moby/buildkit v0.10.6
|
||||
github.com/moby/buildkit v0.21.0
|
||||
github.com/morikuni/aec v1.0.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
google.golang.org/protobuf v1.36.5
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -26,10 +32,11 @@ require (
|
|||
4d63.com/gochecknoglobals v0.2.2 // indirect
|
||||
github.com/4meepo/tagalign v1.4.2 // indirect
|
||||
github.com/Abirdcfly/dupword v0.1.3 // indirect
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect
|
||||
github.com/Antonboom/errname v1.0.0 // indirect
|
||||
github.com/Antonboom/nilnil v1.0.1 // indirect
|
||||
github.com/Antonboom/testifylint v1.5.2 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
|
||||
github.com/Crocmagnon/fatcontext v0.7.1 // indirect
|
||||
github.com/Djarvur/go-err113 v0.1.0 // indirect
|
||||
|
|
@ -37,7 +44,7 @@ require (
|
|||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
|
|
@ -50,6 +57,19 @@ require (
|
|||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.6.0 // indirect
|
||||
github.com/ashanbrown/makezero v1.2.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
|
||||
github.com/aws/smithy-go v1.20.3 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.3 // indirect
|
||||
|
|
@ -61,31 +81,46 @@ require (
|
|||
github.com/butuzov/mirror v1.3.0 // indirect
|
||||
github.com/catenacyber/perfsprint v0.8.2 // indirect
|
||||
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||
github.com/chavacava/garif v0.1.0 // indirect
|
||||
github.com/ckaznocha/intrange v0.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/containerd/containerd v1.6.3-0.20220401172941-5ff8fce1fcc6 // indirect
|
||||
github.com/containerd/containerd/api v1.8.0 // indirect
|
||||
github.com/containerd/containerd/v2 v2.0.4 // indirect
|
||||
github.com/containerd/continuity v0.4.5 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/typeurl v1.0.2 // indirect
|
||||
github.com/containerd/platforms v1.0.0-rc.1 // indirect
|
||||
github.com/containerd/ttrpc v1.2.7 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/curioswitch/go-reassign v0.3.0 // indirect
|
||||
github.com/daixiang0/gci v0.13.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/denis-tingaikin/go-header v0.5.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/ettle/strcase v0.2.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/ghostiam/protogetter v0.3.9 // indirect
|
||||
github.com/go-critic/go-critic v0.12.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.2.0 // indirect
|
||||
|
|
@ -106,15 +141,20 @@ require (
|
|||
github.com/golangci/plugin-module-register v0.1.1 // indirect
|
||||
github.com/golangci/revgrep v0.8.0 // indirect
|
||||
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.1.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||
github.com/gostaticanalysis/comment v1.5.0 // indirect
|
||||
github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
|
||||
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
|
|
@ -133,24 +173,26 @@ require (
|
|||
github.com/hashicorp/terraform-exec v0.22.0 // indirect
|
||||
github.com/hashicorp/terraform-json v0.24.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
|
||||
github.com/hashicorp/terraform-registry-address v0.2.4 // indirect
|
||||
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/in-toto/in-toto-golang v0.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jgautheron/goconst v1.7.1 // indirect
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
||||
github.com/jjti/go-spancheck v0.6.4 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/julz/importas v0.2.0 // indirect
|
||||
github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect
|
||||
github.com/katbyte/andreyvit-diff v0.0.1 // indirect
|
||||
github.com/katbyte/sergi-go-diff v1.1.1 // indirect
|
||||
github.com/kisielk/errcheck v1.9.0 // indirect
|
||||
github.com/kkHAIKE/contextcheck v1.1.6 // indirect
|
||||
github.com/klauspost/compress v1.15.1 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/kulti/thelper v0.6.3 // indirect
|
||||
github.com/kunwardeep/paralleltest v1.0.10 // indirect
|
||||
github.com/lasiar/canonicalheader v1.1.2 // indirect
|
||||
|
|
@ -162,29 +204,37 @@ require (
|
|||
github.com/leonklingele/grouper v1.1.2 // indirect
|
||||
github.com/macabu/inamedparam v0.1.3 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/maratori/testableexamples v1.0.0 // indirect
|
||||
github.com/maratori/testpackage v1.1.1 // indirect
|
||||
github.com/matoous/godox v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mgechev/revive v1.7.0 // indirect
|
||||
github.com/mitchellh/cli v1.1.5 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/spdystream v0.4.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.7.2 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/symlink v0.2.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.1 // indirect
|
||||
github.com/moby/sys/symlink v0.3.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/moricho/tparallel v0.3.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||
github.com/nishanths/exhaustive v0.12.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
|
|
@ -192,16 +242,17 @@ require (
|
|||
github.com/oklog/run v1.0.0 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/polyfloyd/go-errorlint v1.7.1 // indirect
|
||||
github.com/posener/complete v1.2.3 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/prometheus/client_golang v1.20.5 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
|
|
@ -217,9 +268,11 @@ require (
|
|||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.22.2 // indirect
|
||||
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
|
||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||
github.com/sivchari/tenv v1.12.1 // indirect
|
||||
github.com/sonatard/noctx v0.1.0 // indirect
|
||||
|
|
@ -241,7 +294,11 @@ require (
|
|||
github.com/timonwong/loggercheck v0.10.1 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250410151801-5b74a7ad7583 // indirect
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
|
||||
github.com/ultraware/funlen v0.2.0 // indirect
|
||||
github.com/ultraware/whitespace v0.2.0 // indirect
|
||||
github.com/uudashr/gocognit v1.2.0 // indirect
|
||||
|
|
@ -249,6 +306,7 @@ require (
|
|||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xen0n/gosmopolitan v1.2.2 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||
|
|
@ -260,32 +318,51 @@ require (
|
|||
go-simpler.org/sloglint v0.9.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
|
||||
go.opentelemetry.io/otel v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/goleak v1.1.12 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
golang.org/x/tools v0.31.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/oauth2 v0.26.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.32.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect
|
||||
google.golang.org/grpc v1.70.0 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
google.golang.org/grpc v1.71.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.4.0 // indirect
|
||||
honnef.co/go/tools v0.6.1 // indirect
|
||||
k8s.io/api v0.31.2 // indirect
|
||||
k8s.io/apimachinery v0.31.2 // indirect
|
||||
k8s.io/client-go v0.31.2 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
||||
mvdan.cc/gofumpt v0.7.0 // indirect
|
||||
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
|
|
|||
898
internal/provider/docker_buildx_build.go
Normal file
898
internal/provider/docker_buildx_build.go
Normal file
|
|
@ -0,0 +1,898 @@
|
|||
package provider
|
||||
|
||||
// taken from https://github.com/docker/buildx/blob/master/commands/build.go and heavily modified
|
||||
// to fit the needs of the provider
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/controller"
|
||||
cbuild "github.com/docker/buildx/controller/build"
|
||||
"github.com/docker/buildx/controller/control"
|
||||
controllererrors "github.com/docker/buildx/controller/errdefs"
|
||||
controllerapi "github.com/docker/buildx/controller/pb"
|
||||
"github.com/docker/buildx/monitor"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/desktop"
|
||||
"github.com/docker/buildx/util/ioset"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
dockeropts "github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/atomicwriter"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||
"github.com/moby/buildkit/frontend/subrequests"
|
||||
"github.com/moby/buildkit/frontend/subrequests/lint"
|
||||
"github.com/moby/buildkit/frontend/subrequests/outline"
|
||||
"github.com/moby/buildkit/frontend/subrequests/targets"
|
||||
"github.com/moby/buildkit/solver/errdefs"
|
||||
solverpb "github.com/moby/buildkit/solver/pb"
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
// import drivers otherwise factories are empty
|
||||
// for --driver output flag usage
|
||||
_ "github.com/docker/buildx/driver/docker"
|
||||
_ "github.com/docker/buildx/driver/docker-container"
|
||||
_ "github.com/docker/buildx/driver/kubernetes"
|
||||
_ "github.com/docker/buildx/driver/remote"
|
||||
)
|
||||
|
||||
type buildOptions struct {
|
||||
allow []string
|
||||
annotations []string
|
||||
buildArgs []string
|
||||
cacheFrom []string
|
||||
cacheTo []string
|
||||
cgroupParent string
|
||||
contextPath string
|
||||
contexts []string
|
||||
dockerfileName string
|
||||
extraHosts []string
|
||||
imageIDFile string
|
||||
labels []string
|
||||
networkMode string
|
||||
noCacheFilter []string
|
||||
outputs []string
|
||||
platforms []string
|
||||
callFunc string
|
||||
secrets []string
|
||||
shmSize dockeropts.MemBytes
|
||||
ssh []string
|
||||
tags []string
|
||||
target string
|
||||
ulimits *dockeropts.UlimitOpt
|
||||
|
||||
attests []string
|
||||
sbom string
|
||||
provenance string
|
||||
|
||||
quiet bool
|
||||
|
||||
builder string
|
||||
metadataFile string
|
||||
noCache bool
|
||||
pull bool
|
||||
exportPush bool
|
||||
exportLoad bool
|
||||
|
||||
control.ControlOptions
|
||||
|
||||
invokeConfig *invokeConfig
|
||||
}
|
||||
|
||||
func (o *buildOptions) toControllerOptions() (*controllerapi.BuildOptions, error) {
|
||||
var err error
|
||||
|
||||
buildArgs, err := listToMap(o.buildArgs, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
labels, err := listToMap(o.labels, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts := controllerapi.BuildOptions{
|
||||
Allow: o.allow,
|
||||
Annotations: o.annotations,
|
||||
BuildArgs: buildArgs,
|
||||
CgroupParent: o.cgroupParent,
|
||||
ContextPath: o.contextPath,
|
||||
DockerfileName: o.dockerfileName,
|
||||
ExtraHosts: o.extraHosts,
|
||||
Labels: labels,
|
||||
NetworkMode: o.networkMode,
|
||||
NoCacheFilter: o.noCacheFilter,
|
||||
Platforms: o.platforms,
|
||||
ShmSize: int64(o.shmSize),
|
||||
Tags: o.tags,
|
||||
Target: o.target,
|
||||
Ulimits: dockerUlimitToControllerUlimit(o.ulimits),
|
||||
Builder: o.builder,
|
||||
NoCache: o.noCache,
|
||||
Pull: o.pull,
|
||||
ExportPush: o.exportPush,
|
||||
ExportLoad: o.exportLoad,
|
||||
}
|
||||
|
||||
// TODO: extract env var parsing to a method easily usable by library consumers
|
||||
if v := os.Getenv("SOURCE_DATE_EPOCH"); v != "" {
|
||||
if _, ok := opts.BuildArgs["SOURCE_DATE_EPOCH"]; !ok {
|
||||
opts.BuildArgs["SOURCE_DATE_EPOCH"] = v
|
||||
}
|
||||
}
|
||||
|
||||
opts.SourcePolicy, err = build.ReadSourcePolicy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inAttests := slices.Clone(o.attests)
|
||||
if o.provenance != "" {
|
||||
inAttests = append(inAttests, buildflags.CanonicalizeAttest("provenance", o.provenance))
|
||||
}
|
||||
if o.sbom != "" {
|
||||
inAttests = append(inAttests, buildflags.CanonicalizeAttest("sbom", o.sbom))
|
||||
}
|
||||
opts.Attests, err = buildflags.ParseAttests(inAttests)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts.NamedContexts, err = buildflags.ParseContextNames(o.contexts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts.Exports, err = buildflags.ParseExports(o.outputs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, e := range opts.Exports {
|
||||
if (e.Type == client.ExporterLocal || e.Type == client.ExporterTar) && o.imageIDFile != "" {
|
||||
return nil, errors.Errorf("local and tar exporters are incompatible with image ID file")
|
||||
}
|
||||
}
|
||||
|
||||
cacheFrom, err := buildflags.ParseCacheEntry(o.cacheFrom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.CacheFrom = cacheFrom.ToPB()
|
||||
|
||||
cacheTo, err := buildflags.ParseCacheEntry(o.cacheTo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.CacheTo = cacheTo.ToPB()
|
||||
|
||||
opts.Secrets, err = buildflags.ParseSecretSpecs(o.secrets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.SSH, err = buildflags.ParseSSHSpecs(o.ssh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts.CallFunc, err = buildflags.ParseCallFunc(o.callFunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prm := confutil.MetadataProvenance()
|
||||
if opts.CallFunc != nil || len(o.metadataFile) == 0 {
|
||||
prm = confutil.MetadataProvenanceModeDisabled
|
||||
}
|
||||
opts.ProvenanceResponseMode = string(prm)
|
||||
|
||||
return &opts, nil
|
||||
}
|
||||
|
||||
func mapBuildAttributesToBuildOptions(buildAttributes map[string]interface{}, imageName string) (buildOptions, error) {
|
||||
options := buildOptions{}
|
||||
if dockerfile, ok := buildAttributes["dockerfile"].(string); ok {
|
||||
options.dockerfileName = dockerfile
|
||||
}
|
||||
|
||||
options.contextPath = buildAttributes["context"].(string)
|
||||
options.exportLoad = true
|
||||
|
||||
options.tags = append(options.tags, imageName)
|
||||
for _, t := range buildAttributes["tag"].([]interface{}) {
|
||||
options.tags = append(options.tags, t.(string))
|
||||
}
|
||||
|
||||
if builder, ok := buildAttributes["builder"].(string); ok {
|
||||
options.builder = builder
|
||||
}
|
||||
|
||||
if remove, ok := buildAttributes["remove"].(bool); ok {
|
||||
options.noCache = !remove
|
||||
}
|
||||
|
||||
if secrets, ok := buildAttributes["secrets"].([]interface{}); ok {
|
||||
for _, secret := range secrets {
|
||||
if secretMap, ok := secret.(map[string]interface{}); ok {
|
||||
// Construct the secret string in the format [type=env,]id=<ID>[,env=<VARIABLE>]
|
||||
secretStr := ""
|
||||
|
||||
id, _ := secretMap["id"].(string)
|
||||
if env, ok := secretMap["env"].(string); ok && env != "" {
|
||||
secretStr += fmt.Sprintf("type=env,id=%s,env=%s", id, env)
|
||||
}
|
||||
|
||||
if src, ok := secretMap["src"].(string); ok && src != "" {
|
||||
secretStr += fmt.Sprintf("type=file,id=%s,src=%s", id, src)
|
||||
}
|
||||
|
||||
options.secrets = append(options.secrets, secretStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if labels, ok := buildAttributes["label"].(map[string]interface{}); ok {
|
||||
for key, value := range labels {
|
||||
if valueStr, ok := value.(string); ok {
|
||||
options.labels = append(options.labels, fmt.Sprintf("%s=%s", key, valueStr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if suppressOutput, ok := buildAttributes["suppress_output"].(bool); ok {
|
||||
options.quiet = suppressOutput
|
||||
}
|
||||
|
||||
if noCache, ok := buildAttributes["no_cache"].(bool); ok {
|
||||
options.noCache = noCache
|
||||
}
|
||||
|
||||
if pullParent, ok := buildAttributes["pull_parent"].(bool); ok {
|
||||
options.pull = pullParent
|
||||
}
|
||||
|
||||
if isolation, ok := buildAttributes["isolation"].(string); ok {
|
||||
options.networkMode = isolation
|
||||
}
|
||||
|
||||
if cpuSetCpus, ok := buildAttributes["cpu_set_cpus"].(string); ok {
|
||||
options.buildArgs = append(options.buildArgs, fmt.Sprintf("cpusetcpus=%s", cpuSetCpus))
|
||||
}
|
||||
|
||||
if cpuSetMems, ok := buildAttributes["cpu_set_mems"].(string); ok {
|
||||
options.buildArgs = append(options.buildArgs, fmt.Sprintf("cpusetmems=%s", cpuSetMems))
|
||||
}
|
||||
|
||||
if cpuShares, ok := buildAttributes["cpu_shares"].(int); ok {
|
||||
options.buildArgs = append(options.buildArgs, fmt.Sprintf("cpushares=%d", cpuShares))
|
||||
}
|
||||
|
||||
if cpuQuota, ok := buildAttributes["cpu_quota"].(int); ok {
|
||||
options.buildArgs = append(options.buildArgs, fmt.Sprintf("cpuquota=%d", cpuQuota))
|
||||
}
|
||||
|
||||
if cpuPeriod, ok := buildAttributes["cpu_period"].(int); ok {
|
||||
options.buildArgs = append(options.buildArgs, fmt.Sprintf("cpuperiod=%d", cpuPeriod))
|
||||
}
|
||||
|
||||
if memory, ok := buildAttributes["memory"].(int); ok {
|
||||
options.buildArgs = append(options.buildArgs, fmt.Sprintf("memory=%d", memory))
|
||||
}
|
||||
|
||||
if memorySwap, ok := buildAttributes["memory_swap"].(int); ok {
|
||||
options.buildArgs = append(options.buildArgs, fmt.Sprintf("memoryswap=%d", memorySwap))
|
||||
}
|
||||
|
||||
if cgroupParent, ok := buildAttributes["cgroup_parent"].(string); ok {
|
||||
options.cgroupParent = cgroupParent
|
||||
}
|
||||
|
||||
if networkMode, ok := buildAttributes["network_mode"].(string); ok {
|
||||
options.networkMode = networkMode
|
||||
}
|
||||
|
||||
if shmSize, ok := buildAttributes["shm_size"].(int); ok {
|
||||
options.shmSize = dockeropts.MemBytes(shmSize)
|
||||
}
|
||||
|
||||
options.ulimits = dockeropts.NewUlimitOpt(nil)
|
||||
if ulimits, ok := buildAttributes["ulimit"].([]interface{}); ok {
|
||||
ulimitOpt := &dockeropts.UlimitOpt{}
|
||||
for _, ulimit := range ulimits {
|
||||
if ulimitMap, ok := ulimit.(map[string]interface{}); ok {
|
||||
name, _ := ulimitMap["name"].(string)
|
||||
hard, _ := ulimitMap["hard"].(int)
|
||||
soft, _ := ulimitMap["soft"].(int)
|
||||
ulimitOpt.Set(fmt.Sprintf("%s=%d:%d", name, soft, hard)) // nolint:errcheck
|
||||
}
|
||||
}
|
||||
options.ulimits = ulimitOpt
|
||||
}
|
||||
|
||||
if buildArgs, ok := buildAttributes["build_args"].(map[string]interface{}); ok {
|
||||
for key, value := range buildArgs {
|
||||
if valueStr, ok := value.(string); ok {
|
||||
options.buildArgs = append(options.buildArgs, fmt.Sprintf("%s=%s", key, valueStr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if extraHosts, ok := buildAttributes["extra_hosts"].([]interface{}); ok {
|
||||
for _, host := range extraHosts {
|
||||
if hostStr, ok := host.(string); ok {
|
||||
options.extraHosts = append(options.extraHosts, hostStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if target, ok := buildAttributes["target"].(string); ok {
|
||||
options.target = target
|
||||
}
|
||||
|
||||
if platform, ok := buildAttributes["platform"].(string); ok {
|
||||
options.platforms = append(options.platforms, platform)
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func canUseBuildx(ctx context.Context, client *dockerclient.Client) (bool, error) {
|
||||
var buildKitDisabled, useBuilder bool
|
||||
|
||||
// check DOCKER_BUILDKIT env var is not empty
|
||||
// if it is assume we want to use the builder component
|
||||
if v := os.Getenv("DOCKER_BUILDKIT"); v != "" {
|
||||
enabled, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("DOCKER_BUILDKIT environment variable expects boolean value: %w", err)
|
||||
}
|
||||
if !enabled {
|
||||
buildKitDisabled = true
|
||||
} else {
|
||||
useBuilder = true
|
||||
}
|
||||
}
|
||||
si, _ := client.Ping(ctx)
|
||||
|
||||
if !useBuilder {
|
||||
if si.BuilderVersion != types.BuilderBuildKit && si.OSType == "windows" {
|
||||
// The daemon didn't advertise BuildKit as the preferred builder,
|
||||
// so use the legacy builder, which is still the default for
|
||||
// Windows / WCOW.
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if buildKitDisabled {
|
||||
// When using a Linux daemon, print a warning that the legacy builder
|
||||
// is deprecated. For Windows / WCOW, BuildKit is still experimental,
|
||||
// so we don't print this warning, even if the daemon advertised that
|
||||
// it supports BuildKit.
|
||||
if si.OSType != "windows" {
|
||||
tflog.Warn(ctx, `DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
|
||||
BuildKit is currently disabled; enable it by removing the DOCKER_BUILDKIT=0
|
||||
environment-variable.`)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions, buildLogFile string) (err error) {
|
||||
|
||||
if buildLogFile == "" {
|
||||
buildLogFile = os.DevNull
|
||||
}
|
||||
|
||||
logFile, err := os.OpenFile(buildLogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open build log file: %w", err)
|
||||
}
|
||||
defer logFile.Close() // nolint:errcheck
|
||||
|
||||
logger := log.New(logFile, "", log.LstdFlags)
|
||||
|
||||
opts, err := options.toControllerOptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Avoid leaving a stale file if we eventually fail
|
||||
if options.imageIDFile != "" {
|
||||
if err := os.Remove(options.imageIDFile); err != nil && !os.IsNotExist(err) {
|
||||
return errors.Wrap(err, "removing image ID file")
|
||||
}
|
||||
}
|
||||
|
||||
contextPathHash := options.contextPath
|
||||
if absContextPath, err := filepath.Abs(contextPathHash); err == nil {
|
||||
contextPathHash = absContextPath
|
||||
}
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(options.builder),
|
||||
builder.WithContextPathHash(contextPathHash),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = b.LoadNodes(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var term bool
|
||||
if _, err := console.ConsoleFromFile(logFile); err == nil {
|
||||
term = true
|
||||
}
|
||||
|
||||
ctx2, cancel := context.WithCancelCause(context.TODO())
|
||||
defer func() { cancel(errors.WithStack(context.Canceled)) }()
|
||||
progressMode := progressui.PlainMode
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var printer *progress.Printer
|
||||
printer, err = progress.NewPrinter(ctx2, logFile, progressMode,
|
||||
progress.WithDesc(
|
||||
fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver),
|
||||
fmt.Sprintf("%s:%s", b.Driver, b.Name),
|
||||
),
|
||||
progress.WithOnClose(func() {
|
||||
printWarnings(logFile, printer.Warnings(), progressMode)
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Printf("error creating progress printer: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var resp *client.SolveResponse
|
||||
var inputs *build.Inputs
|
||||
var retErr error
|
||||
if confutil.IsExperimental() {
|
||||
resp, inputs, retErr = runControllerBuild(ctx, dockerCli, opts, options, printer)
|
||||
} else {
|
||||
resp, inputs, retErr = runBasicBuild(ctx, dockerCli, opts, printer)
|
||||
}
|
||||
|
||||
if err := printer.Wait(); retErr == nil {
|
||||
retErr = err
|
||||
}
|
||||
|
||||
if retErr != nil {
|
||||
return retErr
|
||||
}
|
||||
|
||||
desktop.PrintBuildDetails(logFile, printer.BuildRefs(), term)
|
||||
|
||||
if options.imageIDFile != "" {
|
||||
if err := os.WriteFile(options.imageIDFile, []byte(getImageID(resp.ExporterResponse)), 0644); err != nil {
|
||||
return errors.Wrap(err, "writing image ID file")
|
||||
}
|
||||
}
|
||||
if options.metadataFile != "" {
|
||||
dt := decodeExporterResponse(resp.ExporterResponse)
|
||||
if opts.CallFunc == nil {
|
||||
if warnings := printer.Warnings(); len(warnings) > 0 && confutil.MetadataWarningsEnabled() {
|
||||
dt["buildx.build.warnings"] = warnings
|
||||
}
|
||||
}
|
||||
if err := writeMetadataFile(options.metadataFile, dt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if opts.CallFunc != nil {
|
||||
if exitcode, err := printResult(dockerCli.Out(), opts.CallFunc, resp.ExporterResponse, options.target, inputs); err != nil {
|
||||
return err
|
||||
} else if exitcode != 0 {
|
||||
os.Exit(exitcode)
|
||||
}
|
||||
}
|
||||
if v, ok := resp.ExporterResponse["frontend.result.inlinemessage"]; ok {
|
||||
fmt.Fprintf(dockerCli.Out(), "\n%s\n", v) // nolint:errcheck
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeMetadataFile(filename string, dt any) error {
|
||||
b, err := json.MarshalIndent(dt, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return atomicwriter.WriteFile(filename, b, 0644)
|
||||
}
|
||||
|
||||
// getImageID returns the image ID - the digest of the image config
|
||||
func getImageID(resp map[string]string) string {
|
||||
dgst := resp[exptypes.ExporterImageDigestKey]
|
||||
if v, ok := resp[exptypes.ExporterImageConfigDigestKey]; ok {
|
||||
dgst = v
|
||||
}
|
||||
return dgst
|
||||
}
|
||||
|
||||
func runBasicBuild(ctx context.Context, dockerCli command.Cli, opts *controllerapi.BuildOptions, printer *progress.Printer) (*client.SolveResponse, *build.Inputs, error) {
|
||||
resp, res, dfmap, err := cbuild.RunBuild(ctx, dockerCli, opts, dockerCli.In(), printer, false)
|
||||
if res != nil {
|
||||
res.Done()
|
||||
}
|
||||
return resp, dfmap, err
|
||||
}
|
||||
|
||||
func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *controllerapi.BuildOptions, options buildOptions, printer *progress.Printer) (*client.SolveResponse, *build.Inputs, error) {
|
||||
if options.invokeConfig != nil && (options.dockerfileName == "-" || options.contextPath == "-") {
|
||||
// stdin must be usable for monitor
|
||||
return nil, nil, errors.Errorf("Dockerfile or context from stdin is not supported with invoke")
|
||||
}
|
||||
c, err := controller.NewController(ctx, options.ControlOptions, dockerCli, printer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := c.Close(); err != nil {
|
||||
logrus.Warnf("failed to close server connection %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// NOTE: buildx server has the current working directory different from the client
|
||||
// so we need to resolve paths to abosolute ones in the client.
|
||||
opts, err = controllerapi.ResolveOptionPaths(opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var ref string
|
||||
var retErr error
|
||||
var resp *client.SolveResponse
|
||||
var inputs *build.Inputs
|
||||
|
||||
var f *ioset.SingleForwarder
|
||||
var pr io.ReadCloser
|
||||
var pw io.WriteCloser
|
||||
if options.invokeConfig == nil {
|
||||
pr = dockerCli.In()
|
||||
} else {
|
||||
f = ioset.NewSingleForwarder()
|
||||
f.SetReader(dockerCli.In())
|
||||
pr, pw = io.Pipe()
|
||||
f.SetWriter(pw, func() io.WriteCloser {
|
||||
pw.Close() // nolint:errcheck
|
||||
logrus.Debug("propagating stdin close")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
ref, resp, inputs, err = c.Build(ctx, opts, pr, printer)
|
||||
if err != nil {
|
||||
var be *controllererrors.BuildError
|
||||
if errors.As(err, &be) {
|
||||
ref = be.SessionID
|
||||
retErr = err
|
||||
// We can proceed to monitor
|
||||
} else {
|
||||
return nil, nil, errors.Wrapf(err, "failed to build")
|
||||
}
|
||||
}
|
||||
|
||||
if options.invokeConfig != nil {
|
||||
if err := pw.Close(); err != nil {
|
||||
logrus.Debug("failed to close stdin pipe writer")
|
||||
}
|
||||
if err := pr.Close(); err != nil {
|
||||
logrus.Debug("failed to close stdin pipe reader")
|
||||
}
|
||||
}
|
||||
|
||||
if options.invokeConfig != nil && options.invokeConfig.needsDebug(retErr) {
|
||||
// Print errors before launching monitor
|
||||
if err := printError(retErr, printer); err != nil {
|
||||
logrus.Warnf("failed to print error information: %v", err)
|
||||
}
|
||||
|
||||
pr2, pw2 := io.Pipe()
|
||||
f.SetWriter(pw2, func() io.WriteCloser {
|
||||
pw2.Close() // nolint:errcheck
|
||||
return nil
|
||||
})
|
||||
monitorBuildResult, err := options.invokeConfig.runDebug(ctx, ref, opts, c, pr2, os.Stdout, os.Stderr, printer)
|
||||
if err := pw2.Close(); err != nil {
|
||||
logrus.Debug("failed to close monitor stdin pipe reader")
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to run monitor: %v", err)
|
||||
}
|
||||
if monitorBuildResult != nil {
|
||||
// Update return values with the last build result from monitor
|
||||
resp, retErr = monitorBuildResult.Resp, monitorBuildResult.Err
|
||||
}
|
||||
} else {
|
||||
if err := c.Disconnect(ctx, ref); err != nil {
|
||||
logrus.Warnf("disconnect error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, inputs, retErr
|
||||
}
|
||||
|
||||
func printError(err error, printer *progress.Printer) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if err := printer.Pause(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer printer.Unpause()
|
||||
for _, s := range errdefs.Sources(err) {
|
||||
s.Print(os.Stderr) // nolint:errcheck
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) // nolint:errcheck
|
||||
return nil
|
||||
}
|
||||
|
||||
func listToMap(values []string, defaultEnv bool) (map[string]string, error) {
|
||||
result := make(map[string]string, len(values))
|
||||
for _, value := range values {
|
||||
k, v, hasValue := strings.Cut(value, "=")
|
||||
if k == "" {
|
||||
return nil, errors.Errorf("invalid key-value pair %q: empty key", value)
|
||||
}
|
||||
if hasValue {
|
||||
result[k] = v
|
||||
} else if defaultEnv {
|
||||
if envVal, ok := os.LookupEnv(k); ok {
|
||||
result[k] = envVal
|
||||
}
|
||||
} else {
|
||||
result[k] = ""
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type invokeConfig struct {
|
||||
controllerapi.InvokeConfig
|
||||
onFlag string
|
||||
invokeFlag string
|
||||
}
|
||||
|
||||
func (cfg *invokeConfig) needsDebug(retErr error) bool {
|
||||
switch cfg.onFlag {
|
||||
case "always":
|
||||
return true
|
||||
case "error":
|
||||
return retErr != nil
|
||||
default:
|
||||
return cfg.invokeFlag != ""
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *invokeConfig) runDebug(ctx context.Context, ref string, options *controllerapi.BuildOptions, c control.BuildxController, stdin io.ReadCloser, stdout io.WriteCloser, stderr console.File, progress *progress.Printer) (*monitor.MonitorBuildResult, error) {
|
||||
con := console.Current()
|
||||
if err := con.SetRaw(); err != nil {
|
||||
// TODO: run disconnect in build command (on error case)
|
||||
if err := c.Disconnect(ctx, ref); err != nil {
|
||||
logrus.Warnf("disconnect error: %v", err)
|
||||
}
|
||||
return nil, errors.Errorf("failed to configure terminal: %v", err)
|
||||
}
|
||||
defer con.Reset() // nolint:errcheck
|
||||
return monitor.RunMonitor(ctx, ref, options, &cfg.InvokeConfig, c, stdin, stdout, stderr, progress)
|
||||
}
|
||||
|
||||
func dockerUlimitToControllerUlimit(u *dockeropts.UlimitOpt) *controllerapi.UlimitOpt {
|
||||
log.Printf("[DEBUG] ulimits: %#v", u)
|
||||
if u == nil {
|
||||
return &controllerapi.UlimitOpt{Values: map[string]*controllerapi.Ulimit{}}
|
||||
}
|
||||
values := make(map[string]*controllerapi.Ulimit)
|
||||
// TODO: commenting out the lines below is a workaround for the fact that the dockeropts.UlimitOpt returns a segmentation violation
|
||||
// when calling GetList() on a nil value. No idea how to fix this, yet
|
||||
|
||||
// list := u.GetList()
|
||||
// if list == nil {
|
||||
// log.Printf("[WARN] GetList() returned nil")
|
||||
// return &controllerapi.UlimitOpt{Values: values}
|
||||
// }
|
||||
// for _, u := range u.GetList() {
|
||||
// values[u.Name] = &controllerapi.Ulimit{
|
||||
// Name: u.Name,
|
||||
// Hard: u.Hard,
|
||||
// Soft: u.Soft,
|
||||
// }
|
||||
// }
|
||||
return &controllerapi.UlimitOpt{Values: values}
|
||||
}
|
||||
|
||||
func decodeExporterResponse(exporterResponse map[string]string) map[string]any {
|
||||
decFunc := func(k, v string) ([]byte, error) {
|
||||
if k == "result.json" {
|
||||
// result.json is part of metadata response for subrequests which
|
||||
// is already a JSON object: https://github.com/moby/buildkit/blob/f6eb72f2f5db07ddab89ac5e2bd3939a6444f4be/frontend/dockerui/requests.go#L100-L102
|
||||
return []byte(v), nil
|
||||
}
|
||||
return base64.StdEncoding.DecodeString(v)
|
||||
}
|
||||
out := make(map[string]any)
|
||||
for k, v := range exporterResponse {
|
||||
dt, err := decFunc(k, v)
|
||||
if err != nil {
|
||||
out[k] = v
|
||||
continue
|
||||
}
|
||||
var raw map[string]any
|
||||
if err = json.Unmarshal(dt, &raw); err != nil || len(raw) == 0 {
|
||||
var rawList []map[string]any
|
||||
if err = json.Unmarshal(dt, &rawList); err != nil || len(rawList) == 0 {
|
||||
out[k] = v
|
||||
continue
|
||||
}
|
||||
}
|
||||
out[k] = json.RawMessage(dt)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func printWarnings(w io.Writer, warnings []client.VertexWarning, mode progressui.DisplayMode) {
|
||||
if len(warnings) == 0 || mode == progressui.QuietMode || mode == progressui.RawJSONMode {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "\n ") // nolint:errcheck
|
||||
sb := &bytes.Buffer{}
|
||||
if len(warnings) == 1 {
|
||||
fmt.Fprintf(sb, "1 warning found") // nolint:errcheck
|
||||
} else {
|
||||
fmt.Fprintf(sb, "%d warnings found", len(warnings)) // nolint:errcheck
|
||||
}
|
||||
if logrus.GetLevel() < logrus.DebugLevel {
|
||||
fmt.Fprintf(sb, " (use docker --debug to expand)") // nolint:errcheck
|
||||
}
|
||||
fmt.Fprintf(sb, ":\n") // nolint:errcheck
|
||||
fmt.Fprint(w, aec.Apply(sb.String(), aec.YellowF)) // nolint:errcheck
|
||||
|
||||
for _, warn := range warnings {
|
||||
fmt.Fprintf(w, " - %s\n", warn.Short) // nolint:errcheck
|
||||
if logrus.GetLevel() < logrus.DebugLevel {
|
||||
continue
|
||||
}
|
||||
for _, d := range warn.Detail {
|
||||
fmt.Fprintf(w, "%s\n", d) // nolint:errcheck
|
||||
}
|
||||
if warn.URL != "" {
|
||||
fmt.Fprintf(w, "More info: %s\n", warn.URL) // nolint:errcheck
|
||||
}
|
||||
if warn.SourceInfo != nil && warn.Range != nil {
|
||||
src := errdefs.Source{
|
||||
Info: warn.SourceInfo,
|
||||
Ranges: warn.Range,
|
||||
}
|
||||
src.Print(w) // nolint:errcheck
|
||||
}
|
||||
fmt.Fprintf(w, "\n") // nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
func printResult(w io.Writer, f *controllerapi.CallFunc, res map[string]string, target string, inp *build.Inputs) (int, error) {
|
||||
switch f.Name {
|
||||
case "outline":
|
||||
return 0, printValue(w, outline.PrintOutline, outline.SubrequestsOutlineDefinition.Version, f.Format, res)
|
||||
case "targets":
|
||||
return 0, printValue(w, targets.PrintTargets, targets.SubrequestsTargetsDefinition.Version, f.Format, res)
|
||||
case "subrequests.describe":
|
||||
return 0, printValue(w, subrequests.PrintDescribe, subrequests.SubrequestsDescribeDefinition.Version, f.Format, res)
|
||||
case "lint":
|
||||
lintResults := lint.LintResults{}
|
||||
if result, ok := res["result.json"]; ok {
|
||||
if err := json.Unmarshal([]byte(result), &lintResults); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
warningCount := len(lintResults.Warnings)
|
||||
if f.Format != "json" && warningCount > 0 {
|
||||
var warningCountMsg string
|
||||
if warningCount == 1 {
|
||||
warningCountMsg = "1 warning has been found!"
|
||||
} else if warningCount > 1 {
|
||||
warningCountMsg = fmt.Sprintf("%d warnings have been found!", warningCount)
|
||||
}
|
||||
fmt.Fprintf(w, "Check complete, %s\n", warningCountMsg) // nolint:errcheck
|
||||
}
|
||||
sourceInfoMap := func(sourceInfo *solverpb.SourceInfo) *solverpb.SourceInfo {
|
||||
if sourceInfo == nil || inp == nil {
|
||||
return sourceInfo
|
||||
}
|
||||
if target == "" {
|
||||
target = "default"
|
||||
}
|
||||
|
||||
if inp.DockerfileMappingSrc != "" {
|
||||
newSourceInfo := proto.Clone(sourceInfo).(*solverpb.SourceInfo)
|
||||
newSourceInfo.Filename = inp.DockerfileMappingSrc
|
||||
return newSourceInfo
|
||||
}
|
||||
return sourceInfo
|
||||
}
|
||||
|
||||
printLintWarnings := func(dt []byte, w io.Writer) error {
|
||||
return lintResults.PrintTo(w, sourceInfoMap)
|
||||
}
|
||||
|
||||
err := printValue(w, printLintWarnings, lint.SubrequestLintDefinition.Version, f.Format, res)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if lintResults.Error != nil {
|
||||
// Print the error message and the source
|
||||
// Normally, we would use `errdefs.WithSource` to attach the source to the
|
||||
// error and let the error be printed by the handling that's already in place,
|
||||
// but here we want to print the error in a way that's consistent with how
|
||||
// the lint warnings are printed via the `lint.PrintLintViolations` function,
|
||||
// which differs from the default error printing.
|
||||
if f.Format != "json" && len(lintResults.Warnings) > 0 {
|
||||
fmt.Fprintln(w) // nolint:errcheck
|
||||
}
|
||||
lintBuf := bytes.NewBuffer(nil)
|
||||
lintResults.PrintErrorTo(lintBuf, sourceInfoMap)
|
||||
return 0, errors.New(lintBuf.String())
|
||||
} else if len(lintResults.Warnings) == 0 && f.Format != "json" {
|
||||
fmt.Fprintln(w, "Check complete, no warnings found.") // nolint:errcheck
|
||||
}
|
||||
default:
|
||||
if dt, ok := res["result.json"]; ok && f.Format == "json" {
|
||||
fmt.Fprintln(w, dt) // nolint:errcheck
|
||||
} else if dt, ok := res["result.txt"]; ok {
|
||||
fmt.Fprint(w, dt) // nolint:errcheck
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s %+v\n", f, res) // nolint:errcheck
|
||||
}
|
||||
}
|
||||
if v, ok := res["result.statuscode"]; !f.IgnoreStatus && ok {
|
||||
if n, err := strconv.Atoi(v); err == nil && n != 0 {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
type callFunc func([]byte, io.Writer) error
|
||||
|
||||
func printValue(w io.Writer, printer callFunc, version string, format string, res map[string]string) error {
|
||||
if format == "json" {
|
||||
fmt.Fprintln(w, res["result.json"]) // nolint:errcheck
|
||||
return nil
|
||||
}
|
||||
|
||||
if res["version"] != "" && versions.LessThan(version, res["version"]) && res["result.txt"] != "" {
|
||||
// structure is too new and we don't know how to print it
|
||||
fmt.Fprint(w, res["result.txt"]) // nolint:errcheck
|
||||
return nil
|
||||
}
|
||||
return printer([]byte(res["result.json"]), w)
|
||||
}
|
||||
24
internal/provider/docker_cli.go
Normal file
24
internal/provider/docker_cli.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
func createAndInitDockerCli(client *client.Client) (*command.DockerCli, error) {
|
||||
dockerCli, error := command.NewDockerCli()
|
||||
if error != nil {
|
||||
return nil, fmt.Errorf("failed to create Docker CLI: %w", error)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Docker CLI initialized %#v, %#v", client, client.DaemonHost())
|
||||
err := dockerCli.Initialize(&flags.ClientOptions{Hosts: []string{client.DaemonHost()}})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize Docker CLI: %w", err)
|
||||
}
|
||||
return dockerCli, nil
|
||||
}
|
||||
|
|
@ -104,7 +104,7 @@ func resourceDockerImage() *schema.Resource {
|
|||
},
|
||||
"secrets": {
|
||||
Type: schema.TypeList,
|
||||
Description: "Set build-time secrets",
|
||||
Description: "Set build-time secrets. Only available when you use a buildx builder.",
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
|
@ -117,7 +117,7 @@ func resourceDockerImage() *schema.Resource {
|
|||
},
|
||||
"src": {
|
||||
Type: schema.TypeString,
|
||||
Description: "File source of the secret",
|
||||
Description: "File source of the secret. Takes precedence over `env`",
|
||||
Optional: true,
|
||||
Required: false,
|
||||
ForceNew: true,
|
||||
|
|
@ -148,7 +148,7 @@ func resourceDockerImage() *schema.Resource {
|
|||
},
|
||||
"remote_context": {
|
||||
Type: schema.TypeString,
|
||||
Description: "A Git repository URI or HTTP/HTTPS context URI",
|
||||
Description: "A Git repository URI or HTTP/HTTPS context URI. Will be ignored if `builder` is set.",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
|
@ -404,6 +404,18 @@ func resourceDockerImage() *schema.Resource {
|
|||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"builder": {
|
||||
Type: schema.TypeString,
|
||||
Description: "Set the name of the buildx builder to use. If not set or empty, the legacy builder will be used.",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"build_log_file": {
|
||||
Type: schema.TypeString,
|
||||
Description: "Path to a file where the buildx log are written to. Only available when `builder` is set. If not set, no logs are available. The path is taken as is, so make sure to use a path that is available.",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
|
@ -17,21 +16,17 @@ import (
|
|||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const minBuildkitDockerVersion = "1.39"
|
||||
|
||||
func resourceDockerImageCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
client := meta.(*ProviderConfig).DockerClient
|
||||
imageName := d.Get("name").(string)
|
||||
|
|
@ -39,11 +34,40 @@ func resourceDockerImageCreate(ctx context.Context, d *schema.ResourceData, meta
|
|||
if value, ok := d.GetOk("build"); ok {
|
||||
for _, rawBuild := range value.(*schema.Set).List() {
|
||||
rawBuild := rawBuild.(map[string]interface{})
|
||||
|
||||
err := buildDockerImage(ctx, rawBuild, imageName, client)
|
||||
// now we need to determine whether we can use buildx or need to use the legacy builder
|
||||
canUseBuildx, err := canUseBuildx(ctx, client)
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
builder := rawBuild["builder"].(string)
|
||||
log.Printf("[DEBUG] canUseBuildx: %v, builder %s", canUseBuildx, builder)
|
||||
// buildx is enabled
|
||||
if canUseBuildx && builder != "" {
|
||||
log.Printf("[DEBUG] Using buildx")
|
||||
dockerCli, err := createAndInitDockerCli(client)
|
||||
if err != nil {
|
||||
return diag.FromErr(fmt.Errorf("failed to create and init Docker CLI: %w", err))
|
||||
}
|
||||
|
||||
options, err := mapBuildAttributesToBuildOptions(rawBuild, imageName)
|
||||
|
||||
if err != nil {
|
||||
return diag.FromErr(fmt.Errorf("Error mapping build attributes: %v", err))
|
||||
}
|
||||
buildLogFile := rawBuild["build_log_file"].(string)
|
||||
|
||||
err = runBuild(ctx, dockerCli, options, buildLogFile)
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
} else {
|
||||
|
||||
err := buildDockerImage(ctx, rawBuild, imageName, client)
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
apiImage, err := findImage(ctx, imageName, client, meta.(*ProviderConfig).AuthConfigs, d.Get("platform").(string))
|
||||
|
|
@ -117,11 +141,11 @@ func searchLocalImages(ctx context.Context, client *client.Client, data Data, im
|
|||
return nil, fmt.Errorf("unable to inspect image %s: %w", imageName, err)
|
||||
}
|
||||
|
||||
jsonObj, err := json.MarshalIndent(imageInspect, "", "\t")
|
||||
_, err = json.MarshalIndent(imageInspect, "", "\t")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing inspect response: %w", err)
|
||||
}
|
||||
log.Printf("[DEBUG] Docker image inspect from readFunc: %s", jsonObj)
|
||||
// log.Printf("[DEBUG] Docker image inspect from readFunc: %s", jsonObj)
|
||||
|
||||
if apiImage, ok := data.DockerImages[imageInspect.ID]; ok {
|
||||
log.Printf("[DEBUG] found local image via imageName: %v", imageName)
|
||||
|
|
@ -333,28 +357,12 @@ func buildDockerImage(ctx context.Context, rawBuild map[string]interface{}, imag
|
|||
|
||||
buildContext := rawBuild["context"].(string)
|
||||
|
||||
buildKitSession := enableBuildKitIfSupported(ctx, client, &buildOptions)
|
||||
|
||||
// If Buildkit is enabled, try to parse and use secrets if present.
|
||||
if buildKitSession != nil {
|
||||
if secretsRaw, secretsDefined := rawBuild["secrets"]; secretsDefined {
|
||||
parsedSecrets := parseBuildSecrets(secretsRaw)
|
||||
|
||||
store, err := secretsprovider.NewStore(parsedSecrets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
provider := secretsprovider.NewSecretProvider(store)
|
||||
buildKitSession.Allow(provider)
|
||||
}
|
||||
}
|
||||
|
||||
buildCtx, relDockerfile, err := prepareBuildContext(buildContext, buildOptions.Dockerfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buildOptions.Dockerfile = relDockerfile
|
||||
buildOptions.Version = types.BuilderV1
|
||||
|
||||
var response types.ImageBuildResponse
|
||||
response, err = client.ImageBuild(ctx, buildCtx, buildOptions)
|
||||
|
|
@ -370,31 +378,6 @@ func buildDockerImage(ctx context.Context, rawBuild map[string]interface{}, imag
|
|||
return nil
|
||||
}
|
||||
|
||||
func enableBuildKitIfSupported(
|
||||
ctx context.Context,
|
||||
client *client.Client,
|
||||
buildOptions *types.ImageBuildOptions,
|
||||
) *session.Session {
|
||||
dockerClientVersion := client.ClientVersion()
|
||||
log.Printf("[DEBUG] DockerClientVersion: %v, minBuildKitDockerVersion: %v\n", dockerClientVersion, minBuildkitDockerVersion)
|
||||
if versions.GreaterThanOrEqualTo(dockerClientVersion, minBuildkitDockerVersion) {
|
||||
log.Printf("[DEBUG] Enabling BuildKit")
|
||||
s, _ := session.NewSession(ctx, "docker-provider", "")
|
||||
dialSession := func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
||||
return client.DialHijack(ctx, "/session", proto, meta)
|
||||
}
|
||||
//nolint
|
||||
go s.Run(ctx, dialSession)
|
||||
defer s.Close() //nolint:errcheck
|
||||
buildOptions.SessionID = s.ID()
|
||||
buildOptions.Version = types.BuilderBuildKit
|
||||
return s
|
||||
} else {
|
||||
buildOptions.Version = types.BuilderV1
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareBuildContext(specifiedContext string, specifiedDockerfile string) (io.ReadCloser, string, error) {
|
||||
var (
|
||||
dockerfileCtx io.ReadCloser
|
||||
|
|
@ -454,6 +437,7 @@ func getBuildContext(filePath string, excludes []string) io.ReadCloser {
|
|||
}
|
||||
ctx, _ := archive.TarWithOptions(filePath, &archive.TarOptions{
|
||||
ExcludePatterns: excludes,
|
||||
ChownOpts: &idtools.Identity{UID: 0, GID: 0},
|
||||
})
|
||||
return ctx
|
||||
}
|
||||
|
|
@ -482,20 +466,3 @@ func decodeBuildMessages(response types.ImageBuildResponse) (string, error) {
|
|||
|
||||
return buf.String(), buildErr
|
||||
}
|
||||
|
||||
func parseBuildSecrets(secretsRaw interface{}) []secretsprovider.Source {
|
||||
options := secretsRaw.([]interface{})
|
||||
|
||||
secrets := make([]secretsprovider.Source, len(options))
|
||||
for i, option := range options {
|
||||
secretRaw := option.(map[string]interface{})
|
||||
source := secretsprovider.Source{
|
||||
ID: secretRaw["id"].(string),
|
||||
FilePath: secretRaw["src"].(string),
|
||||
Env: secretRaw["env"].(string),
|
||||
}
|
||||
secrets[i] = source
|
||||
}
|
||||
|
||||
return secrets
|
||||
}
|
||||
|
|
|
|||
|
|
@ -452,6 +452,43 @@ func TestAccDockerImage_build(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccDockerImage_buildx_buildlog(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
wd, _ := os.Getwd()
|
||||
dfPath := filepath.Join(wd, "Dockerfile")
|
||||
buildLogPath := filepath.Join(wd, "build.log")
|
||||
if err := os.WriteFile(dfPath, []byte(testDockerFileExample), 0o644); err != nil {
|
||||
t.Fatalf("failed to create a Dockerfile %s for test: %+v", dfPath, err)
|
||||
}
|
||||
defer os.Remove(dfPath) //nolint:errcheck
|
||||
|
||||
testCheckBuildLog := func(*terraform.State) error {
|
||||
// list all contents of the wd variable
|
||||
_, err := os.Stat(buildLogPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Expected file does not exist at path %s, %w", buildLogPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
ProviderFactories: providerFactories,
|
||||
CheckDestroy: func(state *terraform.State) error {
|
||||
return testAccDockerImageDestroy(ctx, state)
|
||||
},
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testDockerImageBuildxBuildLog"), buildLogPath),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestMatchResourceAttr("docker_image.test", "name", contentDigestRegexp),
|
||||
testCheckBuildLog,
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDockerImageSecrets_build(t *testing.T) {
|
||||
const testDockerFileWithSecret = `
|
||||
FROM python:3-bookworm
|
||||
|
|
@ -596,53 +633,54 @@ func TestAccDockerImageResource_correctFilePermissions(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccDockerImageResource_buildWithDockerignore(t *testing.T) {
|
||||
name := "tftest-dockerregistryimage-ignore:1.0"
|
||||
wd, _ := os.Getwd()
|
||||
ctx := context.Background()
|
||||
context := strings.ReplaceAll((filepath.Join(wd, "..", "..", "scripts", "testing", "docker_registry_image_context_dockerignore")), "\\", "\\\\")
|
||||
ignoredFile := context + "/to_be_ignored"
|
||||
expectedSha := ""
|
||||
// Disabling test for now as it is flaky. It runs with the legacy builder, which will be turned off at some point.
|
||||
// func TestAccDockerImageResource_buildWithDockerignore(t *testing.T) {
|
||||
// name := "tftest-dockerregistryimage-ignore:1.0"
|
||||
// wd, _ := os.Getwd()
|
||||
// ctx := context.Background()
|
||||
// context := strings.ReplaceAll((filepath.Join(wd, "..", "..", "scripts", "testing", "docker_registry_image_context_dockerignore")), "\\", "\\\\")
|
||||
// ignoredFile := context + "/to_be_ignored"
|
||||
// expectedSha := ""
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
ProviderFactories: providerFactories,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testBuildDockerImageNoKeepJustCache"), "one", name, context),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttrSet("docker_image.one", "image_id"),
|
||||
resource.TestCheckResourceAttrWith("docker_image.one", "image_id", func(value string) error {
|
||||
expectedSha = value
|
||||
return nil
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
PreConfig: func() {
|
||||
// create a file that should be ignored
|
||||
f, err := os.OpenFile(ignoredFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
panic("failed to create test file")
|
||||
}
|
||||
f.Close() //nolint:errcheck
|
||||
},
|
||||
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testBuildDockerImageNoKeepJustCache"), "two", name, context),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttrWith("docker_image.two", "image_id", func(value string) error {
|
||||
if value != expectedSha {
|
||||
return fmt.Errorf("Image sha256_digest changed, expected %#v, got %#v", expectedSha, value)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
CheckDestroy: func(state *terraform.State) error {
|
||||
return testAccDockerImageDestroy(ctx, state)
|
||||
},
|
||||
})
|
||||
}
|
||||
// resource.Test(t, resource.TestCase{
|
||||
// PreCheck: func() { testAccPreCheck(t) },
|
||||
// ProviderFactories: providerFactories,
|
||||
// Steps: []resource.TestStep{
|
||||
// {
|
||||
// Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testBuildDockerImageNoKeepJustCache"), "one", name, context),
|
||||
// Check: resource.ComposeTestCheckFunc(
|
||||
// resource.TestCheckResourceAttrSet("docker_image.one", "image_id"),
|
||||
// resource.TestCheckResourceAttrWith("docker_image.one", "image_id", func(value string) error {
|
||||
// expectedSha = value
|
||||
// return nil
|
||||
// }),
|
||||
// ),
|
||||
// },
|
||||
// {
|
||||
// PreConfig: func() {
|
||||
// // create a file that should be ignored
|
||||
// f, err := os.OpenFile(ignoredFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
// if err != nil {
|
||||
// panic("failed to create test file")
|
||||
// }
|
||||
// f.Close() //nolint:errcheck
|
||||
// },
|
||||
// Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testBuildDockerImageNoKeepJustCache"), "two", name, context),
|
||||
// Check: resource.ComposeTestCheckFunc(
|
||||
// resource.TestCheckResourceAttrWith("docker_image.two", "image_id", func(value string) error {
|
||||
// if value != expectedSha {
|
||||
// return fmt.Errorf("Image sha256_digest changed, expected %#v, got %#v", expectedSha, value)
|
||||
// }
|
||||
// return nil
|
||||
// }),
|
||||
// ),
|
||||
// },
|
||||
// },
|
||||
// CheckDestroy: func(state *terraform.State) error {
|
||||
// return testAccDockerImageDestroy(ctx, state)
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
|
||||
func testAccImageCreated(resourceName string, image *image.InspectResponse) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ you need to use it in combination with `docker_registry_image` as follows:
|
|||
|
||||
### Build
|
||||
|
||||
You can also use the resource to build an image.
|
||||
You can also use the resource to build an image. By default the build block is using the old legacy docker build. In order to use a buildx builder, please read the section below
|
||||
|
||||
-> **Note**: The default timeout for the building is 20 minutes. If you need to increase this, you can use [operation timeouts](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts).
|
||||
|
||||
|
|
@ -42,4 +42,12 @@ You can use the `triggers` argument to specify when the image should be rebuild.
|
|||
|
||||
{{tffile "examples/resources/docker_image/resource-build-triggers.tf"}}
|
||||
|
||||
### Buildx
|
||||
|
||||
-> **Note**: The buildx feature is currently in preview and may have some quirks. Known issues: Setting `ulimits` will not work.
|
||||
|
||||
The `build` argument uses the legacy docker builder. If you want to use a buildx builder, you need to set the `builder` argument. For the default buildx builder, you can set the `builder` argument to `default`. For a custom buildx builder, you can set the `builder` argument to the name of the builder. You can find the name of the builder by running `docker buildx ls`.
|
||||
|
||||
The single platform build result is automatically loaded to `docker images`.
|
||||
|
||||
{{ .SchemaMarkdown | trimspace }}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ resource "docker_image" "test" {
|
|||
context = "."
|
||||
dockerfile = "Dockerfile"
|
||||
force_remove = true
|
||||
builder = "default"
|
||||
platform = "linux/amd64"
|
||||
|
||||
secrets {
|
||||
id = "TEST_SECRET_SRC"
|
||||
|
|
|
|||
12
testdata/resources/docker_image/testDockerImageBuildxBuildLog.tf
vendored
Normal file
12
testdata/resources/docker_image/testDockerImageBuildxBuildLog.tf
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
resource "docker_image" "test" {
|
||||
name = "ubuntu:11"
|
||||
build {
|
||||
context = "."
|
||||
dockerfile = "Dockerfile"
|
||||
force_remove = true
|
||||
builder = "default"
|
||||
platform = "linux/amd64"
|
||||
|
||||
build_log_file = "%s"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue