Compare commits

..

151 Commits

Author SHA1 Message Date
nathan 8d85ad5269 Merge pull request 'master' (#1) from wagshome/buildx:master into master
Reviewed-on: #1
2 years ago
nathan wagner 008ce88567 messing this up 2 years ago
nathan wagner 17a7e99226 Revert "both driver-opt and security-opt options"
This reverts commit a12bc79097.
2 years ago
nathan wagner 027a8c16e7 adding in updates 2 years ago
Nathan a12bc79097 both driver-opt and security-opt options 2 years ago
nathan a0b5a420e8 security options 2 years ago
nathan wagner 0151ea9a3f maskedPath 2 years ago
nathan wagner 221d091b68 removing debugging 2 years ago
nathan 1760b8b586 doing it 2 years ago
nathan wagner 4d142d8b45 doing it 2 years ago
nathan wagner 44dd1f1f5e i want buildx binary to be static on out 2 years ago
CrazyMax f35b2b7cab
Merge pull request #2032 from docker/dependabot/github_actions/actions/checkout-4
build(deps): bump actions/checkout from 3 to 4
2 years ago
dependabot[bot] 29ba5ecef6
build(deps): bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2 years ago
Justin Chadwell 87e8e4b847
Merge pull request #2029 from testwill/loop
chore: slice loop replace
2 years ago
guoguangwu a71a24c0f4 chore: slice loop replace
Signed-off-by: guoguangwu <guoguangwu@magic-shield.com>
2 years ago
Tõnis Tiigi 76119b0f61
Merge pull request #2026 from ktock/fix-invoke-flag
debug: fix short-form custom command name on `--invoke` isn't used
2 years ago
Kohei Tokunaga 7843b5f417
debug: fix short-form custom command name on `--invoke` isn't used
Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
2 years ago
Justin Chadwell da6662975f
Merge pull request #2019 from jedevc/chore-bake-field-reorder 2 years ago
Justin Chadwell de4dbb7d00 chore: reorder target fields
Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
Justin Chadwell 3bd4bca994
Merge pull request #2013 from jedevc/tests-add-imagetools-create
tests: add imagetools tests for copying manifests and indexes
2 years ago
CrazyMax 296832c90e
Merge pull request #2014 from crazy-max/fix-compose-test
test: fix non-deterministic compose context path
2 years ago
CrazyMax 56d55a4137
test: fix non-deterministic compose context path
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
CrazyMax 626e6f8fa3
Merge pull request #1905 from thaJeztah/cgroup_parent_description
update flag-description for --cgroup-parent
2 years ago
Justin Chadwell 5941bf0494 tests: add imagetools tests for copying manifests and indexes
Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
CrazyMax 29a496cdab
Merge pull request #2012 from jedevc/git-propogate-errors
git: propogate failure to locate git binary
2 years ago
Justin Chadwell a43d9a67c7 git: fix error wrapping to ensure internal errors are propogated
Also, tidy up the error printing, so that now we always print out the
"current commit information was not captured by the build" message,
instead of just for not locating the git binary.

Before:

	WARNING: buildx: git was not found in the system. Current commit information was not captured by the build

After:

	WARNING: current commit information was not captured by the build: git was not found in the system: <error message>

Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
Justin Chadwell c47eb3bf5a git: propogate failure to locate git binary
Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
CrazyMax a97e1641a4
Merge pull request #2000 from jedevc/fix-race-container-creation
docker-container: avoid fail if container conflict
2 years ago
Akihiro Suda 86ae8ea854
Merge pull request #1999 from crazy-max/update-k8s
vendor: bump k8s to v0.26.7
2 years ago
Justin Chadwell d37d483097 docker-container: avoid fail if container conflict
Fixes the race condition where two boots are executed simultaneously
across multiple processes.

We initially check to see if the container exists, but if during
container creation we get a name conflict, we don't treat this error as
a hard failure, and instead move immediately into waiting for the node
to boot.

Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
CrazyMax 4e96faa201
vendor: bump k8s to v0.26.7
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
Tõnis Tiigi e5419ef6d7
Merge pull request #1927 from crazy-max/fix-load-status
build: read body response to check for erroneous image export to docker
2 years ago
CrazyMax 14747a490a
Merge pull request #1971 from glours/bump-compose-go-v1.17.0
bump compose-go version to v1.17.0 to fix issue with depends_on
2 years ago
Justin Chadwell e5cee892ed
Merge pull request #1965 from mqasimsarfraz/qasim/oci-annotations 2 years ago
CrazyMax ef4b984df4
build: read body response to check for erroneous image export to docker
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
CrazyMax a8f402e28d
Merge pull request #1993 from kenyon/typo-fix
README: fix typo
2 years ago
Kenyon Ralph 2eba99b40b README: fix whitespace
Signed-off-by: Kenyon Ralph <quic_kralph@quicinc.com>
2 years ago
Kenyon Ralph 7686fa1f16 README: fix typo
Signed-off-by: Kenyon Ralph <quic_kralph@quicinc.com>
2 years ago
Justin Chadwell 51b9bab245
Merge pull request #1987 from jedevc/vendor-buildkit-master-tests 2 years ago
CrazyMax 6b5758f4cd
Merge pull request #1821 from jedevc/allow-debug-env
commands: consume DEBUG environment variable
2 years ago
CrazyMax bd375a14a8
Merge pull request #1940 from kenyon/patch-1
README: clarify the Linux package install instructions
2 years ago
CrazyMax b01693f63e
bake: test compose include
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
Guillaume Lours 4a059d5144
adapt compose unit tests, build context is now transformed to absolute paths by compose-go
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2 years ago
Guillaume Lours f3775c0046
bump compose-go version to v1.17.0 to fix issue with depends_on
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2 years ago
Justin Chadwell 50fbdd86f9
Merge pull request #1985 from jsternberg/integration-test/version 2 years ago
Justin Chadwell 1f61de0fcc
Merge pull request #1988 from jedevc/fix-attests-on-docker-driver 2 years ago
Justin Chadwell e206c585bb build: error on attests on non-multiplatform driver
On drivers that do not support multi-platform builds (the default
`docker` driver), we do not support building attestations (unless using
the containerd store).

We need to check this feature before attempting to build using
attestations.

Also adds a test to ensure that attestations can be pushed to registries
at all, and that it adequately fails on the docker driver.

Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
Justin Chadwell 5e46d8057d tests: add unsupported features detection skeleton
Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
Justin Chadwell 4e7709e54c vendor: update buildkit to master@b49a8873179b
Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
Justin Chadwell 5ed8f1b7d9 tests: avoid hardcoded driver check in testImageIDOutput
To detect if there is a docker daemon available, we can use the sandbox
.DockerAddress() function.

Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
Jonathan A. Sternberg 1d12c1f5b3
Integration test for docker buildx version
An integration test for `docker buildx version` has been created. The
integration test checks that there is one line output, the output is
composed of three sections, and that these sections could feasibly be
the package path, version, and revision information.

The intention of the checks is to find obvious errors in the output like
the package path not existing or the version and revision being swapped.
It is not intended to assert that these values must be certain values
because it is assumed these values may vary depending on the build
process for buildx.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2 years ago
Qasim Sarfraz 3ef93e081c tests: add testImagetoolsAnnotation integration test
Signed-off-by: Qasim Sarfraz <qasimsarfraz@microsoft.com>
2 years ago
Qasim Sarfraz 18894a8e3a allow annotations for OCI image index
Signed-off-by: Qasim Sarfraz <qasimsarfraz@microsoft.com>
2 years ago
Justin Chadwell 13ec635988
Merge pull request #1914 from ktock/updatedebugdocs 2 years ago
Tõnis Tiigi f804b8fa4b
Merge pull request #1982 from thaJeztah/update_go1.20.7
update to go1.20.7
2 years ago
Kenyon Ralph 21a55ff9a1 README: clarify the Linux package install instructions
Signed-off-by: Kenyon Ralph <quic_kralph@quicinc.com>
2 years ago
Sebastiaan van Stijn dd350284df
update to go1.20.7
Includes a fix for CVE-2023-29409

go1.20.7 (released 2023-08-01) includes a security fix to the crypto/tls
package, as well as bug fixes to the assembler and the compiler. See the
Go 1.20.7 milestone on our issue tracker for details:

- https://github.com/golang/go/issues?q=milestone%3AGo1.20.7+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.20.6...go1.20.7

From the mailing list announcement:

[security] Go 1.20.7 and Go 1.19.12 are released

Hello gophers,

We have just released Go versions 1.20.7 and 1.19.12, minor point releases.

These minor releases include 1 security fixes following the security policy:

- crypto/tls: restrict RSA keys in certificates to <= 8192 bits

  Extremely large RSA keys in certificate chains can cause a client/server
  to expend significant CPU time verifying signatures. Limit this by
  restricting the size of RSA keys transmitted during handshakes to <=
  8192 bits.

  Based on a survey of publicly trusted RSA keys, there are currently only
  three certificates in circulation with keys larger than this, and all
  three appear to be test certificates that are not actively deployed. It
  is possible there are larger keys in use in private PKIs, but we target
  the web PKI, so causing breakage here in the interests of increasing the
  default safety of users of crypto/tls seems reasonable.

  Thanks to Mateusz Poliwczak for reporting this issue.

View the release notes for more information:
https://go.dev/doc/devel/release#go1.20.7

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Tõnis Tiigi c010d3de8d
Merge pull request #1967 from thaJeztah/update_cli
vendor: github.com/docker/docker, github.com/docker/cli v24.0.5
2 years ago
Justin Chadwell d11dbbf9f7
Merge pull request #1978 from tonistiigi/imagetools-test 2 years ago
Tonis Tiigi 75cdceb9f1
tests: add integration test for imagetools create
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2 years ago
Tõnis Tiigi 10ff93f190
Merge pull request #1968 from jedevc/reset-dont-modify-input
progress: don't modify ResetTime inputs
2 years ago
CrazyMax bf00185809
Merge pull request #1681 from crazy-max/fup-buildinfo
docs: update since buildinfo removal
2 years ago
CrazyMax 90f03e57c2
Merge pull request #1972 from crazy-max/docs-fix-create
docs: fix platform example for create command
2 years ago
Justin Chadwell a59fd3ebfe
Merge pull request #1970 from ktock/entrypointconfig 2 years ago
Kohei Tokunaga 3eb490153d
remote controller: Fix entrypoint interaction bugs
Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
2 years ago
CrazyMax d957d8b987
docs: fix platform example for create command
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
Justin Chadwell 5a1f252bd9 progress: don't modify ResetTime inputs
No other parts of the progress rendering modify the inputs, so we should
avoid this as well.

This actually fixes an edge case in pushWithMoby which writes the same
VertexStatus multiple times, modifying the timestamps and similar.
However, if the operation takes long enough the small time difference
can accumulate, and move the Start time far into the past.

Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
Sebastiaan van Stijn ab4585f38c
vendor: github.com/docker/cli v24.0.5
Fix a panic when `auths: null` is found in the CLI config file.

full diff: https://github.com/docker/cli/compare/v24.0.4...v24.0.5

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Sebastiaan van Stijn 3003045c0b
vendor: github.com/docker/docker v24.0.5
- client: Client.postHijacked: use Client.buildRequest

full diff: https://github.com/moby/moby/compare/36e9e796c6fc...v24.0.5

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Sebastiaan van Stijn a6f3f290b4
vendor: golang.org/x/net v0.10.0
- http2: properly discard data received after request/response body is closed
- http2: don't reuse connections that are experiencing errors
- internal/socks: permit authenticating with an empty password

full diff: https://github.com/golang/net/compare/v0.8.0...v0.10.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Sebastiaan van Stijn 27d072a099
vendor: golang.org/x/text v0.9.0
no changes in vendored files

full diff: https://github.com/golang/text/compare/v0.8.0...v0.9.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Sebastiaan van Stijn 8e3df1943c
vendor: golang.org/x/term v0.8.0
no changes in vendored files

full diff: https://github.com/golang/term/compare/v0.6.0...v0.8.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Sebastiaan van Stijn 8c54de66ce
vendor: golang.org/x/sys v0.8.0
full diff: https://github.com/golang/sys/compare/v0.7.0...v0.8.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Sebastiaan van Stijn 06b9ac2dc4
vendor: github.com/sirupsen/logrus v1.9.3
Fix a potential denial of service in logrus.Writer() that could be triggered
by logging text longer than 64kb without newlines.

full diff: https://github.com/sirupsen/logrus/compare/v1.9.0...v1.9.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Justin Chadwell b8739d7441
Merge pull request #1934 from jedevc/use-buildkit-client-wait 2 years ago
Justin Chadwell 23fe02993b
Merge pull request #1963 from jedevc/split-to-driver-pairs-fix-type 2 years ago
Justin Chadwell 1d177f00d2 chore: tidy splitToDriverPairs to avoid unneccessary int
Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
CrazyMax ceaba7011f
Merge pull request #1938 from jonapich/feature/custom-annotations
kubernetes driver // allow custom annotations and labels
2 years ago
Jonathan Piché 9c06f383ba allow custom annotations and labels into kubernetes manifests
Co-authored-by: Akihiro Suda <suda.kyoto@gmail.com>

Signed-off-by: Jonathan Piché <jpiche@coveo.com>
2 years ago
Justin Chadwell e11c5e3e96 remote: use buildkit's client.Wait method to bootstrap
This native implementation uses GRPC level waiting, instead of starting
a busy loop. We also a manual max backoff of one second to improve
responsiveness.

Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
CrazyMax f5719f3017
Merge pull request #1959 from thaJeztah/update_cli
vendor: github.com/docker/cli v24.0.4
2 years ago
CrazyMax 163babdca7
Merge pull request #1961 from crazy-max/fix-internal-build
build: set remote bake def and remote dockerfile as internal solve
2 years ago
Sebastiaan van Stijn 094d1aded8
commands: NewRootCmd: remove obsolete logrus filter hook
This hook was added in 278f94a8b6 and
72758fef22 to suppress spurious warnings
printed by the CLI's cli/connhelper/commandconn package;
3fb4fb83df/cli/connhelper/commandconn/commandconn.go (L203-L214)

Those logs were removed in a5ebe2282a
so we can remove the hook.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
CrazyMax 05ef20b434
build: set remote bake def and remote dockerfile as internal solve
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
Sebastiaan van Stijn cc718b3444
vendor: github.com/docker/cli v24.0.4
full diff: https://github.com/docker/cli/compare/v24.0.2...v24.0.4

notable changes:

- ssh: fix error on commandconn close, add ping and default
- commandconn: return original error while closing

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Justin Chadwell e98e8f6ac9
Merge pull request #1948 from thaJeztah/buildkit_0.12 2 years ago
CrazyMax 36541ed9d5
Merge pull request #1954 from crazy-max/result-handle-internal
build: mark result handle build as internal
2 years ago
CrazyMax 418ea82d3a
build: mark result handle build as internal
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
Sebastiaan van Stijn 130bbda00e
vendor: github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353
full diff:

- https://github.com/moby/buildkit/compare/20230620112432...v0.12.0
- https://github.com/moby/buildkit/compare/v0.12.0...faa0cc7da3536923d85b74b2bb2d13c12a6ecc99

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Sebastiaan van Stijn 2666bd6996
vendor: github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531
full diff: 8066bb9726...f9a4f7ef65

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Sebastiaan van Stijn ff2c8da803
vendor: github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb
full diff: 9e7a6df485...36ef4d8c0d

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Sebastiaan van Stijn e094296f37
vendor: github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb
full diff: 4e3ac2762d...02993c407b

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Justin Chadwell 7c3b77fb36
Merge pull request #1950 from thaJeztah/remove_imageutil_dead_code 2 years ago
CrazyMax fb4c4f07ca
Merge pull request #1941 from crazy-max/fix-kube-config
k8s: fix missing kubeconfig check from endpoint
2 years ago
Sebastiaan van Stijn b9e25e82cf
util/imagetools: remove unused Resolver.ImageConfig
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
CrazyMax 089036da29
Merge pull request #1946 from crazy-max/update-go
update go to 1.20.6
2 years ago
CrazyMax 1123bfed10
hack(generated-files): bump golang image to bookworm
#7 [internal] load metadata for docker.io/library/golang:1.20.6-buster
#7 ERROR: docker.io/library/golang:1.20.6-buster: not found

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
CrazyMax 7f2293308b
update go to 1.20.6
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
CrazyMax a65131f9d3
Merge pull request #1945 from crazy-max/bump-docker
vendor: github.com/docker/docker@24.0 36e9e79
2 years ago
CrazyMax 8a3a646c61
vendor: github.com/docker/docker@24.0 36e9e79
client: define a "dummy" hostname to use for local connections
fixes "http: invalid Host header" errors when compiling with go1.20.6
or go1.19.11

full diff: https://github.com/docker/docker/compare/v24.0.2...36e9e796c6fc

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
CrazyMax 4384947be1
k8s: fix missing kubeconfig check from endpoint
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
Tõnis Tiigi 69421182ca
Merge pull request #1891 from droopy4096/auth-token
Add Bearer token support
2 years ago
Justin Chadwell 068382f5df
Merge pull request #1936 from jedevc/hack-set-go-version-to-1.20.5 2 years ago
Justin Chadwell c4bec05466 hack: force go version to 1.20.5
A temporary workaround for "http: invalid Host header" introduced in
go 1.20.6.

Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
CrazyMax 89e1ac0a6e
Merge pull request #1894 from crazy-max/fix-host-gateway
build: fix host-gateway handling
2 years ago
Justin Chadwell b84e0e11b4
Merge pull request #1918 from crazy-max/docs-hidden-fix 2 years ago
Justin Chadwell d95f5f8f3b
Merge pull request #1925 from dvdksn/test/build-progress 2 years ago
David Karlsson b4c0941683 tests: add basic build progress test
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2 years ago
Justin Chadwell cf9798cede
Merge pull request #1919 from crazy-max/fix-build-details-link 2 years ago
CrazyMax 20d2501edc
test: build details output
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
CrazyMax d45601fdc6
build: missing newline when printing build details on error
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
CrazyMax c81a9a89cf
Merge pull request #1913 from yastanotheruser/master
Controller: Include CgroupParent in build.Options
2 years ago
CrazyMax 87b9f9ecfb
docs: update generated content
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
CrazyMax cbc473359a
vendor: update cli-docs-tool to 0.6.0
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
Justin Chadwell 2eba60db75
Merge pull request #1916 from jedevc/add-local-bake-test 2 years ago
Justin Chadwell 0dcbed3f53 tests: add simple local bake test
Signed-off-by: Justin Chadwell <me@jedevc.com>
2 years ago
Kohei Tokunaga ca08eb65e2
docs: debug: update the output of `help` command to the latest message
Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
2 years ago
Kohei Tokunaga 6f37d9bee7
monitor: attach: fix typo in long help message
Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
2 years ago
Jhan S. Álvarez e65f6b8c8b controller: include CgroupParent in build.Options
Signed-off-by: Jhan S. Álvarez <alvarezpcuser@gmail.com>
2 years ago
CrazyMax 707dc43d55
Merge pull request #1903 from crazy-max/fix-bake-compose-profiles
bake: ignore profiles in compose definitions
2 years ago
CrazyMax 8cbb7a9319
build: fix host-gateway handling
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
CrazyMax 4f5a56aadb
Merge pull request #1904 from thaJeztah/cleanup_ParseEntitlements
utils/buildflags: ParseEntitlements(): use BuildKit's parsing
2 years ago
Sebastiaan van Stijn 399beb53d9
utils/buildflags: ParseEntitlements(): use BuildKit's parsing
Use buildkit's parsing of entitlements to make sure that accepted
values match what's accepted by BuildKit.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
Sebastiaan van Stijn 7dec9fd6e7
update flag-description for --cgroup-parent
This attempts to make it clearer that the --cgroup-parent option is only used
for the containers used during build. Instead of mentioning "build container",
I opted for using "RUN instructions" (to match the --network description),
although this may not be ideal (as it assumes the "Dockerfile" front-end, which
of course may not be the case).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2 years ago
CrazyMax 120f3a8918
bake: ignore profiles in compose definitions
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
CrazyMax bd672eaf5b
Merge pull request #1886 from crazy-max/docker-local
build: prefer local image resolution for docker driver
2 years ago
CrazyMax c2500ea2d8
build: prefer local image resolution for docker driver
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2 years ago
Justin Chadwell a4663b4b2e
Merge pull request #1900 from cyphar/build-go_extra_flags 2 years ago
Aleksa Sarai 57c618b83a
build: add GO_EXTRA_FLAGS argument
This is useful for setting things like -buildmode=pie when packaging
docker-buildx for distributions.

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2 years ago
CrazyMax b3a4f95110
Merge pull request #1897 from crazy-max/test-containerd-snap
test: register docker worker with containerd snapshotter
3 years ago
CrazyMax 28a1eb3527
test: fix testImageIDOutput
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
3 years ago
CrazyMax 75ecc15958
test: fix inspect and ls
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
3 years ago
CrazyMax 2235ebce2f
test: register docker worker with containerd snapshotter
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
3 years ago
CrazyMax 7147463418
dockerfile: update docker to 24.0.2
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
3 years ago
Justin Chadwell 010e4c8d54
Merge pull request #1890 from jedevc/tests-share-docker-container-backend 3 years ago
Justin Chadwell 6f394a0691 tests: set a dedicated buildx config dir for each worker
This should help reduce any unexpected config conflict between workers.

Signed-off-by: Justin Chadwell <me@jedevc.com>
3 years ago
Justin Chadwell efd7279118 ci: run docker-container tests in parallel
Signed-off-by: Justin Chadwell <me@jedevc.com>
3 years ago
Justin Chadwell 601056f3a7 tests: share single docker between docker-container backends
This means that we can run our docker-container tests in parallel again,
which can help speed up our test runs by a *significant* amount.

Signed-off-by: Justin Chadwell <me@jedevc.com>
3 years ago
Justin Chadwell 0a7f96cbfb vendor: update buildkit to master@2d91ddcceedc
Signed-off-by: Justin Chadwell <me@jedevc.com>
3 years ago
CrazyMax 1c530c2fe0
Merge pull request #1896 from dvdksn/docs/add-experimental-debugmonitor
docs: add experimental annotation for debug-shell command
3 years ago
David Karlsson 1e576dd7c6 chore: make docs
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
3 years ago
CrazyMax 7a5472153b
docs: set experimental annotation
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
3 years ago
Dmitry Makovey b986ce566b Add Bearer token
Signed-off-by: Dmitry Makovey <dmakovey@gitlab.com>
3 years ago
CrazyMax daba16f4be
Merge pull request #1879 from crazy-max/fix-ctx-validation
builder: skip name validation for docker context
3 years ago
CrazyMax ee36e2264e
Merge pull request #1880 from jedevc/fix-dockerfile-cwd-join
bake: fix incorrect dockerfile resolution against `cwd://` context
3 years ago
CrazyMax 329e98d9f0
Merge pull request #1883 from docker/dependabot/github_actions/peter-evans/create-pull-request-5.0.2
build(deps): Bump peter-evans/create-pull-request from 5.0.1 to 5.0.2
3 years ago
dependabot[bot] f4513f7028
build(deps): Bump peter-evans/create-pull-request from 5.0.1 to 5.0.2
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 5.0.1 to 5.0.2.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](284f54f989...153407881e)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
3 years ago
CrazyMax b1c5449428
builder: skip name validation for docker context
Although a builder from the store cannot be created unless
it has a valid name, this is not the case for a Docker context.

We should skip name validation when checking a node from the
store and fall back to finding one from Docker context instead.

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
3 years ago
Justin Chadwell 431732f5d1 bake: fix incorrect dockerfile resolution against cwd:// context
We need to resolve the strip the cwd:// prefix before attempting to
resolve the dockerfile. Otherwise, we'll get the cwd:// prefix in the
dockerfile name, which isn't stripped out later.

Signed-off-by: Justin Chadwell <me@jedevc.com>
3 years ago
Justin Chadwell d0bff18cee commands: consume DEBUG environment variable
When running in standalone mode, the --debug flag passed to docker
cannot be passed. The docker cli also supports a DEBUG env var, however,
in standalone mode this won't be consumed.

This patch reads the contents of the DEBUG environment variable, and
enables debugging logs when it's been set.

Signed-off-by: Justin Chadwell <me@jedevc.com>
3 years ago
CrazyMax 8ad30d0a35
docs: update since buildinfo removal
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
3 years ago

@ -0,0 +1,25 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/go
{
"name": "Go",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/go:1-1.21-bullseye",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
}
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "go version",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

@ -31,7 +31,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
@ -66,6 +66,7 @@ jobs:
matrix: matrix:
worker: worker:
- docker - docker
- docker\+containerd # same as docker, but with containerd snapshotter
- docker-container - docker-container
- remote - remote
pkg: pkg:
@ -76,7 +77,9 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with:
fetch-depth: 0
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
@ -101,8 +104,8 @@ jobs:
export TEST_REPORT_SUFFIX=-${{ github.job }}-$(echo "${{ matrix.pkg }}-${{ matrix.skip-integration-tests }}-${{ matrix.worker }}" | tr -dc '[:alnum:]-\n\r' | tr '[:upper:]' '[:lower:]') export TEST_REPORT_SUFFIX=-${{ github.job }}-$(echo "${{ matrix.pkg }}-${{ matrix.skip-integration-tests }}-${{ matrix.worker }}" | tr -dc '[:alnum:]-\n\r' | tr '[:upper:]' '[:lower:]')
./hack/test ./hack/test
env: env:
TEST_DOCKERD: "${{ (matrix.worker == 'docker' || matrix.worker == 'docker-container') && '1' || '0' }}" TEST_DOCKERD: "${{ startsWith(matrix.worker, 'docker') && '1' || '0' }}"
TESTFLAGS: "${{ (matrix.worker == 'docker') && env.TESTFLAGS_DOCKER || env.TESTFLAGS }} --run=//worker=${{ matrix.worker }}$" TESTFLAGS: "${{ (matrix.worker == 'docker' || matrix.worker == 'docker\\+containerd') && env.TESTFLAGS_DOCKER || env.TESTFLAGS }} --run=//worker=${{ matrix.worker }}$"
TESTPKGS: "${{ matrix.pkg }}" TESTPKGS: "${{ matrix.pkg }}"
SKIP_INTEGRATION_TESTS: "${{ matrix.skip-integration-tests }}" SKIP_INTEGRATION_TESTS: "${{ matrix.skip-integration-tests }}"
- -
@ -132,7 +135,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Create matrix name: Create matrix
id: platforms id: platforms
@ -159,7 +162,7 @@ jobs:
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
@ -194,7 +197,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
@ -246,7 +249,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Download binaries name: Download binaries
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
@ -280,7 +283,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2

@ -12,7 +12,7 @@ jobs:
steps: steps:
- -
name: Checkout docs repo name: Checkout docs repo
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
token: ${{ secrets.GHPAT_DOCS_DISPATCH }} token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
repository: docker/docs repository: docker/docs
@ -44,7 +44,7 @@ jobs:
git add -A . git add -A .
- -
name: Create PR on docs repo name: Create PR on docs repo
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38
with: with:
token: ${{ secrets.GHPAT_DOCS_DISPATCH }} token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
push-to-fork: docker-tools-robot/docker.github.io push-to-fork: docker-tools-robot/docker.github.io

@ -26,7 +26,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2

@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
@ -96,7 +96,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2

@ -30,7 +30,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG GO_VERSION=1.20.6 ARG GO_VERSION=1.20.7
ARG XX_VERSION=1.2.1 ARG XX_VERSION=1.2.1
ARG DOCKER_VERSION=24.0.2 ARG DOCKER_VERSION=24.0.2
@ -121,7 +121,7 @@ RUN --mount=from=binaries \
--mount=type=bind,from=buildx-version,source=/buildx-version,target=/buildx-version <<EOT --mount=type=bind,from=buildx-version,source=/buildx-version,target=/buildx-version <<EOT
set -e set -e
mkdir -p /out mkdir -p /out
cp buildx* "/out/buildx-$(cat /buildx-version/version).$(echo $TARGETPLATFORM | sed 's/\//-/g')$(ls buildx* | sed -e 's/^buildx//')" cp buildx* "/out/buildx"
EOT EOT
FROM scratch AS release FROM scratch AS release

@ -71,8 +71,9 @@ for Windows and macOS.
## Linux packages ## Linux packages
Docker Linux packages also include Docker Buildx when installed using the Docker Engine package repositories contain Docker Buildx packages when installed according to the
[DEB or RPM packages](https://docs.docker.com/engine/install/). [Docker Engine install documentation](https://docs.docker.com/engine/install/). Install the
`docker-buildx-plugin` package to install the Buildx plugin.
## Manual download ## Manual download

@ -587,9 +587,9 @@ type Target struct {
Name string `json:"-" hcl:"name,label" cty:"name"` Name string `json:"-" hcl:"name,label" cty:"name"`
// Inherits is the only field that cannot be overridden with --set // Inherits is the only field that cannot be overridden with --set
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"` Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"` Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"` Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"` Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
@ -1048,12 +1048,12 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
bi.DockerfileInline = *t.DockerfileInline bi.DockerfileInline = *t.DockerfileInline
} }
updateContext(&bi, inp) updateContext(&bi, inp)
if !build.IsRemoteURL(bi.ContextPath) && bi.ContextState == nil && !path.IsAbs(bi.DockerfilePath) {
bi.DockerfilePath = path.Join(bi.ContextPath, bi.DockerfilePath)
}
if strings.HasPrefix(bi.ContextPath, "cwd://") { if strings.HasPrefix(bi.ContextPath, "cwd://") {
bi.ContextPath = path.Clean(strings.TrimPrefix(bi.ContextPath, "cwd://")) bi.ContextPath = path.Clean(strings.TrimPrefix(bi.ContextPath, "cwd://"))
} }
if !build.IsRemoteURL(bi.ContextPath) && bi.ContextState == nil && !path.IsAbs(bi.DockerfilePath) {
bi.DockerfilePath = path.Join(bi.ContextPath, bi.DockerfilePath)
}
for k, v := range bi.NamedContexts { for k, v := range bi.NamedContexts {
if strings.HasPrefix(v.Path, "cwd://") { if strings.HasPrefix(v.Path, "cwd://") {
bi.NamedContexts[k] = build.NamedContext{Path: path.Clean(strings.TrimPrefix(v.Path, "cwd://"))} bi.NamedContexts[k] = build.NamedContext{Path: path.Clean(strings.TrimPrefix(v.Path, "cwd://"))}

@ -295,6 +295,9 @@ services:
ctx := context.TODO() ctx := context.TODO()
cwd, err := os.Getwd()
require.NoError(t, err)
m, g, err := ReadTargets(ctx, []File{fp, fp2, fp3}, []string{"default"}, nil, nil) m, g, err := ReadTargets(ctx, []File{fp, fp2, fp3}, []string{"default"}, nil, nil)
require.NoError(t, err) require.NoError(t, err)
@ -303,7 +306,7 @@ services:
require.True(t, ok) require.True(t, ok)
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile) require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
require.Equal(t, ".", *m["webapp"].Context) require.Equal(t, cwd, *m["webapp"].Context)
require.Equal(t, ptrstr("1"), m["webapp"].Args["buildno"]) require.Equal(t, ptrstr("1"), m["webapp"].Args["buildno"])
require.Equal(t, ptrstr("12"), m["webapp"].Args["buildno2"]) require.Equal(t, ptrstr("12"), m["webapp"].Args["buildno2"])
@ -342,6 +345,9 @@ services:
ctx := context.TODO() ctx := context.TODO()
cwd, err := os.Getwd()
require.NoError(t, err)
m, _, err := ReadTargets(ctx, []File{fp}, []string{"web.app"}, nil, nil) m, _, err := ReadTargets(ctx, []File{fp}, []string{"web.app"}, nil, nil)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(m)) require.Equal(t, 1, len(m))
@ -364,7 +370,7 @@ services:
_, ok = m["web_app"] _, ok = m["web_app"]
require.True(t, ok) require.True(t, ok)
require.Equal(t, "Dockerfile.webapp", *m["web_app"].Dockerfile) require.Equal(t, "Dockerfile.webapp", *m["web_app"].Dockerfile)
require.Equal(t, ".", *m["web_app"].Context) require.Equal(t, cwd, *m["web_app"].Context)
require.Equal(t, ptrstr("1"), m["web_app"].Args["buildno"]) require.Equal(t, ptrstr("1"), m["web_app"].Args["buildno"])
require.Equal(t, ptrstr("12"), m["web_app"].Args["buildno2"]) require.Equal(t, ptrstr("12"), m["web_app"].Args["buildno2"])
@ -386,18 +392,19 @@ func TestHCLCwdPrefix(t *testing.T) {
m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil) m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(m)) bo, err := TargetsToBuildOpt(m, &Input{})
_, ok := m["app"]
require.True(t, ok)
_, err = TargetsToBuildOpt(m, &Input{})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(g))
require.Equal(t, []string{"app"}, g["default"].Targets)
require.Equal(t, 1, len(m))
require.Contains(t, m, "app")
require.Equal(t, "test", *m["app"].Dockerfile) require.Equal(t, "test", *m["app"].Dockerfile)
require.Equal(t, "foo", *m["app"].Context) require.Equal(t, "foo", *m["app"].Context)
require.Equal(t, 1, len(g)) require.Equal(t, "foo/test", bo["app"].Inputs.DockerfilePath)
require.Equal(t, []string{"app"}, g["default"].Targets) require.Equal(t, "foo", bo["app"].Inputs.ContextPath)
} }
func TestOverrideMerge(t *testing.T) { func TestOverrideMerge(t *testing.T) {
@ -542,6 +549,9 @@ services:
ctx := context.TODO() ctx := context.TODO()
cwd, err := os.Getwd()
require.NoError(t, err)
m, _, err := ReadTargets(ctx, []File{fp, fp2}, []string{"app1", "app2"}, nil, nil) m, _, err := ReadTargets(ctx, []File{fp, fp2}, []string{"app1", "app2"}, nil, nil)
require.NoError(t, err) require.NoError(t, err)
@ -554,7 +564,7 @@ services:
require.Equal(t, "Dockerfile", *m["app1"].Dockerfile) require.Equal(t, "Dockerfile", *m["app1"].Dockerfile)
require.Equal(t, ".", *m["app1"].Context) require.Equal(t, ".", *m["app1"].Context)
require.Equal(t, "Dockerfile", *m["app2"].Dockerfile) require.Equal(t, "Dockerfile", *m["app2"].Dockerfile)
require.Equal(t, ".", *m["app2"].Context) require.Equal(t, cwd, *m["app2"].Context)
} }
func TestReadContextFromTargetChain(t *testing.T) { func TestReadContextFromTargetChain(t *testing.T) {

@ -22,7 +22,7 @@ services:
build: build:
context: ./dir context: ./dir
additional_contexts: additional_contexts:
foo: /bar foo: ./bar
dockerfile: Dockerfile-alternate dockerfile: Dockerfile-alternate
network: network:
none none
@ -49,6 +49,9 @@ secrets:
file: /root/.aws/credentials file: /root/.aws/credentials
`) `)
cwd, err := os.Getwd()
require.NoError(t, err)
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil) c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
require.NoError(t, err) require.NoError(t, err)
@ -62,12 +65,12 @@ secrets:
return c.Targets[i].Name < c.Targets[j].Name return c.Targets[i].Name < c.Targets[j].Name
}) })
require.Equal(t, "db", c.Targets[0].Name) require.Equal(t, "db", c.Targets[0].Name)
require.Equal(t, "./db", *c.Targets[0].Context) require.Equal(t, filepath.Join(cwd, "db"), *c.Targets[0].Context)
require.Equal(t, []string{"docker.io/tonistiigi/db"}, c.Targets[0].Tags) require.Equal(t, []string{"docker.io/tonistiigi/db"}, c.Targets[0].Tags)
require.Equal(t, "webapp", c.Targets[1].Name) require.Equal(t, "webapp", c.Targets[1].Name)
require.Equal(t, "./dir", *c.Targets[1].Context) require.Equal(t, filepath.Join(cwd, "dir"), *c.Targets[1].Context)
require.Equal(t, map[string]string{"foo": "/bar"}, c.Targets[1].Contexts) require.Equal(t, map[string]string{"foo": filepath.Join(cwd, "bar")}, c.Targets[1].Contexts)
require.Equal(t, "Dockerfile-alternate", *c.Targets[1].Dockerfile) require.Equal(t, "Dockerfile-alternate", *c.Targets[1].Dockerfile)
require.Equal(t, 1, len(c.Targets[1].Args)) require.Equal(t, 1, len(c.Targets[1].Args))
require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"]) require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"])
@ -80,7 +83,7 @@ secrets:
}, c.Targets[1].Secrets) }, c.Targets[1].Secrets)
require.Equal(t, "webapp2", c.Targets[2].Name) require.Equal(t, "webapp2", c.Targets[2].Name)
require.Equal(t, "./dir", *c.Targets[2].Context) require.Equal(t, filepath.Join(cwd, "dir"), *c.Targets[2].Context)
require.Equal(t, "FROM alpine\n", *c.Targets[2].DockerfileInline) require.Equal(t, "FROM alpine\n", *c.Targets[2].DockerfileInline)
} }
@ -656,6 +659,66 @@ services:
require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, c.Targets[0].Args) require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, c.Targets[0].Args)
} }
func TestDependsOn(t *testing.T) {
var dt = []byte(`
services:
foo:
build:
context: .
ports:
- 3306:3306
depends_on:
- bar
bar:
build:
context: .
`)
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
require.NoError(t, err)
}
func TestInclude(t *testing.T) {
tmpdir := t.TempDir()
err := os.WriteFile(filepath.Join(tmpdir, "compose-foo.yml"), []byte(`
services:
foo:
build:
context: .
target: buildfoo
ports:
- 3306:3306
`), 0644)
require.NoError(t, err)
var dt = []byte(`
include:
- compose-foo.yml
services:
bar:
build:
context: .
target: buildbar
`)
chdir(t, tmpdir)
c, err := ParseComposeFiles([]File{{
Name: "compose.yml",
Data: dt,
}})
require.NoError(t, err)
require.Equal(t, 2, len(c.Targets))
sort.Slice(c.Targets, func(i, j int) bool {
return c.Targets[i].Name < c.Targets[j].Name
})
require.Equal(t, "bar", c.Targets[0].Name)
require.Equal(t, "buildbar", *c.Targets[0].Target)
require.Equal(t, "foo", c.Targets[1].Name)
require.Equal(t, "buildfoo", *c.Targets[1].Target)
}
// chdir changes the current working directory to the named directory, // chdir changes the current working directory to the named directory,
// and then restore the original working directory at the end of the test. // and then restore the original working directory at the end of the test.
func chdir(t *testing.T, dir string) { func chdir(t *testing.T, dir string) {

@ -267,11 +267,11 @@ func resolveDriversBase(ctx context.Context, nodes []builder.Node, opt map[strin
} }
undetectedPlatform := false undetectedPlatform := false
allPlatforms := map[string]int{} allPlatforms := map[string]struct{}{}
for _, opt := range opt { for _, opt := range opt {
for _, p := range opt.Platforms { for _, p := range opt.Platforms {
k := platforms.Format(p) k := platforms.Format(p)
allPlatforms[k] = -1 allPlatforms[k] = struct{}{}
if _, ok := availablePlatforms[k]; !ok { if _, ok := availablePlatforms[k]; !ok {
undetectedPlatform = true undetectedPlatform = true
} }
@ -454,7 +454,7 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op
attests[k] = *v attests[k] = *v
} }
} }
supportsAttestations := bopts.LLBCaps.Contains(apicaps.CapID("exporter.image.attestations")) supportsAttestations := bopts.LLBCaps.Contains(apicaps.CapID("exporter.image.attestations")) && nodeDriver.Features(ctx)[driver.MultiPlatform]
if len(attests) > 0 { if len(attests) > 0 {
if !supportsAttestations { if !supportsAttestations {
return nil, nil, errors.Errorf("attestations are not supported by the current buildkitd") return nil, nil, errors.Errorf("attestations are not supported by the current buildkitd")
@ -593,7 +593,10 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op
} }
if opt.Pull { if opt.Pull {
so.FrontendAttrs["image-resolve-mode"] = "pull" so.FrontendAttrs["image-resolve-mode"] = pb.AttrImageResolveModeForcePull
} else if nodeDriver.IsMobyDriver() {
// moby driver always resolves local images by default
so.FrontendAttrs["image-resolve-mode"] = pb.AttrImageResolveModePreferLocal
} }
if opt.Target != "" { if opt.Target != "" {
so.FrontendAttrs["target"] = opt.Target so.FrontendAttrs["target"] = opt.Target
@ -734,7 +737,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
hasMobyDriver := false hasMobyDriver := false
gitattrs, err := getGitAttributes(ctx, opt.Inputs.ContextPath, opt.Inputs.DockerfilePath) gitattrs, err := getGitAttributes(ctx, opt.Inputs.ContextPath, opt.Inputs.DockerfilePath)
if err != nil { if err != nil {
logrus.Warn(err) logrus.WithError(err).Warn("current commit information was not captured by the build")
} }
for i, np := range m[k] { for i, np := range m[k] {
node := nodes[np.driverIndex] node := nodes[np.driverIndex]
@ -1098,7 +1101,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
} }
} }
dt, desc, err := itpull.Combine(ctx, srcs) dt, desc, err := itpull.Combine(ctx, srcs, nil)
if err != nil { if err != nil {
return err return err
} }

@ -51,21 +51,21 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st
gitc, err := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd)) gitc, err := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd))
if err != nil { if err != nil {
if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() { if st, err1 := os.Stat(path.Join(wd, ".git")); err1 == nil && st.IsDir() {
return res, errors.New("buildx: git was not found in the system. Current commit information was not captured by the build") return res, errors.Wrap(err, "git was not found in the system")
} }
return return
} }
if !gitc.IsInsideWorkTree() { if !gitc.IsInsideWorkTree() {
if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() { if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() {
return res, errors.New("buildx: failed to read current commit information with git rev-parse --is-inside-work-tree") return res, errors.New("failed to read current commit information with git rev-parse --is-inside-work-tree")
} }
return res, nil return res, nil
} }
if sha, err := gitc.FullCommit(); err != nil && !gitutil.IsUnknownRevision(err) { if sha, err := gitc.FullCommit(); err != nil && !gitutil.IsUnknownRevision(err) {
return res, errors.Wrapf(err, "buildx: failed to get git commit") return res, errors.Wrap(err, "failed to get git commit")
} else if sha != "" { } else if sha != "" {
checkDirty := false checkDirty := false
if v, ok := os.LookupEnv("BUILDX_GIT_CHECK_DIRTY"); ok { if v, ok := os.LookupEnv("BUILDX_GIT_CHECK_DIRTY"); ok {
@ -95,7 +95,7 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st
if setGitLabels { if setGitLabels {
if root, err := gitc.RootDir(); err != nil { if root, err := gitc.RootDir(); err != nil {
return res, errors.Wrapf(err, "buildx: failed to get git root dir") return res, errors.Wrap(err, "failed to get git root dir")
} else if root != "" { } else if root != "" {
if dockerfilePath == "" { if dockerfilePath == "" {
dockerfilePath = filepath.Join(wd, "Dockerfile") dockerfilePath = filepath.Join(wd, "Dockerfile")

@ -388,7 +388,7 @@ func populateProcessConfigFromResult(req *gateway.StartRequest, res *gateway.Res
} else if img != nil { } else if img != nil {
args = append(args, img.Config.Entrypoint...) args = append(args, img.Config.Entrypoint...)
} }
if cfg.Cmd != nil { if !cfg.NoCmd {
args = append(args, cfg.Cmd...) args = append(args, cfg.Cmd...)
} else if img != nil { } else if img != nil {
args = append(args, img.Config.Cmd...) args = append(args, img.Config.Cmd...)

@ -109,7 +109,7 @@ func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err e
} }
} }
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, factory, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, b.opts.contextPathHash) d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, factory, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.SecurityOpts, n.Platforms, b.opts.contextPathHash)
if err != nil { if err != nil {
node.Err = err node.Err = err
return nil return nil

@ -464,7 +464,7 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
flags.StringArrayVar(&options.cacheTo, "cache-to", []string{}, `Cache export destinations (e.g., "user/app:cache", "type=local,dest=path/to/dir")`) flags.StringArrayVar(&options.cacheTo, "cache-to", []string{}, `Cache export destinations (e.g., "user/app:cache", "type=local,dest=path/to/dir")`)
flags.StringVar(&options.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container") flags.StringVar(&options.cgroupParent, "cgroup-parent", "", `Set the parent cgroup for the "RUN" instructions during build`)
flags.SetAnnotation("cgroup-parent", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#cgroup-parent"}) flags.SetAnnotation("cgroup-parent", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#cgroup-parent"})
flags.StringArrayVar(&options.contexts, "build-context", []string{}, "Additional build contexts (e.g., name=path)") flags.StringArrayVar(&options.contexts, "build-context", []string{}, "Additional build contexts (e.g., name=path)")
@ -698,6 +698,7 @@ func (cfg *invokeConfig) needsMonitor(retErr error) bool {
func parseInvokeConfig(invoke string) (cfg invokeConfig, err error) { func parseInvokeConfig(invoke string) (cfg invokeConfig, err error) {
cfg.invokeFlag = invoke cfg.invokeFlag = invoke
cfg.Tty = true cfg.Tty = true
cfg.NoCmd = true
switch invoke { switch invoke {
case "default", "debug-shell": case "default", "debug-shell":
return cfg, nil return cfg, nil
@ -706,6 +707,7 @@ func parseInvokeConfig(invoke string) (cfg invokeConfig, err error) {
// TODO: make this configurable via flags or restorable from LLB. // TODO: make this configurable via flags or restorable from LLB.
// Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113295900 // Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113295900
cfg.Cmd = []string{"/bin/sh"} cfg.Cmd = []string{"/bin/sh"}
cfg.NoCmd = false
return cfg, nil return cfg, nil
} }
@ -717,6 +719,7 @@ func parseInvokeConfig(invoke string) (cfg invokeConfig, err error) {
} }
if len(fields) == 1 && !strings.Contains(fields[0], "=") { if len(fields) == 1 && !strings.Contains(fields[0], "=") {
cfg.Cmd = []string{fields[0]} cfg.Cmd = []string{fields[0]}
cfg.NoCmd = false
return cfg, nil return cfg, nil
} }
cfg.NoUser = true cfg.NoUser = true
@ -731,10 +734,12 @@ func parseInvokeConfig(invoke string) (cfg invokeConfig, err error) {
switch key { switch key {
case "args": case "args":
cfg.Cmd = append(cfg.Cmd, maybeJSONArray(value)...) cfg.Cmd = append(cfg.Cmd, maybeJSONArray(value)...)
cfg.NoCmd = false
case "entrypoint": case "entrypoint":
cfg.Entrypoint = append(cfg.Entrypoint, maybeJSONArray(value)...) cfg.Entrypoint = append(cfg.Entrypoint, maybeJSONArray(value)...)
if cfg.Cmd == nil { if cfg.Cmd == nil {
cfg.Cmd = []string{} cfg.Cmd = []string{}
cfg.NoCmd = false
} }
case "env": case "env":
cfg.Env = append(cfg.Env, maybeJSONArray(value)...) cfg.Env = append(cfg.Env, maybeJSONArray(value)...)

@ -42,6 +42,7 @@ type createOptions struct {
flags string flags string
configFile string configFile string
driverOpts []string driverOpts []string
securityOpts []string
bootstrap bool bootstrap bool
// upgrade bool // perform upgrade of the driver // upgrade bool // perform upgrade of the driver
} }
@ -239,6 +240,11 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
return err return err
} }
s, err := csvToMap(in.securityOpts)
if err != nil {
return err
}
if in.configFile == "" { if in.configFile == "" {
// if buildkit config is not provided, check if the default one is // if buildkit config is not provided, check if the default one is
// available and use it // available and use it
@ -248,7 +254,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
} }
} }
if err := ng.Update(in.nodeName, ep, in.platform, setEp, in.actionAppend, flags, in.configFile, m); err != nil { if err := ng.Update(in.nodeName, ep, in.platform, setEp, in.actionAppend, flags, in.configFile, m, s); err != nil {
return err return err
} }
} }
@ -340,6 +346,7 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
flags.StringVar(&options.configFile, "config", "", "BuildKit config file") flags.StringVar(&options.configFile, "config", "", "BuildKit config file")
flags.StringArrayVar(&options.platform, "platform", []string{}, "Fixed platforms for current node") flags.StringArrayVar(&options.platform, "platform", []string{}, "Fixed platforms for current node")
flags.StringArrayVar(&options.driverOpts, "driver-opt", []string{}, "Options for the driver") flags.StringArrayVar(&options.driverOpts, "driver-opt", []string{}, "Options for the driver")
flags.StringArrayVar(&options.securityOpts, "security-opt", []string{}, "Options for the security profile of driver")
flags.BoolVar(&options.bootstrap, "bootstrap", false, "Boot builder after creation") flags.BoolVar(&options.bootstrap, "bootstrap", false, "Boot builder after creation")
flags.BoolVar(&options.actionAppend, "append", false, "Append a node to builder instead of changing it") flags.BoolVar(&options.actionAppend, "append", false, "Append a node to builder instead of changing it")

@ -25,6 +25,7 @@ type createOptions struct {
builder string builder string
files []string files []string
tags []string tags []string
annotations []string
dryrun bool dryrun bool
actionAppend bool actionAppend bool
progress string progress string
@ -82,6 +83,11 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
return errors.Errorf("no repositories specified, please set a reference in tag or source") return errors.Errorf("no repositories specified, please set a reference in tag or source")
} }
ann, err := parseAnnotations(in.annotations)
if err != nil {
return err
}
var defaultRepo *string var defaultRepo *string
if len(repos) == 1 { if len(repos) == 1 {
for repo := range repos { for repo := range repos {
@ -154,7 +160,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
} }
} }
dt, desc, err := r.Combine(ctx, srcs) dt, desc, err := r.Combine(ctx, srcs, ann)
if err != nil { if err != nil {
return err return err
} }
@ -264,6 +270,18 @@ func parseSource(in string) (*imagetools.Source, error) {
return &s, nil return &s, nil
} }
func parseAnnotations(in []string) (map[string]string, error) {
out := make(map[string]string)
for _, i := range in {
kv := strings.SplitN(i, "=", 2)
if len(kv) != 2 {
return nil, errors.Errorf("invalid annotation %q, expected key=value", in)
}
out[kv[0]] = kv[1]
}
return out, nil
}
func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command { func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
var options createOptions var options createOptions
@ -283,6 +301,7 @@ func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
flags.BoolVar(&options.dryrun, "dry-run", false, "Show final image instead of pushing") flags.BoolVar(&options.dryrun, "dry-run", false, "Show final image instead of pushing")
flags.BoolVar(&options.actionAppend, "append", false, "Append to existing manifest") flags.BoolVar(&options.actionAppend, "append", false, "Append to existing manifest")
flags.StringVar(&options.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`) flags.StringVar(&options.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`)
flags.StringArrayVarP(&options.annotations, "annotation", "", []string{}, "Add annotation to the image")
return cmd return cmd
} }

@ -82,6 +82,13 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
if len(driverOpts) > 0 { if len(driverOpts) > 0 {
fmt.Fprintf(w, "Driver Options:\t%s\n", strings.Join(driverOpts, " ")) fmt.Fprintf(w, "Driver Options:\t%s\n", strings.Join(driverOpts, " "))
} }
var securityOpts []string
for k, v := range n.SecurityOpts {
securityOpts = append(securityOpts, fmt.Sprintf("%s=%q", k, v))
}
if len(securityOpts) > 0 {
fmt.Fprintf(w, "Security Options:\t%s\n", strings.Join(driverOpts, " "))
}
if err := n.Err; err != nil { if err := n.Err; err != nil {
fmt.Fprintf(w, "Error:\t%s\n", err.Error()) fmt.Fprintf(w, "Error:\t%s\n", err.Error())

@ -11,6 +11,7 @@ import (
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/plugin" "github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/debug"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@ -40,6 +41,11 @@ func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Comman
cmd.TraverseChildren = true cmd.TraverseChildren = true
cmd.DisableFlagsInUseLine = true cmd.DisableFlagsInUseLine = true
cli.DisableFlagsInUseLine(cmd) cli.DisableFlagsInUseLine(cmd)
// DEBUG=1 should perform the same as --debug at the docker root level
if debug.IsEnabled() {
debug.Enable()
}
} }
logrus.SetFormatter(&logutil.Formatter{}) logrus.SetFormatter(&logutil.Formatter{})

@ -1527,6 +1527,7 @@ func (m *InitMessage) GetInvokeConfig() *InvokeConfig {
type InvokeConfig struct { type InvokeConfig struct {
Entrypoint []string `protobuf:"bytes,1,rep,name=Entrypoint,proto3" json:"Entrypoint,omitempty"` Entrypoint []string `protobuf:"bytes,1,rep,name=Entrypoint,proto3" json:"Entrypoint,omitempty"`
Cmd []string `protobuf:"bytes,2,rep,name=Cmd,proto3" json:"Cmd,omitempty"` Cmd []string `protobuf:"bytes,2,rep,name=Cmd,proto3" json:"Cmd,omitempty"`
NoCmd bool `protobuf:"varint,11,opt,name=NoCmd,proto3" json:"NoCmd,omitempty"`
Env []string `protobuf:"bytes,3,rep,name=Env,proto3" json:"Env,omitempty"` Env []string `protobuf:"bytes,3,rep,name=Env,proto3" json:"Env,omitempty"`
User string `protobuf:"bytes,4,opt,name=User,proto3" json:"User,omitempty"` User string `protobuf:"bytes,4,opt,name=User,proto3" json:"User,omitempty"`
NoUser bool `protobuf:"varint,5,opt,name=NoUser,proto3" json:"NoUser,omitempty"` NoUser bool `protobuf:"varint,5,opt,name=NoUser,proto3" json:"NoUser,omitempty"`
@ -1578,6 +1579,13 @@ func (m *InvokeConfig) GetCmd() []string {
return nil return nil
} }
func (m *InvokeConfig) GetNoCmd() bool {
if m != nil {
return m.NoCmd
}
return false
}
func (m *InvokeConfig) GetEnv() []string { func (m *InvokeConfig) GetEnv() []string {
if m != nil { if m != nil {
return m.Env return m.Env
@ -2046,125 +2054,126 @@ func init() {
func init() { proto.RegisterFile("controller.proto", fileDescriptor_ed7f10298fa1d90f) } func init() { proto.RegisterFile("controller.proto", fileDescriptor_ed7f10298fa1d90f) }
var fileDescriptor_ed7f10298fa1d90f = []byte{ var fileDescriptor_ed7f10298fa1d90f = []byte{
// 1881 bytes of a gzipped FileDescriptorProto // 1890 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x58, 0x5f, 0x6f, 0xdb, 0xc8, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x58, 0x5f, 0x6f, 0xdb, 0xc8,
0x11, 0x2f, 0x25, 0x59, 0x7f, 0x46, 0x96, 0xe3, 0x6c, 0x9d, 0x74, 0xc3, 0xa4, 0x17, 0x87, 0x49, 0x11, 0x2f, 0x25, 0x59, 0x7f, 0x46, 0x96, 0xe3, 0x6c, 0x9d, 0x74, 0xc3, 0xa4, 0x17, 0x87, 0x49,
0xae, 0x42, 0x53, 0x48, 0x77, 0xbe, 0xa6, 0xbe, 0x5c, 0xee, 0x80, 0xda, 0xb2, 0x05, 0xfb, 0x90, 0xae, 0x42, 0x53, 0x48, 0x77, 0xbe, 0xa6, 0xbe, 0x5c, 0xee, 0x80, 0xda, 0xb2, 0x05, 0xfb, 0x90,
0xd8, 0xc6, 0xca, 0xc9, 0xa1, 0x2d, 0xd0, 0x80, 0x92, 0xd6, 0x32, 0x21, 0x8a, 0xab, 0x72, 0x57, 0xd8, 0xc6, 0xca, 0xc9, 0xa1, 0x2d, 0xd0, 0x80, 0x92, 0xd6, 0x32, 0x21, 0x8a, 0xab, 0x72, 0x57,
0xb6, 0xd5, 0xa7, 0xbe, 0xf4, 0xad, 0xe8, 0xf7, 0x28, 0xfa, 0x11, 0xfa, 0xd2, 0x7e, 0xa1, 0xa2, 0xb6, 0xd5, 0xa7, 0x3e, 0xb4, 0x6f, 0x45, 0xbf, 0x47, 0xd1, 0x8f, 0xd0, 0x97, 0xf6, 0x1b, 0xf5,
0x1f, 0xa1, 0xd8, 0x3f, 0xa4, 0x48, 0x4b, 0x94, 0xed, 0xf6, 0x49, 0x3b, 0xc3, 0xdf, 0x6f, 0x76, 0x23, 0x14, 0xfb, 0x87, 0x14, 0x69, 0x89, 0xb2, 0xdd, 0x3e, 0x69, 0x67, 0xf8, 0xfb, 0xcd, 0xec,
0x67, 0x38, 0x3b, 0x33, 0x14, 0xac, 0xf7, 0x58, 0x20, 0x42, 0xe6, 0xfb, 0x34, 0x6c, 0x8c, 0x43, 0x2c, 0x67, 0x67, 0x86, 0x82, 0xf5, 0x1e, 0x0b, 0x44, 0xc8, 0x7c, 0x9f, 0x86, 0x8d, 0x71, 0xc8,
0x26, 0x18, 0xda, 0xe8, 0x4e, 0x3c, 0xbf, 0x7f, 0xd5, 0x48, 0x3c, 0xb8, 0xf8, 0xd2, 0x7e, 0x3b, 0x04, 0x43, 0x1b, 0xdd, 0x89, 0xe7, 0xf7, 0xaf, 0x1a, 0x89, 0x07, 0x17, 0x5f, 0xda, 0x6f, 0x07,
0xf0, 0xc4, 0xf9, 0xa4, 0xdb, 0xe8, 0xb1, 0x51, 0x73, 0xc4, 0xba, 0xd3, 0xa6, 0x42, 0x0d, 0x3d, 0x9e, 0x38, 0x9f, 0x74, 0x1b, 0x3d, 0x36, 0x6a, 0x8e, 0x58, 0x77, 0xda, 0x54, 0xa8, 0xa1, 0x27,
0xd1, 0x74, 0xc7, 0x5e, 0x93, 0xd3, 0xf0, 0xc2, 0xeb, 0x51, 0xde, 0x34, 0xa4, 0xe8, 0x57, 0x9b, 0x9a, 0xee, 0xd8, 0x6b, 0x72, 0x1a, 0x5e, 0x78, 0x3d, 0xca, 0x9b, 0x86, 0x14, 0xfd, 0x6a, 0x93,
0xb4, 0x5f, 0x67, 0x92, 0x39, 0x9b, 0x84, 0x3d, 0x3a, 0x66, 0xbe, 0xd7, 0x9b, 0x36, 0xc7, 0xdd, 0xf6, 0xeb, 0x4c, 0x32, 0x67, 0x93, 0xb0, 0x47, 0xc7, 0xcc, 0xf7, 0x7a, 0xd3, 0xe6, 0xb8, 0xdb,
0xa6, 0x5e, 0x69, 0x9a, 0x53, 0x87, 0x8d, 0x77, 0x1e, 0x17, 0x27, 0x21, 0xeb, 0x51, 0xce, 0x29, 0xd4, 0x2b, 0x4d, 0x73, 0xea, 0xb0, 0xf1, 0xce, 0xe3, 0xe2, 0x24, 0x64, 0x3d, 0xca, 0x39, 0xe5,
0x27, 0xf4, 0x0f, 0x13, 0xca, 0x05, 0x5a, 0x87, 0x3c, 0xa1, 0x67, 0xd8, 0xda, 0xb4, 0xea, 0x15, 0x84, 0xfe, 0x61, 0x42, 0xb9, 0x40, 0xeb, 0x90, 0x27, 0xf4, 0x0c, 0x5b, 0x9b, 0x56, 0xbd, 0x42,
0x22, 0x97, 0xce, 0x09, 0x3c, 0xb8, 0x86, 0xe4, 0x63, 0x16, 0x70, 0x8a, 0xb6, 0x61, 0xe5, 0x30, 0xe4, 0xd2, 0x39, 0x81, 0x07, 0xd7, 0x90, 0x7c, 0xcc, 0x02, 0x4e, 0xd1, 0x36, 0xac, 0x1c, 0x06,
0x38, 0x63, 0x1c, 0x5b, 0x9b, 0xf9, 0x7a, 0x75, 0xeb, 0x59, 0x63, 0x91, 0x73, 0x0d, 0xc3, 0x93, 0x67, 0x8c, 0x63, 0x6b, 0x33, 0x5f, 0xaf, 0x6e, 0x3d, 0x6b, 0x2c, 0x0a, 0xae, 0x61, 0x78, 0x12,
0x48, 0xa2, 0xf1, 0x0e, 0x87, 0x6a, 0x42, 0x8b, 0x9e, 0x40, 0x25, 0x12, 0xf7, 0xcc, 0xc6, 0x33, 0x49, 0x34, 0xde, 0xe1, 0x50, 0x4d, 0x68, 0xd1, 0x13, 0xa8, 0x44, 0xe2, 0x9e, 0x71, 0x3c, 0x53,
0x05, 0x6a, 0xc3, 0xea, 0x61, 0x70, 0xc1, 0x86, 0xb4, 0xc5, 0x82, 0x33, 0x6f, 0x80, 0x73, 0x9b, 0xa0, 0x36, 0xac, 0x1e, 0x06, 0x17, 0x6c, 0x48, 0x5b, 0x2c, 0x38, 0xf3, 0x06, 0x38, 0xb7, 0x69,
0x56, 0xbd, 0xba, 0xe5, 0x2c, 0xde, 0x2c, 0x89, 0x24, 0x29, 0x9e, 0xf3, 0x3d, 0xe0, 0x3d, 0x8f, 0xd5, 0xab, 0x5b, 0xce, 0x62, 0x67, 0x49, 0x24, 0x49, 0xf1, 0x9c, 0xef, 0x01, 0xef, 0x79, 0xbc,
0xf7, 0x58, 0x10, 0xd0, 0x5e, 0xe4, 0x4c, 0xa6, 0xd3, 0xe9, 0x33, 0xe5, 0xae, 0x9d, 0xc9, 0x79, 0xc7, 0x82, 0x80, 0xf6, 0xa2, 0x60, 0x32, 0x83, 0x4e, 0xef, 0x29, 0x77, 0x6d, 0x4f, 0xce, 0x63,
0x0c, 0x8f, 0x16, 0xd8, 0xd2, 0x61, 0x71, 0x7e, 0x0f, 0xab, 0xbb, 0xf2, 0x6c, 0xd9, 0xc6, 0xbf, 0x78, 0xb4, 0xc0, 0x96, 0x3e, 0x16, 0xe7, 0xf7, 0xb0, 0xba, 0x2b, 0xf7, 0x96, 0x6d, 0xfc, 0x5b,
0x85, 0xd2, 0xf1, 0x58, 0x78, 0x2c, 0xe0, 0xcb, 0xbd, 0x51, 0x66, 0x0c, 0x92, 0x44, 0x14, 0xe7, 0x28, 0x1d, 0x8f, 0x85, 0xc7, 0x02, 0xbe, 0x3c, 0x1a, 0x65, 0xc6, 0x20, 0x49, 0x44, 0x71, 0xfe,
0x9f, 0x55, 0xb3, 0x81, 0x51, 0xa0, 0x4d, 0xa8, 0xb6, 0x58, 0x20, 0xe8, 0x95, 0x38, 0x71, 0xc5, 0x55, 0x35, 0x0e, 0x8c, 0x02, 0x6d, 0x42, 0xb5, 0xc5, 0x02, 0x41, 0xaf, 0xc4, 0x89, 0x2b, 0xce,
0xb9, 0xd9, 0x28, 0xa9, 0x42, 0x9f, 0xc3, 0xda, 0x1e, 0xeb, 0x0d, 0x69, 0x78, 0xe6, 0xf9, 0xf4, 0x8d, 0xa3, 0xa4, 0x0a, 0x7d, 0x0e, 0x6b, 0x7b, 0xac, 0x37, 0xa4, 0xe1, 0x99, 0xe7, 0xd3, 0x23,
0xc8, 0x1d, 0x51, 0xe3, 0xd2, 0x35, 0x2d, 0xfa, 0x4e, 0x7a, 0xed, 0x05, 0xa2, 0x3d, 0x09, 0x7a, 0x77, 0x44, 0x4d, 0x48, 0xd7, 0xb4, 0xe8, 0x3b, 0x19, 0xb5, 0x17, 0x88, 0xf6, 0x24, 0xe8, 0xe1,
0x38, 0xaf, 0x8e, 0xf6, 0x34, 0xeb, 0xad, 0x1a, 0x18, 0x99, 0x31, 0xd0, 0xef, 0xa0, 0x26, 0xcd, 0xbc, 0xda, 0xda, 0xd3, 0xac, 0xb7, 0x6a, 0x60, 0x64, 0xc6, 0x40, 0xbf, 0x83, 0x9a, 0x34, 0xd3,
0xf4, 0xcd, 0xd6, 0x1c, 0x17, 0x54, 0x62, 0xbc, 0xbe, 0xd9, 0xbb, 0x46, 0x8a, 0xb7, 0x1f, 0x88, 0x37, 0xae, 0x39, 0x2e, 0xa8, 0xc4, 0x78, 0x7d, 0x73, 0x74, 0x8d, 0x14, 0x6f, 0x3f, 0x10, 0xe1,
0x70, 0x4a, 0xd2, 0xb6, 0xd0, 0x06, 0xac, 0xec, 0xf8, 0x3e, 0xbb, 0xc4, 0x2b, 0x9b, 0xf9, 0x7a, 0x94, 0xa4, 0x6d, 0xa1, 0x0d, 0x58, 0xd9, 0xf1, 0x7d, 0x76, 0x89, 0x57, 0x36, 0xf3, 0xf5, 0x0a,
0x85, 0x68, 0x01, 0xfd, 0x0a, 0x4a, 0x3b, 0x42, 0x50, 0x2e, 0x38, 0x2e, 0xaa, 0xcd, 0x9e, 0x2c, 0xd1, 0x02, 0xfa, 0x15, 0x94, 0x76, 0x84, 0xa0, 0x5c, 0x70, 0x5c, 0x54, 0xce, 0x9e, 0x2c, 0x76,
0xde, 0x4c, 0x83, 0x48, 0x04, 0x46, 0xc7, 0x50, 0x51, 0xfb, 0xef, 0x84, 0x03, 0x8e, 0x4b, 0x8a, 0xa6, 0x41, 0x24, 0x02, 0xa3, 0x63, 0xa8, 0x28, 0xff, 0x3b, 0xe1, 0x80, 0xe3, 0x92, 0x62, 0x7e,
0xf9, 0xe5, 0x2d, 0x8e, 0x19, 0x73, 0xf4, 0x11, 0x67, 0x36, 0xd0, 0x3e, 0x54, 0x5a, 0x6e, 0xef, 0x79, 0x8b, 0x6d, 0xc6, 0x1c, 0xbd, 0xc5, 0x99, 0x0d, 0xb4, 0x0f, 0x95, 0x96, 0xdb, 0x3b, 0xa7,
0x9c, 0xb6, 0x43, 0x36, 0xc2, 0x65, 0x65, 0xf0, 0x67, 0x8b, 0x0d, 0x2a, 0x98, 0x31, 0x68, 0xcc, 0xed, 0x90, 0x8d, 0x70, 0x59, 0x19, 0xfc, 0xd9, 0x62, 0x83, 0x0a, 0x66, 0x0c, 0x1a, 0x33, 0x31,
0xc4, 0x4c, 0xb4, 0x03, 0x25, 0x25, 0x9c, 0x32, 0x5c, 0xb9, 0x9b, 0x91, 0x88, 0x87, 0x1c, 0x58, 0x13, 0xed, 0x40, 0x49, 0x09, 0xa7, 0x0c, 0x57, 0xee, 0x66, 0x24, 0xe2, 0x21, 0x07, 0x56, 0x5b,
0x6d, 0x0d, 0x42, 0x36, 0x19, 0x9f, 0xb8, 0x21, 0x0d, 0x04, 0x06, 0xf5, 0xaa, 0x53, 0x3a, 0xf4, 0x83, 0x90, 0x4d, 0xc6, 0x27, 0x6e, 0x48, 0x03, 0x81, 0x41, 0xbd, 0xea, 0x94, 0x0e, 0xbd, 0x85,
0x16, 0x4a, 0xfb, 0x57, 0x63, 0x16, 0x0a, 0x8e, 0xab, 0xcb, 0x2e, 0xaf, 0x06, 0x99, 0x0d, 0x0c, 0xd2, 0xfe, 0xd5, 0x98, 0x85, 0x82, 0xe3, 0xea, 0xb2, 0xcb, 0xab, 0x41, 0xc6, 0x81, 0x61, 0xa0,
0x03, 0x7d, 0x06, 0xb0, 0x7f, 0x25, 0x42, 0xf7, 0x80, 0xc9, 0xb0, 0xaf, 0xaa, 0xd7, 0x91, 0xd0, 0xcf, 0x00, 0xf6, 0xaf, 0x44, 0xe8, 0x1e, 0x30, 0x79, 0xec, 0xab, 0xea, 0x75, 0x24, 0x34, 0xa8,
0xa0, 0x36, 0x14, 0xdf, 0xb9, 0x5d, 0xea, 0x73, 0x5c, 0x53, 0xb6, 0x1b, 0xb7, 0x08, 0xac, 0x26, 0x0d, 0xc5, 0x77, 0x6e, 0x97, 0xfa, 0x1c, 0xd7, 0x94, 0xed, 0xc6, 0x2d, 0x0e, 0x56, 0x13, 0xb4,
0xe8, 0x8d, 0x0c, 0x5b, 0xe6, 0xf5, 0x11, 0x15, 0x97, 0x2c, 0x1c, 0xbe, 0x67, 0x7d, 0x8a, 0xd7, 0x23, 0xc3, 0x96, 0x79, 0x7d, 0x44, 0xc5, 0x25, 0x0b, 0x87, 0xef, 0x59, 0x9f, 0xe2, 0x35, 0x9d,
0x74, 0x5e, 0x27, 0x54, 0xe8, 0x05, 0xd4, 0x8e, 0x98, 0x0e, 0x9e, 0xe7, 0x0b, 0x1a, 0xe2, 0x7b, 0xd7, 0x09, 0x15, 0x7a, 0x01, 0xb5, 0x23, 0xa6, 0x0f, 0xcf, 0xf3, 0x05, 0x0d, 0xf1, 0x3d, 0xb5,
0xea, 0x30, 0x69, 0xa5, 0xba, 0xcb, 0xbe, 0x2b, 0xce, 0x58, 0x38, 0xe2, 0x78, 0x5d, 0x21, 0x66, 0x99, 0xb4, 0x52, 0xdd, 0x65, 0xdf, 0x15, 0x67, 0x2c, 0x1c, 0x71, 0xbc, 0xae, 0x10, 0x33, 0x85,
0x0a, 0x99, 0x41, 0x1d, 0xda, 0x0b, 0xa9, 0xe0, 0xf8, 0xfe, 0xb2, 0x0c, 0xd2, 0x20, 0x12, 0x81, 0xcc, 0xa0, 0x0e, 0xed, 0x85, 0x54, 0x70, 0x7c, 0x7f, 0x59, 0x06, 0x69, 0x10, 0x89, 0xc0, 0x08,
0x11, 0x86, 0x52, 0xe7, 0x7c, 0xd4, 0xf1, 0xfe, 0x48, 0x31, 0xda, 0xb4, 0xea, 0x79, 0x12, 0x89, 0x43, 0xa9, 0x73, 0x3e, 0xea, 0x78, 0x7f, 0xa4, 0x18, 0x6d, 0x5a, 0xf5, 0x3c, 0x89, 0x44, 0xf4,
0xe8, 0x15, 0xe4, 0x3b, 0x9d, 0x03, 0xfc, 0x63, 0x65, 0xed, 0x51, 0x86, 0xb5, 0xce, 0x01, 0x91, 0x0a, 0xf2, 0x9d, 0xce, 0x01, 0xfe, 0xb1, 0xb2, 0xf6, 0x28, 0xc3, 0x5a, 0xe7, 0x80, 0x48, 0x14,
0x28, 0x84, 0xa0, 0x70, 0xea, 0x0e, 0x38, 0xde, 0x50, 0xe7, 0x52, 0x6b, 0xf4, 0x10, 0x8a, 0xa7, 0x42, 0x50, 0x38, 0x75, 0x07, 0x1c, 0x6f, 0xa8, 0x7d, 0xa9, 0x35, 0x7a, 0x08, 0xc5, 0x53, 0x37,
0x6e, 0x38, 0xa0, 0x02, 0x3f, 0x50, 0x3e, 0x1b, 0x09, 0xbd, 0x81, 0xd2, 0x07, 0xdf, 0x1b, 0x79, 0x1c, 0x50, 0x81, 0x1f, 0xa8, 0x98, 0x8d, 0x84, 0xde, 0x40, 0xe9, 0x83, 0xef, 0x8d, 0x3c, 0xc1,
0x82, 0xe3, 0x87, 0xcb, 0x2e, 0xa7, 0x06, 0x1d, 0x8f, 0x05, 0x89, 0xf0, 0xf2, 0xb4, 0x2a, 0xde, 0xf1, 0xc3, 0x65, 0x97, 0x53, 0x83, 0x8e, 0xc7, 0x82, 0x44, 0x78, 0xb9, 0x5b, 0x75, 0xde, 0x34,
0x34, 0xc4, 0x3f, 0x51, 0x36, 0x23, 0x51, 0x3e, 0x31, 0xe1, 0xc2, 0x78, 0xd3, 0xaa, 0x97, 0x49, 0xc4, 0x3f, 0x51, 0x36, 0x23, 0x51, 0x3e, 0x31, 0xc7, 0x85, 0xf1, 0xa6, 0x55, 0x2f, 0x93, 0x48,
0x24, 0xca, 0xa3, 0x9d, 0x4c, 0x7c, 0x1f, 0x3f, 0x52, 0x6a, 0xb5, 0xd6, 0xef, 0x5e, 0xa6, 0xc1, 0x94, 0x5b, 0x3b, 0x99, 0xf8, 0x3e, 0x7e, 0xa4, 0xd4, 0x6a, 0xad, 0xdf, 0xbd, 0x4c, 0x83, 0x93,
0xc9, 0x84, 0x9f, 0x63, 0x5b, 0x3d, 0x49, 0x68, 0x66, 0xcf, 0xdf, 0x31, 0xb7, 0x8f, 0x1f, 0x27, 0x09, 0x3f, 0xc7, 0xb6, 0x7a, 0x92, 0xd0, 0xcc, 0x9e, 0xbf, 0x63, 0x6e, 0x1f, 0x3f, 0x4e, 0x3e,
0x9f, 0x4b, 0x0d, 0x3a, 0x84, 0xd5, 0x8e, 0x6a, 0x4b, 0x27, 0xaa, 0x19, 0xe1, 0x27, 0xca, 0x8f, 0x97, 0x1a, 0x74, 0x08, 0xab, 0x1d, 0xd5, 0x96, 0x4e, 0x54, 0x33, 0xc2, 0x4f, 0x54, 0x1c, 0x2f,
0x97, 0x0d, 0xd9, 0xb9, 0x1a, 0x51, 0xe7, 0x92, 0x3e, 0x24, 0x9b, 0x57, 0x43, 0x83, 0x49, 0x8a, 0x1b, 0xb2, 0x73, 0x35, 0xa2, 0xce, 0x25, 0x63, 0x48, 0x36, 0xaf, 0x86, 0x06, 0x93, 0x14, 0xd5,
0x6a, 0xff, 0x1a, 0xd0, 0x7c, 0xd5, 0x90, 0xd5, 0x76, 0x48, 0xa7, 0x51, 0xb5, 0x1d, 0xd2, 0xa9, 0xfe, 0x35, 0xa0, 0xf9, 0xaa, 0x21, 0xab, 0xed, 0x90, 0x4e, 0xa3, 0x6a, 0x3b, 0xa4, 0x53, 0x59,
0x2c, 0x1c, 0x17, 0xae, 0x3f, 0x89, 0x6a, 0x9e, 0x16, 0xbe, 0xc9, 0x7d, 0x6d, 0xd9, 0xdf, 0xc2, 0x38, 0x2e, 0x5c, 0x7f, 0x12, 0xd5, 0x3c, 0x2d, 0x7c, 0x93, 0xfb, 0xda, 0xb2, 0xbf, 0x85, 0xb5,
0x5a, 0xfa, 0x42, 0xdf, 0x89, 0xfd, 0x06, 0xaa, 0x89, 0xac, 0xbd, 0x0b, 0xd5, 0xf9, 0x97, 0x05, 0xf4, 0x85, 0xbe, 0x13, 0xfb, 0x0d, 0x54, 0x13, 0x59, 0x7b, 0x17, 0xaa, 0xf3, 0x6f, 0x0b, 0xaa,
0xd5, 0xc4, 0xd5, 0x52, 0x49, 0x30, 0x1d, 0x53, 0x43, 0x56, 0x6b, 0xb4, 0x0b, 0x2b, 0x3b, 0x42, 0x89, 0xab, 0xa5, 0x92, 0x60, 0x3a, 0xa6, 0x86, 0xac, 0xd6, 0x68, 0x17, 0x56, 0x76, 0x84, 0x08,
0x84, 0xb2, 0x45, 0xc8, 0x3c, 0xfa, 0xc5, 0x8d, 0x17, 0xb4, 0xa1, 0xe0, 0xfa, 0x0a, 0x69, 0xaa, 0x65, 0x8b, 0x90, 0x79, 0xf4, 0x8b, 0x1b, 0x2f, 0x68, 0x43, 0xc1, 0xf5, 0x15, 0xd2, 0x54, 0x79,
0xbc, 0x41, 0x7b, 0x94, 0x0b, 0x2f, 0x70, 0xe5, 0x2d, 0x53, 0x15, 0xbd, 0x42, 0x92, 0x2a, 0xfb, 0x83, 0xf6, 0x28, 0x17, 0x5e, 0xe0, 0xca, 0x5b, 0xa6, 0x2a, 0x7a, 0x85, 0x24, 0x55, 0xf6, 0xd7,
0x6b, 0x80, 0x19, 0xed, 0x4e, 0x3e, 0xfc, 0xdd, 0x82, 0xfb, 0x73, 0x55, 0x68, 0xa1, 0x27, 0x07, 0x00, 0x33, 0xda, 0x9d, 0x62, 0xf8, 0x87, 0x05, 0xf7, 0xe7, 0xaa, 0xd0, 0xc2, 0x48, 0x0e, 0xd2,
0x69, 0x4f, 0xb6, 0x6e, 0x59, 0xd1, 0xe6, 0xfd, 0xf9, 0x3f, 0x4e, 0x7b, 0x04, 0x45, 0x5d, 0xfa, 0x91, 0x6c, 0xdd, 0xb2, 0xa2, 0xcd, 0xc7, 0xf3, 0x7f, 0xec, 0xf6, 0x08, 0x8a, 0xba, 0xf4, 0x2f,
0x17, 0x9e, 0xd0, 0x86, 0xf2, 0x9e, 0xc7, 0xdd, 0xae, 0x4f, 0xfb, 0x8a, 0x5a, 0x26, 0xb1, 0xac, 0xdc, 0xa1, 0x0d, 0xe5, 0x3d, 0x8f, 0xbb, 0x5d, 0x9f, 0xf6, 0x15, 0xb5, 0x4c, 0x62, 0x59, 0xf5,
0xfa, 0x8e, 0x3a, 0xbd, 0x8e, 0x9e, 0x16, 0x1c, 0x7d, 0xc7, 0xd1, 0x1a, 0xe4, 0xe2, 0x99, 0x25, 0x1d, 0xb5, 0x7b, 0x7d, 0x7a, 0x5a, 0x70, 0xf4, 0x1d, 0x47, 0x6b, 0x90, 0x8b, 0x67, 0x96, 0xdc,
0x77, 0xb8, 0x27, 0xc1, 0xb2, 0xe1, 0x6a, 0x57, 0x2b, 0x44, 0x0b, 0x4e, 0x1b, 0x8a, 0xba, 0x6a, 0xe1, 0x9e, 0x04, 0xcb, 0x86, 0xab, 0x43, 0xad, 0x10, 0x2d, 0x38, 0x6d, 0x28, 0xea, 0xaa, 0x31,
0xcc, 0xe1, 0x6d, 0x28, 0xb7, 0x3d, 0x9f, 0xaa, 0xbe, 0xad, 0xcf, 0x1c, 0xcb, 0xd2, 0xbd, 0xfd, 0x87, 0xb7, 0xa1, 0xdc, 0xf6, 0x7c, 0xaa, 0xfa, 0xb6, 0xde, 0x73, 0x2c, 0xcb, 0xf0, 0xf6, 0x83,
0xe0, 0xc2, 0x6c, 0x2b, 0x97, 0xce, 0x76, 0xa2, 0x3d, 0x4b, 0x3f, 0x54, 0x27, 0x37, 0x7e, 0xa8, 0x0b, 0xe3, 0x56, 0x2e, 0x9d, 0xed, 0x44, 0x7b, 0x96, 0x71, 0xa8, 0x4e, 0x6e, 0xe2, 0x50, 0xfd,
0xfe, 0xfd, 0x10, 0x8a, 0x6d, 0x16, 0x8e, 0x5c, 0x61, 0x8c, 0x19, 0xc9, 0x71, 0x60, 0xed, 0x30, 0xfb, 0x21, 0x14, 0xdb, 0x2c, 0x1c, 0xb9, 0xc2, 0x18, 0x33, 0x92, 0xe3, 0xc0, 0xda, 0x61, 0xc0,
0xe0, 0x63, 0xda, 0x13, 0xd9, 0x63, 0xde, 0x31, 0xdc, 0x8b, 0x31, 0x66, 0xc0, 0x4b, 0xcc, 0x29, 0xc7, 0xb4, 0x27, 0xb2, 0xc7, 0xbc, 0x63, 0xb8, 0x17, 0x63, 0xcc, 0x80, 0x97, 0x98, 0x53, 0xac,
0xd6, 0xdd, 0xe7, 0x94, 0xbf, 0x59, 0x50, 0x89, 0x2b, 0x11, 0x6a, 0x41, 0x51, 0xbd, 0x8d, 0x68, 0xbb, 0xcf, 0x29, 0x7f, 0xb7, 0xa0, 0x12, 0x57, 0x22, 0xd4, 0x82, 0xa2, 0x7a, 0x1b, 0xd1, 0xb4,
0x5a, 0x7c, 0x75, 0x43, 0xe9, 0x6a, 0x7c, 0x54, 0x68, 0xd3, 0x11, 0x34, 0xd5, 0xfe, 0x01, 0xaa, 0xf8, 0xea, 0x86, 0xd2, 0xd5, 0xf8, 0xa8, 0xd0, 0xa6, 0x23, 0x68, 0xaa, 0xfd, 0x03, 0x54, 0x13,
0x09, 0xf5, 0x82, 0x04, 0xd8, 0x4a, 0x26, 0x40, 0x66, 0x29, 0xd7, 0x9b, 0x24, 0xd3, 0x63, 0x0f, 0xea, 0x05, 0x09, 0xb0, 0x95, 0x4c, 0x80, 0xcc, 0x52, 0xae, 0x9d, 0x24, 0xd3, 0x63, 0x0f, 0x8a,
0x8a, 0x5a, 0xb9, 0x30, 0xac, 0x08, 0x0a, 0x07, 0x6e, 0xa8, 0x53, 0x23, 0x4f, 0xd4, 0x5a, 0xea, 0x5a, 0xb9, 0xf0, 0x58, 0x11, 0x14, 0x0e, 0xdc, 0x50, 0xa7, 0x46, 0x9e, 0xa8, 0xb5, 0xd4, 0x75,
0x3a, 0xec, 0x4c, 0xa8, 0xd7, 0x93, 0x27, 0x6a, 0xed, 0xfc, 0xc3, 0x82, 0x9a, 0x19, 0xfd, 0x4c, 0xd8, 0x99, 0x50, 0xaf, 0x27, 0x4f, 0xd4, 0xda, 0xf9, 0xa7, 0x05, 0x35, 0x33, 0xfa, 0x99, 0x13,
0x04, 0x29, 0xac, 0xeb, 0x1b, 0x4a, 0xc3, 0x48, 0x67, 0xfc, 0x7f, 0xb3, 0x24, 0x94, 0x11, 0xb4, 0xa4, 0xb0, 0xae, 0x6f, 0x28, 0x0d, 0x23, 0x9d, 0x89, 0xff, 0xcd, 0x92, 0xa3, 0x8c, 0xa0, 0x8d,
0x71, 0x9d, 0xab, 0xa3, 0x31, 0x67, 0xd2, 0x6e, 0xc1, 0x83, 0x85, 0xd0, 0x3b, 0x5d, 0x91, 0x97, 0xeb, 0x5c, 0x7d, 0x1a, 0x73, 0x26, 0xed, 0x16, 0x3c, 0x58, 0x08, 0xbd, 0xd3, 0x15, 0x79, 0x09,
0x70, 0x7f, 0x36, 0xd4, 0x66, 0xe7, 0xc9, 0x06, 0xa0, 0x24, 0xcc, 0x0c, 0xbd, 0x4f, 0xa1, 0x2a, 0xf7, 0x67, 0x43, 0x6d, 0x76, 0x9e, 0x6c, 0x00, 0x4a, 0xc2, 0xcc, 0xd0, 0xfb, 0x14, 0xaa, 0xf2,
0x3f, 0x12, 0xb2, 0x69, 0x0e, 0xac, 0x6a, 0x80, 0x89, 0x0c, 0x82, 0xc2, 0x90, 0x4e, 0x75, 0x36, 0x23, 0x21, 0x9b, 0xe6, 0xc0, 0xaa, 0x06, 0x98, 0x93, 0x41, 0x50, 0x18, 0xd2, 0xa9, 0xce, 0x86,
0x54, 0x88, 0x5a, 0x3b, 0x7f, 0xb5, 0xe4, 0xac, 0x3f, 0x9e, 0x88, 0xf7, 0x94, 0x73, 0x77, 0x20, 0x0a, 0x51, 0x6b, 0xe7, 0x6f, 0x96, 0x9c, 0xf5, 0xc7, 0x13, 0xf1, 0x9e, 0x72, 0xee, 0x0e, 0x64,
0x13, 0xb0, 0x70, 0x18, 0x78, 0xc2, 0x64, 0xdf, 0xe7, 0x59, 0x33, 0xff, 0x78, 0x22, 0x24, 0xcc, 0x02, 0x16, 0x0e, 0x03, 0x4f, 0x98, 0xec, 0xfb, 0x3c, 0x6b, 0xe6, 0x1f, 0x4f, 0x84, 0x84, 0x19,
0xb0, 0x0e, 0x7e, 0x44, 0x14, 0x0b, 0x6d, 0x43, 0x61, 0xcf, 0x15, 0xae, 0xc9, 0x85, 0x8c, 0x09, 0xd6, 0xc1, 0x8f, 0x88, 0x62, 0xa1, 0x6d, 0x28, 0xec, 0xb9, 0xc2, 0x35, 0xb9, 0x90, 0x31, 0xe1,
0x47, 0x22, 0x12, 0x44, 0x29, 0xee, 0x96, 0xe4, 0x87, 0xcd, 0x78, 0x22, 0x9c, 0x17, 0xb0, 0x7e, 0x48, 0x44, 0x82, 0x28, 0xc5, 0xdd, 0x92, 0xfc, 0xb0, 0x19, 0x4f, 0x84, 0xf3, 0x02, 0xd6, 0xaf,
0xdd, 0xfa, 0x02, 0xd7, 0xbe, 0x82, 0x6a, 0xc2, 0x8a, 0xba, 0xb7, 0xc7, 0x6d, 0x05, 0x28, 0x13, 0x5b, 0x5f, 0x10, 0xda, 0x57, 0x50, 0x4d, 0x58, 0x51, 0xf7, 0xf6, 0xb8, 0xad, 0x00, 0x65, 0x22,
0xb9, 0x94, 0xbe, 0xc6, 0x07, 0x59, 0xd5, 0x7b, 0x38, 0xf7, 0xa0, 0xa6, 0x4c, 0xc7, 0x11, 0xfc, 0x97, 0x32, 0xd6, 0x78, 0x23, 0xab, 0xda, 0x87, 0x73, 0x0f, 0x6a, 0xca, 0x74, 0x7c, 0x82, 0x7f,
0x53, 0x0e, 0x4a, 0x91, 0x89, 0xed, 0x94, 0xdf, 0xcf, 0xb2, 0xfc, 0x9e, 0x77, 0xf9, 0x35, 0x14, 0xca, 0x41, 0x29, 0x32, 0xb1, 0x9d, 0x8a, 0xfb, 0x59, 0x56, 0xdc, 0xf3, 0x21, 0xbf, 0x86, 0x82,
0x64, 0xfd, 0x30, 0x2e, 0x67, 0x8c, 0x07, 0xed, 0x7e, 0x82, 0x26, 0xe1, 0xe8, 0x3b, 0x28, 0x12, 0xac, 0x1f, 0x26, 0xe4, 0x8c, 0xf1, 0xa0, 0xdd, 0x4f, 0xd0, 0x24, 0x1c, 0x7d, 0x07, 0x45, 0x42,
0xca, 0xe5, 0x28, 0xa3, 0x87, 0xfe, 0xe7, 0x8b, 0x89, 0x1a, 0x33, 0x23, 0x1b, 0x92, 0xa4, 0x77, 0xb9, 0x1c, 0x65, 0xf4, 0xd0, 0xff, 0x7c, 0x31, 0x51, 0x63, 0x66, 0x64, 0x43, 0x92, 0xf4, 0x8e,
0xbc, 0x41, 0xe0, 0xfa, 0xb8, 0xb0, 0x8c, 0xae, 0x31, 0x09, 0xba, 0x56, 0xcc, 0xc2, 0xfd, 0x67, 0x37, 0x08, 0x5c, 0x1f, 0x17, 0x96, 0xd1, 0x35, 0x26, 0x41, 0xd7, 0x8a, 0xd9, 0x71, 0xff, 0xc5,
0x0b, 0xaa, 0x4b, 0x43, 0xbd, 0xfc, 0xb3, 0x6c, 0xee, 0x53, 0x31, 0xff, 0x3f, 0x7e, 0x2a, 0xfe, 0x82, 0xea, 0xd2, 0xa3, 0x5e, 0xfe, 0x59, 0x36, 0xf7, 0xa9, 0x98, 0xff, 0x1f, 0x3f, 0x15, 0xff,
0xdb, 0x4a, 0x1b, 0x52, 0x53, 0x8d, 0xbc, 0x4f, 0x63, 0xe6, 0x05, 0xc2, 0xa4, 0x6c, 0x42, 0x23, 0x9c, 0x4b, 0x1b, 0x52, 0x53, 0x8d, 0xbc, 0x4f, 0x63, 0xe6, 0x05, 0xc2, 0xa4, 0x6c, 0x42, 0x23,
0x0f, 0xda, 0x1a, 0xf5, 0x4d, 0xd1, 0x97, 0xcb, 0x59, 0xf1, 0xce, 0x9b, 0xe2, 0x2d, 0x93, 0xe0, 0x37, 0xda, 0x1a, 0xf5, 0x4d, 0xd1, 0x97, 0x4b, 0x79, 0xcd, 0x8e, 0x98, 0xd4, 0x55, 0x55, 0x1a,
0x03, 0xa7, 0xa1, 0x0a, 0x51, 0x85, 0xa8, 0xb5, 0xac, 0xd7, 0x47, 0x4c, 0x69, 0x57, 0x54, 0xb6, 0x68, 0x61, 0x56, 0xd2, 0xf3, 0xa6, 0xa4, 0xcb, 0xd4, 0xf8, 0xc0, 0x69, 0xa8, 0x0e, 0xae, 0x42,
0x18, 0x49, 0xd9, 0xbb, 0xec, 0xe3, 0xa2, 0x76, 0xbc, 0x75, 0xa9, 0xba, 0xd0, 0x11, 0x93, 0xba, 0xd4, 0x5a, 0x56, 0xf1, 0x23, 0xa6, 0xb4, 0x2b, 0x8a, 0x6c, 0x24, 0xe5, 0xe5, 0xb2, 0x8f, 0x8b,
0x92, 0x02, 0x6a, 0x41, 0xe2, 0x4e, 0xc5, 0x14, 0x97, 0x75, 0xaa, 0x9d, 0x8a, 0xa9, 0x6c, 0x28, 0xfa, 0x38, 0x5a, 0x97, 0x91, 0x97, 0xcb, 0x3e, 0x2e, 0xc5, 0x5e, 0x2e, 0x95, 0x97, 0x53, 0x31,
0x84, 0xf9, 0x7e, 0xd7, 0xed, 0x0d, 0x71, 0x45, 0x77, 0xb2, 0x48, 0x96, 0x93, 0x9e, 0x8c, 0xae, 0xc5, 0x65, 0x9d, 0x80, 0xa7, 0x62, 0x2a, 0xdb, 0x0c, 0x61, 0xbe, 0xdf, 0x75, 0x7b, 0x43, 0x5c,
0xe7, 0xfa, 0xea, 0x9b, 0xa0, 0x4c, 0x22, 0xd1, 0xd9, 0x81, 0x4a, 0x9c, 0x14, 0xb2, 0x47, 0xb5, 0xd1, 0xfd, 0x2d, 0x92, 0xe5, 0xfc, 0x27, 0xcf, 0xdc, 0x73, 0x7d, 0xf5, 0xa5, 0x50, 0x26, 0x91,
0xfb, 0x2a, 0xe8, 0x35, 0x92, 0x6b, 0xf7, 0xa3, 0x7c, 0xce, 0xcd, 0xe7, 0x73, 0x3e, 0x91, 0xcf, 0xe8, 0xec, 0x40, 0x25, 0x4e, 0x15, 0xd9, 0xb9, 0xda, 0x7d, 0xf5, 0x2a, 0x6a, 0x24, 0xd7, 0xee,
0xdb, 0x50, 0x4b, 0xa5, 0x87, 0x04, 0x11, 0x76, 0xc9, 0x8d, 0x21, 0xb5, 0x96, 0xba, 0x16, 0xf3, 0x47, 0x59, 0x9e, 0x9b, 0xcf, 0xf2, 0x7c, 0x22, 0xcb, 0xb7, 0xa1, 0x96, 0x4a, 0x1a, 0x09, 0x22,
0xf5, 0x57, 0x6f, 0x8d, 0xa8, 0xb5, 0xf3, 0x1c, 0x6a, 0xa9, 0xc4, 0x58, 0x54, 0x81, 0x9d, 0x67, 0xec, 0x92, 0x1b, 0x43, 0x6a, 0x2d, 0x75, 0x2d, 0xe6, 0xeb, 0x6f, 0xe1, 0x1a, 0x51, 0x6b, 0xe7,
0x50, 0xeb, 0x08, 0x57, 0x4c, 0x96, 0xfc, 0x4d, 0xf1, 0x1f, 0x0b, 0xd6, 0x22, 0x8c, 0xa9, 0x31, 0x39, 0xd4, 0x52, 0xe9, 0xb2, 0xa8, 0x2e, 0x3b, 0xcf, 0xa0, 0xd6, 0x11, 0xae, 0x98, 0x2c, 0xf9,
0xbf, 0x84, 0xf2, 0x05, 0x0d, 0x05, 0xbd, 0x8a, 0xbb, 0x0e, 0x9e, 0x1f, 0x34, 0x3f, 0x2a, 0x04, 0xf3, 0xe2, 0x3f, 0x16, 0xac, 0x45, 0x18, 0x53, 0x79, 0x7e, 0x09, 0xe5, 0x0b, 0x1a, 0x0a, 0x7a,
0x89, 0x91, 0xe8, 0x1b, 0x28, 0x73, 0x65, 0x87, 0x46, 0x13, 0xcb, 0x67, 0x59, 0x2c, 0xb3, 0x5f, 0x15, 0xf7, 0x22, 0x3c, 0x3f, 0x7e, 0x7e, 0x54, 0x08, 0x12, 0x23, 0xd1, 0x37, 0x50, 0xe6, 0xca,
0x8c, 0x47, 0x4d, 0x28, 0xf8, 0x6c, 0xc0, 0xd5, 0x7b, 0xaf, 0x6e, 0x3d, 0xce, 0xe2, 0xbd, 0x63, 0x0e, 0x8d, 0xe6, 0x98, 0xcf, 0xb2, 0x58, 0xc6, 0x5f, 0x8c, 0x47, 0x4d, 0x28, 0xf8, 0x6c, 0xc0,
0x03, 0xa2, 0x80, 0xe8, 0x2d, 0x94, 0x2f, 0xdd, 0x30, 0xf0, 0x82, 0x41, 0xf4, 0xb5, 0xfc, 0x34, 0xd5, 0x7b, 0xaf, 0x6e, 0x3d, 0xce, 0xe2, 0xbd, 0x63, 0x03, 0xa2, 0x80, 0xe8, 0x2d, 0x94, 0x2f,
0x8b, 0xf4, 0x83, 0xc6, 0x91, 0x98, 0xe0, 0xd4, 0xe4, 0x75, 0x39, 0x63, 0x26, 0x26, 0xce, 0x6f, 0xdd, 0x30, 0xf0, 0x82, 0x41, 0xf4, 0x0d, 0xfd, 0x34, 0x8b, 0xf4, 0x83, 0xc6, 0x91, 0x98, 0xe0,
0x64, 0xd6, 0x4a, 0xd1, 0xb8, 0x7f, 0x08, 0x35, 0x9d, 0xf9, 0x1f, 0x69, 0xc8, 0xe5, 0xfc, 0x67, 0xd4, 0xe4, 0x25, 0x3a, 0x63, 0xe6, 0x4c, 0x9c, 0xdf, 0xc8, 0x5c, 0x96, 0xa2, 0x09, 0xff, 0x10,
0x2d, 0xbb, 0x9d, 0xbb, 0x49, 0x28, 0x49, 0x33, 0x9d, 0x4f, 0xa6, 0xb1, 0x45, 0x0a, 0x99, 0x4b, 0x6a, 0xfa, 0x3e, 0x7c, 0xa4, 0x21, 0x97, 0x53, 0xa1, 0xb5, 0xec, 0xce, 0xee, 0x26, 0xa1, 0x24,
0x63, 0xb7, 0x37, 0x74, 0x07, 0xd1, 0x7b, 0x8a, 0x44, 0xf9, 0xe4, 0xc2, 0xec, 0xa7, 0x2f, 0x68, 0xcd, 0x74, 0x3e, 0x99, 0x76, 0x17, 0x29, 0x64, 0x2e, 0x8d, 0xdd, 0xde, 0xd0, 0x1d, 0x44, 0xef,
0x24, 0xca, 0xdc, 0x0c, 0xe9, 0x85, 0xc7, 0x67, 0xa3, 0x68, 0x2c, 0x6f, 0xfd, 0xa5, 0x04, 0xd0, 0x29, 0x12, 0xe5, 0x93, 0x0b, 0xe3, 0x4f, 0x5f, 0xdb, 0x48, 0x94, 0xb9, 0x19, 0xd2, 0x0b, 0x8f,
0x8a, 0xcf, 0x83, 0x4e, 0x60, 0x45, 0xed, 0x87, 0x9c, 0xa5, 0x6d, 0x52, 0xf9, 0x6d, 0x3f, 0xbf, 0xcf, 0x06, 0xd4, 0x58, 0xde, 0xfa, 0x6b, 0x09, 0xa0, 0x15, 0xef, 0x07, 0x9d, 0xc0, 0x8a, 0xf2,
0x45, 0x2b, 0x45, 0x1f, 0x65, 0xf2, 0xab, 0xf1, 0x06, 0xbd, 0xc8, 0x2a, 0x08, 0xc9, 0x09, 0xc9, 0x87, 0x9c, 0xa5, 0xcd, 0x53, 0xc5, 0x6d, 0x3f, 0xbf, 0x45, 0x83, 0x45, 0x1f, 0x65, 0xf2, 0xab,
0x7e, 0x79, 0x03, 0xca, 0xd8, 0xfd, 0x00, 0x45, 0x9d, 0x05, 0x28, 0xab, 0xea, 0x25, 0xf3, 0xd6, 0xa1, 0x07, 0xbd, 0xc8, 0x2a, 0x13, 0xc9, 0xb9, 0xc9, 0x7e, 0x79, 0x03, 0xca, 0xd8, 0xfd, 0x00,
0x7e, 0xb1, 0x1c, 0xa4, 0x8d, 0x7e, 0x61, 0x21, 0x62, 0x6a, 0x22, 0x72, 0x96, 0x34, 0x3d, 0x73, 0x45, 0x9d, 0x05, 0x28, 0xab, 0x16, 0x26, 0xf3, 0xd6, 0x7e, 0xb1, 0x1c, 0xa4, 0x8d, 0x7e, 0x61,
0x63, 0xb2, 0x02, 0x90, 0xea, 0x2f, 0x75, 0x0b, 0x7d, 0x0f, 0x45, 0x5d, 0xd5, 0xd0, 0x4f, 0x17, 0x21, 0x62, 0x2a, 0x25, 0x72, 0x96, 0xb4, 0x42, 0x73, 0x63, 0xb2, 0x0e, 0x20, 0xd5, 0x75, 0xea,
0x13, 0x22, 0x7b, 0xcb, 0x1f, 0xd7, 0xad, 0x2f, 0x2c, 0xf4, 0x1e, 0x0a, 0xb2, 0x9d, 0xa3, 0x8c, 0x16, 0xfa, 0x1e, 0x8a, 0xba, 0xd6, 0xa1, 0x9f, 0x2e, 0x26, 0x44, 0xf6, 0x96, 0x3f, 0xae, 0x5b,
0xde, 0x94, 0x98, 0x05, 0x6c, 0x67, 0x19, 0xc4, 0x44, 0xf1, 0x13, 0xc0, 0x6c, 0xa8, 0x40, 0x19, 0x5f, 0x58, 0xe8, 0x3d, 0x14, 0x64, 0x93, 0x47, 0x19, 0x1d, 0x2b, 0x31, 0x21, 0xd8, 0xce, 0x32,
0xff, 0x79, 0xcc, 0x4d, 0x27, 0x76, 0xfd, 0x66, 0xa0, 0xd9, 0xe0, 0xbd, 0xec, 0xa8, 0x67, 0x0c, 0x88, 0x39, 0xc5, 0x4f, 0x00, 0xb3, 0x51, 0x03, 0x65, 0xfc, 0x13, 0x32, 0x37, 0xb3, 0xd8, 0xf5,
0x65, 0xf6, 0xd2, 0xf8, 0x1a, 0xd9, 0xce, 0x32, 0x88, 0x31, 0x77, 0x0e, 0xb5, 0xd4, 0x7f, 0xa2, 0x9b, 0x81, 0xc6, 0xc1, 0x7b, 0xd9, 0x67, 0xcf, 0x18, 0xca, 0xec, 0xb0, 0xf1, 0x35, 0xb2, 0x9d,
0xe8, 0xe7, 0xd9, 0x4e, 0x5e, 0xff, 0x8b, 0xd5, 0x7e, 0x75, 0x2b, 0xac, 0xd9, 0x49, 0x24, 0xa7, 0x65, 0x10, 0x63, 0xee, 0x1c, 0x6a, 0xa9, 0x7f, 0x4a, 0xd1, 0xcf, 0xb3, 0x83, 0xbc, 0xfe, 0xc7,
0x32, 0xf3, 0x18, 0x35, 0x6e, 0xf2, 0x3b, 0xfd, 0xff, 0xa6, 0xdd, 0xbc, 0x35, 0x5e, 0xef, 0xba, 0xab, 0xfd, 0xea, 0x56, 0x58, 0xe3, 0x49, 0x24, 0x67, 0x35, 0xf3, 0x18, 0x35, 0x6e, 0x8a, 0x3b,
0x5b, 0xf8, 0x6d, 0x6e, 0xdc, 0xed, 0x16, 0xd5, 0x5f, 0xc5, 0x5f, 0xfd, 0x37, 0x00, 0x00, 0xff, 0xfd, 0xaf, 0xa7, 0xdd, 0xbc, 0x35, 0x5e, 0x7b, 0xdd, 0x2d, 0xfc, 0x36, 0x37, 0xee, 0x76, 0x8b,
0xff, 0xc1, 0x4b, 0x2d, 0x65, 0xc8, 0x16, 0x00, 0x00, 0xea, 0x0f, 0xe4, 0xaf, 0xfe, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x18, 0x4b, 0xe5, 0x8f, 0xde, 0x16,
0x00, 0x00,
} }
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.

@ -192,6 +192,7 @@ message InitMessage {
message InvokeConfig { message InvokeConfig {
repeated string Entrypoint = 1; repeated string Entrypoint = 1;
repeated string Cmd = 2; repeated string Cmd = 2;
bool NoCmd = 11; // Do not set cmd but use the image's default
repeated string Env = 3; repeated string Env = 3;
string User = 4; string User = 4;
bool NoUser = 5; // Do not set user but use the image's default bool NoUser = 5; // Do not set user but use the image's default

@ -57,9 +57,7 @@ func (m *Server) ListProcesses(ctx context.Context, req *pb.ListProcessesRequest
return nil, errors.Errorf("unknown ref %q", req.Ref) return nil, errors.Errorf("unknown ref %q", req.Ref)
} }
res = new(pb.ListProcessesResponse) res = new(pb.ListProcessesResponse)
for _, p := range s.processes.ListProcesses() { res.Infos = append(res.Infos, s.processes.ListProcesses()...)
res.Infos = append(res.Infos, p)
}
return res, nil return res, nil
} }

@ -116,12 +116,12 @@ Available commands are:
disconnect disconnect a client from a buildx server. Specific session ID can be specified an arg disconnect disconnect a client from a buildx server. Specific session ID can be specified an arg
exec execute a process in the interactive container exec execute a process in the interactive container
exit exits monitor exit exits monitor
help shows this message help shows this message. Optionally pass a command name as an argument to print the detailed usage.
kill kill buildx server kill kill buildx server
list list buildx sessions list list buildx sessions
ps list processes invoked by "exec". Use "attach" to attach IO to that process ps list processes invoked by "exec". Use "attach" to attach IO to that process
reload reloads the context and build it reload reloads the context and build it
rollback re-runs the interactive container with initial rootfs contents rollback re-runs the interactive container with the step's rootfs contents
``` ```
## Build controllers ## Build controllers

@ -23,7 +23,7 @@ Start a build
| [`--builder`](#builder) | `string` | | Override the configured builder instance | | [`--builder`](#builder) | `string` | | Override the configured builder instance |
| [`--cache-from`](#cache-from) | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) | | [`--cache-from`](#cache-from) | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
| [`--cache-to`](#cache-to) | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) | | [`--cache-to`](#cache-to) | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
| [`--cgroup-parent`](https://docs.docker.com/engine/reference/commandline/build/#cgroup-parent) | `string` | | Optional parent cgroup for the container | | [`--cgroup-parent`](https://docs.docker.com/engine/reference/commandline/build/#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
| `--detach` | | | Detach buildx server (supported only on linux) | | `--detach` | | | Detach buildx server (supported only on linux) |
| [`-f`](https://docs.docker.com/engine/reference/commandline/build/#file), [`--file`](https://docs.docker.com/engine/reference/commandline/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) | | [`-f`](https://docs.docker.com/engine/reference/commandline/build/#file), [`--file`](https://docs.docker.com/engine/reference/commandline/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
| `--iidfile` | `string` | | Write the image ID to the file | | `--iidfile` | `string` | | Write the image ID to the file |

@ -141,7 +141,7 @@ to achieve that.
Passes additional driver-specific options. Passes additional driver-specific options.
Note: When using quoted values for example for the `nodeselector` or Note: When using quoted values for the `nodeselector`, `annotations`, `labels` or
`tolerations` options, ensure that quotes are escaped correctly for your shell. `tolerations` options, ensure that quotes are escaped correctly for your shell.
#### `docker` driver #### `docker` driver
@ -165,6 +165,8 @@ No driver options.
- `limits.memory` - Sets the limit memory value specified in bytes or with a valid suffix. Example `limits.memory=500Mi`, `limits.memory=4G` - `limits.memory` - Sets the limit memory value specified in bytes or with a valid suffix. Example `limits.memory=500Mi`, `limits.memory=4G`
- `serviceaccount` - Sets the created pod's service account. Example `serviceaccount=example-sa` - `serviceaccount` - Sets the created pod's service account. Example `serviceaccount=example-sa`
- `"nodeselector=label1=value1,label2=value2"` - Sets the kv of `Pod` nodeSelector. No Defaults. Example `nodeselector=kubernetes.io/arch=arm64` - `"nodeselector=label1=value1,label2=value2"` - Sets the kv of `Pod` nodeSelector. No Defaults. Example `nodeselector=kubernetes.io/arch=arm64`
- `"annotations=domain/thing1=value1,domain/thing2=value2"` - Sets additional annotations on the deployments and pods. No Defaults. Example `annotations=example.com/owner=sarah`
- `"labels=domain/thing1=value1,domain/thing2=value2"` - Sets additional labels on the deployments and pods. No Defaults. Example `labels=example.com/team=rd`
- `"tolerations=key=foo,value=bar;key=foo2,operator=exists;key=foo3,effect=NoSchedule"` - Sets the `Pod` tolerations. Accepts the same values as the kube manifest tolera>tions. Key-value pairs are separated by `,`, tolerations are separated by `;`. No Defaults. Example `tolerations=operator=exists` - `"tolerations=key=foo,value=bar;key=foo2,operator=exists;key=foo3,effect=NoSchedule"` - Sets the `Pod` tolerations. Accepts the same values as the kube manifest tolera>tions. Key-value pairs are separated by `,`, tolerations are separated by `;`. No Defaults. Example `tolerations=operator=exists`
- `rootless=(true|false)` - Run the container as a non-root user without `securityContext.privileged`. Needs Kubernetes 1.19 or later. [Using Ubuntu host kernel is recommended](https://github.com/moby/buildkit/blob/master/docs/rootless.md). Defaults to false. - `rootless=(true|false)` - Run the container as a non-root user without `securityContext.privileged`. Needs Kubernetes 1.19 or later. [Using Ubuntu host kernel is recommended](https://github.com/moby/buildkit/blob/master/docs/rootless.md). Defaults to false.
- `loadbalance=(sticky|random)` - Load-balancing strategy. If set to "sticky", the pod is chosen using the hash of the context path. Defaults to "sticky" - `loadbalance=(sticky|random)` - Load-balancing strategy. If set to "sticky", the pod is chosen using the hash of the context path. Defaults to "sticky"
@ -221,7 +223,7 @@ building for the same platform.
```console ```console
$ docker buildx create --platform linux/amd64 $ docker buildx create --platform linux/amd64
$ docker buildx create --platform linux/arm64,linux/arm/v8 $ docker buildx create --platform linux/arm64,linux/arm/v7
``` ```
### <a name="use"></a> Automatically switch to the newly created builder (--use) ### <a name="use"></a> Automatically switch to the newly created builder (--use)

@ -11,6 +11,7 @@ Create a new image based on source images
| Name | Type | Default | Description | | Name | Type | Default | Description |
|:---------------------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------| |:---------------------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------|
| `--annotation` | `stringArray` | | Add annotation to the image |
| [`--append`](#append) | | | Append to existing manifest | | [`--append`](#append) | | | Append to existing manifest |
| [`--builder`](#builder) | `string` | | Override the configured builder instance | | [`--builder`](#builder) | `string` | | Override the configured builder instance |
| [`--dry-run`](#dry-run) | | | Show final image instead of pushing | | [`--dry-run`](#dry-run) | | | Show final image instead of pushing |

@ -8,6 +8,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"sync/atomic" "sync/atomic"
"time" "time"
@ -22,6 +23,7 @@ import (
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
"github.com/docker/docker/errdefs"
dockerarchive "github.com/docker/docker/pkg/archive" dockerarchive "github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/stdcopy"
@ -40,6 +42,7 @@ type Driver struct {
netMode string netMode string
image string image string
cgroupParent string cgroupParent string
securityOpts map[string]string
env []string env []string
} }
@ -108,7 +111,6 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
if d.InitConfig.BuildkitFlags != nil { if d.InitConfig.BuildkitFlags != nil {
cfg.Cmd = d.InitConfig.BuildkitFlags cfg.Cmd = d.InitConfig.BuildkitFlags
} }
useInit := true // let it cleanup exited processes created by BuildKit's container API useInit := true // let it cleanup exited processes created by BuildKit's container API
if err := l.Wrap("creating container "+d.Name, func() error { if err := l.Wrap("creating container "+d.Name, func() error {
hc := &container.HostConfig{ hc := &container.HostConfig{
@ -126,6 +128,13 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
hc.NetworkMode = container.NetworkMode(d.netMode) hc.NetworkMode = container.NetworkMode(d.netMode)
} }
if info, err := d.DockerAPI.Info(ctx); err == nil { if info, err := d.DockerAPI.Info(ctx); err == nil {
secOpts, err := dockertypes.DecodeSecurityOptions(info.SecurityOptions)
l.Wrap("driverOpts"+info.CgroupDriver, func() error {
return nil
})
if err != nil {
return err
}
if info.CgroupDriver == "cgroupfs" { if info.CgroupDriver == "cgroupfs" {
// Place all buildkit containers inside this cgroup by default so limits can be attached // Place all buildkit containers inside this cgroup by default so limits can be attached
// to all build activity on the host. // to all build activity on the host.
@ -134,28 +143,39 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
hc.CgroupParent = d.cgroupParent hc.CgroupParent = d.cgroupParent
} }
} }
secOpts, err := dockertypes.DecodeSecurityOptions(info.SecurityOptions)
if err != nil {
return err
}
for _, f := range secOpts { for _, f := range secOpts {
if f.Name == "userns" { if f.Name == "userns" {
hc.UsernsMode = "host" hc.UsernsMode = "host"
break break
} }
} }
for i, k := range d.securityOpts {
switch {
case i == "systempaths":
hc.MaskedPaths = []string{}
hc.ReadonlyPaths = []string{}
case i == "privileged":
val, err := strconv.ParseBool(k)
if err != nil {
return errors.Errorf("invalid value privleged security option, options are true/false")
}
hc.Privileged = val
default:
hc.SecurityOpt = append(hc.SecurityOpt, i+"="+k)
}
}
} }
_, err := d.DockerAPI.ContainerCreate(ctx, cfg, hc, &network.NetworkingConfig{}, nil, d.Name) _, err := d.DockerAPI.ContainerCreate(ctx, cfg, hc, &network.NetworkingConfig{}, nil, d.Name)
if err != nil { if err != nil && !errdefs.IsConflict(err) {
return err
}
if err := d.copyToContainer(ctx, d.InitConfig.Files); err != nil {
return err return err
} }
if err := d.start(ctx, l); err != nil { if err == nil {
return err if err := d.copyToContainer(ctx, d.InitConfig.Files); err != nil {
return err
}
if err := d.start(ctx, l); err != nil {
return err
}
} }
if err := d.wait(ctx, l); err != nil { if err := d.wait(ctx, l); err != nil {
return err return err

@ -40,6 +40,7 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
return nil, errors.Errorf("%s driver requires docker API access", f.Name()) return nil, errors.Errorf("%s driver requires docker API access", f.Name())
} }
d := &Driver{factory: f, InitConfig: cfg} d := &Driver{factory: f, InitConfig: cfg}
d.securityOpts = make(map[string]string)
for k, v := range cfg.DriverOpts { for k, v := range cfg.DriverOpts {
switch { switch {
case k == "network": case k == "network":
@ -57,11 +58,32 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
return nil, errors.Errorf("invalid env option %q, expecting env.FOO=bar", k) return nil, errors.Errorf("invalid env option %q, expecting env.FOO=bar", k)
} }
d.env = append(d.env, fmt.Sprintf("%s=%s", envName, v)) d.env = append(d.env, fmt.Sprintf("%s=%s", envName, v))
case k == "seccomp":
d.securityOpts[k] = v
case k == "apparmor":
d.securityOpts[k] = v
case k == "systempaths":
d.securityOpts[k] = v
case k == "privileged":
d.securityOpts[k] = v
default: default:
return nil, errors.Errorf("invalid driver option %s for docker-container driver", k) return nil, errors.Errorf("invalid driver option %s for docker-container driver", k)
} }
} }
for i, _ := range cfg.SecurityOpts {
switch {
case i == "seccomp":
continue
case i == "apparmor":
continue
case i == "systempaths":
continue
case i == "privileged":
continue
default:
return nil, errors.Errorf("invalid Security option %s for docker-container driver", i)
}
}
return d, nil return d, nil
} }

@ -80,7 +80,6 @@ func Boot(ctx, clientContext context.Context, d *DriverHandle, pw progress.Write
return nil, err return nil, err
} }
} }
c, err := d.Client(clientContext) c, err := d.Client(clientContext)
if err != nil { if err != nil {
if errors.Cause(err) == ErrNotRunning && try <= 2 { if errors.Cause(err) == ErrNotRunning && try <= 2 {

@ -23,6 +23,7 @@ type EndpointMeta struct {
AuthProvider *clientcmdapi.AuthProviderConfig `json:",omitempty"` AuthProvider *clientcmdapi.AuthProviderConfig `json:",omitempty"`
Exec *clientcmdapi.ExecConfig `json:",omitempty"` Exec *clientcmdapi.ExecConfig `json:",omitempty"`
UsernamePassword *UsernamePassword `json:"usernamePassword,omitempty"` UsernamePassword *UsernamePassword `json:"usernamePassword,omitempty"`
Token string `json:"token,omitempty"`
} }
// UsernamePassword contains username/password auth info // UsernamePassword contains username/password auth info
@ -77,6 +78,9 @@ func (c *Endpoint) KubernetesConfig() clientcmd.ClientConfig {
authInfo.Username = c.UsernamePassword.Username authInfo.Username = c.UsernamePassword.Username
authInfo.Password = c.UsernamePassword.Password authInfo.Password = c.UsernamePassword.Password
} }
if c.Token != "" {
authInfo.Token = c.Token
}
authInfo.AuthProvider = c.AuthProvider authInfo.AuthProvider = c.AuthProvider
authInfo.Exec = c.Exec authInfo.Exec = c.Exec
cfg.Clusters["cluster"] = cluster cfg.Clusters["cluster"] = cluster

@ -68,6 +68,7 @@ func FromKubeConfig(kubeconfig, kubeContext, namespaceOverride string) (Endpoint
AuthProvider: clientcfg.AuthProvider, AuthProvider: clientcfg.AuthProvider,
Exec: clientcfg.ExecProvider, Exec: clientcfg.ExecProvider,
UsernamePassword: usernamePassword, UsernamePassword: usernamePassword,
Token: clientcfg.BearerToken,
}, },
TLSData: tlsData, TLSData: tlsData,
}, nil }, nil

@ -148,15 +148,20 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
case "serviceaccount": case "serviceaccount":
deploymentOpt.ServiceAccountName = v deploymentOpt.ServiceAccountName = v
case "nodeselector": case "nodeselector":
kvs := strings.Split(strings.Trim(v, `"`), ",") deploymentOpt.NodeSelector, err = splitMultiValues(v, ",", "=")
s := map[string]string{} if err != nil {
for i := range kvs { return nil, "", "", errors.Wrap(err, "cannot parse node selector")
kv := strings.Split(kvs[i], "=") }
if len(kv) == 2 { case "annotations":
s[kv[0]] = kv[1] deploymentOpt.CustomAnnotations, err = splitMultiValues(v, ",", "=")
} if err != nil {
return nil, "", "", errors.Wrap(err, "cannot parse annotations")
}
case "labels":
deploymentOpt.CustomLabels, err = splitMultiValues(v, ",", "=")
if err != nil {
return nil, "", "", errors.Wrap(err, "cannot parse labels")
} }
deploymentOpt.NodeSelector = s
case "tolerations": case "tolerations":
ts := strings.Split(v, ";") ts := strings.Split(v, ";")
deploymentOpt.Tolerations = []corev1.Toleration{} deploymentOpt.Tolerations = []corev1.Toleration{}
@ -217,6 +222,19 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
return deploymentOpt, loadbalance, namespace, nil return deploymentOpt, loadbalance, namespace, nil
} }
func splitMultiValues(in string, itemsep string, kvsep string) (map[string]string, error) {
kvs := strings.Split(strings.Trim(in, `"`), itemsep)
s := map[string]string{}
for i := range kvs {
kv := strings.Split(kvs[i], kvsep)
if len(kv) != 2 {
return nil, errors.Errorf("invalid key-value pair: %s", kvs[i])
}
s[kv[0]] = kv[1]
}
return s, nil
}
func (f *factory) AllowsInstances() bool { func (f *factory) AllowsInstances() bool {
return true return true
} }

@ -47,13 +47,13 @@ func TestFactory_processDriverOpts(t *testing.T) {
"rootless": "true", "rootless": "true",
"nodeselector": "selector1=value1,selector2=value2", "nodeselector": "selector1=value1,selector2=value2",
"tolerations": "key=tolerationKey1,value=tolerationValue1,operator=Equal,effect=NoSchedule,tolerationSeconds=60;key=tolerationKey2,operator=Exists", "tolerations": "key=tolerationKey1,value=tolerationValue1,operator=Equal,effect=NoSchedule,tolerationSeconds=60;key=tolerationKey2,operator=Exists",
"annotations": "example.com/expires-after=annotation1,example.com/other=annotation2",
"labels": "example.com/owner=label1,example.com/other=label2",
"loadbalance": "random", "loadbalance": "random",
"qemu.install": "true", "qemu.install": "true",
"qemu.image": "qemu:latest", "qemu.image": "qemu:latest",
} }
ns := "test" r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, "test", cfg)
r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, ns, cfg)
nodeSelectors := map[string]string{ nodeSelectors := map[string]string{
"selector1": "value1", "selector1": "value1",
@ -75,6 +75,16 @@ func TestFactory_processDriverOpts(t *testing.T) {
}, },
} }
customAnnotations := map[string]string{
"example.com/expires-after": "annotation1",
"example.com/other": "annotation2",
}
customLabels := map[string]string{
"example.com/owner": "label1",
"example.com/other": "label2",
}
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "test-ns", ns) require.Equal(t, "test-ns", ns)
@ -86,6 +96,8 @@ func TestFactory_processDriverOpts(t *testing.T) {
require.Equal(t, "64Mi", r.LimitsMemory) require.Equal(t, "64Mi", r.LimitsMemory)
require.True(t, r.Rootless) require.True(t, r.Rootless)
require.Equal(t, nodeSelectors, r.NodeSelector) require.Equal(t, nodeSelectors, r.NodeSelector)
require.Equal(t, customAnnotations, r.CustomAnnotations)
require.Equal(t, customLabels, r.CustomLabels)
require.Equal(t, tolerations, r.Tolerations) require.Equal(t, tolerations, r.Tolerations)
require.Equal(t, LoadbalanceRandom, loadbalance) require.Equal(t, LoadbalanceRandom, loadbalance)
require.True(t, r.Qemu.Install) require.True(t, r.Qemu.Install)
@ -110,6 +122,8 @@ func TestFactory_processDriverOpts(t *testing.T) {
require.Equal(t, "", r.LimitsMemory) require.Equal(t, "", r.LimitsMemory)
require.False(t, r.Rootless) require.False(t, r.Rootless)
require.Empty(t, r.NodeSelector) require.Empty(t, r.NodeSelector)
require.Empty(t, r.CustomAnnotations)
require.Empty(t, r.CustomLabels)
require.Empty(t, r.Tolerations) require.Empty(t, r.Tolerations)
require.Equal(t, LoadbalanceSticky, loadbalance) require.Equal(t, LoadbalanceSticky, loadbalance)
require.False(t, r.Qemu.Install) require.False(t, r.Qemu.Install)
@ -137,6 +151,8 @@ func TestFactory_processDriverOpts(t *testing.T) {
require.Equal(t, "", r.LimitsMemory) require.Equal(t, "", r.LimitsMemory)
require.True(t, r.Rootless) require.True(t, r.Rootless)
require.Empty(t, r.NodeSelector) require.Empty(t, r.NodeSelector)
require.Empty(t, r.CustomAnnotations)
require.Empty(t, r.CustomLabels)
require.Empty(t, r.Tolerations) require.Empty(t, r.Tolerations)
require.Equal(t, LoadbalanceSticky, loadbalance) require.Equal(t, LoadbalanceSticky, loadbalance)
require.False(t, r.Qemu.Install) require.False(t, r.Qemu.Install)
@ -149,9 +165,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"replicas": "invalid", "replicas": "invalid",
} }
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@ -161,9 +175,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"rootless": "invalid", "rootless": "invalid",
} }
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@ -173,9 +185,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"tolerations": "key=foo,value=bar,invalid=foo2", "tolerations": "key=foo,value=bar,invalid=foo2",
} }
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@ -185,9 +195,27 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"tolerations": "key=foo,value=bar,tolerationSeconds=invalid", "tolerations": "key=foo,value=bar,tolerationSeconds=invalid",
} }
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err)
},
)
t.Run(
"InvalidCustomAnnotation", func(t *testing.T) {
cfg.DriverOpts = map[string]string{
"annotations": "key,value",
}
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err)
},
)
t.Run(
"InvalidCustomLabel", func(t *testing.T) {
cfg.DriverOpts = map[string]string{
"labels": "key=value=foo",
}
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@ -197,9 +225,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"loadbalance": "invalid", "loadbalance": "invalid",
} }
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@ -209,9 +235,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"qemu.install": "invalid", "qemu.install": "invalid",
} }
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )
@ -221,9 +245,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
cfg.DriverOpts = map[string]string{ cfg.DriverOpts = map[string]string{
"invalid": "foo", "invalid": "foo",
} }
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
require.Error(t, err) require.Error(t, err)
}, },
) )

@ -7,6 +7,7 @@ import (
"github.com/docker/buildx/util/platformutil" "github.com/docker/buildx/util/platformutil"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
@ -31,24 +32,32 @@ type DeploymentOpt struct {
// files mounted at /etc/buildkitd // files mounted at /etc/buildkitd
ConfigFiles map[string][]byte ConfigFiles map[string][]byte
Rootless bool Rootless bool
NodeSelector map[string]string NodeSelector map[string]string
Tolerations []corev1.Toleration CustomAnnotations map[string]string
RequestsCPU string CustomLabels map[string]string
RequestsMemory string Tolerations []corev1.Toleration
LimitsCPU string RequestsCPU string
LimitsMemory string RequestsMemory string
Platforms []v1.Platform LimitsCPU string
LimitsMemory string
Platforms []v1.Platform
} }
const ( const (
containerName = "buildkitd" containerName = "buildkitd"
AnnotationPlatform = "buildx.docker.com/platform" AnnotationPlatform = "buildx.docker.com/platform"
LabelApp = "app"
)
var (
ErrReservedAnnotationPlatform = errors.Errorf("the annotation \"%s\" is reserved and cannot be customized", AnnotationPlatform)
ErrReservedLabelApp = errors.Errorf("the label \"%s\" is reserved and cannot be customized", LabelApp)
) )
func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, c []*corev1.ConfigMap, err error) { func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, c []*corev1.ConfigMap, err error) {
labels := map[string]string{ labels := map[string]string{
"app": opt.Name, LabelApp: opt.Name,
} }
annotations := map[string]string{} annotations := map[string]string{}
replicas := int32(opt.Replicas) replicas := int32(opt.Replicas)
@ -59,6 +68,20 @@ func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, c []*corev1.Config
annotations[AnnotationPlatform] = strings.Join(platformutil.Format(opt.Platforms), ",") annotations[AnnotationPlatform] = strings.Join(platformutil.Format(opt.Platforms), ",")
} }
for k, v := range opt.CustomAnnotations {
if k == AnnotationPlatform {
return nil, nil, ErrReservedAnnotationPlatform
}
annotations[k] = v
}
for k, v := range opt.CustomLabels {
if k == LabelApp {
return nil, nil, ErrReservedLabelApp
}
labels[k] = v
}
d = &appsv1.Deployment{ d = &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
APIVersion: appsv1.SchemeGroupVersion.String(), APIVersion: appsv1.SchemeGroupVersion.String(),

@ -56,6 +56,7 @@ type InitConfig struct {
BuildkitFlags []string BuildkitFlags []string
Files map[string][]byte Files map[string][]byte
DriverOpts map[string]string DriverOpts map[string]string
SecurityOpts map[string]string
Auth Auth Auth Auth
Platforms []specs.Platform Platforms []specs.Platform
// ContextPathHash can be used for determining pods in the driver instance // ContextPathHash can be used for determining pods in the driver instance
@ -104,7 +105,7 @@ func GetFactory(name string, instanceRequired bool) (Factory, error) {
return nil, errors.Errorf("failed to find driver %q", name) return nil, errors.Errorf("failed to find driver %q", name)
} }
func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string, api dockerclient.APIClient, auth Auth, kcc KubeClientConfig, flags []string, files map[string][]byte, do map[string]string, platforms []specs.Platform, contextPathHash string) (*DriverHandle, error) { func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string, api dockerclient.APIClient, auth Auth, kcc KubeClientConfig, flags []string, files map[string][]byte, do map[string]string, so map[string]string, platforms []specs.Platform, contextPathHash string) (*DriverHandle, error) {
ic := InitConfig{ ic := InitConfig{
EndpointAddr: endpointAddr, EndpointAddr: endpointAddr,
DockerAPI: api, DockerAPI: api,
@ -112,6 +113,7 @@ func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string,
Name: name, Name: name,
BuildkitFlags: flags, BuildkitFlags: flags,
DriverOpts: do, DriverOpts: do,
SecurityOpts: so,
Auth: auth, Auth: auth,
Platforms: platforms, Platforms: platforms,
ContextPathHash: contextPathHash, ContextPathHash: contextPathHash,

@ -7,6 +7,8 @@ import (
"github.com/docker/buildx/driver" "github.com/docker/buildx/driver"
"github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/progress"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"google.golang.org/grpc"
"google.golang.org/grpc/backoff"
) )
type Driver struct { type Driver struct {
@ -23,25 +25,11 @@ type tlsOpts struct {
} }
func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error { func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
for i := 0; ; i++ { c, err := d.Client(ctx)
info, err := d.Info(ctx) if err != nil {
if err != nil { return err
return err
}
if info.Status != driver.Inactive {
return nil
}
select {
case <-ctx.Done():
return ctx.Err()
default:
if i > 10 {
i = 10
}
time.Sleep(time.Duration(i) * time.Second)
}
} }
return c.Wait(ctx)
} }
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) { func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
@ -77,6 +65,13 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
func (d *Driver) Client(ctx context.Context) (*client.Client, error) { func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
opts := []client.ClientOpt{} opts := []client.ClientOpt{}
backoffConfig := backoff.DefaultConfig
backoffConfig.MaxDelay = 1 * time.Second
opts = append(opts, client.WithGRPCDialOption(
grpc.WithConnectParams(grpc.ConnectParams{Backoff: backoffConfig}),
))
if d.tlsOpts != nil { if d.tlsOpts != nil {
opts = append(opts, []client.ClientOpt{ opts = append(opts, []client.ClientOpt{
client.WithServerConfig(d.tlsOpts.serverName, d.tlsOpts.caCert), client.WithServerConfig(d.tlsOpts.serverName, d.tlsOpts.caCert),

@ -5,15 +5,16 @@ go 1.20
require ( require (
github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/semver/v3 v3.2.1
github.com/aws/aws-sdk-go-v2/config v1.18.16 github.com/aws/aws-sdk-go-v2/config v1.18.16
github.com/compose-spec/compose-go v1.14.0 github.com/compose-spec/compose-go v1.17.0
github.com/containerd/console v1.0.3 github.com/containerd/console v1.0.3
github.com/containerd/containerd v1.7.2 github.com/containerd/containerd v1.7.2
github.com/containerd/continuity v0.4.1 github.com/containerd/continuity v0.4.1
github.com/containerd/typeurl/v2 v2.1.1 github.com/containerd/typeurl/v2 v2.1.1
github.com/docker/cli v24.0.4+incompatible github.com/creack/pty v1.1.18
github.com/docker/cli v24.0.5+incompatible
github.com/docker/cli-docs-tool v0.6.0 github.com/docker/cli-docs-tool v0.6.0
github.com/docker/distribution v2.8.2+incompatible github.com/docker/distribution v2.8.2+incompatible
github.com/docker/docker v24.0.5-0.20230714235725-36e9e796c6fc+incompatible // 24.0 github.com/docker/docker v24.0.5+incompatible
github.com/docker/go-units v0.5.0 github.com/docker/go-units v0.5.0
github.com/gofrs/flock v0.8.1 github.com/gofrs/flock v0.8.1
github.com/gogo/protobuf v1.3.2 github.com/gogo/protobuf v1.3.2
@ -22,7 +23,7 @@ require (
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840 github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840
github.com/hashicorp/hcl/v2 v2.8.2 github.com/hashicorp/hcl/v2 v2.8.2
github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353 // v0.12.1-dev github.com/moby/buildkit v0.12.1-0.20230804094609-b49a8873179b
github.com/moby/sys/mountinfo v0.6.2 github.com/moby/sys/mountinfo v0.6.2
github.com/moby/sys/signal v0.7.0 github.com/moby/sys/signal v0.7.0
github.com/morikuni/aec v1.0.0 github.com/morikuni/aec v1.0.0
@ -31,21 +32,22 @@ require (
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml v1.9.5
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/zclconf/go-cty v1.10.0 github.com/zclconf/go-cty v1.10.0
go.opentelemetry.io/otel v1.14.0 go.opentelemetry.io/otel v1.14.0
go.opentelemetry.io/otel/trace v1.14.0 go.opentelemetry.io/otel/trace v1.14.0
golang.org/x/sync v0.2.0 golang.org/x/mod v0.11.0
golang.org/x/term v0.6.0 golang.org/x/sync v0.3.0
golang.org/x/term v0.8.0
google.golang.org/grpc v1.53.0 google.golang.org/grpc v1.53.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.26.2 k8s.io/api v0.26.7
k8s.io/apimachinery v0.26.2 k8s.io/apimachinery v0.26.7
k8s.io/apiserver v0.26.2 k8s.io/apiserver v0.26.7
k8s.io/client-go v0.26.2 k8s.io/client-go v0.26.7
) )
require ( require (
@ -103,7 +105,7 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // 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/in-toto/in-toto-golang v0.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/gorm v1.9.2 // indirect github.com/jinzhu/gorm v1.9.2 // indirect
@ -155,11 +157,11 @@ require (
go.opentelemetry.io/otel/sdk v1.14.0 // indirect go.opentelemetry.io/otel/sdk v1.14.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect
golang.org/x/crypto v0.2.0 // indirect golang.org/x/crypto v0.2.0 // indirect
golang.org/x/mod v0.9.0 // indirect golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
golang.org/x/net v0.8.0 // indirect golang.org/x/net v0.10.0 // indirect
golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/sys v0.7.0 // indirect golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect golang.org/x/tools v0.7.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect

@ -122,8 +122,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/compose-spec/compose-go v1.14.0 h1:/+tQxBEPIrfsi87Qh7/VjMzcJN3BRNER/RO71ku+u6E= github.com/compose-spec/compose-go v1.17.0 h1:cvje90CU94dQyTnJoHJYjx9yE4Iggse1XmGcO3Qi5ts=
github.com/compose-spec/compose-go v1.14.0/go.mod h1:m0o4G6MQDHjjz9rY7No9FpnNi+9sKic262rzrwuCqic= github.com/compose-spec/compose-go v1.17.0/go.mod h1:zR2tP1+kZHi5vJz7PjpW6oMoDji/Js3GHjP+hfjf70Q=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
@ -142,6 +142,7 @@ github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3H
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -149,14 +150,14 @@ 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa h1:L9Ay/slwQ4ERSPaurC+TVkZrM0K98GNrEEo1En3e8as= github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa h1:L9Ay/slwQ4ERSPaurC+TVkZrM0K98GNrEEo1En3e8as=
github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI=
github.com/docker/cli v24.0.4+incompatible h1:Y3bYF9ekNTm2VFz5U/0BlMdJy73D+Y1iAAZ8l63Ydzw= github.com/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc=
github.com/docker/cli v24.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v24.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.6.0 h1:Z9x10SaZgFaB6jHgz3OWooynhSa40CsWkpe5hEnG/qA= github.com/docker/cli-docs-tool v0.6.0 h1:Z9x10SaZgFaB6jHgz3OWooynhSa40CsWkpe5hEnG/qA=
github.com/docker/cli-docs-tool v0.6.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o= github.com/docker/cli-docs-tool v0.6.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.5-0.20230714235725-36e9e796c6fc+incompatible h1:sdGvA1bxu/1J51gAs1XU0bZC+2WxncYnI210as3c6g8= github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY=
github.com/docker/docker v24.0.5-0.20230714235725-36e9e796c6fc+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
@ -315,8 +316,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl/v2 v2.8.2 h1:wmFle3D1vu0okesm8BTLVDyJ6/OL9DCLUwn0b2OptiY= github.com/hashicorp/hcl/v2 v2.8.2 h1:wmFle3D1vu0okesm8BTLVDyJ6/OL9DCLUwn0b2OptiY=
github.com/hashicorp/hcl/v2 v2.8.2/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= github.com/hashicorp/hcl/v2 v2.8.2/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/in-toto/in-toto-golang v0.5.0 h1:hb8bgwr0M2hGdDsLjkJ3ZqJ8JFLL/tgYdAxF/XEFBbY= github.com/in-toto/in-toto-golang v0.5.0 h1:hb8bgwr0M2hGdDsLjkJ3ZqJ8JFLL/tgYdAxF/XEFBbY=
github.com/in-toto/in-toto-golang v0.5.0/go.mod h1:/Rq0IZHLV7Ku5gielPT4wPHJfH1GdHMCq8+WPxw8/BE= github.com/in-toto/in-toto-golang v0.5.0/go.mod h1:/Rq0IZHLV7Ku5gielPT4wPHJfH1GdHMCq8+WPxw8/BE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@ -371,8 +372,8 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzC
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353 h1:/ZIwqvOF3QKObJbjX96xVvAKtnWdw/AuEqysbbujaZA= github.com/moby/buildkit v0.12.1-0.20230804094609-b49a8873179b h1:LUpEbvxcyM0NuWk54WwNjDVZ5YujyCm1CudzZpqaohE=
github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353/go.mod h1:+n9GmkxwBCjVz4u7wmiyh+oqvjIjQM+1zk3iJrWfdos= github.com/moby/buildkit v0.12.1-0.20230804094609-b49a8873179b/go.mod h1:bs0LeDdh7AQpYXLiPNUt+hzDjRxMg+QeLq1a1r0awFM=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo=
@ -456,8 +457,8 @@ github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spdx/tools-golang v0.5.1 h1:fJg3SVOGG+eIva9ZUBm/hvyA7PIPVFjRxUKe6fdAgwE= github.com/spdx/tools-golang v0.5.1 h1:fJg3SVOGG+eIva9ZUBm/hvyA7PIPVFjRxUKe6fdAgwE=
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
@ -564,6 +565,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -584,8 +587,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -617,8 +620,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -637,8 +640,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -680,19 +683,19 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -873,14 +876,14 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ= k8s.io/api v0.26.7 h1:Lf4iEBEJb5OFNmawtBfSZV/UNi9riSJ0t1qdhyZqI40=
k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= k8s.io/api v0.26.7/go.mod h1:Vk9bMadzA49UHPmHB//lX7VRCQSXGoVwfLd3Sc1SSXI=
k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= k8s.io/apimachinery v0.26.7 h1:590jSBwaSHCAFCqltaEogY/zybFlhGsnLteLpuF2wig=
k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= k8s.io/apimachinery v0.26.7/go.mod h1:qYzLkrQ9lhrZRh0jNKo2cfvf/R1/kQONnSiyB7NUJU0=
k8s.io/apiserver v0.26.2 h1:Pk8lmX4G14hYqJd1poHGC08G03nIHVqdJMR0SD3IH3o= k8s.io/apiserver v0.26.7 h1:NX/zBZZn4R+Cq6shwyn8Pn8REd0yJJ16dbtv9WkEVEU=
k8s.io/apiserver v0.26.2/go.mod h1:GHcozwXgXsPuOJ28EnQ/jXEM9QeG6HT22YxSNmpYNh8= k8s.io/apiserver v0.26.7/go.mod h1:r0wDRWHI7VL/KlQLTkJJBVGZ3KeNfv+VetlyRtr86xs=
k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= k8s.io/client-go v0.26.7 h1:hyU9aKHlwVOykgyxzGYkrDSLCc4+mimZVyUJjPyUn1E=
k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= k8s.io/client-go v0.26.7/go.mod h1:okYjy0jtq6sdeztALDvCh24tg4opOQS1XNvsJlERDAo=
k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=

@ -9,8 +9,9 @@ set -e
: "${CGO_ENABLED=0}" : "${CGO_ENABLED=0}"
: "${GO_PKG=github.com/docker/buildx}" : "${GO_PKG=github.com/docker/buildx}"
: "${GO_EXTRA_FLAGS=}"
: "${GO_LDFLAGS=-X ${GO_PKG}/version.Version=${VERSION} -X ${GO_PKG}/version.Revision=${REVISION} -X ${GO_PKG}/version.Package=${PACKAGE}}" : "${GO_LDFLAGS=-X ${GO_PKG}/version.Version=${VERSION} -X ${GO_PKG}/version.Revision=${REVISION} -X ${GO_PKG}/version.Package=${PACKAGE}}"
: "${GO_EXTRA_LDFLAGS=}" : "${GO_EXTRA_LDFLAGS=}"
set -x set -x
CGO_ENABLED=$CGO_ENABLED go build -mod vendor -trimpath -ldflags "${GO_LDFLAGS} ${GO_EXTRA_LDFLAGS}" -o "${DESTDIR}/docker-buildx" ./cmd/buildx CGO_ENABLED=$CGO_ENABLED go build -mod vendor -trimpath ${GO_EXTRA_FLAGS} -ldflags "${GO_LDFLAGS} ${GO_EXTRA_LDFLAGS}" -o "${DESTDIR}/docker-buildx" ./cmd/buildx

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG GO_VERSION=1.20.6 ARG GO_VERSION=1.20.7
ARG FORMATS=md,yaml ARG FORMATS=md,yaml
FROM golang:${GO_VERSION}-alpine AS docsgen FROM golang:${GO_VERSION}-alpine AS docsgen

@ -5,7 +5,7 @@
# Copyright The Buildx Authors. # Copyright The Buildx Authors.
# Licensed under the Apache License, Version 2.0 # Licensed under the Apache License, Version 2.0
ARG GO_VERSION="1.20.6" ARG GO_VERSION="1.20.7"
ARG PROTOC_VERSION="3.11.4" ARG PROTOC_VERSION="3.11.4"
# protoc is dynamically linked to glibc so can't use alpine base # protoc is dynamically linked to glibc so can't use alpine base

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG GO_VERSION=1.20.6 ARG GO_VERSION=1.20.7
FROM golang:${GO_VERSION}-alpine FROM golang:${GO_VERSION}-alpine
RUN apk add --no-cache git gcc musl-dev RUN apk add --no-cache git gcc musl-dev

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG GO_VERSION=1.20.6 ARG GO_VERSION=1.20.7
ARG MODOUTDATED_VERSION=v0.8.0 ARG MODOUTDATED_VERSION=v0.8.0
FROM golang:${GO_VERSION}-alpine AS base FROM golang:${GO_VERSION}-alpine AS base

@ -28,7 +28,7 @@ Usage:
attach ID attach ID
ID is for a session (visible via list command) or a process (visible via ps command). ID is for a session (visible via list command) or a process (visible via ps command).
If you attached to a process, use Ctrl-c-a for switching the monitor to that process's STDIO. If you attached to a process, use Ctrl-a-c for switching the monitor to that process's STDIO.
`, `,
} }
} }

@ -41,6 +41,7 @@ func (cm *ExecCmd) Exec(ctx context.Context, args []string) error {
cfg := controllerapi.InvokeConfig{ cfg := controllerapi.InvokeConfig{
Entrypoint: []string{args[1]}, Entrypoint: []string{args[1]},
Cmd: args[2:], Cmd: args[2:],
NoCmd: false,
// TODO: support other options as well via flags // TODO: support other options as well via flags
Env: cm.invokeConfig.Env, Env: cm.invokeConfig.Env,
User: cm.invokeConfig.User, User: cm.invokeConfig.User,

@ -47,6 +47,7 @@ func (cm *RollbackCmd) Exec(ctx context.Context, args []string) error {
if len(cmds) > 0 { if len(cmds) > 0 {
cfg.Entrypoint = []string{cmds[0]} cfg.Entrypoint = []string{cmds[0]}
cfg.Cmd = cmds[1:] cfg.Cmd = cmds[1:]
cfg.NoCmd = false
} }
} }
id := cm.m.Rollback(ctx, cfg) id := cm.m.Rollback(ctx, cfg)

@ -283,6 +283,7 @@ func (m *monitor) startInvoke(ctx context.Context, pid string, cfg controllerapi
if len(cfg.Entrypoint) == 0 && len(cfg.Cmd) == 0 { if len(cfg.Entrypoint) == 0 && len(cfg.Cmd) == 0 {
cfg.Entrypoint = []string{"sh"} // launch shell by default cfg.Entrypoint = []string{"sh"} // launch shell by default
cfg.Cmd = []string{} cfg.Cmd = []string{}
cfg.NoCmd = false
} }
go func() { go func() {
// Start a new invoke // Start a new invoke

Binary file not shown.

@ -24,11 +24,12 @@ type NodeGroup struct {
} }
type Node struct { type Node struct {
Name string Name string
Endpoint string Endpoint string
Platforms []specs.Platform Platforms []specs.Platform
Flags []string Flags []string
DriverOpts map[string]string DriverOpts map[string]string
SecurityOpts map[string]string
Files map[string][]byte Files map[string][]byte
} }
@ -48,7 +49,7 @@ func (ng *NodeGroup) Leave(name string) error {
return nil return nil
} }
func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpointsSet bool, actionAppend bool, flags []string, configFile string, do map[string]string) error { func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpointsSet bool, actionAppend bool, flags []string, configFile string, do map[string]string, so map[string]string) error {
if ng.Dynamic { if ng.Dynamic {
return errors.New("dynamic node group does not support Update") return errors.New("dynamic node group does not support Update")
} }
@ -91,6 +92,10 @@ func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpoints
n.DriverOpts = do n.DriverOpts = do
needsRestart = true needsRestart = true
} }
if so != nil {
n.SecurityOpts = so
needsRestart = true
}
if configFile != "" { if configFile != "" {
for k, v := range files { for k, v := range files {
n.Files[k] = v n.Files[k] = v
@ -118,12 +123,13 @@ func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpoints
} }
n := Node{ n := Node{
Name: name, Name: name,
Endpoint: endpoint, Endpoint: endpoint,
Platforms: pp, Platforms: pp,
Flags: flags, Flags: flags,
DriverOpts: do, DriverOpts: do,
Files: files, SecurityOpts: so,
Files: files,
} }
ng.Nodes = append(ng.Nodes, n) ng.Nodes = append(ng.Nodes, n)
@ -156,6 +162,10 @@ func (n *Node) Copy() *Node {
for k, v := range n.DriverOpts { for k, v := range n.DriverOpts {
driverOpts[k] = v driverOpts[k] = v
} }
securityOpts := map[string]string{}
for k, v := range n.SecurityOpts {
securityOpts[k] = v
}
files := map[string][]byte{} files := map[string][]byte{}
for k, v := range n.Files { for k, v := range n.Files {
vv := []byte{} vv := []byte{}
@ -163,12 +173,13 @@ func (n *Node) Copy() *Node {
files[k] = vv files[k] = vv
} }
return &Node{ return &Node{
Name: n.Name, Name: n.Name,
Endpoint: n.Endpoint, Endpoint: n.Endpoint,
Platforms: platforms, Platforms: platforms,
Flags: flags, Flags: flags,
DriverOpts: driverOpts, DriverOpts: driverOpts,
Files: files, SecurityOpts: securityOpts,
Files: files,
} }
} }

@ -18,6 +18,7 @@ func bakeCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) {
} }
var bakeTests = []func(t *testing.T, sb integration.Sandbox){ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
testBakeLocal,
testBakeRemote, testBakeRemote,
testBakeRemoteCmdContext, testBakeRemoteCmdContext,
testBakeRemoteCmdContextOverride, testBakeRemoteCmdContextOverride,
@ -26,6 +27,29 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
testBakeRemoteCmdContextEscapeRelative, testBakeRemoteCmdContextEscapeRelative,
} }
func testBakeLocal(t *testing.T, sb integration.Sandbox) {
dockerfile := []byte(`
FROM scratch
COPY foo /foo
`)
bakefile := []byte(`
target "default" {
}
`)
dir := tmpdir(
t,
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
fstest.CreateFile("Dockerfile", dockerfile, 0600),
fstest.CreateFile("foo", []byte("foo"), 0600),
)
dirDest := t.TempDir()
out, err := bakeCmd(sb, withDir(dir), withArgs("--set", "*.output=type=local,dest="+dirDest))
require.NoError(t, err, out)
require.FileExists(t, filepath.Join(dirDest, "foo"))
}
func testBakeRemote(t *testing.T, sb integration.Sandbox) { func testBakeRemote(t *testing.T, sb integration.Sandbox) {
bakefile := []byte(` bakefile := []byte(`
target "default" { target "default" {

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -13,6 +14,7 @@ import (
"github.com/containerd/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/containerd/continuity/fs/fstest" "github.com/containerd/continuity/fs/fstest"
"github.com/creack/pty"
"github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/contentutil"
"github.com/moby/buildkit/util/testutil" "github.com/moby/buildkit/util/testutil"
"github.com/moby/buildkit/util/testutil/integration" "github.com/moby/buildkit/util/testutil/integration"
@ -33,8 +35,11 @@ var buildTests = []func(t *testing.T, sb integration.Sandbox){
testImageIDOutput, testImageIDOutput,
testBuildLocalExport, testBuildLocalExport,
testBuildRegistryExport, testBuildRegistryExport,
testBuildRegistryExportAttestations,
testBuildTarExport, testBuildTarExport,
testBuildMobyFromLocalImage,
testBuildDetailsLink, testBuildDetailsLink,
testBuildProgress,
} }
func testBuild(t *testing.T, sb integration.Sandbox) { func testBuild(t *testing.T, sb integration.Sandbox) {
@ -92,6 +97,40 @@ func testBuildRegistryExport(t *testing.T, sb integration.Sandbox) {
require.Equal(t, img.Layers[0]["bar"].Data, []byte("foo")) require.Equal(t, img.Layers[0]["bar"].Data, []byte("foo"))
} }
func testBuildRegistryExportAttestations(t *testing.T, sb integration.Sandbox) {
dir := createTestProject(t)
registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) {
t.Skip(err.Error())
}
require.NoError(t, err)
target := registry + "/buildx/registry:latest"
out, err := buildCmd(sb, withArgs(fmt.Sprintf("--output=type=image,name=%s,push=true", target), "--provenance=true", dir))
if sb.Name() == "docker" {
require.Error(t, err)
require.Contains(t, out, "attestations are not supported")
return
}
require.NoError(t, err, string(out))
desc, provider, err := contentutil.ProviderFromRef(target)
require.NoError(t, err)
imgs, err := testutil.ReadImages(sb.Context(), provider, desc)
require.NoError(t, err)
pk := platforms.Format(platforms.Normalize(platforms.DefaultSpec()))
img := imgs.Find(pk)
require.NotNil(t, img)
require.Len(t, img.Layers, 1)
require.Equal(t, img.Layers[0]["bar"].Data, []byte("foo"))
att := imgs.FindAttestation(pk)
require.NotNil(t, att)
require.Len(t, att.Layers, 1)
}
func testImageIDOutput(t *testing.T, sb integration.Sandbox) { func testImageIDOutput(t *testing.T, sb integration.Sandbox) {
dockerfile := []byte(`FROM busybox:latest`) dockerfile := []byte(`FROM busybox:latest`)
@ -102,14 +141,14 @@ func testImageIDOutput(t *testing.T, sb integration.Sandbox) {
outFlag := "--output=type=docker" outFlag := "--output=type=docker"
if sb.Name() == "remote" { if sb.DockerAddress() == "" {
// there is no Docker atm to load the image // there is no Docker atm to load the image
outFlag += ",dest=" + targetDir + "/image.tar" outFlag += ",dest=" + targetDir + "/image.tar"
} }
cmd := buildxCmd( cmd := buildxCmd(
sb, sb,
withArgs("build", "-q", outFlag, "--iidfile", filepath.Join(targetDir, "iid.txt"), "--metadata-file", filepath.Join(targetDir, "md.json"), dir), withArgs("build", "-q", "--provenance", "false", outFlag, "--iidfile", filepath.Join(targetDir, "iid.txt"), "--metadata-file", filepath.Join(targetDir, "md.json"), dir),
) )
stdout := bytes.NewBuffer(nil) stdout := bytes.NewBuffer(nil)
cmd.Stdout = stdout cmd.Stdout = stdout
@ -142,6 +181,54 @@ func testImageIDOutput(t *testing.T, sb integration.Sandbox) {
require.Equal(t, dgst, digest.Digest(md.ConfigDigest)) require.Equal(t, dgst, digest.Digest(md.ConfigDigest))
} }
func testBuildMobyFromLocalImage(t *testing.T, sb integration.Sandbox) {
if !isDockerWorker(sb) {
t.Skip("skipping test for non-docker workers")
}
// pull image
cmd := dockerCmd(sb, withArgs("pull", "-q", "busybox:latest"))
stdout := bytes.NewBuffer(nil)
cmd.Stdout = stdout
cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run())
require.Equal(t, "docker.io/library/busybox:latest", strings.TrimSpace(stdout.String()))
// create local tag
cmd = dockerCmd(sb, withArgs("tag", "busybox:latest", "buildx-test:busybox"))
cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run())
// build image
dockerfile := []byte(`FROM buildx-test:busybox`)
dir := tmpdir(t, fstest.CreateFile("Dockerfile", dockerfile, 0600))
cmd = buildxCmd(
sb,
withArgs("build", "-q", "--output=type=cacheonly", dir),
)
cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run())
// create local tag matching a remote one
cmd = dockerCmd(sb, withArgs("tag", "busybox:latest", "busybox:1.35"))
cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run())
// build image and check that it uses the local tag
// (note: the version check should match the version of busybox in pins.go)
dockerfile = []byte(`
FROM busybox:1.35
RUN busybox | head -1 | grep v1.36.1
`)
dir = tmpdir(t, fstest.CreateFile("Dockerfile", dockerfile, 0600))
cmd = buildxCmd(
sb,
withArgs("build", "-q", "--output=type=cacheonly", dir),
)
cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run())
}
func testBuildDetailsLink(t *testing.T, sb integration.Sandbox) { func testBuildDetailsLink(t *testing.T, sb integration.Sandbox) {
buildDetailsPattern := regexp.MustCompile(`(?m)^View build details: docker-desktop://dashboard/build/[^/]+/[^/]+/[^/]+\n$`) buildDetailsPattern := regexp.MustCompile(`(?m)^View build details: docker-desktop://dashboard/build/[^/]+/[^/]+/[^/]+\n$`)
@ -200,3 +287,29 @@ COPY --from=base /etc/bar /bar
) )
return dir return dir
} }
func testBuildProgress(t *testing.T, sb integration.Sandbox) {
dir := createTestProject(t)
driver, _, _ := strings.Cut(sb.Name(), "+")
name := sb.Address()
// progress=tty
cmd := buildxCmd(sb, withArgs("build", "--progress=tty", "--output=type=cacheonly", dir))
f, err := pty.Start(cmd)
require.NoError(t, err)
buf := bytes.NewBuffer(nil)
io.Copy(buf, f)
ttyOutput := buf.String()
require.Contains(t, ttyOutput, "[+] Building")
require.Contains(t, ttyOutput, fmt.Sprintf("%s:%s", driver, name))
require.Contains(t, ttyOutput, "=> [internal] load build definition from Dockerfile")
require.Contains(t, ttyOutput, "=> [base 1/3] FROM docker.io/library/busybox:latest")
// progress=plain
cmd = buildxCmd(sb, withArgs("build", "--progress=plain", "--output=type=cacheonly", dir))
plainOutput, err := cmd.CombinedOutput()
require.NoError(t, err)
require.Contains(t, string(plainOutput), fmt.Sprintf(`#0 building with "%s" instance using %s driver`, name, driver))
require.Contains(t, string(plainOutput), "[internal] load build definition from Dockerfile")
require.Contains(t, string(plainOutput), "[base 1/3] FROM docker.io/library/busybox:latest")
}

@ -0,0 +1,267 @@
package tests
import (
"encoding/json"
"os/exec"
"testing"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/platforms"
"github.com/containerd/continuity/fs/fstest"
"github.com/moby/buildkit/util/testutil/integration"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
var imagetoolsTests = []func(t *testing.T, sb integration.Sandbox){
testImagetoolsCopyManifest,
testImagetoolsCopyIndex,
testImagetoolsInspectAndFilter,
testImagetoolsAnnotation,
}
func testImagetoolsCopyManifest(t *testing.T, sb integration.Sandbox) {
if sb.Name() != "docker-container" {
t.Skip("imagetools tests are not driver specific and only run on docker-container")
}
dir := createDockerfile(t)
registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) {
t.Skip(err.Error())
}
require.NoError(t, err)
target := registry + "/buildx/imtools-manifest:latest"
out, err := buildCmd(sb, withArgs("-t", target, "--push", "--platform=linux/amd64", "--provenance=false", dir))
require.NoError(t, err, string(out))
cmd := buildxCmd(sb, withArgs("imagetools", "inspect", target, "--raw"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
var mfst ocispecs.Manifest
err = json.Unmarshal(dt, &mfst)
require.NoError(t, err)
require.Equal(t, images.MediaTypeDockerSchema2Manifest, mfst.MediaType)
registry2, err := sb.NewRegistry()
require.NoError(t, err)
target2 := registry2 + "/buildx/imtools2-manifest:latest"
cmd = buildxCmd(sb, withArgs("imagetools", "create", "-t", target2, target))
dt, err = cmd.CombinedOutput()
require.NoError(t, err, string(dt))
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", target2, "--raw"))
dt, err = cmd.CombinedOutput()
require.NoError(t, err, string(dt))
var idx2 ocispecs.Index
err = json.Unmarshal(dt, &idx2)
require.NoError(t, err)
require.Equal(t, images.MediaTypeDockerSchema2ManifestList, idx2.MediaType)
require.Equal(t, 1, len(idx2.Manifests))
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", target2+"@"+string(idx2.Manifests[0].Digest), "--raw"))
dt, err = cmd.CombinedOutput()
require.NoError(t, err, string(dt))
var mfst2 ocispecs.Manifest
err = json.Unmarshal(dt, &mfst2)
require.NoError(t, err)
require.Equal(t, images.MediaTypeDockerSchema2Manifest, mfst2.MediaType)
require.Equal(t, mfst.Config.Digest, mfst2.Config.Digest)
require.Equal(t, len(mfst.Layers), len(mfst2.Layers))
for i := range mfst.Layers {
require.Equal(t, mfst.Layers[i].Digest, mfst2.Layers[i].Digest)
}
}
func testImagetoolsCopyIndex(t *testing.T, sb integration.Sandbox) {
if sb.Name() != "docker-container" {
t.Skip("imagetools tests are not driver specific and only run on docker-container")
}
dir := createDockerfile(t)
registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) {
t.Skip(err.Error())
}
require.NoError(t, err)
target := registry + "/buildx/imtools:latest"
out, err := buildCmd(sb, withArgs("-t", target, "--push", "--platform=linux/amd64,linux/arm64", "--provenance=false", dir))
require.NoError(t, err, string(out))
cmd := buildxCmd(sb, withArgs("imagetools", "inspect", target, "--raw"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
var idx ocispecs.Index
err = json.Unmarshal(dt, &idx)
require.NoError(t, err)
require.Equal(t, images.MediaTypeDockerSchema2ManifestList, idx.MediaType)
require.Equal(t, 2, len(idx.Manifests))
registry2, err := sb.NewRegistry()
require.NoError(t, err)
target2 := registry2 + "/buildx/imtools2:latest"
cmd = buildxCmd(sb, withArgs("imagetools", "create", "-t", target2, target))
dt, err = cmd.CombinedOutput()
require.NoError(t, err, string(dt))
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", target2, "--raw"))
dt, err = cmd.CombinedOutput()
require.NoError(t, err, string(dt))
var idx2 ocispecs.Index
err = json.Unmarshal(dt, &idx2)
require.NoError(t, err)
require.Equal(t, images.MediaTypeDockerSchema2ManifestList, idx2.MediaType)
require.Equal(t, len(idx.Manifests), len(idx2.Manifests))
for i := range idx.Manifests {
require.Equal(t, idx.Manifests[i].Digest, idx2.Manifests[i].Digest)
}
}
func testImagetoolsInspectAndFilter(t *testing.T, sb integration.Sandbox) {
if sb.Name() != "docker-container" {
t.Skip("imagetools tests are not driver specific and only run on docker-container")
}
dir := createDockerfile(t)
registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) {
t.Skip(err.Error())
}
require.NoError(t, err)
target := registry + "/buildx/imtools:latest"
out, err := buildCmd(sb, withArgs("-t", target, "--push", "--platform=linux/amd64,linux/arm64", "--provenance=false", dir))
require.NoError(t, err, string(out))
cmd := buildxCmd(sb, withArgs("imagetools", "inspect", target, "--raw"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
var idx ocispecs.Index
err = json.Unmarshal(dt, &idx)
require.NoError(t, err)
require.Equal(t, 2, len(idx.Manifests))
mfst := idx.Manifests[0]
require.Equal(t, "linux/amd64", platforms.Format(*mfst.Platform))
mfst = idx.Manifests[1]
require.Equal(t, "linux/arm64", platforms.Format(*mfst.Platform))
// create amd64 only image
cmd = buildxCmd(sb, withArgs("imagetools", "create", "-t", target+"-arm64", target+"@"+string(idx.Manifests[1].Digest)))
dt, err = cmd.CombinedOutput()
require.NoError(t, err, string(dt))
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", target+"-arm64", "--raw"))
dt, err = cmd.CombinedOutput()
require.NoError(t, err, string(dt))
var idx2 ocispecs.Index
err = json.Unmarshal(dt, &idx2)
require.NoError(t, err)
require.Equal(t, 1, len(idx2.Manifests))
require.Equal(t, idx.Manifests[1].Digest, idx2.Manifests[0].Digest)
require.Equal(t, platforms.Format(*idx.Manifests[1].Platform), platforms.Format(*idx2.Manifests[0].Platform))
}
func testImagetoolsAnnotation(t *testing.T, sb integration.Sandbox) {
if sb.Name() != "docker-container" {
t.Skip("imagetools tests are not driver specific and only run on docker-container")
}
dir := createDockerfile(t)
registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) {
t.Skip(err.Error())
}
require.NoError(t, err)
target := registry + "/buildx/imtools:latest"
out, err := buildCmd(sb, withArgs("--output", "type=registry,oci-mediatypes=true,name="+target, "--platform=linux/amd64,linux/arm64", "--provenance=false", dir))
require.NoError(t, err, string(out))
cmd := buildxCmd(sb, withArgs("imagetools", "inspect", target, "--raw"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
var idx ocispecs.Index
err = json.Unmarshal(dt, &idx)
require.NoError(t, err)
require.Empty(t, idx.Annotations)
imagetoolsCmd := func(source []string) *exec.Cmd {
args := []string{"imagetools", "create", "-t", target, "--annotation", "index:foo=bar", "--annotation", "index:bar=baz",
"--annotation", "manifest-descriptor:foo=bar", "--annotation", "manifest-descriptor[linux/amd64]:bar=baz"}
args = append(args, source...)
return buildxCmd(sb, withArgs(args...))
}
sources := [][]string{
{
target,
},
{
target + "@" + string(idx.Manifests[0].Digest),
target + "@" + string(idx.Manifests[1].Digest),
},
}
for _, source := range sources {
cmd = imagetoolsCmd(source)
dt, err = cmd.CombinedOutput()
require.NoError(t, err, string(dt))
newTarget := registry + "/buildx/imtools:annotations"
cmd = buildxCmd(sb, withArgs("imagetools", "create", "-t", newTarget, target))
dt, err = cmd.CombinedOutput()
require.NoError(t, err, string(dt))
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", newTarget, "--raw"))
dt, err = cmd.CombinedOutput()
require.NoError(t, err, string(dt))
err = json.Unmarshal(dt, &idx)
require.NoError(t, err)
require.Len(t, idx.Annotations, 2)
require.Equal(t, "bar", idx.Annotations["foo"])
require.Equal(t, "baz", idx.Annotations["bar"])
require.Len(t, idx.Manifests, 2)
for _, mfst := range idx.Manifests {
require.Equal(t, "bar", mfst.Annotations["foo"])
if platforms.Format(*mfst.Platform) == "linux/amd64" {
require.Equal(t, "baz", mfst.Annotations["bar"])
} else {
require.Empty(t, mfst.Annotations["bar"])
}
}
}
}
func createDockerfile(t *testing.T) string {
dockerfile := []byte(`
FROM scratch
ARG TARGETARCH
COPY foo-${TARGETARCH} /foo
`)
dir := tmpdir(
t,
fstest.CreateFile("Dockerfile", dockerfile, 0600),
fstest.CreateFile("foo-amd64", []byte("foo-amd64"), 0600),
fstest.CreateFile("foo-arm64", []byte("foo-arm64"), 0600),
)
return dir
}

@ -33,6 +33,8 @@ func testInspect(t *testing.T, sb integration.Sandbox) {
driver = strings.TrimSpace(v) driver = strings.TrimSpace(v)
} }
} }
require.Equal(t, sb.Address(), name) require.Equal(t, sb.Address(), name)
require.Equal(t, sb.Name(), driver) sbDriver, _, _ := strings.Cut(sb.Name(), "+")
require.Equal(t, sbDriver, driver)
} }

@ -3,6 +3,7 @@ package tests
import ( import (
"os" "os"
"os/exec" "os/exec"
"strings"
"testing" "testing"
"github.com/containerd/continuity/fs/fstest" "github.com/containerd/continuity/fs/fstest"
@ -55,3 +56,20 @@ func buildxCmd(sb integration.Sandbox, opts ...cmdOpt) *exec.Cmd {
return cmd return cmd
} }
func dockerCmd(sb integration.Sandbox, opts ...cmdOpt) *exec.Cmd {
cmd := exec.Command("docker")
cmd.Env = append([]string{}, os.Environ()...)
for _, opt := range opts {
opt(cmd)
}
if context := sb.DockerAddress(); context != "" {
cmd.Env = append(cmd.Env, "DOCKER_CONTEXT="+context)
}
return cmd
}
func isDockerWorker(sb integration.Sandbox) bool {
sbDriver, _, _ := strings.Cut(sb.Name(), "+")
return sbDriver == "docker"
}

@ -7,10 +7,11 @@ import (
"github.com/docker/buildx/tests/workers" "github.com/docker/buildx/tests/workers"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/moby/buildkit/util/testutil/integration" "github.com/moby/buildkit/util/testutil/integration"
bkworkers "github.com/moby/buildkit/util/testutil/workers"
) )
func init() { func init() {
if integration.IsTestDockerd() { if bkworkers.IsTestDockerd() {
workers.InitDockerWorker() workers.InitDockerWorker()
workers.InitDockerContainerWorker() workers.InitDockerContainerWorker()
} else { } else {
@ -24,13 +25,15 @@ func TestIntegration(t *testing.T) {
tests = append(tests, bakeTests...) tests = append(tests, bakeTests...)
tests = append(tests, inspectTests...) tests = append(tests, inspectTests...)
tests = append(tests, lsTests...) tests = append(tests, lsTests...)
tests = append(tests, imagetoolsTests...)
tests = append(tests, versionTests...)
testIntegration(t, tests...) testIntegration(t, tests...)
} }
func testIntegration(t *testing.T, funcs ...func(t *testing.T, sb integration.Sandbox)) { func testIntegration(t *testing.T, funcs ...func(t *testing.T, sb integration.Sandbox)) {
mirroredImages := integration.OfficialImages("busybox:latest", "alpine:latest") mirroredImages := integration.OfficialImages("busybox:latest", "alpine:latest")
buildkitImage := "docker.io/moby/buildkit:buildx-stable-1" buildkitImage := "docker.io/moby/buildkit:buildx-stable-1"
if integration.IsTestDockerd() { if bkworkers.IsTestDockerd() {
if img, ok := os.LookupEnv("TEST_BUILDKIT_IMAGE"); ok { if img, ok := os.LookupEnv("TEST_BUILDKIT_IMAGE"); ok {
ref, err := reference.ParseNormalizedNamed(img) ref, err := reference.ParseNormalizedNamed(img)
if err == nil { if err == nil {

@ -23,9 +23,10 @@ func testLs(t *testing.T, sb integration.Sandbox) {
out, err := lsCmd(sb) out, err := lsCmd(sb)
require.NoError(t, err, string(out)) require.NoError(t, err, string(out))
sbDriver, _, _ := strings.Cut(sb.Name(), "+")
for _, line := range strings.Split(out, "\n") { for _, line := range strings.Split(out, "\n") {
if strings.Contains(line, sb.Address()) { if strings.Contains(line, sb.Address()) {
require.Contains(t, line, sb.Name()) require.Contains(t, line, sbDriver)
return return
} }
} }

@ -0,0 +1,55 @@
package tests
import (
"strings"
"testing"
"github.com/moby/buildkit/util/testutil/integration"
"github.com/stretchr/testify/require"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
)
var versionTests = []func(t *testing.T, sb integration.Sandbox){
testVersion,
}
func testVersion(t *testing.T, sb integration.Sandbox) {
cmd := buildxCmd(sb, withArgs("version"))
out, err := cmd.CombinedOutput()
require.NoError(t, err, string(out))
// There should be at least one newline and the first line
// of output should contain the name, version, and possibly a revision.
firstLine, _, hasNewline := strings.Cut(string(out), "\n")
require.True(t, hasNewline, "At least one newline is required in the output")
// Log the output to make debugging easier.
t.Log(firstLine)
// Split by spaces into at least 2 fields.
fields := strings.Fields(firstLine)
require.GreaterOrEqual(t, len(fields), 2, "Expected at least 2 fields in the first line")
// First field should be an import path.
// This can be any valid import path for Go
// so don't set too many restrictions here.
// Just checking if the import path is a valid Go
// path should be suitable enough to make sure this is ok.
// Using CheckImportPath instead of CheckPath as it is less
// restrictive.
importPath := fields[0]
require.NoError(t, module.CheckImportPath(importPath), "First field was not a valid import path: %+v", importPath)
// Second field should be a version.
// This defaults to something that's still compatible
// with semver.
version := fields[1]
require.True(t, semver.IsValid(version), "Second field was not valid semver: %+v", version)
// Revision should be empty or should look like a git hash.
if len(fields) > 2 && len(fields[2]) > 0 {
revision := fields[2]
require.Regexp(t, `[0-9a-f]{40}`, revision, "Third field was not a git revision: %+v", revision)
}
}

@ -1,10 +1,20 @@
package workers package workers
import (
"os"
"strings"
"github.com/moby/buildkit/util/testutil/integration"
)
type backend struct { type backend struct {
builder string builder string
context string context string
unsupportedFeatures []string
} }
var _ integration.Backend = &backend{}
func (s *backend) Address() string { func (s *backend) Address() string {
return s.builder return s.builder
} }
@ -24,3 +34,26 @@ func (s *backend) Snapshotter() string {
func (s *backend) Rootless() bool { func (s *backend) Rootless() bool {
return false return false
} }
func (s backend) Supports(feature string) bool {
if enabledFeatures := os.Getenv("BUILDKIT_TEST_ENABLE_FEATURES"); enabledFeatures != "" {
for _, enabledFeature := range strings.Split(enabledFeatures, ",") {
if feature == enabledFeature {
return true
}
}
}
if disabledFeatures := os.Getenv("BUILDKIT_TEST_DISABLE_FEATURES"); disabledFeatures != "" {
for _, disabledFeature := range strings.Split(disabledFeatures, ",") {
if feature == disabledFeature {
return false
}
}
}
for _, unsupportedFeature := range s.unsupportedFeatures {
if feature == unsupportedFeature {
return false
}
}
return true
}

@ -20,6 +20,8 @@ func InitDockerContainerWorker() {
type containerWorker struct { type containerWorker struct {
id string id string
unsupported []string
docker integration.Backend docker integration.Backend
dockerClose func() error dockerClose func() error
dockerErr error dockerErr error
@ -65,8 +67,9 @@ func (w *containerWorker) New(ctx context.Context, cfg *integration.BackendConfi
} }
return &backend{ return &backend{
context: w.docker.DockerAddress(), context: w.docker.DockerAddress(),
builder: name, builder: name,
unsupportedFeatures: w.unsupported,
}, cl, nil }, cl, nil
} }

@ -7,6 +7,7 @@ import (
"github.com/moby/buildkit/identity" "github.com/moby/buildkit/identity"
"github.com/moby/buildkit/util/testutil/integration" "github.com/moby/buildkit/util/testutil/integration"
bkworkers "github.com/moby/buildkit/util/testutil/workers"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -14,10 +15,16 @@ func InitDockerWorker() {
integration.Register(&dockerWorker{ integration.Register(&dockerWorker{
id: "docker", id: "docker",
}) })
integration.Register(&dockerWorker{
id: "docker+containerd",
containerdSnapshotter: true,
})
} }
type dockerWorker struct { type dockerWorker struct {
id string id string
containerdSnapshotter bool
unsupported []string
} }
func (c dockerWorker) Name() string { func (c dockerWorker) Name() string {
@ -29,8 +36,9 @@ func (c dockerWorker) Rootless() bool {
} }
func (c dockerWorker) New(ctx context.Context, cfg *integration.BackendConfig) (b integration.Backend, cl func() error, err error) { func (c dockerWorker) New(ctx context.Context, cfg *integration.BackendConfig) (b integration.Backend, cl func() error, err error) {
moby := integration.Moby{ moby := bkworkers.Moby{
ID: c.id, ID: c.id,
ContainerdSnapshotter: c.containerdSnapshotter,
} }
bk, bkclose, err := moby.New(ctx, cfg) bk, bkclose, err := moby.New(ctx, cfg)
if err != nil { if err != nil {
@ -60,8 +68,9 @@ func (c dockerWorker) New(ctx context.Context, cfg *integration.BackendConfig) (
} }
return &backend{ return &backend{
builder: name, builder: name,
context: name, context: name,
unsupportedFeatures: c.unsupported,
}, cl, nil }, cl, nil
} }

@ -0,0 +1,13 @@
package workers
import (
"testing"
"github.com/moby/buildkit/util/testutil/integration"
)
var features = map[string]struct{}{}
func CheckFeatureCompat(t *testing.T, sb integration.Sandbox, reason ...string) {
integration.CheckFeatureCompat(t, sb, features, reason...)
}

@ -7,6 +7,7 @@ import (
"github.com/moby/buildkit/identity" "github.com/moby/buildkit/identity"
"github.com/moby/buildkit/util/testutil/integration" "github.com/moby/buildkit/util/testutil/integration"
bkworkers "github.com/moby/buildkit/util/testutil/workers"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -17,7 +18,8 @@ func InitRemoteWorker() {
} }
type remoteWorker struct { type remoteWorker struct {
id string id string
unsupported []string
} }
func (w remoteWorker) Name() string { func (w remoteWorker) Name() string {
@ -29,7 +31,7 @@ func (w remoteWorker) Rootless() bool {
} }
func (w remoteWorker) New(ctx context.Context, cfg *integration.BackendConfig) (b integration.Backend, cl func() error, err error) { func (w remoteWorker) New(ctx context.Context, cfg *integration.BackendConfig) (b integration.Backend, cl func() error, err error) {
oci := integration.OCI{ID: w.id} oci := bkworkers.OCI{ID: w.id}
bk, bkclose, err := oci.New(ctx, cfg) bk, bkclose, err := oci.New(ctx, cfg)
if err != nil { if err != nil {
return bk, cl, err return bk, cl, err
@ -60,7 +62,8 @@ func (w remoteWorker) New(ctx context.Context, cfg *integration.BackendConfig) (
} }
return &backend{ return &backend{
builder: name, builder: name,
unsupportedFeatures: w.unsupported,
}, cl, nil }, cl, nil
} }

@ -1,21 +1,15 @@
package buildflags package buildflags
import ( import "github.com/moby/buildkit/util/entitlements"
"github.com/moby/buildkit/util/entitlements"
"github.com/pkg/errors"
)
func ParseEntitlements(in []string) ([]entitlements.Entitlement, error) { func ParseEntitlements(in []string) ([]entitlements.Entitlement, error) {
out := make([]entitlements.Entitlement, 0, len(in)) out := make([]entitlements.Entitlement, 0, len(in))
for _, v := range in { for _, v := range in {
switch v { e, err := entitlements.Parse(v)
case "security.insecure": if err != nil {
out = append(out, entitlements.EntitlementSecurityInsecure) return nil, err
case "network.host":
out = append(out, entitlements.EntitlementNetworkHost)
default:
return nil, errors.Errorf("invalid entitlement: %v", v)
} }
out = append(out, e)
} }
return out, nil return out, nil
} }

@ -46,17 +46,24 @@ func (c *Client) LoadImage(ctx context.Context, name string, status progress.Wri
w = &waitingWriter{ w = &waitingWriter{
PipeWriter: pw, PipeWriter: pw,
f: func() { f: func() {
resp, err := dapi.ImageLoad(ctx, pr, false) handleErr := func(err error) {
defer close(done)
if err != nil {
pr.CloseWithError(err) pr.CloseWithError(err)
w.mu.Lock() w.mu.Lock()
w.err = err w.err = err
w.mu.Unlock() w.mu.Unlock()
}
resp, err := dapi.ImageLoad(ctx, pr, false)
defer close(done)
if err != nil {
handleErr(err)
return return
} }
prog := progress.WithPrefix(status, "", false) prog := progress.WithPrefix(status, "", false)
progress.FromReader(prog, "importing to docker", resp.Body) if err := fromReader(prog, "importing to docker", resp.Body); err != nil {
handleErr(err)
}
}, },
done: done, done: done,
cancel: cancel, cancel: cancel,

@ -1,15 +1,19 @@
package progress package dockerutil
import ( import (
"errors"
"io" "io"
"time" "time"
"github.com/docker/buildx/util/progress"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/moby/buildkit/identity" "github.com/moby/buildkit/identity"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
) )
func FromReader(w Writer, name string, rc io.ReadCloser) { func fromReader(w progress.Writer, name string, rc io.ReadCloser) error {
dgst := digest.FromBytes([]byte(identity.NewID())) dgst := digest.FromBytes([]byte(identity.NewID()))
tm := time.Now() tm := time.Now()
@ -23,7 +27,12 @@ func FromReader(w Writer, name string, rc io.ReadCloser) {
Vertexes: []*client.Vertex{&vtx}, Vertexes: []*client.Vertex{&vtx},
}) })
_, err := io.Copy(io.Discard, rc) err := jsonmessage.DisplayJSONMessagesToStream(rc, streams.NewOut(io.Discard), nil)
if err != nil {
if jerr, ok := err.(*jsonmessage.JSONError); ok {
err = errors.New(jerr.Message)
}
}
tm2 := time.Now() tm2 := time.Now()
vtx2 := vtx vtx2 := vtx
@ -34,4 +43,5 @@ func FromReader(w Writer, name string, rc io.ReadCloser) {
w.Write(&client.SolveStatus{ w.Write(&client.SolveStatus{
Vertexes: []*client.Vertex{&vtx2}, Vertexes: []*client.Vertex{&vtx2},
}) })
return err
} }

@ -49,7 +49,7 @@ func New(opts ...Option) (*Git, error) {
c.gitpath, err = gitPath(c.wd) c.gitpath, err = gitPath(c.wd)
if err != nil { if err != nil {
return nil, errors.New("git not found in PATH") return nil, err
} }
return c, nil return c, nil

@ -5,6 +5,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"net/url" "net/url"
"regexp"
"strings" "strings"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
@ -13,6 +14,7 @@ import (
"github.com/containerd/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
"github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/contentutil"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go" "github.com/opencontainers/image-spec/specs-go"
@ -26,7 +28,7 @@ type Source struct {
Ref reference.Named Ref reference.Named
} }
func (r *Resolver) Combine(ctx context.Context, srcs []*Source) ([]byte, ocispec.Descriptor, error) { func (r *Resolver) Combine(ctx context.Context, srcs []*Source, ann map[string]string) ([]byte, ocispec.Descriptor, error) {
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
dts := make([][]byte, len(srcs)) dts := make([][]byte, len(srcs))
@ -75,7 +77,7 @@ func (r *Resolver) Combine(ctx context.Context, srcs []*Source) ([]byte, ocispec
} }
// on single source, return original bytes // on single source, return original bytes
if len(srcs) == 1 { if len(srcs) == 1 && len(ann) == 0 {
if mt := srcs[0].Desc.MediaType; mt == images.MediaTypeDockerSchema2ManifestList || mt == ocispec.MediaTypeImageIndex { if mt := srcs[0].Desc.MediaType; mt == images.MediaTypeDockerSchema2ManifestList || mt == ocispec.MediaTypeImageIndex {
return dts[0], srcs[0].Desc, nil return dts[0], srcs[0].Desc, nil
} }
@ -138,12 +140,39 @@ func (r *Resolver) Combine(ctx context.Context, srcs []*Source) ([]byte, ocispec
mt = ocispec.MediaTypeImageIndex mt = ocispec.MediaTypeImageIndex
} }
// annotations are only allowed on OCI indexes
indexAnnotation := make(map[string]string)
if mt == ocispec.MediaTypeImageIndex {
annotations, err := parseAnnotations(ann)
if err != nil {
return nil, ocispec.Descriptor{}, err
}
if len(annotations[exptypes.AnnotationIndex]) > 0 {
for k, v := range annotations[exptypes.AnnotationIndex] {
indexAnnotation[k.Key] = v
}
}
if len(annotations[exptypes.AnnotationManifestDescriptor]) > 0 {
for i := 0; i < len(newDescs); i++ {
if newDescs[i].Annotations == nil {
newDescs[i].Annotations = map[string]string{}
}
for k, v := range annotations[exptypes.AnnotationManifestDescriptor] {
if k.Platform == nil || k.PlatformString() == platforms.Format(*newDescs[i].Platform) {
newDescs[i].Annotations[k.Key] = v
}
}
}
}
}
idxBytes, err := json.MarshalIndent(ocispec.Index{ idxBytes, err := json.MarshalIndent(ocispec.Index{
MediaType: mt, MediaType: mt,
Versioned: specs.Versioned{ Versioned: specs.Versioned{
SchemaVersion: 2, SchemaVersion: 2,
}, },
Manifests: newDescs, Manifests: newDescs,
Annotations: indexAnnotation,
}, "", " ") }, "", " ")
if err != nil { if err != nil {
return nil, ocispec.Descriptor{}, errors.Wrap(err, "failed to marshal index") return nil, ocispec.Descriptor{}, errors.Wrap(err, "failed to marshal index")
@ -266,3 +295,52 @@ func detectMediaType(dt []byte) (string, error) {
return images.MediaTypeDockerSchema2ManifestList, nil return images.MediaTypeDockerSchema2ManifestList, nil
} }
func parseAnnotations(ann map[string]string) (map[string]map[exptypes.AnnotationKey]string, error) {
// TODO: use buildkit's annotation parser once it supports setting custom prefix and ":" separator
annotationRegexp := regexp.MustCompile(`^([a-z-]+)(?:\[([A-Za-z0-9_/-]+)\])?:(\S+)$`)
indexAnnotations := make(map[exptypes.AnnotationKey]string)
manifestDescriptorAnnotations := make(map[exptypes.AnnotationKey]string)
for k, v := range ann {
groups := annotationRegexp.FindStringSubmatch(k)
if groups == nil {
return nil, errors.Errorf("invalid annotation format, expected <type>:<key>=<value>, got %q", k)
}
typ, platform, key := groups[1], groups[2], groups[3]
var ociPlatform *ocispec.Platform
if platform != "" {
p, err := platforms.Parse(platform)
if err != nil {
return nil, errors.Wrapf(err, "invalid platform %q", platform)
}
ociPlatform = &p
}
switch typ {
case exptypes.AnnotationIndex:
ak := exptypes.AnnotationKey{
Type: typ,
Platform: ociPlatform,
Key: key,
}
indexAnnotations[ak] = v
case exptypes.AnnotationManifestDescriptor:
ak := exptypes.AnnotationKey{
Type: typ,
Platform: ociPlatform,
Key: key,
}
manifestDescriptorAnnotations[ak] = v
case exptypes.AnnotationManifest:
return nil, errors.Errorf("%q annotations are not supported yet", typ)
case exptypes.AnnotationIndexDescriptor:
return nil, errors.Errorf("%q annotations are invalid while creating an image", typ)
default:
return nil, errors.Errorf("unknown annotation type %q", typ)
}
}
return map[string]map[exptypes.AnnotationKey]string{
exptypes.AnnotationIndex: indexAnnotations,
exptypes.AnnotationManifestDescriptor: manifestDescriptorAnnotations,
}, nil
}

@ -20,7 +20,9 @@ func (w *pw) Write(st *client.SolveStatus) {
} }
} }
if w.diff != nil { if w.diff != nil {
vertexes := make([]*client.Vertex, 0, len(st.Vertexes))
for _, v := range st.Vertexes { for _, v := range st.Vertexes {
v := *v
if v.Started != nil { if v.Started != nil {
d := v.Started.Add(-*w.diff) d := v.Started.Add(-*w.diff)
v.Started = &d v.Started = &d
@ -29,8 +31,12 @@ func (w *pw) Write(st *client.SolveStatus) {
d := v.Completed.Add(-*w.diff) d := v.Completed.Add(-*w.diff)
v.Completed = &d v.Completed = &d
} }
vertexes = append(vertexes, &v)
} }
statuses := make([]*client.VertexStatus, 0, len(st.Statuses))
for _, v := range st.Statuses { for _, v := range st.Statuses {
v := *v
if v.Started != nil { if v.Started != nil {
d := v.Started.Add(-*w.diff) d := v.Started.Add(-*w.diff)
v.Started = &d v.Started = &d
@ -40,9 +46,21 @@ func (w *pw) Write(st *client.SolveStatus) {
v.Completed = &d v.Completed = &d
} }
v.Timestamp = v.Timestamp.Add(-*w.diff) v.Timestamp = v.Timestamp.Add(-*w.diff)
statuses = append(statuses, &v)
} }
logs := make([]*client.VertexLog, 0, len(st.Logs))
for _, v := range st.Logs { for _, v := range st.Logs {
v := *v
v.Timestamp = v.Timestamp.Add(-*w.diff) v.Timestamp = v.Timestamp.Add(-*w.diff)
logs = append(logs, &v)
}
st = &client.SolveStatus{
Vertexes: vertexes,
Statuses: statuses,
Logs: logs,
Warnings: st.Warnings,
} }
} }
w.Writer.Write(st) w.Writer.Write(st)

@ -17,7 +17,6 @@
package cli package cli
import ( import (
"bytes"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
@ -250,7 +249,7 @@ func WithDotEnv(o *ProjectOptions) error {
if err != nil { if err != nil {
return err return err
} }
envMap, err := GetEnvFromFile(o.Environment, wd, o.EnvFiles) envMap, err := dotenv.GetEnvFromFile(o.Environment, wd, o.EnvFiles)
if err != nil { if err != nil {
return err return err
} }
@ -262,65 +261,6 @@ func WithDotEnv(o *ProjectOptions) error {
return nil return nil
} }
func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames []string) (map[string]string, error) {
envMap := make(map[string]string)
dotEnvFiles := filenames
if len(dotEnvFiles) == 0 {
dotEnvFiles = append(dotEnvFiles, filepath.Join(workingDir, ".env"))
}
for _, dotEnvFile := range dotEnvFiles {
abs, err := filepath.Abs(dotEnvFile)
if err != nil {
return envMap, err
}
dotEnvFile = abs
s, err := os.Stat(dotEnvFile)
if os.IsNotExist(err) {
if len(filenames) == 0 {
return envMap, nil
}
return envMap, errors.Errorf("Couldn't find env file: %s", dotEnvFile)
}
if err != nil {
return envMap, err
}
if s.IsDir() {
if len(filenames) == 0 {
return envMap, nil
}
return envMap, errors.Errorf("%s is a directory", dotEnvFile)
}
b, err := os.ReadFile(dotEnvFile)
if os.IsNotExist(err) {
return nil, errors.Errorf("Couldn't read env file: %s", dotEnvFile)
}
if err != nil {
return envMap, err
}
env, err := dotenv.ParseWithLookup(bytes.NewReader(b), func(k string) (string, bool) {
v, ok := currentEnv[k]
if ok {
return v, true
}
v, ok = envMap[k]
return v, ok
})
if err != nil {
return envMap, errors.Wrapf(err, "failed to read %s", dotEnvFile)
}
for k, v := range env {
envMap[k] = v
}
}
return envMap, nil
}
// WithInterpolation set ProjectOptions to enable/skip interpolation // WithInterpolation set ProjectOptions to enable/skip interpolation
func WithInterpolation(interpolation bool) ProjectOptionsFn { func WithInterpolation(interpolation bool) ProjectOptionsFn {
return func(o *ProjectOptions) error { return func(o *ProjectOptions) error {

@ -0,0 +1,84 @@
/*
Copyright 2020 The Compose Specification Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dotenv
import (
"bytes"
"os"
"path/filepath"
"github.com/pkg/errors"
)
func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames []string) (map[string]string, error) {
envMap := make(map[string]string)
dotEnvFiles := filenames
if len(dotEnvFiles) == 0 {
dotEnvFiles = append(dotEnvFiles, filepath.Join(workingDir, ".env"))
}
for _, dotEnvFile := range dotEnvFiles {
abs, err := filepath.Abs(dotEnvFile)
if err != nil {
return envMap, err
}
dotEnvFile = abs
s, err := os.Stat(dotEnvFile)
if os.IsNotExist(err) {
if len(filenames) == 0 {
return envMap, nil
}
return envMap, errors.Errorf("Couldn't find env file: %s", dotEnvFile)
}
if err != nil {
return envMap, err
}
if s.IsDir() {
if len(filenames) == 0 {
return envMap, nil
}
return envMap, errors.Errorf("%s is a directory", dotEnvFile)
}
b, err := os.ReadFile(dotEnvFile)
if os.IsNotExist(err) {
return nil, errors.Errorf("Couldn't read env file: %s", dotEnvFile)
}
if err != nil {
return envMap, err
}
env, err := ParseWithLookup(bytes.NewReader(b), func(k string) (string, bool) {
v, ok := currentEnv[k]
if ok {
return v, true
}
v, ok = envMap[k]
return v, ok
})
if err != nil {
return envMap, errors.Wrapf(err, "failed to read %s", dotEnvFile)
}
for k, v := range env {
envMap[k] = v
}
}
return envMap, nil
}

@ -123,8 +123,8 @@ loop:
} }
return "", "", inherited, fmt.Errorf( return "", "", inherited, fmt.Errorf(
`line %d: unexpected character %q in variable name`, `line %d: unexpected character %q in variable name %q`,
p.line, string(rune)) p.line, string(rune), strings.Split(src, "\n")[0])
} }
} }
@ -153,17 +153,24 @@ func (p *parser) extractVarValue(src string, envMap map[string]string, lookupFn
return retVal, rest, err return retVal, rest, err
} }
previousCharIsEscape := false
// lookup quoted string terminator // lookup quoted string terminator
for i := 1; i < len(src); i++ { for i := 1; i < len(src); i++ {
if src[i] == '\n' { if src[i] == '\n' {
p.line++ p.line++
} }
if char := src[i]; char != quote { if char := src[i]; char != quote {
if !previousCharIsEscape && char == '\\' {
previousCharIsEscape = true
} else {
previousCharIsEscape = false
}
continue continue
} }
// skip escaped quote symbol (\" or \', depends on quote) // skip escaped quote symbol (\" or \', depends on quote)
if prevChar := src[i-1]; prevChar == '\\' { if previousCharIsEscape {
previousCharIsEscape = false
continue continue
} }

@ -24,7 +24,7 @@ services:
- bar - bar
labels: [FOO=BAR] labels: [FOO=BAR]
additional_contexts: additional_contexts:
foo: /bar foo: ./bar
secrets: secrets:
- secret1 - secret1
- source: secret2 - source: secret2
@ -181,6 +181,7 @@ services:
timeout: 1s timeout: 1s
retries: 5 retries: 5
start_period: 15s start_period: 15s
start_interval: 5s
# Any valid image reference - repo, tag, id, sha # Any valid image reference - repo, tag, id, sha
image: redis image: redis

@ -0,0 +1,120 @@
/*
Copyright 2020 The Compose Specification Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package loader
import (
"fmt"
"path/filepath"
"github.com/compose-spec/compose-go/dotenv"
"github.com/compose-spec/compose-go/types"
"github.com/pkg/errors"
)
// LoadIncludeConfig parse the require config from raw yaml
func LoadIncludeConfig(source []interface{}) ([]types.IncludeConfig, error) {
var requires []types.IncludeConfig
err := Transform(source, &requires)
return requires, err
}
var transformIncludeConfig TransformerFunc = func(data interface{}) (interface{}, error) {
switch value := data.(type) {
case string:
return map[string]interface{}{"path": value}, nil
case map[string]interface{}:
return value, nil
default:
return data, errors.Errorf("invalid type %T for `include` configuration", value)
}
}
func loadInclude(configDetails types.ConfigDetails, model *types.Config, options *Options, loaded []string) (*types.Config, error) {
for _, r := range model.Include {
for i, p := range r.Path {
if !filepath.IsAbs(p) {
r.Path[i] = filepath.Join(configDetails.WorkingDir, p)
}
}
if r.ProjectDirectory == "" {
r.ProjectDirectory = filepath.Dir(r.Path[0])
}
loadOptions := options.clone()
loadOptions.SetProjectName(model.Name, true)
loadOptions.ResolvePaths = true
loadOptions.SkipNormalization = true
loadOptions.SkipConsistencyCheck = true
env, err := dotenv.GetEnvFromFile(configDetails.Environment, r.ProjectDirectory, r.EnvFile)
if err != nil {
return nil, err
}
imported, err := load(types.ConfigDetails{
WorkingDir: r.ProjectDirectory,
ConfigFiles: types.ToConfigFiles(r.Path),
Environment: env,
}, loadOptions, loaded)
if err != nil {
return nil, err
}
err = importResources(model, imported, r.Path)
if err != nil {
return nil, err
}
}
model.Include = nil
return model, nil
}
// importResources import into model all resources defined by imported, and report error on conflict
func importResources(model *types.Config, imported *types.Project, path []string) error {
services := mapByName(model.Services)
for _, service := range imported.Services {
if _, ok := services[service.Name]; ok {
return fmt.Errorf("imported compose file %s defines conflicting service %s", path, service.Name)
}
model.Services = append(model.Services, service)
}
for n, network := range imported.Networks {
if _, ok := model.Networks[n]; ok {
return fmt.Errorf("imported compose file %s defines conflicting network %s", path, n)
}
model.Networks[n] = network
}
for n, volume := range imported.Volumes {
if _, ok := model.Volumes[n]; ok {
return fmt.Errorf("imported compose file %s defines conflicting volume %s", path, n)
}
model.Volumes[n] = volume
}
for n, secret := range imported.Secrets {
if _, ok := model.Secrets[n]; ok {
return fmt.Errorf("imported compose file %s defines conflicting secret %s", path, n)
}
model.Secrets[n] = secret
}
for n, config := range imported.Configs {
if _, ok := model.Configs[n]; ok {
return fmt.Errorf("imported compose file %s defines conflicting config %s", path, n)
}
model.Configs[n] = config
}
return nil
}

@ -56,6 +56,8 @@ type Options struct {
SkipConsistencyCheck bool SkipConsistencyCheck bool
// Skip extends // Skip extends
SkipExtends bool SkipExtends bool
// SkipInclude will ignore `include` and only load model from file(s) set by ConfigDetails
SkipInclude bool
// Interpolation options // Interpolation options
Interpolate *interp.Options Interpolate *interp.Options
// Discard 'env_file' entries after resolving to 'environment' section // Discard 'env_file' entries after resolving to 'environment' section
@ -68,6 +70,24 @@ type Options struct {
Profiles []string Profiles []string
} }
func (o *Options) clone() *Options {
return &Options{
SkipValidation: o.SkipValidation,
SkipInterpolation: o.SkipInterpolation,
SkipNormalization: o.SkipNormalization,
ResolvePaths: o.ResolvePaths,
ConvertWindowsPaths: o.ConvertWindowsPaths,
SkipConsistencyCheck: o.SkipConsistencyCheck,
SkipExtends: o.SkipExtends,
SkipInclude: o.SkipInclude,
Interpolate: o.Interpolate,
discardEnvFiles: o.discardEnvFiles,
projectName: o.projectName,
projectNameImperativelySet: o.projectNameImperativelySet,
Profiles: o.Profiles,
}
}
func (o *Options) SetProjectName(name string, imperativelySet bool) { func (o *Options) SetProjectName(name string, imperativelySet bool) {
o.projectName = name o.projectName = name
o.projectNameImperativelySet = imperativelySet o.projectNameImperativelySet = imperativelySet
@ -185,6 +205,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
LookupValue: configDetails.LookupEnv, LookupValue: configDetails.LookupEnv,
TypeCastMapping: interpolateTypeCastMapping, TypeCastMapping: interpolateTypeCastMapping,
}, },
ResolvePaths: true,
} }
for _, op := range options { for _, op := range options {
@ -195,8 +216,22 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
if err != nil { if err != nil {
return nil, err return nil, err
} }
opts.projectName = projectName
return load(configDetails, opts, nil)
}
func load(configDetails types.ConfigDetails, opts *Options, loaded []string) (*types.Project, error) {
var model *types.Config var model *types.Config
mainFile := configDetails.ConfigFiles[0].Filename
for _, f := range loaded {
if f == mainFile {
loaded = append(loaded, mainFile)
return nil, errors.Errorf("include cycle detected:\n%s\n include %s", loaded[0], strings.Join(loaded[1:], "\n include "))
}
}
loaded = append(loaded, mainFile)
for i, file := range configDetails.ConfigFiles { for i, file := range configDetails.ConfigFiles {
var postProcessor PostProcessor var postProcessor PostProcessor
configDict := file.Config configDict := file.Config
@ -231,10 +266,18 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
return nil, err return nil, err
} }
if !opts.SkipInclude {
cfg, err = loadInclude(configDetails, cfg, opts, loaded)
if err != nil {
return nil, err
}
}
if i == 0 { if i == 0 {
model = cfg model = cfg
continue continue
} }
merged, err := merge([]*types.Config{model, cfg}) merged, err := merge([]*types.Config{model, cfg})
if err != nil { if err != nil {
return nil, err return nil, err
@ -248,16 +291,8 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
model = merged model = merged
} }
for _, s := range model.Services {
var newEnvFiles types.StringList
for _, ef := range s.EnvFile {
newEnvFiles = append(newEnvFiles, absPath(configDetails.WorkingDir, ef))
}
s.EnvFile = newEnvFiles
}
project := &types.Project{ project := &types.Project{
Name: projectName, Name: opts.projectName,
WorkingDir: configDetails.WorkingDir, WorkingDir: configDetails.WorkingDir,
Services: model.Services, Services: model.Services,
Networks: model.Networks, Networks: model.Networks,
@ -269,14 +304,30 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
} }
if !opts.SkipNormalization { if !opts.SkipNormalization {
err = Normalize(project, opts.ResolvePaths) err := Normalize(project)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
if opts.ResolvePaths {
err := ResolveRelativePaths(project)
if err != nil {
return nil, err
}
}
if opts.ConvertWindowsPaths {
for i, service := range project.Services {
for j, volume := range service.Volumes {
service.Volumes[j] = convertVolumePath(volume)
}
project.Services[i] = service
}
}
if !opts.SkipConsistencyCheck { if !opts.SkipConsistencyCheck {
err = checkConsistency(project) err := checkConsistency(project)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -287,7 +338,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
} }
project.ApplyProfiles(opts.Profiles) project.ApplyProfiles(opts.Profiles)
err = project.ResolveServicesEnvironment(opts.discardEnvFiles) err := project.ResolveServicesEnvironment(opts.discardEnvFiles)
return project, err return project, err
} }
@ -419,7 +470,6 @@ func loadSections(filename string, config map[string]interface{}, configDetails
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.Networks, err = LoadNetworks(getSection(config, "networks")) cfg.Networks, err = LoadNetworks(getSection(config, "networks"))
if err != nil { if err != nil {
return nil, err return nil, err
@ -428,11 +478,15 @@ func loadSections(filename string, config map[string]interface{}, configDetails
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"), configDetails, opts.ResolvePaths) cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"), configDetails, opts.ResolvePaths) cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"))
if err != nil {
return nil, err
}
cfg.Include, err = LoadIncludeConfig(getSequence(config, "include"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -451,6 +505,14 @@ func getSection(config map[string]interface{}, key string) map[string]interface{
return section.(map[string]interface{}) return section.(map[string]interface{})
} }
func getSequence(config map[string]interface{}, key string) []interface{} {
section, ok := config[key]
if !ok {
return make([]interface{}, 0)
}
return section.([]interface{})
}
// ForbiddenPropertiesError is returned when there are properties in the Compose // ForbiddenPropertiesError is returned when there are properties in the Compose
// file that are forbidden. // file that are forbidden.
type ForbiddenPropertiesError struct { type ForbiddenPropertiesError struct {
@ -515,6 +577,7 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
reflect.TypeOf(types.ExtendsConfig{}): transformExtendsConfig, reflect.TypeOf(types.ExtendsConfig{}): transformExtendsConfig,
reflect.TypeOf(types.DeviceRequest{}): transformServiceDeviceRequest, reflect.TypeOf(types.DeviceRequest{}): transformServiceDeviceRequest,
reflect.TypeOf(types.SSHConfig{}): transformSSHConfig, reflect.TypeOf(types.SSHConfig{}): transformSSHConfig,
reflect.TypeOf(types.IncludeConfig{}): transformIncludeConfig,
} }
for _, transformer := range additionalTransformers { for _, transformer := range additionalTransformers {
@ -605,6 +668,7 @@ func LoadServices(filename string, servicesDict map[string]interface{}, workingD
for k, v := range x.(map[string]interface{}) { for k, v := range x.(map[string]interface{}) {
servicesDict[k] = v servicesDict[k] = v
} }
delete(servicesDict, extensions)
} }
for name := range servicesDict { for name := range servicesDict {
@ -633,7 +697,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
target = map[string]interface{}{} target = map[string]interface{}{}
} }
serviceConfig, err := LoadService(name, target.(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths, opts.ConvertWindowsPaths) serviceConfig, err := LoadService(name, target.(map[string]interface{}))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -671,21 +735,9 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
// make the paths relative to `file` rather than `baseFilePath` so // make the paths relative to `file` rather than `baseFilePath` so
// that the resulting paths won't be absolute if `file` isn't an // that the resulting paths won't be absolute if `file` isn't an
// absolute path. // absolute path.
baseFileParent := filepath.Dir(file)
if baseService.Build != nil {
baseService.Build.Context = resolveBuildContextPath(baseFileParent, baseService.Build.Context)
}
for i, vol := range baseService.Volumes {
if vol.Type != types.VolumeTypeBind {
continue
}
baseService.Volumes[i].Source = resolveMaybeUnixPath(vol.Source, baseFileParent, lookupEnv)
}
for i, envFile := range baseService.EnvFile { baseFileParent := filepath.Dir(file)
baseService.EnvFile[i] = resolveMaybeUnixPath(envFile, baseFileParent, lookupEnv) ResolveServiceRelativePaths(baseFileParent, baseService)
}
} }
serviceConfig, err = _merge(baseService, serviceConfig) serviceConfig, err = _merge(baseService, serviceConfig)
@ -698,22 +750,9 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
return serviceConfig, nil return serviceConfig, nil
} }
func resolveBuildContextPath(baseFileParent string, context string) string {
// Checks if the context is an HTTP(S) URL or a remote git repository URL
for _, prefix := range []string{"https://", "http://", "git://", "github.com/", "git@"} {
if strings.HasPrefix(context, prefix) {
return context
}
}
// Note that the Dockerfile is always defined relative to the
// build context, so there's no need to update the Dockerfile field.
return absPath(baseFileParent, context)
}
// LoadService produces a single ServiceConfig from a compose file Dict // LoadService produces a single ServiceConfig from a compose file Dict
// the serviceDict is not validated if directly used. Use Load() to enable validation // the serviceDict is not validated if directly used. Use Load() to enable validation
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool, convertPaths bool) (*types.ServiceConfig, error) { func LoadService(name string, serviceDict map[string]interface{}) (*types.ServiceConfig, error) {
serviceConfig := &types.ServiceConfig{ serviceConfig := &types.ServiceConfig{
Scale: 1, Scale: 1,
} }
@ -730,13 +769,6 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str
return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`) return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`)
} }
if resolvePaths || convertPaths {
volume = resolveVolumePath(volume, workingDir, lookupEnv)
}
if convertPaths {
volume = convertVolumePath(volume)
}
serviceConfig.Volumes[i] = volume serviceConfig.Volumes[i] = volume
} }
@ -758,8 +790,8 @@ func convertVolumePath(volume types.ServiceVolumeConfig) types.ServiceVolumeConf
return volume return volume
} }
func resolveMaybeUnixPath(path string, workingDir string, lookupEnv template.Mapping) string { func resolveMaybeUnixPath(workingDir string, path string) string {
filePath := expandUser(path, lookupEnv) filePath := expandUser(path)
// Check if source is an absolute path (either Unix or Windows), to // Check if source is an absolute path (either Unix or Windows), to
// handle a Windows client with a Unix daemon or vice-versa. // handle a Windows client with a Unix daemon or vice-versa.
// //
@ -772,20 +804,8 @@ func resolveMaybeUnixPath(path string, workingDir string, lookupEnv template.Map
return filePath return filePath
} }
func resolveVolumePath(volume types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) types.ServiceVolumeConfig {
volume.Source = resolveMaybeUnixPath(volume.Source, workingDir, lookupEnv)
return volume
}
func resolveSecretsPath(secret types.SecretConfig, workingDir string, lookupEnv template.Mapping) types.SecretConfig {
if !secret.External.External && secret.File != "" {
secret.File = resolveMaybeUnixPath(secret.File, workingDir, lookupEnv)
}
return secret
}
// TODO: make this more robust // TODO: make this more robust
func expandUser(path string, lookupEnv template.Mapping) string { func expandUser(path string) string {
if strings.HasPrefix(path, "~") { if strings.HasPrefix(path, "~") {
home, err := os.UserHomeDir() home, err := os.UserHomeDir()
if err != nil { if err != nil {
@ -885,44 +905,39 @@ func LoadVolumes(source map[string]interface{}) (map[string]types.VolumeConfig,
// LoadSecrets produces a SecretConfig map from a compose file Dict // LoadSecrets produces a SecretConfig map from a compose file Dict
// the source Dict is not validated if directly used. Use Load() to enable validation // the source Dict is not validated if directly used. Use Load() to enable validation
func LoadSecrets(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.SecretConfig, error) { func LoadSecrets(source map[string]interface{}) (map[string]types.SecretConfig, error) {
secrets := make(map[string]types.SecretConfig) secrets := make(map[string]types.SecretConfig)
if err := Transform(source, &secrets); err != nil { if err := Transform(source, &secrets); err != nil {
return secrets, err return secrets, err
} }
for name, secret := range secrets { for name, secret := range secrets {
obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details, false) obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret))
if err != nil { if err != nil {
return nil, err return nil, err
} }
secretConfig := types.SecretConfig(obj) secrets[name] = types.SecretConfig(obj)
if resolvePaths {
secretConfig = resolveSecretsPath(secretConfig, details.WorkingDir, details.LookupEnv)
}
secrets[name] = secretConfig
} }
return secrets, nil return secrets, nil
} }
// LoadConfigObjs produces a ConfigObjConfig map from a compose file Dict // LoadConfigObjs produces a ConfigObjConfig map from a compose file Dict
// the source Dict is not validated if directly used. Use Load() to enable validation // the source Dict is not validated if directly used. Use Load() to enable validation
func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.ConfigObjConfig, error) { func LoadConfigObjs(source map[string]interface{}) (map[string]types.ConfigObjConfig, error) {
configs := make(map[string]types.ConfigObjConfig) configs := make(map[string]types.ConfigObjConfig)
if err := Transform(source, &configs); err != nil { if err := Transform(source, &configs); err != nil {
return configs, err return configs, err
} }
for name, config := range configs { for name, config := range configs {
obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details, resolvePaths) obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config))
if err != nil { if err != nil {
return nil, err return nil, err
} }
configConfig := types.ConfigObjConfig(obj) configs[name] = types.ConfigObjConfig(obj)
configs[name] = configConfig
} }
return configs, nil return configs, nil
} }
func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails, resolvePaths bool) (types.FileObjectConfig, error) { func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig) (types.FileObjectConfig, error) {
// if "external: true" // if "external: true"
switch { switch {
case obj.External.External: case obj.External.External:
@ -942,26 +957,11 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi
if obj.File != "" { if obj.File != "" {
return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name) return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name)
} }
default:
if obj.File != "" && resolvePaths {
obj.File = absPath(details.WorkingDir, obj.File)
}
} }
return obj, nil return obj, nil
} }
func absPath(workingDir string, filePath string) string {
if strings.HasPrefix(filePath, "~") {
home, _ := os.UserHomeDir()
return filepath.Join(home, filePath[1:])
}
if filepath.IsAbs(filePath) {
return filePath
}
return filepath.Join(workingDir, filePath)
}
var transformMapStringString TransformerFunc = func(data interface{}) (interface{}, error) { var transformMapStringString TransformerFunc = func(data interface{}) (interface{}, error) {
switch value := data.(type) { switch value := data.(type) {
case map[string]interface{}: case map[string]interface{}:
@ -1088,13 +1088,24 @@ var transformDependsOnConfig TransformerFunc = func(data interface{}) (interface
for _, serviceIntf := range value { for _, serviceIntf := range value {
service, ok := serviceIntf.(string) service, ok := serviceIntf.(string)
if !ok { if !ok {
return data, errors.Errorf("invalid type %T for service depends_on elementn, expected string", value) return data, errors.Errorf("invalid type %T for service depends_on element, expected string", value)
} }
transformed[service] = map[string]interface{}{"condition": types.ServiceConditionStarted} transformed[service] = map[string]interface{}{"condition": types.ServiceConditionStarted, "required": true}
} }
return transformed, nil return transformed, nil
case map[string]interface{}: case map[string]interface{}:
return groupXFieldsIntoExtensions(data.(map[string]interface{})), nil transformed := map[string]interface{}{}
for service, val := range value {
dependsConfigIntf, ok := val.(map[string]interface{})
if !ok {
return data, errors.Errorf("invalid type %T for service depends_on element", value)
}
if _, ok := dependsConfigIntf["required"]; !ok {
dependsConfigIntf["required"] = true
}
transformed[service] = dependsConfigIntf
}
return groupXFieldsIntoExtensions(transformed), nil
default: default:
return data, errors.Errorf("invalid type %T for service depends_on", value) return data, errors.Errorf("invalid type %T for service depends_on", value)
} }

@ -150,13 +150,12 @@ func unique(slice []string) []string {
return nil return nil
} }
uniqMap := make(map[string]struct{}) uniqMap := make(map[string]struct{})
var uniqSlice []string
for _, v := range slice { for _, v := range slice {
uniqMap[v] = struct{}{} if _, ok := uniqMap[v]; !ok {
} uniqSlice = append(uniqSlice, v)
uniqMap[v] = struct{}{}
uniqSlice := make([]string, 0, len(uniqMap)) }
for v := range uniqMap {
uniqSlice = append(uniqSlice, v)
} }
return uniqSlice return uniqSlice
} }

@ -18,8 +18,6 @@ package loader
import ( import (
"fmt" "fmt"
"os"
"path/filepath"
"strings" "strings"
"github.com/compose-spec/compose-go/errdefs" "github.com/compose-spec/compose-go/errdefs"
@ -29,19 +27,7 @@ import (
) )
// Normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults // Normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults
func Normalize(project *types.Project, resolvePaths bool) error { func Normalize(project *types.Project) error {
absWorkingDir, err := filepath.Abs(project.WorkingDir)
if err != nil {
return err
}
project.WorkingDir = absWorkingDir
absComposeFiles, err := absComposeFiles(project.ComposeFiles)
if err != nil {
return err
}
project.ComposeFiles = absComposeFiles
if project.Networks == nil { if project.Networks == nil {
project.Networks = make(map[string]types.NetworkConfig) project.Networks = make(map[string]types.NetworkConfig)
} }
@ -51,8 +37,7 @@ func Normalize(project *types.Project, resolvePaths bool) error {
project.Networks["default"] = types.NetworkConfig{} project.Networks["default"] = types.NetworkConfig{}
} }
err = relocateExternalName(project) if err := relocateExternalName(project); err != nil {
if err != nil {
return err return err
} }
@ -72,38 +57,16 @@ func Normalize(project *types.Project, resolvePaths bool) error {
} }
if s.Build != nil { if s.Build != nil {
if s.Build.Context == "" {
s.Build.Context = "."
}
if s.Build.Dockerfile == "" && s.Build.DockerfileInline == "" { if s.Build.Dockerfile == "" && s.Build.DockerfileInline == "" {
s.Build.Dockerfile = "Dockerfile" s.Build.Dockerfile = "Dockerfile"
} }
if resolvePaths {
// Build context might be a remote http/git context. Unfortunately supported "remote"
// syntax is highly ambiguous in moby/moby and not defined by compose-spec,
// so let's assume runtime will check
localContext := absPath(project.WorkingDir, s.Build.Context)
if _, err := os.Stat(localContext); err == nil {
s.Build.Context = localContext
}
for name, path := range s.Build.AdditionalContexts {
if strings.Contains(path, "://") { // `docker-image://` or any builder specific context type
continue
}
path = absPath(project.WorkingDir, path)
if _, err := os.Stat(path); err == nil {
s.Build.AdditionalContexts[name] = path
}
}
}
s.Build.Args = s.Build.Args.Resolve(fn) s.Build.Args = s.Build.Args.Resolve(fn)
} }
for j, f := range s.EnvFile {
s.EnvFile[j] = absPath(project.WorkingDir, f)
}
s.Environment = s.Environment.Resolve(fn) s.Environment = s.Environment.Resolve(fn)
if s.Extends != nil && s.Extends.File != "" {
s.Extends.File = absPath(project.WorkingDir, s.Extends.File)
}
for _, link := range s.Links { for _, link := range s.Links {
parts := strings.Split(link, ":") parts := strings.Split(link, ":")
if len(parts) == 2 { if len(parts) == 2 {
@ -112,6 +75,7 @@ func Normalize(project *types.Project, resolvePaths bool) error {
s.DependsOn = setIfMissing(s.DependsOn, link, types.ServiceDependency{ s.DependsOn = setIfMissing(s.DependsOn, link, types.ServiceDependency{
Condition: types.ServiceConditionStarted, Condition: types.ServiceConditionStarted,
Restart: true, Restart: true,
Required: true,
}) })
} }
@ -121,6 +85,7 @@ func Normalize(project *types.Project, resolvePaths bool) error {
s.DependsOn = setIfMissing(s.DependsOn, name, types.ServiceDependency{ s.DependsOn = setIfMissing(s.DependsOn, name, types.ServiceDependency{
Condition: types.ServiceConditionStarted, Condition: types.ServiceConditionStarted,
Restart: true, Restart: true,
Required: true,
}) })
} }
} }
@ -131,6 +96,7 @@ func Normalize(project *types.Project, resolvePaths bool) error {
s.DependsOn = setIfMissing(s.DependsOn, spec[0], types.ServiceDependency{ s.DependsOn = setIfMissing(s.DependsOn, spec[0], types.ServiceDependency{
Condition: types.ServiceConditionStarted, Condition: types.ServiceConditionStarted,
Restart: false, Restart: false,
Required: true,
}) })
} }
} }
@ -160,14 +126,6 @@ func Normalize(project *types.Project, resolvePaths bool) error {
project.Services[i] = s project.Services[i] = s
} }
for name, config := range project.Volumes {
if config.Driver == "local" && config.DriverOpts["o"] == "bind" {
// This is actually a bind mount
config.DriverOpts["device"] = absPath(project.WorkingDir, config.DriverOpts["device"])
project.Volumes[name] = config
}
}
setNameFromKey(project) setNameFromKey(project)
return nil return nil
@ -223,6 +181,7 @@ func inferImplicitDependencies(service *types.ServiceConfig) {
if _, ok := service.DependsOn[d]; !ok { if _, ok := service.DependsOn[d]; !ok {
service.DependsOn[d] = types.ServiceDependency{ service.DependsOn[d] = types.ServiceDependency{
Condition: types.ServiceConditionStarted, Condition: types.ServiceConditionStarted,
Required: true,
} }
} }
} }
@ -254,18 +213,6 @@ func relocateScale(s *types.ServiceConfig) error {
return nil return nil
} }
func absComposeFiles(composeFiles []string) ([]string, error) {
absComposeFiles := make([]string, len(composeFiles))
for i, composeFile := range composeFiles {
absComposefile, err := filepath.Abs(composeFile)
if err != nil {
return nil, err
}
absComposeFiles[i] = absComposefile
}
return absComposeFiles, nil
}
// Resources with no explicit name are actually named by their key in map // Resources with no explicit name are actually named by their key in map
func setNameFromKey(project *types.Project) { func setNameFromKey(project *types.Project) {
for i, n := range project.Networks { for i, n := range project.Networks {

@ -0,0 +1,135 @@
/*
Copyright 2020 The Compose Specification Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package loader
import (
"os"
"path/filepath"
"strings"
"github.com/compose-spec/compose-go/types"
)
// ResolveRelativePaths resolves relative paths based on project WorkingDirectory
func ResolveRelativePaths(project *types.Project) error {
absWorkingDir, err := filepath.Abs(project.WorkingDir)
if err != nil {
return err
}
project.WorkingDir = absWorkingDir
absComposeFiles, err := absComposeFiles(project.ComposeFiles)
if err != nil {
return err
}
project.ComposeFiles = absComposeFiles
for i, s := range project.Services {
ResolveServiceRelativePaths(project.WorkingDir, &s)
project.Services[i] = s
}
for i, obj := range project.Configs {
if obj.File != "" {
obj.File = absPath(project.WorkingDir, obj.File)
project.Configs[i] = obj
}
}
for i, obj := range project.Secrets {
if obj.File != "" {
obj.File = resolveMaybeUnixPath(project.WorkingDir, obj.File)
project.Secrets[i] = obj
}
}
for name, config := range project.Volumes {
if config.Driver == "local" && config.DriverOpts["o"] == "bind" {
// This is actually a bind mount
config.DriverOpts["device"] = resolveMaybeUnixPath(project.WorkingDir, config.DriverOpts["device"])
project.Volumes[name] = config
}
}
return nil
}
func ResolveServiceRelativePaths(workingDir string, s *types.ServiceConfig) {
if s.Build != nil {
if !isRemoteContext(s.Build.Context) {
s.Build.Context = absPath(workingDir, s.Build.Context)
}
for name, path := range s.Build.AdditionalContexts {
if strings.Contains(path, "://") { // `docker-image://` or any builder specific context type
continue
}
if isRemoteContext(path) {
continue
}
s.Build.AdditionalContexts[name] = absPath(workingDir, path)
}
}
for j, f := range s.EnvFile {
s.EnvFile[j] = absPath(workingDir, f)
}
if s.Extends != nil && s.Extends.File != "" {
s.Extends.File = absPath(workingDir, s.Extends.File)
}
for i, vol := range s.Volumes {
if vol.Type != types.VolumeTypeBind {
continue
}
s.Volumes[i].Source = resolveMaybeUnixPath(workingDir, vol.Source)
}
}
func absPath(workingDir string, filePath string) string {
if strings.HasPrefix(filePath, "~") {
home, _ := os.UserHomeDir()
return filepath.Join(home, filePath[1:])
}
if filepath.IsAbs(filePath) {
return filePath
}
return filepath.Join(workingDir, filePath)
}
func absComposeFiles(composeFiles []string) ([]string, error) {
for i, composeFile := range composeFiles {
absComposefile, err := filepath.Abs(composeFile)
if err != nil {
return nil, err
}
composeFiles[i] = absComposefile
}
return composeFiles, nil
}
// isRemoteContext returns true if the value is a Git reference or HTTP(S) URL.
//
// Any other value is assumed to be a local filesystem path and returns false.
//
// See: https://github.com/moby/buildkit/blob/18fc875d9bfd6e065cd8211abc639434ba65aa56/frontend/dockerui/context.go#L76-L79
func isRemoteContext(maybeURL string) bool {
for _, prefix := range []string{"https://", "http://", "git://", "ssh://", "github.com/", "git@"} {
if strings.HasPrefix(maybeURL, prefix) {
return true
}
}
return false
}

@ -1,5 +1,5 @@
{ {
"$schema": "http://json-schema.org/draft/2019-09/schema#", "$schema": "https://json-schema.org/draft/2019-09/schema#",
"id": "compose_spec.json", "id": "compose_spec.json",
"type": "object", "type": "object",
"title": "Compose Specification", "title": "Compose Specification",
@ -17,6 +17,15 @@
"description": "define the Compose project name, until user defines one explicitly." "description": "define the Compose project name, until user defines one explicitly."
}, },
"include": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/include"
},
"description": "compose sub-projects to be included."
},
"services": { "services": {
"id": "#/properties/services", "id": "#/properties/services",
"type": "object", "type": "object",
@ -84,6 +93,7 @@
"properties": { "properties": {
"deploy": {"$ref": "#/definitions/deployment"}, "deploy": {"$ref": "#/definitions/deployment"},
"annotations": {"$ref": "#/definitions/list_or_dict"}, "annotations": {"$ref": "#/definitions/list_or_dict"},
"attach": {"type": "boolean"},
"build": { "build": {
"oneOf": [ "oneOf": [
{"type": "string"}, {"type": "string"},
@ -181,6 +191,10 @@
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"restart": {"type": "boolean"}, "restart": {"type": "boolean"},
"required": {
"type": "boolean",
"default": true
},
"condition": { "condition": {
"type": "string", "type": "string",
"enum": ["service_started", "service_healthy", "service_completed_successfully"] "enum": ["service_started", "service_healthy", "service_completed_successfully"]
@ -443,7 +457,8 @@
] ]
}, },
"timeout": {"type": "string", "format": "duration"}, "timeout": {"type": "string", "format": "duration"},
"start_period": {"type": "string", "format": "duration"} "start_period": {"type": "string", "format": "duration"},
"start_interval": {"type": "string", "format": "duration"}
}, },
"additionalProperties": false, "additionalProperties": false,
"patternProperties": {"^x-": {}} "patternProperties": {"^x-": {}}
@ -588,6 +603,22 @@
} }
}, },
"include": {
"id": "#/definitions/include",
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"path": {"$ref": "#/definitions/string_or_list"},
"env_file": {"$ref": "#/definitions/string_or_list"},
"project_directory": {"type": "string"}
},
"additionalProperties": false
}
]
},
"network": { "network": {
"id": "#/definitions/network", "id": "#/definitions/network",
"type": ["object", "null"], "type": ["object", "null"],

@ -17,19 +17,18 @@
package schema package schema
import ( import (
// Enable support for embedded static resources
_ "embed"
"fmt" "fmt"
"strings" "strings"
"time" "time"
"github.com/xeipuuv/gojsonschema" "github.com/xeipuuv/gojsonschema"
// Enable support for embedded static resources
_ "embed"
) )
type portsFormatChecker struct{} type portsFormatChecker struct{}
func (checker portsFormatChecker) IsFormat(input interface{}) bool { func (checker portsFormatChecker) IsFormat(_ interface{}) bool {
// TODO: implement this // TODO: implement this
return true return true
} }

@ -17,6 +17,7 @@
package template package template
import ( import (
"errors"
"fmt" "fmt"
"regexp" "regexp"
"sort" "sort"
@ -71,77 +72,148 @@ type Mapping func(string) (string, bool)
// the substitution and an error. // the substitution and an error.
type SubstituteFunc func(string, Mapping) (string, bool, error) type SubstituteFunc func(string, Mapping) (string, bool, error)
// SubstituteWith substitute variables in the string with their values. // ReplacementFunc is a user-supplied function that is apply to the matching
// It accepts additional substitute function. // substring. Returns the value as a string and an error.
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) { type ReplacementFunc func(string, Mapping, *Config) (string, error)
var outerErr error
var returnErr error
result := pattern.ReplaceAllStringFunc(template, func(substring string) string { type Config struct {
_, subsFunc := getSubstitutionFunctionForTemplate(substring) pattern *regexp.Regexp
if len(subsFuncs) > 0 { substituteFunc SubstituteFunc
subsFunc = subsFuncs[0] replacementFunc ReplacementFunc
} logging bool
}
closingBraceIndex := getFirstBraceClosingIndex(substring) type Option func(*Config)
rest := ""
if closingBraceIndex > -1 {
rest = substring[closingBraceIndex+1:]
substring = substring[0 : closingBraceIndex+1]
}
matches := pattern.FindStringSubmatch(substring) func WithPattern(pattern *regexp.Regexp) Option {
groups := matchGroups(matches, pattern) return func(cfg *Config) {
if escaped := groups["escaped"]; escaped != "" { cfg.pattern = pattern
return escaped }
} }
braced := false func WithSubstitutionFunction(subsFunc SubstituteFunc) Option {
substitution := groups["named"] return func(cfg *Config) {
if substitution == "" { cfg.substituteFunc = subsFunc
substitution = groups["braced"] }
braced = true }
}
if substitution == "" { func WithReplacementFunction(replacementFunc ReplacementFunc) Option {
outerErr = &InvalidTemplateError{Template: template} return func(cfg *Config) {
if returnErr == nil { cfg.replacementFunc = replacementFunc
returnErr = outerErr }
} }
return ""
} func WithoutLogging(cfg *Config) {
cfg.logging = false
}
// SubstituteWithOptions substitute variables in the string with their values.
// It accepts additional options such as a custom function or pattern.
func SubstituteWithOptions(template string, mapping Mapping, options ...Option) (string, error) {
var returnErr error
cfg := &Config{
pattern: defaultPattern,
replacementFunc: DefaultReplacementFunc,
logging: true,
}
for _, o := range options {
o(cfg)
}
if braced { result := cfg.pattern.ReplaceAllStringFunc(template, func(substring string) string {
var ( replacement, err := cfg.replacementFunc(substring, mapping, cfg)
value string if err != nil {
applied bool // Add the template for template errors
) var tmplErr *InvalidTemplateError
value, applied, outerErr = subsFunc(substitution, mapping) if errors.As(err, &tmplErr) {
if outerErr != nil { if tmplErr.Template == "" {
if returnErr == nil { tmplErr.Template = template
returnErr = outerErr
} }
return ""
} }
if applied { // Save the first error to be returned
interpolatedNested, err := SubstituteWith(rest, mapping, pattern) if returnErr == nil {
if err != nil { returnErr = err
return ""
}
return value + interpolatedNested
} }
}
value, ok := mapping(substitution)
if !ok {
logrus.Warnf("The %q variable is not set. Defaulting to a blank string.", substitution)
} }
return value return replacement
}) })
return result, returnErr return result, returnErr
} }
func DefaultReplacementFunc(substring string, mapping Mapping, cfg *Config) (string, error) {
value, _, err := DefaultReplacementAppliedFunc(substring, mapping, cfg)
return value, err
}
func DefaultReplacementAppliedFunc(substring string, mapping Mapping, cfg *Config) (string, bool, error) {
pattern := cfg.pattern
subsFunc := cfg.substituteFunc
if subsFunc == nil {
_, subsFunc = getSubstitutionFunctionForTemplate(substring)
}
closingBraceIndex := getFirstBraceClosingIndex(substring)
rest := ""
if closingBraceIndex > -1 {
rest = substring[closingBraceIndex+1:]
substring = substring[0 : closingBraceIndex+1]
}
matches := pattern.FindStringSubmatch(substring)
groups := matchGroups(matches, pattern)
if escaped := groups["escaped"]; escaped != "" {
return escaped, true, nil
}
braced := false
substitution := groups["named"]
if substitution == "" {
substitution = groups["braced"]
braced = true
}
if substitution == "" {
return "", false, &InvalidTemplateError{}
}
if braced {
value, applied, err := subsFunc(substitution, mapping)
if err != nil {
return "", false, err
}
if applied {
interpolatedNested, err := SubstituteWith(rest, mapping, pattern)
if err != nil {
return "", false, err
}
return value + interpolatedNested, true, nil
}
}
value, ok := mapping(substitution)
if !ok && cfg.logging {
logrus.Warnf("The %q variable is not set. Defaulting to a blank string.", substitution)
}
return value, ok, nil
}
// SubstituteWith substitute variables in the string with their values.
// It accepts additional substitute function.
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
options := []Option{
WithPattern(pattern),
}
if len(subsFuncs) > 0 {
options = append(options, WithSubstitutionFunction(subsFuncs[0]))
}
return SubstituteWithOptions(template, mapping, options...)
}
func getSubstitutionFunctionForTemplate(template string) (string, SubstituteFunc) { func getSubstitutionFunctionForTemplate(template string) (string, SubstituteFunc) {
interpolationMapping := []struct { interpolationMapping := []struct {
string string

@ -67,16 +67,24 @@ type ConfigFile struct {
Config map[string]interface{} Config map[string]interface{}
} }
func ToConfigFiles(path []string) (f []ConfigFile) {
for _, p := range path {
f = append(f, ConfigFile{Filename: p})
}
return
}
// Config is a full compose file configuration and model // Config is a full compose file configuration and model
type Config struct { type Config struct {
Filename string `yaml:"-" json:"-"` Filename string `yaml:"-" json:"-"`
Name string `yaml:",omitempty" json:"name,omitempty"` Name string `yaml:"name,omitempty" json:"name,omitempty"`
Services Services `json:"services"` Services Services `yaml:"services" json:"services"`
Networks Networks `yaml:",omitempty" json:"networks,omitempty"` Networks Networks `yaml:"networks,omitempty" json:"networks,omitempty"`
Volumes Volumes `yaml:",omitempty" json:"volumes,omitempty"` Volumes Volumes `yaml:"volumes,omitempty" json:"volumes,omitempty"`
Secrets Secrets `yaml:",omitempty" json:"secrets,omitempty"` Secrets Secrets `yaml:"secrets,omitempty" json:"secrets,omitempty"`
Configs Configs `yaml:",omitempty" json:"configs,omitempty"` Configs Configs `yaml:"configs,omitempty" json:"configs,omitempty"`
Extensions Extensions `yaml:",inline" json:"-"` Extensions Extensions `yaml:",inline" json:"-"`
Include []IncludeConfig `yaml:"include,omitempty" json:"include,omitempty"`
} }
// Volumes is a map of VolumeConfig // Volumes is a map of VolumeConfig

@ -24,6 +24,8 @@ import (
"path/filepath" "path/filepath"
"sort" "sort"
"github.com/compose-spec/compose-go/utils"
"github.com/compose-spec/compose-go/dotenv" "github.com/compose-spec/compose-go/dotenv"
"github.com/distribution/distribution/v3/reference" "github.com/distribution/distribution/v3/reference"
godigest "github.com/opencontainers/go-digest" godigest "github.com/opencontainers/go-digest"
@ -102,10 +104,19 @@ func (p *Project) ConfigNames() []string {
// GetServices retrieve services by names, or return all services if no name specified // GetServices retrieve services by names, or return all services if no name specified
func (p *Project) GetServices(names ...string) (Services, error) { func (p *Project) GetServices(names ...string) (Services, error) {
services, servicesNotFound := p.getServicesByNames(names...)
if len(servicesNotFound) > 0 {
return services, fmt.Errorf("no such service: %s", servicesNotFound[0])
}
return services, nil
}
func (p *Project) getServicesByNames(names ...string) (Services, []string) {
if len(names) == 0 { if len(names) == 0 {
return p.Services, nil return p.Services, nil
} }
services := Services{} services := Services{}
var servicesNotFound []string
for _, name := range names { for _, name := range names {
var serviceConfig *ServiceConfig var serviceConfig *ServiceConfig
for _, s := range p.Services { for _, s := range p.Services {
@ -115,11 +126,12 @@ func (p *Project) GetServices(names ...string) (Services, error) {
} }
} }
if serviceConfig == nil { if serviceConfig == nil {
return services, fmt.Errorf("no such service: %s", name) servicesNotFound = append(servicesNotFound, name)
continue
} }
services = append(services, *serviceConfig) services = append(services, *serviceConfig)
} }
return services, nil return services, servicesNotFound
} }
// GetDisabledService retrieve disabled service by name // GetDisabledService retrieve disabled service by name
@ -159,26 +171,30 @@ func (p *Project) WithServices(names []string, fn ServiceFunc, options ...Depend
// backward compatibility // backward compatibility
options = []DependencyOption{IncludeDependencies} options = []DependencyOption{IncludeDependencies}
} }
return p.withServices(names, fn, map[string]bool{}, options) return p.withServices(names, fn, map[string]bool{}, options, map[string]ServiceDependency{})
} }
func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]bool, options []DependencyOption) error { func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]bool, options []DependencyOption, dependencies map[string]ServiceDependency) error {
services, err := p.GetServices(names...) services, servicesNotFound := p.getServicesByNames(names...)
if err != nil { if len(servicesNotFound) > 0 {
return err for _, serviceNotFound := range servicesNotFound {
if dependency, ok := dependencies[serviceNotFound]; !ok || dependency.Required {
return fmt.Errorf("no such service: %s", serviceNotFound)
}
}
} }
for _, service := range services { for _, service := range services {
if seen[service.Name] { if seen[service.Name] {
continue continue
} }
seen[service.Name] = true seen[service.Name] = true
var dependencies []string var dependencies map[string]ServiceDependency
for _, policy := range options { for _, policy := range options {
switch policy { switch policy {
case IncludeDependents: case IncludeDependents:
dependencies = append(dependencies, p.GetDependentsForService(service)...) dependencies = utils.MapsAppend(dependencies, p.dependentsForService(service))
case IncludeDependencies: case IncludeDependencies:
dependencies = append(dependencies, service.GetDependencies()...) dependencies = utils.MapsAppend(dependencies, service.DependsOn)
case IgnoreDependencies: case IgnoreDependencies:
// Noop // Noop
default: default:
@ -186,7 +202,7 @@ func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]b
} }
} }
if len(dependencies) > 0 { if len(dependencies) > 0 {
err := p.withServices(dependencies, fn, seen, options) err := p.withServices(utils.MapKeys(dependencies), fn, seen, options, dependencies)
if err != nil { if err != nil {
return err return err
} }
@ -199,11 +215,15 @@ func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]b
} }
func (p *Project) GetDependentsForService(s ServiceConfig) []string { func (p *Project) GetDependentsForService(s ServiceConfig) []string {
var dependent []string return utils.MapKeys(p.dependentsForService(s))
}
func (p *Project) dependentsForService(s ServiceConfig) map[string]ServiceDependency {
dependent := make(map[string]ServiceDependency)
for _, service := range p.Services { for _, service := range p.Services {
for name := range service.DependsOn { for name, dependency := range service.DependsOn {
if name == s.Name { if name == s.Name {
dependent = append(dependent, service.Name) dependent[service.Name] = dependency
} }
} }
} }
@ -507,7 +527,7 @@ func (p Project) ResolveServicesEnvironment(discardEnvFiles bool) error {
fileVars, err := dotenv.ParseWithLookup(bytes.NewBuffer(b), resolve) fileVars, err := dotenv.ParseWithLookup(bytes.NewBuffer(b), resolve)
if err != nil { if err != nil {
return err return errors.Wrapf(err, "failed to read %s", envFile)
} }
environment.OverrideBy(Mapping(fileVars).ToMappingWithEquals()) environment.OverrideBy(Mapping(fileVars).ToMappingWithEquals())
} }

@ -89,6 +89,7 @@ type ServiceConfig struct {
Profiles []string `yaml:"profiles,omitempty" json:"profiles,omitempty"` Profiles []string `yaml:"profiles,omitempty" json:"profiles,omitempty"`
Annotations Mapping `yaml:"annotations,omitempty" json:"annotations,omitempty"` Annotations Mapping `yaml:"annotations,omitempty" json:"annotations,omitempty"`
Attach *bool `yaml:"attach,omitempty" json:"attach,omitempty"`
Build *BuildConfig `yaml:"build,omitempty" json:"build,omitempty"` Build *BuildConfig `yaml:"build,omitempty" json:"build,omitempty"`
BlkioConfig *BlkioConfig `yaml:"blkio_config,omitempty" json:"blkio_config,omitempty"` BlkioConfig *BlkioConfig `yaml:"blkio_config,omitempty" json:"blkio_config,omitempty"`
CapAdd []string `yaml:"cap_add,omitempty" json:"cap_add,omitempty"` CapAdd []string `yaml:"cap_add,omitempty" json:"cap_add,omitempty"`
@ -602,12 +603,13 @@ type DeployConfig struct {
// HealthCheckConfig the healthcheck configuration for a service // HealthCheckConfig the healthcheck configuration for a service
type HealthCheckConfig struct { type HealthCheckConfig struct {
Test HealthCheckTest `yaml:"test,omitempty" json:"test,omitempty"` Test HealthCheckTest `yaml:"test,omitempty" json:"test,omitempty"`
Timeout *Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"` Timeout *Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
Interval *Duration `yaml:"interval,omitempty" json:"interval,omitempty"` Interval *Duration `yaml:"interval,omitempty" json:"interval,omitempty"`
Retries *uint64 `yaml:"retries,omitempty" json:"retries,omitempty"` Retries *uint64 `yaml:"retries,omitempty" json:"retries,omitempty"`
StartPeriod *Duration `yaml:"start_period,omitempty" json:"start_period,omitempty"` StartPeriod *Duration `yaml:"start_period,omitempty" json:"start_period,omitempty"`
Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"` StartInterval *Duration `yaml:"start_interval,omitempty" json:"start_interval,omitempty"`
Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"`
Extensions Extensions `yaml:"#extensions,inline" json:"-"` Extensions Extensions `yaml:"#extensions,inline" json:"-"`
} }
@ -815,6 +817,8 @@ const (
VolumeTypeTmpfs = "tmpfs" VolumeTypeTmpfs = "tmpfs"
// VolumeTypeNamedPipe is the type for mounting Windows named pipes // VolumeTypeNamedPipe is the type for mounting Windows named pipes
VolumeTypeNamedPipe = "npipe" VolumeTypeNamedPipe = "npipe"
// VolumeTypeCluster is the type for mounting container storage interface (CSI) volumes
VolumeTypeCluster = "cluster"
// SElinuxShared share the volume content // SElinuxShared share the volume content
SElinuxShared = "z" SElinuxShared = "z"
@ -1023,6 +1027,7 @@ type ServiceDependency struct {
Condition string `yaml:"condition,omitempty" json:"condition,omitempty"` Condition string `yaml:"condition,omitempty" json:"condition,omitempty"`
Restart bool `yaml:"restart,omitempty" json:"restart,omitempty"` Restart bool `yaml:"restart,omitempty" json:"restart,omitempty"`
Extensions Extensions `yaml:"#extensions,inline" json:"-"` Extensions Extensions `yaml:"#extensions,inline" json:"-"`
Required bool `yaml:"required" json:"required"`
} }
type ExtendsConfig struct { type ExtendsConfig struct {
@ -1035,3 +1040,9 @@ type SecretConfig FileObjectConfig
// ConfigObjConfig is the config for the swarm "Config" object // ConfigObjConfig is the config for the swarm "Config" object
type ConfigObjConfig FileObjectConfig type ConfigObjConfig FileObjectConfig
type IncludeConfig struct {
Path StringList `yaml:"path,omitempty" json:"path,omitempty"`
ProjectDirectory string `yaml:"project_directory,omitempty" json:"project_directory,omitempty"`
EnvFile StringList `yaml:"env_file,omitempty" json:"env_file,omitempty"`
}

@ -0,0 +1,51 @@
/*
Copyright 2020 The Compose Specification Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import "golang.org/x/exp/slices"
func MapKeys[T comparable, U any](theMap map[T]U) []T {
var result []T
for key := range theMap {
result = append(result, key)
}
return result
}
func MapsAppend[T comparable, U any](target map[T]U, source map[T]U) map[T]U {
if target == nil {
return source
}
if source == nil {
return target
}
for key, value := range source {
if _, ok := target[key]; !ok {
target[key] = value
}
}
return target
}
func ArrayContains[T comparable](source []T, toCheck []T) bool {
for _, value := range toCheck {
if !slices.Contains(source, value) {
return false
}
}
return true
}

@ -0,0 +1,4 @@
[568].out
_go*
_test*
_obj

@ -0,0 +1,17 @@
ARG GOVERSION=1.14
FROM golang:${GOVERSION}
# Set base env.
ARG GOOS=linux
ARG GOARCH=amd64
ENV GOOS=${GOOS} GOARCH=${GOARCH} CGO_ENABLED=0 GOFLAGS='-v -ldflags=-s -ldflags=-w'
# Pre compile the stdlib for 386/arm (32bits).
RUN go build -a std
# Add the code to the image.
WORKDIR pty
ADD . .
# Build the lib.
RUN go build

@ -0,0 +1,23 @@
# NOTE: Using 1.13 as a base to build the RISCV compiler, the resulting version is based on go1.6.
FROM golang:1.13
# Clone and complie a riscv compatible version of the go compiler.
RUN git clone https://review.gerrithub.io/riscv/riscv-go /riscv-go
# riscvdev branch HEAD as of 2019-06-29.
RUN cd /riscv-go && git checkout 04885fddd096d09d4450726064d06dd107e374bf
ENV PATH=/riscv-go/misc/riscv:/riscv-go/bin:$PATH
RUN cd /riscv-go/src && GOROOT_BOOTSTRAP=$(go env GOROOT) ./make.bash
ENV GOROOT=/riscv-go
# Set the base env.
ENV GOOS=linux GOARCH=riscv CGO_ENABLED=0 GOFLAGS='-v -ldflags=-s -ldflags=-w'
# Pre compile the stdlib.
RUN go build -a std
# Add the code to the image.
WORKDIR pty
ADD . .
# Build the lib.
RUN go build

@ -0,0 +1,23 @@
Copyright (c) 2011 Keith Rarick
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall
be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,107 @@
# pty
Pty is a Go package for using unix pseudo-terminals.
## Install
```sh
go get github.com/creack/pty
```
## Examples
Note that those examples are for demonstration purpose only, to showcase how to use the library. They are not meant to be used in any kind of production environment.
### Command
```go
package main
import (
"io"
"os"
"os/exec"
"github.com/creack/pty"
)
func main() {
c := exec.Command("grep", "--color=auto", "bar")
f, err := pty.Start(c)
if err != nil {
panic(err)
}
go func() {
f.Write([]byte("foo\n"))
f.Write([]byte("bar\n"))
f.Write([]byte("baz\n"))
f.Write([]byte{4}) // EOT
}()
io.Copy(os.Stdout, f)
}
```
### Shell
```go
package main
import (
"io"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
"github.com/creack/pty"
"golang.org/x/term"
)
func test() error {
// Create arbitrary command.
c := exec.Command("bash")
// Start the command with a pty.
ptmx, err := pty.Start(c)
if err != nil {
return err
}
// Make sure to close the pty at the end.
defer func() { _ = ptmx.Close() }() // Best effort.
// Handle pty size.
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for range ch {
if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
log.Printf("error resizing pty: %s", err)
}
}
}()
ch <- syscall.SIGWINCH // Initial resize.
defer func() { signal.Stop(ch); close(ch) }() // Cleanup signals when done.
// Set stdin in raw mode.
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
defer func() { _ = term.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
// Copy stdin to the pty and the pty to stdout.
// NOTE: The goroutine will keep reading until the next keystroke before returning.
go func() { _, _ = io.Copy(ptmx, os.Stdin) }()
_, _ = io.Copy(os.Stdout, ptmx)
return nil
}
func main() {
if err := test(); err != nil {
log.Fatal(err)
}
}
```

@ -0,0 +1,18 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc
//+build gc
#include "textflag.h"
//
// System calls for amd64, Solaris are implemented in runtime/syscall_solaris.go
//
TEXT ·sysvicall6(SB),NOSPLIT,$0-88
JMP syscall·sysvicall6(SB)
TEXT ·rawSysvicall6(SB),NOSPLIT,$0-88
JMP syscall·rawSysvicall6(SB)

@ -0,0 +1,16 @@
// Package pty provides functions for working with Unix terminals.
package pty
import (
"errors"
"os"
)
// ErrUnsupported is returned if a function is not
// available on the current platform.
var ErrUnsupported = errors.New("unsupported")
// Open a pty and its corresponding tty.
func Open() (pty, tty *os.File, err error) {
return open()
}

@ -0,0 +1,19 @@
//go:build !windows && !solaris && !aix
// +build !windows,!solaris,!aix
package pty
import "syscall"
const (
TIOCGWINSZ = syscall.TIOCGWINSZ
TIOCSWINSZ = syscall.TIOCSWINSZ
)
func ioctl(fd, cmd, ptr uintptr) error {
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
if e != 0 {
return e
}
return nil
}

@ -0,0 +1,40 @@
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
// +build darwin dragonfly freebsd netbsd openbsd
package pty
// from <sys/ioccom.h>
const (
_IOC_VOID uintptr = 0x20000000
_IOC_OUT uintptr = 0x40000000
_IOC_IN uintptr = 0x80000000
_IOC_IN_OUT uintptr = _IOC_OUT | _IOC_IN
_IOC_DIRMASK = _IOC_VOID | _IOC_OUT | _IOC_IN
_IOC_PARAM_SHIFT = 13
_IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1
)
func _IOC_PARM_LEN(ioctl uintptr) uintptr {
return (ioctl >> 16) & _IOC_PARAM_MASK
}
func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr {
return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num
}
func _IO(group byte, ioctl_num uintptr) uintptr {
return _IOC(_IOC_VOID, group, ioctl_num, 0)
}
func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
return _IOC(_IOC_OUT, group, ioctl_num, param_len)
}
func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
return _IOC(_IOC_IN, group, ioctl_num, param_len)
}
func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len)
}

@ -0,0 +1,48 @@
//go:build solaris
// +build solaris
package pty
import (
"syscall"
"unsafe"
)
//go:cgo_import_dynamic libc_ioctl ioctl "libc.so"
//go:linkname procioctl libc_ioctl
var procioctl uintptr
const (
// see /usr/include/sys/stropts.h
I_PUSH = uintptr((int32('S')<<8 | 002))
I_STR = uintptr((int32('S')<<8 | 010))
I_FIND = uintptr((int32('S')<<8 | 013))
// see /usr/include/sys/ptms.h
ISPTM = (int32('P') << 8) | 1
UNLKPT = (int32('P') << 8) | 2
PTSSTTY = (int32('P') << 8) | 3
ZONEPT = (int32('P') << 8) | 4
OWNERPT = (int32('P') << 8) | 5
// see /usr/include/sys/termios.h
TIOCSWINSZ = (uint32('T') << 8) | 103
TIOCGWINSZ = (uint32('T') << 8) | 104
)
type strioctl struct {
icCmd int32
icTimeout int32
icLen int32
icDP unsafe.Pointer
}
// Defined in asm_solaris_amd64.s.
func sysvicall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
func ioctl(fd, cmd, ptr uintptr) error {
if _, _, errno := sysvicall6(uintptr(unsafe.Pointer(&procioctl)), 3, fd, cmd, ptr, 0, 0, 0); errno != 0 {
return errno
}
return nil
}

@ -0,0 +1,13 @@
//go:build aix
// +build aix
package pty
const (
TIOCGWINSZ = 0
TIOCSWINSZ = 0
)
func ioctl(fd, cmd, ptr uintptr) error {
return ErrUnsupported
}

@ -0,0 +1,19 @@
#!/usr/bin/env bash
GOOSARCH="${GOOS}_${GOARCH}"
case "$GOOSARCH" in
_* | *_ | _)
echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2
exit 1
;;
esac
GODEFS="go tool cgo -godefs"
$GODEFS types.go |gofmt > ztypes_$GOARCH.go
case $GOOS in
freebsd|dragonfly|netbsd|openbsd)
$GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go
;;
esac

@ -0,0 +1,68 @@
//go:build darwin
// +build darwin
package pty
import (
"errors"
"os"
"syscall"
"unsafe"
)
func open() (pty, tty *os.File, err error) {
pFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC, 0)
if err != nil {
return nil, nil, err
}
p := os.NewFile(uintptr(pFD), "/dev/ptmx")
// In case of error after this point, make sure we close the ptmx fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p)
if err != nil {
return nil, nil, err
}
if err := grantpt(p); err != nil {
return nil, nil, err
}
if err := unlockpt(p); err != nil {
return nil, nil, err
}
t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
if err != nil {
return nil, nil, err
}
return p, t, nil
}
func ptsname(f *os.File) (string, error) {
n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME))
err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
if err != nil {
return "", err
}
for i, c := range n {
if c == 0 {
return string(n[:i]), nil
}
}
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
}
func grantpt(f *os.File) error {
return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0)
}
func unlockpt(f *os.File) error {
return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0)
}

@ -0,0 +1,83 @@
//go:build dragonfly
// +build dragonfly
package pty
import (
"errors"
"os"
"strings"
"syscall"
"unsafe"
)
// same code as pty_darwin.go
func open() (pty, tty *os.File, err error) {
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
if err != nil {
return nil, nil, err
}
// In case of error after this point, make sure we close the ptmx fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p)
if err != nil {
return nil, nil, err
}
if err := grantpt(p); err != nil {
return nil, nil, err
}
if err := unlockpt(p); err != nil {
return nil, nil, err
}
t, err := os.OpenFile(sname, os.O_RDWR, 0)
if err != nil {
return nil, nil, err
}
return p, t, nil
}
func grantpt(f *os.File) error {
_, err := isptmaster(f.Fd())
return err
}
func unlockpt(f *os.File) error {
_, err := isptmaster(f.Fd())
return err
}
func isptmaster(fd uintptr) (bool, error) {
err := ioctl(fd, syscall.TIOCISPTMASTER, 0)
return err == nil, err
}
var (
emptyFiodgnameArg fiodgnameArg
ioctl_FIODNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
)
func ptsname(f *os.File) (string, error) {
name := make([]byte, _C_SPECNAMELEN)
fa := fiodgnameArg{Name: (*byte)(unsafe.Pointer(&name[0])), Len: _C_SPECNAMELEN, Pad_cgo_0: [4]byte{0, 0, 0, 0}}
err := ioctl(f.Fd(), ioctl_FIODNAME, uintptr(unsafe.Pointer(&fa)))
if err != nil {
return "", err
}
for i, c := range name {
if c == 0 {
s := "/dev/" + string(name[:i])
return strings.Replace(s, "ptm", "pts", -1), nil
}
}
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
}

@ -0,0 +1,81 @@
//go:build freebsd
// +build freebsd
package pty
import (
"errors"
"os"
"syscall"
"unsafe"
)
func posixOpenpt(oflag int) (fd int, err error) {
r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
fd = int(r0)
if e1 != 0 {
err = e1
}
return fd, err
}
func open() (pty, tty *os.File, err error) {
fd, err := posixOpenpt(syscall.O_RDWR | syscall.O_CLOEXEC)
if err != nil {
return nil, nil, err
}
p := os.NewFile(uintptr(fd), "/dev/pts")
// In case of error after this point, make sure we close the pts fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p)
if err != nil {
return nil, nil, err
}
t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0)
if err != nil {
return nil, nil, err
}
return p, t, nil
}
func isptmaster(fd uintptr) (bool, error) {
err := ioctl(fd, syscall.TIOCPTMASTER, 0)
return err == nil, err
}
var (
emptyFiodgnameArg fiodgnameArg
ioctlFIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
)
func ptsname(f *os.File) (string, error) {
master, err := isptmaster(f.Fd())
if err != nil {
return "", err
}
if !master {
return "", syscall.EINVAL
}
const n = _C_SPECNAMELEN + 1
var (
buf = make([]byte, n)
arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))}
)
if err := ioctl(f.Fd(), ioctlFIODGNAME, uintptr(unsafe.Pointer(&arg))); err != nil {
return "", err
}
for i, c := range buf {
if c == 0 {
return string(buf[:i]), nil
}
}
return "", errors.New("FIODGNAME string not NUL-terminated")
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save