Compare commits

..

234 Commits

Author SHA1 Message Date
Tõnis Tiigi
780fad46f2 Merge pull request #471 from crazy-max/remove-travis
Remove travis support and unused buildmode
2020-12-09 20:40:15 -08:00
CrazyMax
2ca5ffa06a Remove travis support and unused buildmode
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-09 07:51:32 +01:00
Tõnis Tiigi
33e3ca524e Merge pull request #442 from tonistiigi/moby-push
build: add push support to docker driver
2020-12-08 15:01:37 -08:00
Tõnis Tiigi
ea1a71dc07 Merge pull request #467 from cpuguy83/deterministic_output
Get multi-platform buildkit frontend opt from args
2020-12-08 15:00:57 -08:00
Tõnis Tiigi
ae820293a2 Merge pull request #468 from crazy-max/ghactions-release
GitHub Actions release
2020-12-08 13:57:38 -08:00
Tonis Tiigi
f68f42cb11 build: add push support to docker driver
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-12-08 13:54:41 -08:00
Brian Goff
7f58ad45fa Get multi-platform buildkit frontend opt from args
This allows builders to opt into determnistic output regardless of
multi-platform output or not.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2020-12-08 13:18:56 -08:00
Tõnis Tiigi
aa7c17989b Merge pull request #469 from tonistiigi/buildkit-update-1207
vendor: update buildkit to v0.8
2020-12-08 13:10:07 -08:00
Tonis Tiigi
6b6afc4077 build: add logger for auth
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-12-07 23:43:28 -08:00
Tonis Tiigi
69a1419ab1 vendor: update buildkit to v0.8
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-12-07 23:16:06 -08:00
Tõnis Tiigi
080e9981c7 Merge pull request #398 from tonistiigi/remote-bake
bake: remote inputs support
2020-12-07 23:07:24 -08:00
CrazyMax
8cc00ab486 GitHub Actions release
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-08 07:20:41 +01:00
Tonis Tiigi
40fad4bbb5 progress: make sure all channels have written before returning
Possible write on closed channel on cancellation before.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-12-07 22:02:51 -08:00
Tonis Tiigi
232af9aa0d move moby check to driver interface
Driver caching masked the method detection

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-12-07 22:02:51 -08:00
Tonis Tiigi
5bf2ff98c9 bake: support filenames without suffix
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-12-07 22:02:51 -08:00
Tonis Tiigi
570e733a51 bake: support inline dockerfile
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-12-07 22:02:51 -08:00
Tonis Tiigi
cffcd57edb bake: support for remote files
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-12-07 22:02:51 -08:00
Tonis Tiigi
1496ac9b55 util: simplify progress syncronization
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-12-07 22:02:51 -08:00
Tonis Tiigi
290e25917c build: allow dockerfile from URL
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-12-07 22:02:51 -08:00
Tõnis Tiigi
0360668cc1 Merge pull request #428 from zanven42/master
fixes #427: Handle empty strings in elements enabling conditional logic
2020-12-07 21:59:43 -08:00
Tõnis Tiigi
343a4753c7 Merge pull request #460 from morlay/master
fix: buildx in k8s pod with kube client config in cluster
2020-12-07 21:59:23 -08:00
Wang
d827f42d38 fix: buildx in k8s pod with kube client config in cluster
Signed-off-by: Wang <morlay.null@gmail.com>
2020-12-08 13:17:23 +08:00
Tonis Tiigi
5843e67a90 fix indentations in example
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-12-07 21:02:48 -08:00
Tõnis Tiigi
517df133e3 Merge pull request #403 from jygastaud/master
fixes #401 : Replace error generated by `quiet` option by a warning.
2020-12-07 20:57:09 -08:00
Tõnis Tiigi
621114fbe1 Merge pull request #465 from crazy-max/update-vendor
Update vendor and binaries script
2020-12-07 20:55:32 -08:00
Tõnis Tiigi
2066051d3a Merge pull request #466 from crazy-max/ghactions-cross
GitHub Actions cross
2020-12-07 20:53:09 -08:00
CrazyMax
d94cbd870c GitHub Actions cross
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-07 17:29:35 +01:00
CrazyMax
48f15dcf3d Update vendor and binaries script
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-07 17:14:45 +01:00
Tõnis Tiigi
35a60b8e04 Merge pull request #441 from tonistiigi/buildkit-pull-creds2
refactor driver auth for easier passing
2020-12-05 00:02:38 -08:00
CrazyMax
4b3df09155 Merge pull request #463 from crazy-max/ghactions-test
GitHub Actions for test
2020-12-05 08:13:41 +01:00
CrazyMax
b1215c2ce2 GitHub Actions for test
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-05 04:18:28 +01:00
Tõnis Tiigi
99ac03f9f3 Merge pull request #451 from docker/ghactions-validate
Refactor validate for GitHub Actions
2020-12-04 15:19:37 -08:00
Tõnis Tiigi
a0aa45a4a7 Merge pull request #462 from zencargo/userns
Disable user namespace remapping in docker-container driver
2020-12-03 20:03:32 -08:00
Tõnis Tiigi
aab3a92890 Merge pull request #444 from tonistiigi/lowercase-dockerfile
build: handle lowercase Dockerfile name as a fallback
2020-12-03 17:54:47 -08:00
Andrew Haines
37020dc8da Disable user namespace remapping in docker-container driver
Signed-off-by: Andrew Haines <andrew.haines@zencargo.com>
2020-12-03 10:50:00 +00:00
CrazyMax
d66d3a2d09 Refactor validate for GitHub Actions
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-11-30 08:14:28 +01:00
Tonis Tiigi
f057195a4f build: handle lowercase Dockerfile name as a fallback
This was supported by the legacy builder: moby#10858

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-11-18 22:11:35 -08:00
Tonis Tiigi
378bf70d4b refactor driver auth for easier passing
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-11-15 20:49:58 -08:00
Tõnis Tiigi
1ccf0bd7d8 Merge pull request #433 from tonistiigi/buildkit-pull-creds
docker-container: ensure credentials are passed when pulling buildkit
2020-11-15 20:13:27 -08:00
Tõnis Tiigi
ddbfddce88 Merge pull request #425 from tonistiigi/default-builder
allow builder flag to switch to default instance
2020-11-12 11:07:37 -08:00
Tonis Tiigi
ea19cf9d8d inspect: make sure to show boot error from driver
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-11-04 14:39:54 -08:00
Tonis Tiigi
3b69482a2f docker-container: ensure credentials are passed when pulling buildkit image
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-11-04 13:04:04 -08:00
Tõnis Tiigi
778fbb4669 Merge pull request #390 from tonistiigi/fix-warn
build: avoid warn on empty config value
2020-11-02 19:36:20 -08:00
Tõnis Tiigi
13533e359a Merge pull request #432 from thaJeztah/bump_console_v1.0.1
vendor: containerd/console v1.0.1, and remove golang.org/x/sys replace
2020-11-02 19:35:34 -08:00
Sebastiaan van Stijn
3c2d0aa667 vendor: containerd/console v1.0.1, and remove golang.org/x/sys replace
this removes the replace rule that was added in 3c94621142,
to fix compile failures for macOS.

containerd/console v1.0.1 fixes those issues, which allows us to remove the
replace rule again.

full diff: https://github.com/containerd/console/compare/v1.0.0...v1.0.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-10-31 14:18:57 +01:00
Sebastiaan van Stijn
5551de4b8a go.mod: reformat file
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-10-31 13:50:45 +01:00
Sebastiaan van Stijn
fa51b90094 vendor: fix docker/docker vendoring (update to 9f28837c1d93
commit c41b006be1 updated the version of
docker/docker in go.mod, but possibly overlooked that there was still a
replace rule present. As a result the version was not actually updated.

This patch removes the replace rule, updating docker/docker to 9f28837c1d93

full diff: 4634ce647c...9f28837c1d

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-10-31 13:50:34 +01:00
Anthony Poschen
bfd1ea3877 Added doco for conditonal tags in bake file.
Signed-off-by: Anthony Poschen <zanven42@gmail.com>
2020-10-26 16:37:28 +11:00
Anthony Poschen
abfb2c064d Add support for empty strings in target elements and compact func.
Signed-off-by: Anthony Poschen <zanven42@gmail.com>
2020-10-26 15:56:18 +11:00
Tonis Tiigi
4f7517115c allow builder flag to switch to default instance
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-10-21 23:19:28 -07:00
Tonis Tiigi
1621b9bad0 build: avoid warn on empty config value
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-10-16 23:05:37 -07:00
Tibor Vass
d2bf42f8b4 Merge pull request #397 from tonistiigi/update-buildkit-20200919
vendor: update buildkit to 2943a0838
2020-10-14 01:03:56 -07:00
Tõnis Tiigi
d1a46faf84 Merge pull request #404 from thaJeztah/remove_annotations
Remove "version" annotations from du and prune commands
2020-09-29 14:35:05 -07:00
Sebastiaan van Stijn
39f1d99dcc Remove "version" annotations from du and prune commands
These annotations were picked up by the YAML docs generator, and shows up as
"minimum API version". I couldn't find a reference to these annotations in the
PR that added them, so thought it would be ok to remove

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-09-29 23:13:53 +02:00
Jean-Yves Gastaud
1f04ec9575 fixes #401
Replace error generated by `quiet` option by a warning.

Signed-off-by: Jean-Yves Gastaud <jygastaud@gmail.com>
2020-09-28 11:24:14 +02:00
Tõnis Tiigi
ac2e081528 Merge pull request #391 from tonistiigi/hcl-errors
bake: format hcl errors with source definition
2020-09-19 23:19:41 -07:00
Tonis Tiigi
95ac9ebb8a bake: format hcl errors with source definition
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-09-19 22:50:47 -07:00
Tonis Tiigi
c41b006be1 vendor: update buildkit to 2943a0838
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-09-19 22:49:12 -07:00
Sebastiaan van Stijn
92fb995505 Merge pull request #389 from tonistiigi/buildkit-update-20200913
vendor: update buildkit with typed errors support
2020-09-17 12:40:34 +02:00
Tonis Tiigi
3c94621142 vendor: downgrade sys unix
containerd and moby packages do not build in darwin anymore

6fcdbc0bbc

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-09-14 20:50:09 -07:00
Tonis Tiigi
2d720a1e0b vendor: update buildkit with typed errors support
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-09-13 21:28:20 -07:00
Tõnis Tiigi
0269388aa7 Merge pull request #385 from errordeveloper/fix-308
Allow users to have separate store paths
2020-09-10 10:24:05 -07:00
Ilya Dmitrichenko
4b2aab09b5 Allow users to have separate store paths
- decouple store path from `$DOCKER_CONFIG`
- improve containerised build setup
- introduce new `$BUILDX_CONFIG` environment variable

Signed-off-by: Ilya Dmitrichenko <errordeveloper@gmail.com>
2020-09-10 17:14:41 +01:00
Tõnis Tiigi
1c7434a8f0 Merge pull request #372 from thaJeztah/arch_detect
hack/util: take other arches into account on Darwin
2020-09-02 08:30:22 -07:00
Sebastiaan van Stijn
20f8f67928 hack/util: take other arches into account on Darwin
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-31 11:40:10 +02:00
Tõnis Tiigi
159535261d Merge pull request #370 from morlay/kubernetes-driver-enhancement
feat: enhance kubernetes driver
2020-08-30 19:35:13 -07:00
Tõnis Tiigi
a840e891fe Merge pull request #371 from tonistiigi/update-travis-image
travis: update base image
2020-08-30 19:33:34 -07:00
Wang Jinglei
a7c704c39d feat: enhance kubernetes driver
Signed-off-by: Wang Jinglei <morlay.null@gmail.com>
2020-08-31 09:20:49 +08:00
Tonis Tiigi
e1c0eb2187 travis: update base image
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-08-30 17:26:20 -07:00
Sebastiaan van Stijn
aa8ab9fcca hack/util: fix mixed tab/spaces for indentation
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-30 13:00:37 +02:00
Sebastiaan van Stijn
a746959fc1 hack: fix SC2069 : to redirect stdout+stderr, 2>&1 must be last
See https://github.com/koalaman/shellcheck/wiki/SC2069

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-30 12:57:19 +02:00
Tõnis Tiigi
ee34eb2180 Merge pull request #368 from morlay/master
feat: use k8s cluster config when buildx used in k8s cluster
2020-08-29 12:15:17 -07:00
Wang Jinglei
844b901005 feat: use k8s cluster config when buildx used in k8s cluster
Signed-off-by: Wang Jinglei <morlay.null@gmail.com>
2020-08-28 17:51:18 +08:00
Tõnis Tiigi
83ebc13a37 Merge pull request #297 from AkihiroSuda/doc-linuxkit-binfmt
README.md: add usage of tonistiigi/binfmt
2020-08-26 16:49:01 -07:00
Akihiro Suda
82c8f2d8f0 README.md: add usage of tonistiigi/binfmt
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2020-08-26 13:52:26 +09:00
Tõnis Tiigi
a11cc8840e Merge pull request #360 from kotaroooo0/fix-error-message-for-push-option
build: fix error message for --push option
2020-08-25 15:59:14 -07:00
Tõnis Tiigi
35ee6ce62d Merge pull request #363 from thaJeztah/bump_deps
Update dependencies
2020-08-25 11:37:26 -07:00
Sebastiaan van Stijn
37861cb99f vendor: hashicorp/hcl v2.6.0
full diff: https://github.com/hashicorp/hcl/compare/v2.4.0...v2.6.0

v2.6.0
-------------------------

Enhancements:

- hcldec: Add a new Spec, ValidateSpec, which allows custom validation of values at decode-time.

Bugs Fixed:

- hclsyntax: Fix panic with combination of sequences and null arguments
- hclsyntax: Fix handling of unknown values and sequences

v2.5.1
-------------------------

- hclwrite: handle legacy dot access of numeric indexes. (#369)
- hclwrite: Fix panic for dotted full splat (foo.*)

v2.5.0
-------------------------

- hclwrite: Generate multi-line objects and maps.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-24 11:04:40 +02:00
Sebastiaan van Stijn
a178d05023 vendor: github.com/gofrs/flock v0.7.3
full diff: https://github.com/gofrs/flock/compare/v0.7.0...v0.7.3

v0.7.3
-------------------------

- Fix issues in the license file, update year.

v0.7.2
-------------------------

- Ensure we release file handle if we failed to take an exclusive lock

v0.7.1
-------------------------

- Fix linting issues and add goreportcard badge

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-24 11:02:16 +02:00
Sebastiaan van Stijn
ee9ccfe2e3 vendor: buildkit v0.7.2
v0.7.2
----------------

Fixes:

- solver: gracefully handle cache loading errors
- remotecache: only visit each item once when walking results
- cache: avoid possible nil dereference on error handling
- contenthash: allow security.capability in cache checksum
- contenthash: treat unix sockets as regular files
- push: fix race condition on pushing the same layers in parallel
- inline cache: fix handling of duplicate blobs in same image
- gateway: fix metadata getting lost on subsolve in external frontend
- filesync: avoid ignoring close error
- runc: update runc binary to v1.0.0-rc91
- buildctl-daemonless: allow max retries on socket connect for buildctl
- buildctl-daemonless: fix shell args expansion
- buildctl-daemonless: show log on startup timeout

v0.7.1
----------------

Fixes:

- git: use --force flag on fetch
- tar exporter: handle symlinks properly
- resolver: disable http2 for pushing

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-24 10:30:00 +02:00
Sebastiaan van Stijn
c6c4b4a871 vendor: sirupsen/logrus v1.6.0
1.6.0
--------------------------

Fixes:
  * end of line cleanup
  * revert the entry concurrency bug fix whic leads to deadlock under some circumstances
  * update dependency on go-windows-terminal-sequences to fix a crash with go 1.14

Features:
  * add an option to the `TextFormatter` to completely disable fields quoting

1.5.0
--------------------------

Code quality:
  * add golangci linter run on travis

Fixes:
  * add mutex for hooks concurrent access on `Entry` data
  * caller function field for go1.14
  * fix build issue for gopherjs target

Feature:
  * add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level
  * add a `DisableHTMLEscape` option in the `JSONFormatter`
  * add `ForceQuote` and `PadLevelText` options in the `TextFormatter`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-24 10:26:07 +02:00
Sebastiaan van Stijn
273c6a75a2 vendor: update Cobra v1.0.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-24 10:20:59 +02:00
Kotaro Adachi
1384bf02f9 error message for --push option
Signed-off-by: Kotaro Adachi <k33asby@gmail.com>
2020-08-23 00:03:13 +09:00
Tõnis Tiigi
fb7b670b76 Merge pull request #338 from tonistiigi/load-no-warn
build: avoid warning if default load disabled
2020-08-21 19:36:07 -07:00
Tõnis Tiigi
9ac5b075cf Merge pull request #323 from saulshanabrook/patch-1
Increase ls and inspect timeouts
2020-08-21 12:44:33 -07:00
Tonis Tiigi
0124b6b9c9 build: avoid warning if default load disabled
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-08-21 12:43:49 -07:00
Tõnis Tiigi
691a1479fc Merge pull request #351 from tonistiigi/multi-iidfile
build: remove warning for multi-platform iidfile
2020-08-21 12:29:47 -07:00
Tonis Tiigi
c9d69b082b build: remove warning for multi-platform iidfile
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-08-17 22:34:45 -07:00
Tõnis Tiigi
e24e04be57 Merge pull request #332 from tonistiigi/bootstrap-logs
docker-container: show logs on bootstrap error
2020-07-29 19:33:08 -07:00
Tõnis Tiigi
38f9241316 Merge pull request #337 from tonistiigi/cacheonly-exporter
build: support cacheonly exporter
2020-07-29 19:32:54 -07:00
Tonis Tiigi
3862ff269b build: add opt-out from default load behavior
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-07-27 22:38:06 -07:00
Tonis Tiigi
9e5321eab8 build: support cacheonly exporter
cacheonly is supported by moby so add support for buildx
as well so same flags can be used

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-07-27 22:33:44 -07:00
Tibor Vass
f2cf7cf281 Merge pull request #333 from tonistiigi/dockerd-log
demo-env: correct dockerd logging
2020-07-27 17:44:06 +02:00
Tonis Tiigi
8961d3573e hack: correct dockerd logging
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-07-26 00:13:02 -07:00
Tonis Tiigi
26570d05c1 docker-container: increase bootstrap timeout
Previous value was only 2 sec

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-07-26 00:11:33 -07:00
Tonis Tiigi
8627f668f2 docker-container: show logs on bootstrap error
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-07-26 00:11:27 -07:00
Tõnis Tiigi
d8ca46066d Merge pull request #321 from errordeveloper/fix-320
bake: ensure `--builder` is wired from root options
2020-07-19 15:58:33 -07:00
Saul Shanabrook
c00c5a89e5 Increase inspect timeout from 5 to 20 seconds
Signed-off-by: Saul Shanabrook <s.shanabrook@gmail.com>
2020-07-16 11:56:40 -04:00
Saul Shanabrook
14b7936c3b Increase ls timeout from 7 to 20 seconds
Signed-off-by: Saul Shanabrook <s.shanabrook@gmail.com>
2020-07-16 11:56:40 -04:00
Ilya Dmitrichenko
40b41ac6e4 bake: ensure --builder is wired from root options
Signed-off-by: Ilya Dmitrichenko <errordeveloper@gmail.com>
2020-07-08 12:03:15 +01:00
Tõnis Tiigi
fd6de6b6ae Merge pull request #281 from tonistiigi/load-fix
build: improve error checking on load
2020-07-07 09:06:34 -07:00
Tõnis Tiigi
f3111bcbef Merge pull request #312 from donhui/master
README.md: update the content which not display in markdown
2020-06-20 17:41:31 -07:00
Donghui Wang
e6be472831 update the content which not display in markdown
Signed-off-by: Donghui Wang <977675308@qq.com>
2020-06-19 17:30:49 +08:00
Tibor Vass
e5217f26e2 Merge pull request #296 from tonistiigi/seed-fix
cmd: seed math rand
2020-05-18 12:03:57 -07:00
Tonis Tiigi
7f7acf7837 cmd: seed math rand
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-05-17 16:21:11 -07:00
Tonis Tiigi
baae4b2e71 build: improve error checking on load
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-05-08 17:46:28 -07:00
Tõnis Tiigi
42448c5f37 Merge pull request #280 from vanstee/hcl-json-support
Support parsing json config with hcl v2
2020-05-08 09:25:25 -07:00
Tõnis Tiigi
fc7875675c Merge pull request #277 from cpuguy83/moar_hcl_stdlib_funcs
Update go-cty to pull in more stdlib funcs.
2020-05-08 09:23:47 -07:00
Patrick Van Stee
355261e49e Parse bake config as hcl falling back to json
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
2020-05-07 23:53:49 -04:00
Patrick Van Stee
44c840b31d Add test of parsing a json bake config
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
2020-05-07 23:53:49 -04:00
Patrick Van Stee
1bc068a583 Fix json keys for groups and targets
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
2020-05-07 23:53:49 -04:00
Patrick Van Stee
340686a383 Support parsing json config with hcl v2
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
2020-05-07 23:53:49 -04:00
Brian Goff
1ad87c6ba6 Update go-cty to pull in more stdlib funcs.
I needed "split" specifically so I can do something like:

```hcl
variable PLATFORMS {
  default = "linux/amd64"
}

target foo {
  platforms = split(",", "${PLATFORMS}")
  # other stuff
}
```

Where the existing "csvdecode" does not work for this because it parses
the string into a list of objects instead of a list of strings.

I went ahead and just added all the available new functions.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2020-05-07 16:05:17 -07:00
Tõnis Tiigi
eadf5eddbc Merge pull request #270 from thaJeztah/prune_force_shorthand
Add -f shorthand flag for prune --force
2020-05-07 11:41:32 -07:00
Sebastiaan van Stijn
f4f58003fb Add -f shorthand flag for prune --force
The docker builder prune command has a shorthand `-f` flag for `--force`:

    docker builder prune --help

    Usage:	docker builder prune

    Remove build cache

    Options:
      -a, --all                  Remove all unused build cache, not just dangling ones
          --filter filter        Provide filter values (e.g. 'until=24h')
      -f, --force                Do not prompt for confirmation
          --keep-storage bytes   Amount of disk space to keep for cache

Given that `buildx` can be used as a drop-in replacement for the native build
commands, it should match the UI, and also have a shorthand flag.

This patch also updates the flag's description to be in line with the docker commandline

With this patch applied;

    buildx prune --help
    Remove build cache

    Usage:
      buildx prune [flags]

    Flags:
      -a, --all                  Remove all unused images, not just dangling ones
          --filter filter        Provide filter values (e.g. 'until=24h')
      -f, --force                Do not prompt for confirmation
      -h, --help                 help for prune
          --keep-storage bytes   Amount of disk space to keep for cache
          --verbose              Provide a more verbose output

    Global Flags:
          --builder string   Override the configured builder instance

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-05-02 21:29:23 +02:00
Tõnis Tiigi
bda4882a65 Merge pull request #268 from tiborvass/fix-tristate
Fix --pull and --no-cache behavior
2020-04-30 15:08:21 -07:00
Tibor Vass
77ddee9314 bake: fix pull and no-cache overrides
Signed-off-by: Tibor Vass <tibor@docker.com>
2020-04-30 14:05:21 -07:00
Tonis Tiigi
c9676c79d1 bake: fix hcl tags
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-04-30 13:41:49 -07:00
Tonis Tiigi
18095ee87b bake: reset no-cache and pull if not set
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-04-30 13:01:45 -07:00
Tibor Vass
c4d07f67e3 commands: check if flag is set instead of using flagutil.Tristate
Fixes --pull and --no-cache without argument

Signed-off-by: Tibor Vass <tibor@docker.com>
2020-04-30 12:25:41 -07:00
Tõnis Tiigi
205165bec5 Merge pull request #192 from vanstee/hcl2-with-interpolation
Upgrade to hcl2 to support variables and functions
2020-04-29 10:55:53 -07:00
Patrick Van Stee
870b38837b Allow for user defined functions
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
2020-04-29 08:52:48 -04:00
Patrick Van Stee
10d4b7a878 Add example of interpolation to the README
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
2020-04-29 08:52:20 -04:00
Patrick Van Stee
abed97cf33 Include test cases of different hcl files
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
2020-04-29 08:51:26 -04:00
Patrick Van Stee
f10d8dab5e Define variables as blocks with defaults
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
2020-04-29 08:51:26 -04:00
Patrick Van Stee
5185d534bc Include go-cty stdlib functions in HCL file scope
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
2020-04-29 08:51:26 -04:00
Patrick Van Stee
a520de447e Provide current env as variables in eval context
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
2020-04-29 08:51:26 -04:00
Patrick Van Stee
4121ae50b5 Modify parsing functions and config structs to accept hcl changes
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
2020-04-29 08:51:26 -04:00
Patrick Van Stee
87c4bf1df9 Upgrade hcl to v2
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
2020-04-28 22:07:52 -04:00
Tibor Vass
09339bf500 Merge pull request #263 from tonistiigi/platforms-print
separate manual and automatically detected platforms
2020-04-28 17:46:09 -07:00
Tõnis Tiigi
af9edb6ba4 Merge pull request #246 from cpuguy83/override_instance
Add option to build/bake to override instance
2020-04-28 17:38:38 -07:00
Brian Goff
b2ec1d331c Add builder as a global flag.
This allows all subcommands to use this flag.
Additionally reads the default value for the flag from the
`BUILDX_BUILDER` env var.

Precedence is:

CLI ARG > flag > env var > config file

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2020-04-27 14:37:17 -07:00
Brian Goff
213d3af3b0 Add option to build/bake to override instance
This helps prevent race conditions with concurrent build invocations.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2020-04-27 11:12:31 -07:00
Tonis Tiigi
4804824c78 separate manual and automatically detected platforms
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-04-24 20:08:43 -07:00
Tõnis Tiigi
d89e3f3014 Merge pull request #249 from tonistiigi/prune
add prune and du commands
2020-04-23 12:17:49 -07:00
Tõnis Tiigi
9ab9b852c2 Merge pull request #165 from tiborvass/pertarget-nocache
bake: allow overriding no-cache and pull per target via --set
2020-04-23 12:17:30 -07:00
Tibor Vass
2a257a8252 bake: allow overriding no-cache and pull per target via --set
Signed-off-by: Tibor Vass <tibor@docker.com>
2020-04-23 18:11:22 +00:00
Tõnis Tiigi
0e1f0e3c73 Merge pull request #164 from tiborvass/multitarget-set
bake: allow pattern matching for target names in --set
2020-04-23 09:23:12 -07:00
Tibor Vass
078b65905a bake: add test cases for pattern matching
Signed-off-by: Tibor Vass <tibor@docker.com>
2020-04-23 05:54:11 +00:00
Tibor Vass
417f52e001 bake: add --load and --push shorthands for --set
Signed-off-by: Tibor Vass <tibor@docker.com>
2020-04-23 05:54:11 +00:00
Tibor Vass
2bca8fa677 bake: allow pattern matching for target names in --set
Although bake is for running multiple targets, --set required a single
target name for overriding a property. This change allows matching
multiple targets for overrides.

Signed-off-by: Tibor Vass <tibor@docker.com>
2020-04-23 05:54:11 +00:00
Tõnis Tiigi
721b63f3a0 Merge pull request #259 from tiborvass/fix-inherits-override
bake: fix override bug with inheritance
2020-04-22 22:24:12 -07:00
Tibor Vass
14e65ff3b4 bake: fix override+inheritance bug
Signed-off-by: Tibor Vass <tibor@docker.com>
2020-04-21 22:42:03 +00:00
Tibor Vass
3282dae09b bake: add tests for override+inheritance bug
Signed-off-by: Tibor Vass <tibor@docker.com>
2020-04-21 22:38:06 +00:00
Sebastiaan van Stijn
7b297eb895 Merge pull request #251 from philips/patch-1
README: add mkdir for .docker/cli-plugins
2020-04-14 18:06:04 +02:00
Brandon Philips
bae6b1cec8 README: add mkdir for .docker/cli-plugins
this dir doesn't exist by default so add a mkdir
2020-04-11 08:54:09 -07:00
Tibor Vass
f4ac640252 Merge pull request #250 from tonistiigi/buildkit-bump
vendor: update buildkit to v0.7.0
2020-04-10 13:43:09 -07:00
Tonis Tiigi
7c627da986 vendor: update buildkit to v0.7.0
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-04-09 22:37:06 -07:00
Tonis Tiigi
d52f5db6ba commands: add du command
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-04-06 19:32:21 -07:00
Tonis Tiigi
66672b4052 commands: add prune command
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2020-04-06 18:03:30 -07:00
Tõnis Tiigi
ed6be92de4 Merge pull request #245 from developer-guy/master
i added kubernetes driver information to drivers section
2020-03-30 15:07:50 -07:00
Batuhan Apaydın
2def02ea74 added kubernetes driver information 2020-03-30 18:34:22 +03:00
Tibor Vass
52b0ea328f Merge pull request #233 from silvin-lubecki/go-1.13
bump to Go 1.13
2020-03-04 12:19:16 -08:00
Silvin Lubecki
960107d00f Bump golang to 1.13 in Dockerfiles
Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
2020-03-04 18:37:46 +01:00
Silvin Lubecki
bbc902b4d6 Bump buildkit to master and fix versions incompatible with go mod 1.13
Bump github.com/gogo/googleapis to v1.3.2
Bump github.com/docker/cli to master

Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
2020-03-04 18:37:42 +01:00
Sebastiaan van Stijn
54549235da Merge pull request #234 from thaJeztah/remove_fossa_scan
Remove FOSSA checks from Jenkins CI
2020-03-04 13:38:47 +01:00
Sebastiaan van Stijn
231f983600 Revert "Add FOSSA checks to Jenkins CI"
This reverts commit 5f4d4a87f7.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-03-04 13:01:41 +01:00
Tõnis Tiigi
891d355679 Merge pull request #225 from cpuguy83/k8s_priority
Make k8s driver priority lower
2020-02-16 11:11:40 -08:00
Brian Goff
87fbc406f5 Make k8s driver priority lower
Otherwise it ends up being default and it's probably not the normal
case.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2020-02-12 16:26:53 -08:00
Tõnis Tiigi
08b5a52ccd Merge pull request #219 from ArturKlauser/patch-1
Explain binfmt_misc requirements
2020-01-13 09:38:16 -08:00
Artur Klauser
14a28d7fc3 Update README.md 2020-01-12 10:01:13 +01:00
Tõnis Tiigi
5a79b401b0 Merge pull request #221 from cpuguy83/build_args_env
build: only use env for args if set
2020-01-08 15:30:01 -08:00
Brian Goff
5e4444823c build: only use env for args if set
When following this pattern:

  buildx build --arg FOO

Where we want to pull `FOO` from env, currently we always set `FOO`
regardless if the `FOO` env var is even set.

This change makes it so that `FOO` would only be set if it has been set
in the env (even if it is set to empty).

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2020-01-06 12:02:06 -08:00
Artur Klauser
5ff7635447 Explain binfmt_misc requirements
Getting the QEMU interpreters to work transparently inside containers seems to be a bit of a sticking point, with major distributions still on kernels and/or binfmt-support package versions that don't support the fix_binary flag yet. Give the reader a little more guidance what to look out for.
2020-01-04 00:00:40 +01:00
Tõnis Tiigi
709ef36b4f Merge pull request #207 from ulyssessouza/use-file-interface
Bump containerd/console to use console.File instead of os.File
2019-12-11 17:03:00 -08:00
ulyssessouza
7f0b59dc37 Remove replace for docker/docker and restore prefix on docker/cli
Signed-off-by: ulyssessouza <ulyssessouza@gmail.com>
2019-12-12 01:27:56 +01:00
ulyssessouza
9e8c532e61 Bump docker/cli to 06f34ba50786ec67761745c818e87baecc2ba139
Signed-off-by: ulyssessouza <ulyssessouza@gmail.com>
2019-12-11 14:55:53 +01:00
ulyssessouza
f2be09f4e4 Bump microsoft/hcsshim to v0.8.7
Signed-off-by: ulyssessouza <ulyssessouza@gmail.com>
2019-12-11 14:35:32 +01:00
ulyssessouza
3ff9abca3a Bump moby/buildkit
Signed-off-by: ulyssessouza <ulyssessouza@gmail.com>
2019-12-11 14:13:56 +01:00
ulyssessouza
3d630c6f7f Replace usage of *os.File by console.File interface on printer
Signed-off-by: ulyssessouza <ulyssessouza@gmail.com>
2019-12-10 23:37:42 +01:00
ulyssessouza
9f4f945d4f Bump docker/docker and containerd/console
Signed-off-by: ulyssessouza <ulyssessouza@gmail.com>
2019-12-10 23:37:11 +01:00
Tõnis Tiigi
a0490a8720 Merge pull request #204 from tiborvass/remove-fossa
Revert "ADDED .fossa file for fossa scans"
2019-12-10 10:13:04 -08:00
Tibor Vass
aba962c12c Revert "ADDED .fossa file for fossa scans"
This reverts commit 6e1fd0eab6.
2019-12-10 09:11:39 -08:00
Tõnis Tiigi
aa21e3c731 Merge pull request #200 from jingxiaolu/inspect_image
docker-container: check local image store if pulling image failed
2019-12-08 18:00:55 -08:00
l00397676
5b9d88b3ad docker-container: check local image store if pulling image failed
When booting `docker-container` driver, it will pull and run image
`moby/buildkit:buildx-stable-1`.
If current node cannot connect to dockerhub, driver `docker-container`
will always booting failure.
But user may already load the image manually or pull it from a priviate
registry.
Buildx should check local docker image store after pull failed.

Fixes: #199 issuecomment-561996661

Signed-off-by: Lu Jingxiao <lujingxiao@huawei.com>
2019-12-09 09:29:56 +08:00
Tibor Vass
8bce430f4d Merge pull request #167 from AkihiroSuda/kube
new driver: kubernetes
2019-11-21 09:46:00 -08:00
Akihiro Suda
c6f8de90aa kubernetes: show Kubernetes Pods as buildx "Nodes" in docker buildx inspect
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2019-11-21 10:30:39 +09:00
Akihiro Suda
6b65b0c982 new driver: kubernetes
Tested with `kind` and GKE.

Note: "nodes" shown in `docker buildx ls` are unrelated to Kubernetes "nodes".
Probably buildx should come up with an alternative term.

Usage:

  $ kind create cluster
  $ export KUBECONFIG="$(kind get kubeconfig-path --name="kind")"

  $ docker buildx create --driver kubernetes --driver-opt replicas=3 --use
  $ docker buildx build -t foo --load .

`--load` loads the image into the local Docker.

Driver opts:

  - `image=IMAGE` - Sets the container image to be used for running buildkit.
  - `namespace=NS` - Sets the Kubernetes namespace. Defaults to the current namespace.
  - `replicas=N` - Sets the number of `Pod` replicas. Defaults to 1.
  - `rootless=(true|false)` - Run the container as a non-root user without `securityContext.privileged`. 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"

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2019-11-21 10:30:39 +09:00
Tõnis Tiigi
f5c2673878 Merge pull request #184 from cpuguy83/bake_args_from_env
Support reading from env on bake --set <t>.args
2019-11-20 10:32:12 -08:00
Tõnis Tiigi
8e92bfc8f0 Merge pull request #188 from shykes/patch-1
Clarify documentation structure
2019-11-07 10:50:04 -08:00
Solomon Hykes
d7adb9ef6e Clarify documentation structure
Move a paragraph in README to clarify where it fits in the structure.

- Before the move, the paragraph seems to apply to the `--output=local` section when in fact it applies to the entire `--output` section. This is especially confusing for the sentence "if just the path is specified as a value, `buildx` will use the local exporter with this path as the destination".

- After the move, it is clear that the paragraph applies to `--output`
2019-11-06 16:42:12 -08:00
Brian Goff
6634f1e75c Support reading from env on bake --set <t>.args
This works just like the `build` command where if you have `--build-arg
FOO`, it will read the variable from env and only set a value if the
variable is defined.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2019-10-30 15:31:38 -07:00
Tõnis Tiigi
6aba19193a Merge pull request #182 from tonistiigi/raw-newline
imagetools: avoid printing newline on raw mode
2019-10-28 16:37:53 -07:00
Tonis Tiigi
eb1aabe9e3 imagetools: avoid printing newline on raw mode
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2019-10-28 16:14:35 -07:00
Tõnis Tiigi
714f181d81 Merge pull request #170 from sirlatrom/169-docker-container-driver-envs
Support environment variables in docker-container driver
2019-10-28 15:05:58 -07:00
Sune Keller
fd44accc79 Support environment variables in docker-container driver
Fixes #169

Signed-off-by: Sune Keller <absukl@almbrand.dk>
2019-10-28 21:56:14 +01:00
Tõnis Tiigi
43edd6b77e Merge pull request #162 from daixiang0/patch-1
Update README.md
2019-10-25 21:58:27 -07:00
Xiang Dai
427c19d65c Update README.md
Comment that with docker 19.03-, can not use buildx as docker plugin.
2019-10-26 11:07:27 +08:00
Tõnis Tiigi
6db68d0295 Merge pull request #155 from tiborvass/vendor-buildkit
vendor: update buildkit to docker-19.03 (ae10b292)
2019-09-27 10:36:16 -07:00
Tibor Vass
abe8ba769e vendor: update buildkit to docker-19.03 (ae10b292)
Signed-off-by: Tibor Vass <tibor@docker.com>
2019-09-27 17:18:25 +00:00
Tõnis Tiigi
96fb17b711 Merge pull request #154 from tiborvass/fix-149
build: fix scoping issue in closure inside loop
2019-09-26 11:32:04 -07:00
Tibor Vass
63e5633d62 build: fix scoping issue in closure inside loop
Signed-off-by: Tibor Vass <tibor@docker.com>
2019-09-26 18:01:29 +00:00
Tibor Vass
299d41660b Merge pull request #153 from tonistiigi/stdin-dockerfile
build: fix stdin dockerfile filename
2019-09-26 10:53:28 -07:00
Tibor Vass
1ec87b7beb Merge pull request #152 from tonistiigi/stream-input
build: use correct in-memory input
2019-09-26 10:45:55 -07:00
Tonis Tiigi
0475107882 build: fix stdin dockerfile filename
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2019-09-26 09:17:04 -07:00
Tonis Tiigi
75f8d7ebb5 build: use correct in-memory input
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2019-09-26 09:10:39 -07:00
Tibor Vass
7c97854b6f Merge pull request #144 from droopy4096/master
Add FOSSA checks to Jenkins CI
2019-09-17 14:56:00 -07:00
Dmytro Makovey
5f4d4a87f7 Add FOSSA checks to Jenkins CI
Signed-off-by: Dmytro Makovey <dmytro.makovey@docker.com>
Signed-off-by: Tibor Vass <tibor@docker.com>
2019-09-17 21:27:29 +00:00
Tõnis Tiigi
c1ce7300d5 Merge pull request #146 from gfrancesco/master
README typo
2019-09-17 10:19:34 -07:00
gfrancesco
e118c4d8e9 UPD: Readme typo 2019-09-17 18:13:16 +02:00
Tibor Vass
5fe779703d Merge pull request #134 from tonistiigi/group-merge
bake: merge targets on same groups
2019-09-05 17:15:01 -07:00
Tonis Tiigi
15a5a42eb1 bake: merge targets on same groups
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2019-08-19 15:48:42 -07:00
Tõnis Tiigi
5b974158f9 Merge pull request #131 from gracenoah/patch-1
Fix some quotes in the readme
2019-08-14 12:16:13 -07:00
gracenoah
1c0a7f14e8 Fix some quotes in the readme 2019-08-13 14:27:10 +02:00
Tibor Vass
7ec8912591 Merge pull request #125 from tiborvass/docs-allow
Document build --allow
2019-08-01 18:18:00 -07:00
Tibor Vass
83da6a3378 docs: crosslink buildkitd-flags and config flags in create
Signed-off-by: Tibor Vass <tibor@docker.com>
2019-08-01 17:56:05 -07:00
Tibor Vass
cad02a4681 docs: document build --allow
Signed-off-by: Tibor Vass <tibor@docker.com>
2019-08-01 17:56:05 -07:00
Tõnis Tiigi
c967f1d570 Merge pull request #124 from tiborvass/update-docs
Update docs
2019-08-01 16:41:26 -07:00
Tibor Vass
be3efc979b docs: add documentation for --buildkitd-flags, --config, --driver-opt on create
Signed-off-by: Tibor Vass <tibor@docker.com>
2019-08-01 16:15:11 -07:00
Tibor Vass
5c5f54c6d6 docs: Update install instructions with Docker CE 19.03
Signed-off-by: Tibor Vass <tibor@docker.com>
2019-08-01 15:23:02 -07:00
Tibor Vass
6f8f04e1f8 Merge pull request #122 from tonistiigi/custom-image
driver: allow setting driver opts
2019-08-01 11:41:49 -07:00
Tonis Tiigi
afd821010d docker-container: allow setting custom buildkit image
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2019-07-31 22:46:37 -07:00
Tonis Tiigi
bcc882cbf1 docker-container: allow using host network
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2019-07-31 17:42:49 -07:00
Tonis Tiigi
75b80c277f driver: allow setting driver opts
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2019-07-31 17:25:25 -07:00
Tibor Vass
096d1befc9 Merge pull request #104 from tonistiigi/entitlements
build: add allowed entitlements
2019-07-31 15:36:13 -07:00
Tibor Vass
2bf6187a88 Merge pull request #121 from tonistiigi/config
driver: allow setting buildkit config file
2019-07-31 15:21:17 -07:00
Tonis Tiigi
8ed8795268 driver: allow setting buildkit config file
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
Co-Authored-By: Tibor Vass <tiborvass@users.noreply.github.com>
2019-07-31 15:08:26 -07:00
Tõnis Tiigi
6e32ea3418 Merge pull request #118 from tiborvass/bake-no-cache-pull
bake: honor --no-cache and --pull
2019-07-31 10:59:59 -07:00
Tibor Vass
8b2171f78a bake: honor --no-cache and --pull
Signed-off-by: Tibor Vass <tibor@docker.com>
2019-07-30 19:39:01 -07:00
Tibor Vass
92f1234aaa Merge pull request #116 from tonistiigi/build-arg-default
build: load default build args from env
2019-07-30 19:20:09 -07:00
Tibor Vass
73645c8348 Merge pull request #117 from tonistiigi/compose-env
bake: replace env in compose files
2019-07-30 19:14:21 -07:00
Tonis Tiigi
662c0768cb bake: replace env in compose files
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2019-07-30 16:44:05 -07:00
Tonis Tiigi
43150ef849 build: load default build args from env
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2019-07-30 16:32:36 -07:00
Tibor Vass
3f18b659a0 Merge pull request #102 from tonistiigi/buildkitd-flags
driver: allow configuring buildkitd flags
2019-07-09 17:27:17 -07:00
Tonis Tiigi
6b81b0bed6 build: add allowed entitlements
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2019-07-08 15:59:53 -07:00
Tonis Tiigi
f0af89a204 driver: allow configuring buildkitd flags
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2019-07-08 15:29:43 -07:00
Tõnis Tiigi
550c2b9042 Merge pull request #100 from FernandoMiguel/patch-1
add chmod
2019-07-06 12:38:08 -07:00
Fernando Miguel
c8cda08209 add chmod 2019-07-05 12:14:40 +01:00
Tõnis Tiigi
2b03339235 Merge pull request #93 from zelahi/enable-fossa-scan
[TAR-853] ADDED .fossa file for fossa scans
2019-06-17 09:21:16 -07:00
zelahi
6e1fd0eab6 ADDED .fossa file for fossa scans 2019-06-14 10:49:12 -07:00
Tõnis Tiigi
5336e74bd4 Merge pull request #89 from khs1994/master
Fix Dockerfile format
2019-06-05 13:56:29 -07:00
Tõnis Tiigi
afeaed790f Merge pull request #86 from AkihiroSuda/driver-ls
Put driver names to create --help
2019-06-05 13:55:44 -07:00
khs1994
aed531a8a9 Fix Dockerfile format
Signed-off-by: Kang HuaiShuai <khs1994@khs1994.com>
2019-06-04 17:43:39 +08:00
Akihiro Suda
eee78c6c10 Put driver names to create --help
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2019-06-02 00:02:20 +09:00
3062 changed files with 601242 additions and 201797 deletions

185
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,185 @@
name: build
on:
workflow_dispatch:
push:
branches:
- 'master'
tags:
- 'v*'
pull_request:
branches:
- 'master'
env:
REPO_SLUG_ORIGIN: "moby/buildkit:master"
CACHEKEY_BINARIES: "binaries"
PLATFORMS: "linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/s390x,linux/ppc64le"
jobs:
base:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Cache ${{ env.CACHEKEY_BINARIES }}
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
-
name: Build ${{ env.CACHEKEY_BINARIES }}
run: |
./hack/build_ci_first_pass binaries
env:
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
CACHEDIR_TO: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}-new
-
# FIXME: Temp fix for https://github.com/moby/buildkit/issues/1850
name: Move cache
run: |
rm -rf /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
mv /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}-new /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
test:
runs-on: ubuntu-latest
needs: [base]
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Cache ${{ env.CACHEKEY_BINARIES }}
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
-
name: Test
run: |
make test
env:
TEST_COVERAGE: 1
TESTFLAGS: -v --parallel=6 --timeout=20m
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
-
name: Send to Codecov
uses: codecov/codecov-action@v1
with:
file: ./coverage/coverage.txt
cross:
runs-on: ubuntu-latest
needs: [base]
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Cache ${{ env.CACHEKEY_BINARIES }}
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
-
name: Cross
run: |
make cross
env:
TARGETPLATFORM: ${{ env.PLATFORMS }},darwin/amd64,windows/amd64
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
binaries:
runs-on: ubuntu-latest
needs: [test, cross]
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Prepare
id: prep
run: |
TAG=pr
if [[ $GITHUB_REF == refs/tags/v* ]]; then
TAG=${GITHUB_REF#refs/tags/}
elif [[ $GITHUB_REF == refs/heads/* ]]; then
TAG=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g')
fi
echo ::set-output name=tag::${TAG}
-
name: Cache ${{ env.CACHEKEY_BINARIES }}
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
-
name: Build ${{ steps.prep.outputs.tag }}
run: |
./hack/release "${{ steps.prep.outputs.tag }}" release-out
env:
PLATFORMS: ${{ env.PLATFORMS }},darwin/amd64,windows/amd64
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
-
name: Move artifacts
run: |
mv ./release-out/**/* ./release-out/
-
name: Upload artifacts
uses: actions/upload-artifact@v2
with:
name: buildx
path: ./release-out/*
if-no-files-found: error
-
name: GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
files: ./release-out/*
name: ${{ steps.prep.outputs.tag }}

25
.github/workflows/godev.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
# Workflow used to make a request to proxy.golang.org to refresh cache on https://pkg.go.dev/github.com/docker/buildx
# when a released of buildx is produced
name: godev
on:
push:
tags:
- 'v*'
jobs:
update:
runs-on: ubuntu-latest
steps:
-
name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.13
-
name: Call pkg.go.dev
run: |
go get github.com/${GITHUB_REPOSITORY}@${GITHUB_REF#refs/tags/}
env:
GO111MODULE: on
GOPROXY: https://proxy.golang.org

38
.github/workflows/validate.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: validate
on:
workflow_dispatch:
push:
branches:
- 'master'
tags:
- 'v*'
pull_request:
branches:
- 'master'
env:
REPO_SLUG_ORIGIN: "moby/buildkit:master"
jobs:
validate:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
- lint
- validate-vendor
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
-
name: Run
run: |
make ${{ matrix.target }}

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
bin
coverage
cross-out

View File

@@ -1,35 +0,0 @@
dist: trusty
sudo: required
install:
- docker run --name buildkit --rm -d --privileged -p 1234:1234 $REPO_SLUG_ORIGIN --addr tcp://0.0.0.0:1234
- sudo docker cp buildkit:/usr/bin/buildctl /usr/bin/
- export BUILDKIT_HOST=tcp://0.0.0.0:1234
env:
global:
- PLATFORMS="linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/s390x,linux/ppc64le"
- CROSS_PLATFORMS="${PLATFORMS},darwin/amd64,windows/amd64"
- PREFER_BUILDCTL="1"
script:
- make binaries validate-all && TARGETPLATFORM="${CROSS_PLATFORMS}" ./hack/cross
deploy:
- provider: script
script: PLATFORMS="${CROSS_PLATFORMS}" ./hack/release $TRAVIS_TAG release-out
on:
repo: docker/buildx
tags: true
condition: $TRAVIS_TAG =~ ^v[0-9]
- provider: releases
api_key:
secure: "VKVL+tyS3BfqjM4VMGHoHJbcKY4mqq4AGrclVEvBnt0gm1LkGeKxSheCZgF1EC4oSV8rCy6dkoRWL0PLkl895MIl20Z4v53o1NOQ4Fn0A+eptnrld8jYUkL5PcD+kdEqv2GkBn7vO6E/fwYY/wH9FYlE+fXUa0c/YQGqNGS+XVDtgkftqBV+F2EzaIwk+D+QClFBRmKvIbXrUQASi1K6K2eT3gvzR4zh679TSdI2nbnTKtE06xG1PBFVmb1Ux3/Jz4yHFvf2d3M1mOyqIBsozKoyxisiFQxnm3FjhPrdlZJ9oy/nsQM3ahQKJ3DF8hiLI1LxcxRa6wo//t3uu2eJSYl/c5nu0T7gVw4sChQNy52fUhEGoDTDwYoAxsLSDXcpj1jevRsKvxt/dh2e2De1a9HYj5oM+z2O+pcyiY98cKDbhe2miUqUdiYMBy24xUunB46zVcJF3pIqCYtw5ts8ES6Ixn3u+4OGV/hMDrVdiG2bOZtNVkdbKMEkOEBGa3parPJ69jh6og639kdAD3DFxyZn3YKYuJlcNShn3tj6iPokBYhlLwwf8vuEV7gK7G0rDS9yxuF03jgkwpBBF2wy+u1AbJv241T7v2ZB8H8VlYyHA0E5pnoWbw+lIOTy4IAc8gIesMvDuFFi4r1okhiAt/24U0p4aAohjh1nPuU3spY="
file: release-out/**/*
skip_cleanup: true
file_glob: true
on:
repo: docker/buildx
tags: true
condition: $TRAVIS_TAG =~ ^v[0-9]

13
.yamllint.yml Normal file
View File

@@ -0,0 +1,13 @@
ignore: |
/vendor
extends: default
yaml-files:
- '*.yaml'
- '*.yml'
rules:
truthy: disable
line-length: disable
document-start: disable

View File

@@ -8,7 +8,7 @@ FROM docker:$DOCKERD_VERSION AS dockerd-release
# xgo is a helper for golang cross-compilation
FROM --platform=$BUILDPLATFORM tonistiigi/xx:golang@sha256:6f7d999551dd471b58f70716754290495690efa8421e0a1fcf18eb11d0c0a537 AS xgo
FROM --platform=$BUILDPLATFORM golang:1.12-alpine AS gobase
FROM --platform=$BUILDPLATFORM golang:1.13-alpine AS gobase
COPY --from=xgo / /
RUN apk add --no-cache file git
ENV GOFLAGS=-mod=vendor

View File

@@ -7,6 +7,9 @@ binaries:
binaries-cross:
EXPORT_LOCAL=cross-out ./hack/cross
cross:
./hack/cross
install: binaries
mkdir -p ~/.docker/cli-plugins
cp bin/buildx ~/.docker/cli-plugins/docker-buildx

348
README.md
View File

@@ -1,9 +1,13 @@
# buildx
### Docker CLI plugin for extended build capabilities with BuildKit
_buildx is Tech Preview_
[![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/docker/buildx)
[![Build Status](https://github.com/docker/buildx/workflows/build/badge.svg)](https://github.com/docker/buildx/actions?query=workflow%3Abuild)
[![Go Report Card](https://goreportcard.com/badge/github.com/docker/buildx)](https://goreportcard.com/report/github.com/docker/buildx)
[![codecov](https://codecov.io/gh/docker/buildx/branch/master/graph/badge.svg)](https://codecov.io/gh/docker/buildx)
### TL;DR
`buildx` is a Docker CLI plugin for extended build capabilities with [BuildKit](https://github.com/moby/buildkit).
Key features:
- Familiar UI from `docker build`
- Full BuildKit capabilities with container driver
@@ -11,7 +15,7 @@ _buildx is Tech Preview_
- Multi-node builds for cross-platform images
- Compose build support
- WIP: High-level build constructs (`bake`)
- TODO: In-container driver support
- In-container driver support (both Docker and Kubernetes)
# Table of Contents
@@ -41,32 +45,21 @@ _buildx is Tech Preview_
# Installing
Using `buildx` as a docker CLI plugin requires using Docker 19.03.0 beta. A limited set of functionality works with older versions of Docker when invoking the binary directly.
Using `buildx` as a docker CLI plugin requires using Docker 19.03. A limited set of functionality works with older versions of Docker when invoking the binary directly.
### Docker Desktop (Edge)
### Docker CE
`buildx` is included with Docker Desktop Edge builds since 19.03.0-beta3.
For more information see https://docs.docker.com/docker-for-mac/edge-release-notes/
### Docker CE nightly builds
`buildx` comes bundled with the Docker CE nightly builds.
- Mac: https://download.docker.com/mac/static/nightly/
- Linux:
```
$ # uncomment next line to uninstall previous Docker CE installation if present
$ # apt purge docker-ce docker-ce-cli
$ curl -fsSL https://get.docker.com/ -o docker-install.sh
$ CHANNEL=nightly sh docker-install.sh
```
`buildx` comes bundled with Docker CE starting with 19.03, but requires experimental mode to be enabled on the Docker CLI.
To enable it, `"experimental": "enabled"` can be added to the CLI configuration file `~/.docker/config.json`. An alternative is to set the `DOCKER_CLI_EXPERIMENTAL=enabled` environment variable.
### Binary release
Download the latest binary release from https://github.com/docker/buildx/releases/latest and copy it to `~/.docker/cli-plugins` folder with name `docker-buildx`.
After installing you can run `docker buildx` to see the new commands.
Change the permission to execute:
```sh
chmod a+x ~/.docker/cli-plugins/docker-buildx
```
# Building
@@ -80,6 +73,7 @@ $ make install
```
$ export DOCKER_BUILDKIT=1
$ docker build --platform=local -o . git://github.com/docker/buildx
$ mkdir -p ~/.docker/cli-plugins
$ mv buildx ~/.docker/cli-plugins/docker-buildx
```
@@ -89,7 +83,7 @@ $ mv buildx ~/.docker/cli-plugins/docker-buildx
Buildx is a Docker CLI plugin that extends the `docker build` command with the full support of the features provided by [Moby BuildKit](https://github.com/moby/buildkit) builder toolkit. It provides the same user experience as `docker build` with many new features like creating scoped builder instances and building against multiple nodes concurrently.
After installation, buildx can be accessed through the `docker buildx` command. `docker buildx build` is the command for starting a new build.
After installation, buildx can be accessed through the `docker buildx` command with Docker 19.03. `docker buildx build` is the command for starting a new build. With Docker versions older than 19.03 buildx binary can be called directly to access the `docker buildx` subcommands.
```
$ docker buildx build .
@@ -127,7 +121,11 @@ When invoking a build, the `--platform` flag can be used to specify the target p
Multi-platform images can be built by mainly three different strategies that are all supported by buildx and Dockerfiles. You can use the QEMU emulation support in the kernel, build on multiple native nodes using the same builder instance or use a stage in Dockerfile to cross-compile to different architectures.
QEMU is the easiest way to get started if your node already supports it (e.g. if you are using Docker Desktop). It requires no changes to your Dockerfile and BuildKit will automatically detect the secondary architectures that are available. When BuildKit needs to run a binary for a different architecture it will automatically load it through a binary registered in the binfmt_misc handler.
QEMU is the easiest way to get started if your node already supports it (e.g. if you are using Docker Desktop). It requires no changes to your Dockerfile and BuildKit will automatically detect the secondary architectures that are available. When BuildKit needs to run a binary for a different architecture it will automatically load it through a binary registered in the binfmt_misc handler. For QEMU binaries registered with binfmt_misc on the host OS to work transparently inside containers they must be registed with the fix_binary flag. This requires a kernel >= 4.8 and binfmt-support >= 2.1.7. You can check for proper registration by checking if `F` is among the flags in `/proc/sys/fs/binfmt_misc/qemu-*`. While Docker Desktop comes preconfigured with binfmt_misc support for additional platforms, for other installations it likely needs to be installed using [`tonistiigi/binfmt`](https://github.com/tonistiigi/binfmt) image.
```
$ docker run --privileged --rm tonistiigi/binfmt --install all
```
Using multiple native nodes provides better support for more complicated cases not handled by QEMU and generally have better performance. Additional nodes can be added to the builder instance with `--append` flag.
@@ -142,7 +140,7 @@ $ docker buildx build --platform linux/amd64,linux/arm64 .
Finally, depending on your project, the language that you use may have good support for cross-compilation. In that case, multi-stage builds in Dockerfiles can be effectively used to build binaries for the platform specified with `--platform` using the native architecture of the build node. List of build arguments like `BUILDPLATFORM` and `TARGETPLATFORM` are available automatically inside your Dockerfile and can be leveraged by the processes running as part of your build.
```
FROM --platform $BUILDPLATFORM golang:alpine AS build
FROM --platform=$BUILDPLATFORM golang:alpine AS build
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" > /log
@@ -174,6 +172,7 @@ Options:
| Flag | Description |
| --- | --- |
| --add-host [] | Add a custom host-to-IP mapping (host:ip)
| --allow [] | Allow extra privileged entitlement, e.g. network.host, security.insecure
| --build-arg [] | Set build-time variables
| --cache-from [] | External cache sources (eg. user/app:cache, type=local,src=path/to/dir)
| --cache-to [] | Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir)
@@ -189,7 +188,7 @@ Options:
| --pull | Always attempt to pull a newer version of the image
| --push | Shorthand for --output=type=registry
| --secret [] | Secret file to expose to the build: id=mysecret,src=/local/secret
| --ssh [] | SSH agent socket or keys to expose to the build (format: default|<id>[=<socket>|<key>[,<key>]])
| --ssh [] | SSH agent socket or keys to expose to the build (format: default\|&#60;id&#62;[=&#60;socket&#62;\|&#60;key&#62;[,&#60;key&#62;]])
| --tag [] | Name and optionally a tag in the 'name:tag' format
| --target string | Set the target build stage to build.
@@ -218,6 +217,21 @@ docker buildx build --platform=darwin .
Sets the export action for the build result. In `docker build` all builds finish by creating a container image and exporting it to `docker images`. `buildx` makes this step configurable allowing results to be exported directly to the client, oci image tarballs, registry etc.
Buildx with `docker` driver currently only supports local, tarball exporter and image exporter. `docker-container` driver supports all the exporters.
If just the path is specified as a value, `buildx` will use the local exporter with this path as the destination. If the value is “-”, `buildx` will use `tar` exporter and write to `stdout`.
Examples:
```
docker buildx build -o . .
docker buildx build -o outdir .
docker buildx build -o - - > out.tar
docker buildx build -o type=docker .
docker buildx build -o type=docker,dest=- . > myimage.tar
docker buildx build -t tonistiigi/foo -o type=registry
````
Supported exported types are:
##### `local`
@@ -269,23 +283,7 @@ Attribute keys:
The `registry` exporter is a shortcut for `type=image,push=true`.
Buildx with `docker` driver currently only supports local, tarball exporter and image exporter. `docker-container` driver supports all the exporters.
If just the path is specified as a value, `buildx` will use the local exporter with this path as the destination. If the value is “-”, `buildx` will use `tar` exporter and write to `stdout`.
Examples:
```
docker buildx build -o . .
docker buildx build -o outdir .
docker buildx build -o - - > out.tar
docker buildx build -o type=docker .
docker buildx build -o type=docker,dest=- . > myimage.tar
docker buildx build -t tonistiigi/foo -o type=registry
````
#### `--push`
#### `--push`
Shorthand for [`--output=type=registry`](#registry). Will automatically push the build result to registry.
@@ -295,7 +293,7 @@ Shorthand for [`--output=type=docker`](#docker). Will automatically load the sin
#### `--cache-from=[NAME|type=TYPE[,KEY=VALUE]]`
Use an external cache source for a build. Supported types are `registry` and `local`. The `registry` source can import cache from a cache manifest or (special) image configuration on the registry. The `local` source can export cache from local files previously exported with `--cache-to`.
Use an external cache source for a build. Supported types are `registry` and `local`. The `registry` source can import cache from a cache manifest or (special) image configuration on the registry. The `local` source can import cache from local files previously exported with `--cache-to`.
If no type is specified, `registry` exporter is used with a specified reference.
@@ -327,6 +325,20 @@ docker buildx build --cache-to=type=registry,ref=user/app .
docker buildx build --cache-to=type=local,dest=path/to/cache .
```
#### `--allow=ENTITLEMENT`
Allow extra privileged entitlement. List of entitlements:
- `network.host` - Allows executions with host networking.
- `security.insecure` - Allows executions without sandbox. See [related Dockerfile extensions](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md#run---securityinsecuresandbox).
For entitlements to be enabled, the `buildkitd` daemon also needs to allow them with `--allow-insecure-entitlement` (see [`create --buildkitd-flags`](#--buildkitd-flags-flags))
Example:
```
$ docker buildx create --use --name insecure-builder --buildkitd-flags '--allow-insecure-entitlement security.insecure'
$ docker buildx build --allow security.insecure .
```
### `buildx create [OPTIONS] [CONTEXT|ENDPOINT]`
@@ -341,21 +353,16 @@ Options:
| Flag | Description |
| --- | --- |
| --append | Append a node to builder instead of changing it
| --buildkitd-flags string | Flags for buildkitd daemon
| --config string | BuildKit config file
| --driver string | Driver to use (eg. docker-container)
| --driver-opt stringArray | Options for the driver
| --leave | Remove a node from builder instead of changing it
| --name string | Builder instance name
| --node string | Create/modify node with given name
| --platform stringArray | Fixed platforms for current node
| --use | Set the current builder instance
#### `--driver DRIVER`
Sets the builder driver to be used. There are two available drivers, each have their own specificities.
- `docker` - Uses the builder that is built into the docker daemon. With this driver, the [`--load`](#--load) flag is implied by default on `buildx build`. However, building multi-platform images or exporting cache is not currently supported.
- `docker-container` - Uses a buildkit container that will be spawned via docker. With this driver, both building multi-platform images and exporting cache are supported. However, images built will not automatically appear in `docker images` (see [`build --load`](#--load)).
#### `--append`
Changes the action of the command to appends a new node to an existing builder specified by `--name`. Buildx will choose an appropriate node for a build based on the platforms it supports.
@@ -368,6 +375,50 @@ $ docker buildx create --name eager_beaver --append mycontext2
eager_beaver
```
#### `--buildkitd-flags FLAGS`
Adds flags when starting the buildkitd daemon. They take precedence over the configuration file specified by [`--config`](#--config-file). See `buildkitd --help` for the available flags.
Example:
```
--buildkitd-flags '--debug --debugaddr 0.0.0.0:6666'
```
#### `--config FILE`
Specifies the configuration file for the buildkitd daemon to use. The configuration can be overridden by [`--buildkitd-flags`](#--buildkitd-flags-flags). See an [example buildkitd configuration file](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md).
#### `--driver DRIVER`
Sets the builder driver to be used. There are two available drivers, each have their own specificities.
- `docker` - Uses the builder that is built into the docker daemon. With this driver, the [`--load`](#--load) flag is implied by default on `buildx build`. However, building multi-platform images or exporting cache is not currently supported.
- `docker-container` - Uses a buildkit container that will be spawned via docker. With this driver, both building multi-platform images and exporting cache are supported. However, images built will not automatically appear in `docker images` (see [`build --load`](#--load)).
- `kubernetes` - Uses a kubernetes pods. With this driver , you can spin up pods with defined buildkit container image to build your images.
#### `--driver-opt OPTIONS`
Passes additional driver-specific options. Details for each driver:
- `docker` - No driver options
- `docker-container`
- `image=IMAGE` - Sets the container image to be used for running buildkit.
- `network=NETMODE` - Sets the network mode for running the buildkit container.
- Example:
```
--driver docker-container --driver-opt image=moby/buildkit:master,network=host
```
- `kubernetes`
- `image=IMAGE` - Sets the container image to be used for running buildkit.
- `namespace=NS` - Sets the Kubernetes namespace. Defaults to the current namespace.
- `replicas=N` - Sets the number of `Pod` replicas. Defaults to 1.
- `nodeselector="label1=value1,label2=value2"` - Sets the kv of `Pod` nodeSelector. No Defaults. Example `nodeselector=kubernetes.io/arch=arm64`
- `rootless=(true|false)` - Run the container as a non-root user without `securityContext.privileged`. [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"
#### `--leave`
Changes the action of the command to removes a node from a builder. The builder needs to be specified with `--name` and node that is removed is set with `--node`.
@@ -465,11 +516,13 @@ Options:
| Flag | Description |
| --- | --- |
| -f, --file stringArray | Build definition file
| --load | Shorthand for --set=*.output=type=docker
| --no-cache | Do not use cache when building the image
| --print | Print the options without building
| --progress string | Set type of progress output (auto, plain, tty). Use plain to show container output (default "auto")
| --pull | Always attempt to pull a newer version of the image
| --set stringArray | Override target value (eg: target.key=value)
| --push | Shorthand for --set=*.output=type=registry
| --set stringArray | Override target value (eg: targetpattern.key=value)
#### `-f, --file FILE`
@@ -512,16 +565,22 @@ Same as `build --progress`. Set type of progress output (auto, plain, tty). Use
Same as `build --pull`.
#### `--set target.key[.subkey]=value`
#### `--set targetpattern.key[.subkey]=value`
Override target configurations from command line.
Override target configurations from command line. The pattern matching syntax is defined in https://golang.org/pkg/path/#Match.
Example:
```
docker buildx bake --set target.args.mybuildarg=value
docker buildx bake --set target.platform=linux/arm64
docker buildx bake --set foo*.args.mybuildarg=value # overrides build arg for all targets starting with 'foo'
docker buildx bake --set *.platform=linux/arm64 # overrides platform for all targets
docker buildx bake --set foo*.no-cache # bypass caching only for targets starting with 'foo'
```
Complete list of overridable fields:
args, cache-from, cache-to, context, dockerfile, labels, no-cache, output, platform, pull, secrets, ssh, tags, target
#### File definition
In addition to compose files, bake supports a JSON and an equivalent HCL file format for defining build groups and targets.
@@ -541,23 +600,184 @@ Note: Design of bake command is work in progress, the user experience may change
Example HCL defintion:
```
group default {
targets = [db, webapp-dev]
group "default" {
targets = ["db", "webapp-dev"]
}
target webapp-dev {
target "webapp-dev" {
dockerfile = "Dockerfile.webapp"
tags = ["docker.io/username/webapp"]
}
target webapp-release {
inherits = [webapp-dev]
platforms = [linux/amd64, linux/arm64]
target "webapp-release" {
inherits = ["webapp-dev"]
platforms = ["linux/amd64", "linux/arm64"]
}
target db {
target "db" {
dockerfile = "Dockerfile.db"
tags = [docker.io/username/db]
tags = ["docker.io/username/db"]
}
```
Complete list of valid target fields:
args, cache-from, cache-to, context, dockerfile, inherits, labels, no-cache, output, platform, pull, secrets, ssh, tags, target
#### HCL variables and functions
Similar to how Terraform provides a way to [define variables](https://www.terraform.io/docs/configuration/variables.html#declaring-an-input-variable), the HCL file format also supports variable block definitions. These can be used to define variables with values provided by the current environment or a default value when unset.
Example of using interpolation to tag an image with the git sha:
```
$ cat <<'EOF' > docker-bake.hcl
variable "TAG" {
default = "latest"
}
group "default" {
targets = ["webapp"]
}
target "webapp" {
tags = ["docker.io/username/webapp:${TAG}"]
}
EOF
$ docker buildx bake --print webapp
{
"target": {
"webapp": {
"context": ".",
"dockerfile": "Dockerfile",
"tags": [
"docker.io/username/webapp:latest"
]
}
}
}
$ TAG=$(git rev-parse --short HEAD) docker buildx bake --print webapp
{
"target": {
"webapp": {
"context": ".",
"dockerfile": "Dockerfile",
"tags": [
"docker.io/username/webapp:985e9e9"
]
}
}
}
```
A [set of generally useful functions](https://github.com/docker/buildx/blob/master/bake/hcl.go#L19-L65) provided by [go-cty](https://github.com/zclconf/go-cty/tree/master/cty/function/stdlib) are avaialble for use in HCL files. In addition, [user defined functions](https://github.com/hashicorp/hcl/tree/hcl2/ext/userfunc) are also supported.
Example of using the `add` function:
```
$ cat <<'EOF' > docker-bake.hcl
variable "TAG" {
default = "latest"
}
group "default" {
targets = ["webapp"]
}
target "webapp" {
args = {
buildno = "${add(123, 1)}"
}
}
EOF
$ docker buildx bake --print webapp
{
"target": {
"webapp": {
"context": ".",
"dockerfile": "Dockerfile",
"args": {
"buildno": "124"
}
}
}
}
```
Example of defining an `increment` function:
```
$ cat <<'EOF' > docker-bake.hcl
function "increment" {
params = [number]
result = number + 1
}
group "default" {
targets = ["webapp"]
}
target "webapp" {
args = {
buildno = "${increment(123)}"
}
}
EOF
$ docker buildx bake --print webapp
{
"target": {
"webapp": {
"context": ".",
"dockerfile": "Dockerfile",
"args": {
"buildno": "124"
}
}
}
}
```
Example of only adding tags if a variable is not empty using an `notequal` function:
```
$ cat <<'EOF' > docker-bake.hcl
variable "TAG" {default="" }
group "default" {
targets = [
"webapp",
]
}
target "webapp" {
context="."
dockerfile="Dockerfile"
tags = [
"my-image:latest",
notequal("",TAG) ? "my-image:${TAG}": "",
]
}
EOF
$ docker buildx bake --print webapp
{
"target": {
"webapp": {
"context": ".",
"dockerfile": "Dockerfile",
"tags": [
"my-image:latest"
]
}
}
}
```

View File

@@ -5,60 +5,100 @@ import (
"io/ioutil"
"os"
"path"
"regexp"
"strconv"
"strings"
"github.com/docker/buildx/build"
"github.com/docker/buildx/util/platformutil"
"github.com/docker/docker/pkg/urlutil"
hcl "github.com/hashicorp/hcl/v2"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/pkg/errors"
)
func ReadTargets(ctx context.Context, files, targets, overrides []string) (map[string]Target, error) {
var httpPrefix = regexp.MustCompile(`^https?://`)
var gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)
type File struct {
Name string
Data []byte
}
func defaultFilenames() []string {
return []string{
"docker-compose.yml", // support app
"docker-compose.yaml", // support app
"docker-bake.json",
"docker-bake.override.json",
"docker-bake.hcl",
"docker-bake.override.hcl",
}
}
func ReadLocalFiles(names []string) ([]File, error) {
isDefault := false
if len(names) == 0 {
isDefault = true
names = defaultFilenames()
}
out := make([]File, 0, len(names))
for _, n := range names {
dt, err := ioutil.ReadFile(n)
if err != nil {
if isDefault && errors.Is(err, os.ErrNotExist) {
continue
}
return nil, err
}
out = append(out, File{Name: n, Data: dt})
}
return out, nil
}
func ReadTargets(ctx context.Context, files []File, targets, overrides []string) (map[string]*Target, error) {
var c Config
for _, f := range files {
cfg, err := ParseFile(f)
cfg, err := ParseFile(f.Data, f.Name)
if err != nil {
return nil, err
}
c = mergeConfig(c, *cfg)
}
if err := c.setOverrides(overrides); err != nil {
o, err := c.newOverrides(overrides)
if err != nil {
return nil, err
}
m := map[string]Target{}
m := map[string]*Target{}
for _, n := range targets {
for _, n := range c.ResolveGroup(n) {
t, err := c.ResolveTarget(n)
t, err := c.ResolveTarget(n, o)
if err != nil {
return nil, err
}
if t != nil {
m[n] = *t
m[n] = t
}
}
}
return m, nil
}
func ParseFile(fn string) (*Config, error) {
dt, err := ioutil.ReadFile(fn)
if err != nil {
return nil, err
}
func ParseFile(dt []byte, fn string) (*Config, error) {
fnl := strings.ToLower(fn)
if strings.HasSuffix(fnl, ".yml") || strings.HasSuffix(fnl, ".yaml") {
return ParseCompose(dt)
}
if strings.HasSuffix(fnl, ".json") || strings.HasSuffix(fnl, ".hcl") {
return ParseHCL(dt)
return ParseHCL(dt, fn)
}
cfg, err := ParseCompose(dt)
if err != nil {
cfg, err2 := ParseHCL(dt)
cfg, err2 := ParseHCL(dt, fn)
if err2 != nil {
return nil, errors.Errorf("failed to parse %s: parsing yaml: %s, parsing hcl: %s", fn, err.Error(), err2.Error())
}
@@ -68,46 +108,110 @@ func ParseFile(fn string) (*Config, error) {
}
type Config struct {
Group map[string]Group
Target map[string]Target
Variables []*Variable `json:"-" hcl:"variable,block"`
Groups []*Group `json:"group" hcl:"group,block"`
Targets []*Target `json:"target" hcl:"target,block"`
Remain hcl.Body `json:"-" hcl:",remain"`
}
func mergeConfig(c1, c2 Config) Config {
for k, g := range c2.Group {
if c1.Group == nil {
c1.Group = map[string]Group{}
}
c1.Group[k] = g
if c1.Groups == nil {
c1.Groups = []*Group{}
}
for k, t := range c2.Target {
if c1.Target == nil {
c1.Target = map[string]Target{}
for _, g2 := range c2.Groups {
var g1 *Group
for _, g := range c1.Groups {
if g2.Name == g.Name {
g1 = g
break
}
if base, ok := c1.Target[k]; ok {
t = merge(base, t)
}
c1.Target[k] = t
if g1 == nil {
c1.Groups = append(c1.Groups, g2)
continue
}
nextTarget:
for _, t2 := range g2.Targets {
for _, t1 := range g1.Targets {
if t1 == t2 {
continue nextTarget
}
}
g1.Targets = append(g1.Targets, t2)
}
c1.Groups = append(c1.Groups, g1)
}
if c1.Targets == nil {
c1.Targets = []*Target{}
}
for _, t2 := range c2.Targets {
var t1 *Target
for _, t := range c1.Targets {
if t2.Name == t.Name {
t1 = t
break
}
}
if t1 != nil {
t2 = merge(t1, t2)
}
c1.Targets = append(c1.Targets, t2)
}
return c1
}
func (c Config) setOverrides(v []string) error {
for _, v := range v {
parts := strings.SplitN(v, "=", 2)
if len(parts) != 2 {
return errors.Errorf("invalid override %s, expected target.name=value", v)
func (c Config) expandTargets(pattern string) ([]string, error) {
for _, target := range c.Targets {
if target.Name == pattern {
return []string{pattern}, nil
}
}
var names []string
for _, target := range c.Targets {
ok, err := path.Match(pattern, target.Name)
if err != nil {
return nil, errors.Wrapf(err, "could not match targets with '%s'", pattern)
}
if ok {
names = append(names, target.Name)
}
}
if len(names) == 0 {
return nil, errors.Errorf("could not find any target matching '%s'", pattern)
}
return names, nil
}
func (c Config) newOverrides(v []string) (map[string]*Target, error) {
m := map[string]*Target{}
for _, v := range v {
parts := strings.SplitN(v, "=", 2)
keys := strings.SplitN(parts[0], ".", 3)
if len(keys) < 2 {
return errors.Errorf("invalid override key %s, expected target.name", parts[0])
return nil, errors.Errorf("invalid override key %s, expected target.name", parts[0])
}
name := keys[0]
pattern := keys[0]
if len(parts) != 2 && keys[1] != "args" {
return nil, errors.Errorf("invalid override %s, expected target.name=value", v)
}
t, ok := c.Target[name]
names, err := c.expandTargets(pattern)
if err != nil {
return nil, err
}
for _, name := range names {
t, ok := m[name]
if !ok {
return errors.Errorf("unknown target %s", name)
t = &Target{}
}
switch keys[1] {
@@ -117,15 +221,22 @@ func (c Config) setOverrides(v []string) error {
t.Dockerfile = &parts[1]
case "args":
if len(keys) != 3 {
return errors.Errorf("invalid key %s, args requires name", parts[0])
return nil, errors.Errorf("invalid key %s, args requires name", parts[0])
}
if t.Args == nil {
t.Args = map[string]string{}
}
if len(parts) < 2 {
v, ok := os.LookupEnv(keys[2])
if ok {
t.Args[keys[2]] = v
}
} else {
t.Args[keys[2]] = parts[1]
}
case "labels":
if len(keys) != 3 {
return errors.Errorf("invalid key %s, lanels requires name", parts[0])
return nil, errors.Errorf("invalid key %s, lanels requires name", parts[0])
}
if t.Labels == nil {
t.Labels = map[string]string{}
@@ -148,12 +259,25 @@ func (c Config) setOverrides(v []string) error {
t.Platforms = append(t.Platforms, parts[1])
case "output":
t.Outputs = append(t.Outputs, parts[1])
case "no-cache":
noCache, err := strconv.ParseBool(parts[1])
if err != nil {
return nil, errors.Errorf("invalid value %s for boolean key no-cache", parts[1])
}
t.NoCache = &noCache
case "pull":
pull, err := strconv.ParseBool(parts[1])
if err != nil {
return nil, errors.Errorf("invalid value %s for boolean key pull", parts[1])
}
t.Pull = &pull
default:
return errors.Errorf("unknown key: %s", keys[1])
return nil, errors.Errorf("unknown key: %s", keys[1])
}
c.Target[name] = t
m[name] = t
}
return nil
}
return m, nil
}
func (c Config) ResolveGroup(name string) []string {
@@ -164,8 +288,14 @@ func (c Config) group(name string, visited map[string]struct{}) []string {
if _, ok := visited[name]; ok {
return nil
}
g, ok := c.Group[name]
if !ok {
var g *Group
for _, group := range c.Groups {
if group.Name == name {
g = group
break
}
}
if g == nil {
return []string{name}
}
visited[name] = struct{}{}
@@ -176,8 +306,8 @@ func (c Config) group(name string, visited map[string]struct{}) []string {
return targets
}
func (c Config) ResolveTarget(name string) (*Target, error) {
t, err := c.target(name, map[string]struct{}{})
func (c Config) ResolveTarget(name string, overrides map[string]*Target) (*Target, error) {
t, err := c.target(name, map[string]struct{}{}, overrides)
if err != nil {
return nil, err
}
@@ -192,50 +322,74 @@ func (c Config) ResolveTarget(name string) (*Target, error) {
return t, nil
}
func (c Config) target(name string, visited map[string]struct{}) (*Target, error) {
func (c Config) target(name string, visited map[string]struct{}, overrides map[string]*Target) (*Target, error) {
if _, ok := visited[name]; ok {
return nil, nil
}
visited[name] = struct{}{}
t, ok := c.Target[name]
if !ok {
var t *Target
for _, target := range c.Targets {
if target.Name == name {
t = target
break
}
}
if t == nil {
return nil, errors.Errorf("failed to find target %s", name)
}
var tt Target
tt := &Target{}
for _, name := range t.Inherits {
t, err := c.target(name, visited)
t, err := c.target(name, visited, overrides)
if err != nil {
return nil, err
}
if t != nil {
tt = merge(tt, *t)
tt = merge(tt, t)
}
}
t.Inherits = nil
tt = merge(merge(defaultTarget(), t), tt)
tt = merge(merge(defaultTarget(), tt), t)
if override, ok := overrides[name]; ok {
tt = merge(tt, override)
}
tt.normalize()
return &tt, nil
return tt, nil
}
type Variable struct {
Name string `json:"-" hcl:"name,label"`
Default string `json:"default,omitempty" hcl:"default,optional"`
}
type Group struct {
Targets []string
Name string `json:"-" hcl:"name,label"`
Targets []string `json:"targets" hcl:"targets"`
// Target // TODO?
}
type Target struct {
Inherits []string `json:"inherits,omitempty" hcl:"inherits,omitempty"`
Context *string `json:"context,omitempty" hcl:"context,omitempty"`
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,omitempty"`
Args map[string]string `json:"args,omitempty" hcl:"args,omitempty"`
Labels map[string]string `json:"labels,omitempty" hcl:"labels,omitempty"`
Tags []string `json:"tags,omitempty" hcl:"tags,omitempty"`
CacheFrom []string `json:"cache-from,omitempty" hcl:"cache-from,omitempty"`
CacheTo []string `json:"cache-to,omitempty" hcl:"cache-to,omitempty"`
Target *string `json:"target,omitempty" hcl:"target,omitempty"`
Secrets []string `json:"secret,omitempty" hcl:"secret,omitempty"`
SSH []string `json:"ssh,omitempty" hcl:"ssh,omitempty"`
Platforms []string `json:"platforms,omitempty" hcl:"platforms,omitempty"`
Outputs []string `json:"output,omitempty" hcl:"output,omitempty"`
Name string `json:"-" hcl:"name,label"`
// Inherits is the only field that cannot be overridden with --set
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional"`
Context *string `json:"context,omitempty" hcl:"context,optional"`
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional"`
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional"`
Args map[string]string `json:"args,omitempty" hcl:"args,optional"`
Labels map[string]string `json:"labels,omitempty" hcl:"labels,optional"`
Tags []string `json:"tags,omitempty" hcl:"tags,optional"`
CacheFrom []string `json:"cache-from,omitempty" hcl:"cache-from,optional"`
CacheTo []string `json:"cache-to,omitempty" hcl:"cache-to,optional"`
Target *string `json:"target,omitempty" hcl:"target,optional"`
Secrets []string `json:"secret,omitempty" hcl:"secret,optional"`
SSH []string `json:"ssh,omitempty" hcl:"ssh,optional"`
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional"`
Outputs []string `json:"output,omitempty" hcl:"output,optional"`
Pull *bool `json:"pull,omitempty" hcl:"pull,optional"`
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional"`
// IMPORTANT: if you add more fields here, do not forget to update newOverrides and README.
}
func (t *Target) normalize() {
@@ -248,10 +402,10 @@ func (t *Target) normalize() {
t.Outputs = removeDupes(t.Outputs)
}
func TargetsToBuildOpt(m map[string]Target) (map[string]build.Options, error) {
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
m2 := make(map[string]build.Options, len(m))
for k, v := range m {
bo, err := toBuildOpt(v)
bo, err := toBuildOpt(v, inp)
if err != nil {
return nil, err
}
@@ -260,7 +414,19 @@ func TargetsToBuildOpt(m map[string]Target) (map[string]build.Options, error) {
return m2, nil
}
func toBuildOpt(t Target) (*build.Options, error) {
func updateContext(t *build.Inputs, inp *Input) {
if inp == nil || inp.State == nil {
return
}
if t.ContextPath == "." {
t.ContextPath = inp.URL
return
}
st := llb.Scratch().File(llb.Copy(*inp.State, t.ContextPath, "/"), llb.WithCustomNamef("set context to %s", t.ContextPath))
t.ContextState = &st
}
func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
if v := t.Context; v != nil && *v == "-" {
return nil, errors.Errorf("context from stdin not allowed in bake")
}
@@ -272,6 +438,7 @@ func toBuildOpt(t Target) (*build.Options, error) {
if t.Context != nil {
contextPath = *t.Context
}
contextPath = path.Clean(contextPath)
dockerfilePath := "Dockerfile"
if t.Dockerfile != nil {
dockerfilePath = *t.Dockerfile
@@ -281,14 +448,31 @@ func toBuildOpt(t Target) (*build.Options, error) {
dockerfilePath = path.Join(contextPath, dockerfilePath)
}
bo := &build.Options{
Inputs: build.Inputs{
noCache := false
if t.NoCache != nil {
noCache = *t.NoCache
}
pull := false
if t.Pull != nil {
pull = *t.Pull
}
bi := build.Inputs{
ContextPath: contextPath,
DockerfilePath: dockerfilePath,
},
}
if t.DockerfileInline != nil {
bi.DockerfileInline = *t.DockerfileInline
}
updateContext(&bi, inp)
bo := &build.Options{
Inputs: bi,
Tags: t.Tags,
BuildArgs: t.Args,
Labels: t.Labels,
NoCache: noCache,
Pull: pull,
}
platforms, err := platformutil.Parse(t.Platforms)
@@ -336,17 +520,20 @@ func toBuildOpt(t Target) (*build.Options, error) {
return bo, nil
}
func defaultTarget() Target {
return Target{}
func defaultTarget() *Target {
return &Target{}
}
func merge(t1, t2 Target) Target {
func merge(t1, t2 *Target) *Target {
if t2.Context != nil {
t1.Context = t2.Context
}
if t2.Dockerfile != nil {
t1.Dockerfile = t2.Dockerfile
}
if t2.DockerfileInline != nil {
t1.DockerfileInline = t2.DockerfileInline
}
for k, v := range t2.Args {
if t1.Args == nil {
t1.Args = map[string]string{}
@@ -383,6 +570,12 @@ func merge(t1, t2 Target) Target {
if t2.Outputs != nil { // no merge
t1.Outputs = t2.Outputs
}
if t2.Pull != nil {
t1.Pull = t2.Pull
}
if t2.NoCache != nil {
t1.NoCache = t2.NoCache
}
t1.Inherits = append(t1.Inherits, t2.Inherits...)
return t1
}
@@ -394,6 +587,9 @@ func removeDupes(s []string) []string {
if _, ok := seen[v]; ok {
continue
}
if v == "" {
continue
}
seen[v] = struct{}{}
s[i] = v
i++

View File

@@ -2,9 +2,7 @@ package bake
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
@@ -12,40 +10,182 @@ import (
func TestReadTargets(t *testing.T) {
t.Parallel()
tmpdir, err := ioutil.TempDir("", "bake")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
fp := filepath.Join(tmpdir, "config.hcl")
err = ioutil.WriteFile(fp, []byte(`
target "dep" {
fp := File{
Name: "config.hcl",
Data: []byte(`
target "webDEP" {
args = {
VAR_INHERITED = "webDEP"
VAR_BOTH = "webDEP"
}
no-cache = true
}
target "webapp" {
dockerfile = "Dockerfile.webapp"
inherits = ["dep"]
}`), 0600)
require.NoError(t, err)
args = {
VAR_BOTH = "webapp"
}
inherits = ["webDEP"]
}`),
}
ctx := context.TODO()
m, err := ReadTargets(ctx, []string{fp}, []string{"webapp"}, nil)
t.Run("NoOverrides", func(t *testing.T) {
m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, nil)
require.NoError(t, err)
require.Equal(t, 1, len(m))
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
require.Equal(t, ".", *m["webapp"].Context)
require.Equal(t, "webDEP", m["webapp"].Args["VAR_INHERITED"])
require.Equal(t, true, *m["webapp"].NoCache)
require.Nil(t, m["webapp"].Pull)
})
t.Run("InvalidTargetOverrides", func(t *testing.T) {
_, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"nosuchtarget.context=foo"})
require.NotNil(t, err)
require.Equal(t, err.Error(), "could not find any target matching 'nosuchtarget'")
})
t.Run("ArgsOverrides", func(t *testing.T) {
t.Run("leaf", func(t *testing.T) {
os.Setenv("VAR_FROMENV"+t.Name(), "fromEnv")
defer os.Unsetenv("VAR_FROM_ENV" + t.Name())
m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{
"webapp.args.VAR_UNSET",
"webapp.args.VAR_EMPTY=",
"webapp.args.VAR_SET=bananas",
"webapp.args.VAR_FROMENV" + t.Name(),
"webapp.args.VAR_INHERITED=override",
// not overriding VAR_BOTH on purpose
})
require.NoError(t, err)
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
require.Equal(t, ".", *m["webapp"].Context)
_, isSet := m["webapp"].Args["VAR_UNSET"]
require.False(t, isSet, m["webapp"].Args["VAR_UNSET"])
_, isSet = m["webapp"].Args["VAR_EMPTY"]
require.True(t, isSet, m["webapp"].Args["VAR_EMPTY"])
require.Equal(t, m["webapp"].Args["VAR_SET"], "bananas")
require.Equal(t, m["webapp"].Args["VAR_FROMENV"+t.Name()], "fromEnv")
require.Equal(t, m["webapp"].Args["VAR_BOTH"], "webapp")
require.Equal(t, m["webapp"].Args["VAR_INHERITED"], "override")
})
// building leaf but overriding parent fields
t.Run("parent", func(t *testing.T) {
m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{
"webDEP.args.VAR_INHERITED=override",
"webDEP.args.VAR_BOTH=override",
})
require.NoError(t, err)
require.Equal(t, m["webapp"].Args["VAR_INHERITED"], "override")
require.Equal(t, m["webapp"].Args["VAR_BOTH"], "webapp")
})
})
t.Run("ContextOverride", func(t *testing.T) {
_, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context"})
require.NotNil(t, err)
m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context=foo"})
require.NoError(t, err)
require.Equal(t, "foo", *m["webapp"].Context)
})
t.Run("NoCacheOverride", func(t *testing.T) {
m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.no-cache=false"})
require.NoError(t, err)
require.Equal(t, false, *m["webapp"].NoCache)
})
t.Run("PullOverride", func(t *testing.T) {
m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.pull=false"})
require.NoError(t, err)
require.Equal(t, false, *m["webapp"].Pull)
})
t.Run("PatternOverride", func(t *testing.T) {
// same check for two cases
multiTargetCheck := func(t *testing.T, m map[string]*Target, err error) {
require.NoError(t, err)
require.Equal(t, 2, len(m))
require.Equal(t, "foo", *m["webapp"].Dockerfile)
require.Equal(t, "webDEP", m["webapp"].Args["VAR_INHERITED"])
require.Equal(t, "foo", *m["webDEP"].Dockerfile)
require.Equal(t, "webDEP", m["webDEP"].Args["VAR_INHERITED"])
}
cases := []struct {
name string
targets []string
overrides []string
check func(*testing.T, map[string]*Target, error)
}{
{
name: "multi target single pattern",
targets: []string{"webapp", "webDEP"},
overrides: []string{"web*.dockerfile=foo"},
check: multiTargetCheck,
},
{
name: "multi target multi pattern",
targets: []string{"webapp", "webDEP"},
overrides: []string{"web*.dockerfile=foo", "*.args.VAR_BOTH=bar"},
check: multiTargetCheck,
},
{
name: "single target",
targets: []string{"webapp"},
overrides: []string{"web*.dockerfile=foo"},
check: func(t *testing.T, m map[string]*Target, err error) {
require.NoError(t, err)
require.Equal(t, 1, len(m))
require.Equal(t, "foo", *m["webapp"].Dockerfile)
require.Equal(t, "webDEP", m["webapp"].Args["VAR_INHERITED"])
},
},
{
name: "nomatch",
targets: []string{"webapp"},
overrides: []string{"nomatch*.dockerfile=foo"},
check: func(t *testing.T, m map[string]*Target, err error) {
// NOTE: I am unsure whether failing to match should always error out
// instead of simply skipping that override.
// Let's enforce the error and we can relax it later if users complain.
require.NotNil(t, err)
require.Equal(t, err.Error(), "could not find any target matching 'nomatch*'")
},
},
}
for _, test := range cases {
t.Run(test.name, func(t *testing.T) {
m, err := ReadTargets(ctx, []File{fp}, test.targets, test.overrides)
test.check(t, m, err)
})
}
})
}
func TestReadTargetsCompose(t *testing.T) {
t.Parallel()
tmpdir, err := ioutil.TempDir("", "bake")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
fp := filepath.Join(tmpdir, "docker-compose.yml")
err = ioutil.WriteFile(fp, []byte(`
version: "3"
fp := File{
Name: "docker-compose.yml",
Data: []byte(
`version: "3"
services:
db:
build: .
@@ -56,14 +196,33 @@ services:
dockerfile: Dockerfile.webapp
args:
buildno: 1
`), 0600)
require.NoError(t, err)
`),
}
fp2 := File{
Name: "docker-compose2.yml",
Data: []byte(
`version: "3"
services:
newservice:
build: .
webapp:
build:
args:
buildno2: 12
`),
}
ctx := context.TODO()
m, err := ReadTargets(ctx, []string{fp}, []string{"default"}, nil)
m, err := ReadTargets(ctx, []File{fp, fp2}, []string{"default"}, nil)
require.NoError(t, err)
require.Equal(t, 3, len(m))
_, ok := m["newservice"]
require.True(t, ok)
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
require.Equal(t, ".", *m["webapp"].Context)
require.Equal(t, "1", m["webapp"].Args["buildno"])
require.Equal(t, "12", m["webapp"].Args["buildno2"])
}

View File

@@ -2,7 +2,9 @@ package bake
import (
"fmt"
"os"
"reflect"
"strings"
"github.com/docker/cli/cli/compose/loader"
composetypes "github.com/docker/cli/cli/compose/types"
@@ -19,9 +21,22 @@ func parseCompose(dt []byte) (*composetypes.Config, error) {
Config: parsed,
},
},
Environment: envMap(os.Environ()),
})
}
func envMap(env []string) map[string]string {
result := make(map[string]string, len(env))
for _, s := range env {
kv := strings.SplitN(s, "=", 2)
if len(kv) != 2 {
continue
}
result[kv[0]] = kv[1]
}
return result
}
func ParseCompose(dt []byte) (*Config, error) {
cfg, err := parseCompose(dt)
if err != nil {
@@ -31,10 +46,10 @@ func ParseCompose(dt []byte) (*Config, error) {
var c Config
var zeroBuildConfig composetypes.BuildConfig
if len(cfg.Services) > 0 {
c.Group = map[string]Group{}
c.Target = map[string]Target{}
c.Groups = []*Group{}
c.Targets = []*Target{}
var g Group
g := &Group{Name: "default"}
for _, s := range cfg.Services {
@@ -57,7 +72,8 @@ func ParseCompose(dt []byte) (*Config, error) {
dockerfilePathP = &dockerfilePath
}
g.Targets = append(g.Targets, s.Name)
t := Target{
t := &Target{
Name: s.Name,
Context: contextPathP,
Dockerfile: dockerfilePathP,
Labels: s.Build.Labels,
@@ -72,9 +88,9 @@ func ParseCompose(dt []byte) (*Config, error) {
if s.Image != "" {
t.Tags = []string{s.Image}
}
c.Target[s.Name] = t
c.Targets = append(c.Targets, t)
}
c.Group["default"] = g
c.Groups = append(c.Groups, g)
}
@@ -86,6 +102,8 @@ func toMap(in composetypes.MappingWithEquals) map[string]string {
for k, v := range in {
if v != nil {
m[k] = *v
} else {
m[k] = os.Getenv(k)
}
}
return m

View File

@@ -27,17 +27,23 @@ services:
c, err := ParseCompose(dt)
require.NoError(t, err)
require.Equal(t, 1, len(c.Group))
sort.Strings(c.Group["default"].Targets)
require.Equal(t, []string{"db", "webapp"}, c.Group["default"].Targets)
require.Equal(t, 1, len(c.Groups))
require.Equal(t, c.Groups[0].Name, "default")
sort.Strings(c.Groups[0].Targets)
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
require.Equal(t, 2, len(c.Target))
require.Equal(t, "./db", *c.Target["db"].Context)
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, "db", c.Targets[0].Name)
require.Equal(t, "./db", *c.Targets[0].Context)
require.Equal(t, "./dir", *c.Target["webapp"].Context)
require.Equal(t, "Dockerfile-alternate", *c.Target["webapp"].Dockerfile)
require.Equal(t, 1, len(c.Target["webapp"].Args))
require.Equal(t, "123", c.Target["webapp"].Args["buildno"])
require.Equal(t, "webapp", c.Targets[1].Name)
require.Equal(t, "./dir", *c.Targets[1].Context)
require.Equal(t, "Dockerfile-alternate", *c.Targets[1].Dockerfile)
require.Equal(t, 1, len(c.Targets[1].Args))
require.Equal(t, "123", c.Targets[1].Args["buildno"])
}
func TestNoBuildOutOfTreeService(t *testing.T) {
@@ -52,7 +58,7 @@ services:
`)
c, err := ParseCompose(dt)
require.NoError(t, err)
require.Equal(t, 1, len(c.Group))
require.Equal(t, 1, len(c.Groups))
}
func TestParseComposeTarget(t *testing.T) {
@@ -73,8 +79,14 @@ services:
c, err := ParseCompose(dt)
require.NoError(t, err)
require.Equal(t, "db", *c.Target["db"].Target)
require.Equal(t, "webapp", *c.Target["webapp"].Target)
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, "db", c.Targets[0].Name)
require.Equal(t, "db", *c.Targets[0].Target)
require.Equal(t, "webapp", c.Targets[1].Name)
require.Equal(t, "webapp", *c.Targets[1].Target)
}
func TestComposeBuildWithoutContext(t *testing.T) {
@@ -93,8 +105,14 @@ services:
c, err := ParseCompose(dt)
require.NoError(t, err)
require.Equal(t, "db", *c.Target["db"].Target)
require.Equal(t, "webapp", *c.Target["webapp"].Target)
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, c.Targets[0].Name, "db")
require.Equal(t, "db", *c.Targets[0].Target)
require.Equal(t, c.Targets[1].Name, "webapp")
require.Equal(t, "webapp", *c.Targets[1].Target)
}
func TestBogusCompose(t *testing.T) {

View File

@@ -1,11 +1,229 @@
package bake
import "github.com/hashicorp/hcl"
import (
"os"
"strings"
hcl "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/ext/userfunc"
"github.com/hashicorp/hcl/v2/hclsimple"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/json"
"github.com/moby/buildkit/solver/errdefs"
"github.com/moby/buildkit/solver/pb"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/function/stdlib"
)
// Collection of generally useful functions in cty-using applications, which
// HCL supports. These functions are available for use in HCL files.
var (
stdlibFunctions = map[string]function.Function{
"absolute": stdlib.AbsoluteFunc,
"add": stdlib.AddFunc,
"and": stdlib.AndFunc,
"byteslen": stdlib.BytesLenFunc,
"bytesslice": stdlib.BytesSliceFunc,
"chomp": stdlib.ChompFunc,
"chunklist": stdlib.ChunklistFunc,
"ceil": stdlib.CeilFunc,
"csvdecode": stdlib.CSVDecodeFunc,
"coalesce": stdlib.CoalesceFunc,
"coalescelist": stdlib.CoalesceListFunc,
"compact": stdlib.CompactFunc,
"concat": stdlib.ConcatFunc,
"contains": stdlib.ContainsFunc,
"distinct": stdlib.DistinctFunc,
"divide": stdlib.DivideFunc,
"element": stdlib.ElementFunc,
"equal": stdlib.EqualFunc,
"flatten": stdlib.FlattenFunc,
"floor": stdlib.FloorFunc,
"formatdate": stdlib.FormatDateFunc,
"format": stdlib.FormatFunc,
"formatlist": stdlib.FormatListFunc,
"greaterthan": stdlib.GreaterThanFunc,
"greaterthanorequalto": stdlib.GreaterThanOrEqualToFunc,
"hasindex": stdlib.HasIndexFunc,
"indent": stdlib.IndentFunc,
"index": stdlib.IndexFunc,
"int": stdlib.IntFunc,
"jsondecode": stdlib.JSONDecodeFunc,
"jsonencode": stdlib.JSONEncodeFunc,
"keys": stdlib.KeysFunc,
"join": stdlib.JoinFunc,
"length": stdlib.LengthFunc,
"lessthan": stdlib.LessThanFunc,
"lessthanorequalto": stdlib.LessThanOrEqualToFunc,
"log": stdlib.LogFunc,
"lookup": stdlib.LookupFunc,
"lower": stdlib.LowerFunc,
"max": stdlib.MaxFunc,
"merge": stdlib.MergeFunc,
"min": stdlib.MinFunc,
"modulo": stdlib.ModuloFunc,
"multiply": stdlib.MultiplyFunc,
"negate": stdlib.NegateFunc,
"notequal": stdlib.NotEqualFunc,
"not": stdlib.NotFunc,
"or": stdlib.OrFunc,
"parseint": stdlib.ParseIntFunc,
"pow": stdlib.PowFunc,
"range": stdlib.RangeFunc,
"regexall": stdlib.RegexAllFunc,
"regex": stdlib.RegexFunc,
"reverse": stdlib.ReverseFunc,
"reverselist": stdlib.ReverseListFunc,
"sethaselement": stdlib.SetHasElementFunc,
"setintersection": stdlib.SetIntersectionFunc,
"setsubtract": stdlib.SetSubtractFunc,
"setsymmetricdifference": stdlib.SetSymmetricDifferenceFunc,
"setunion": stdlib.SetUnionFunc,
"signum": stdlib.SignumFunc,
"slice": stdlib.SliceFunc,
"sort": stdlib.SortFunc,
"split": stdlib.SplitFunc,
"strlen": stdlib.StrlenFunc,
"substr": stdlib.SubstrFunc,
"subtract": stdlib.SubtractFunc,
"timeadd": stdlib.TimeAddFunc,
"title": stdlib.TitleFunc,
"trim": stdlib.TrimFunc,
"trimprefix": stdlib.TrimPrefixFunc,
"trimspace": stdlib.TrimSpaceFunc,
"trimsuffix": stdlib.TrimSuffixFunc,
"upper": stdlib.UpperFunc,
"values": stdlib.ValuesFunc,
"zipmap": stdlib.ZipmapFunc,
}
)
// Used in the first pass of decoding instead of the Config struct to disallow
// interpolation while parsing variable blocks.
type staticConfig struct {
Variables []*Variable `hcl:"variable,block"`
Remain hcl.Body `hcl:",remain"`
}
func ParseHCL(dt []byte, fn string) (_ *Config, err error) {
if strings.HasSuffix(fn, ".json") || strings.HasSuffix(fn, ".hcl") {
return parseHCL(dt, fn)
}
cfg, err := parseHCL(dt, fn+".hcl")
if err != nil {
cfg2, err2 := parseHCL(dt, fn+".json")
if err2 == nil {
return cfg2, nil
}
}
return cfg, err
}
func parseHCL(dt []byte, fn string) (_ *Config, err error) {
defer func() {
err = formatHCLError(dt, err)
}()
// Decode user defined functions, first parsing as hcl and falling back to
// json, returning errors based on the file suffix.
file, hcldiags := hclsyntax.ParseConfig(dt, fn, hcl.Pos{Line: 1, Column: 1})
if hcldiags.HasErrors() {
var jsondiags hcl.Diagnostics
file, jsondiags = json.Parse(dt, fn)
if jsondiags.HasErrors() {
fnl := strings.ToLower(fn)
if strings.HasSuffix(fnl, ".json") {
return nil, jsondiags
} else {
return nil, hcldiags
}
}
}
userFunctions, _, diags := userfunc.DecodeUserFunctions(file.Body, "function", func() *hcl.EvalContext {
return &hcl.EvalContext{
Functions: stdlibFunctions,
}
})
if diags.HasErrors() {
return nil, diags
}
var sc staticConfig
// Decode only variable blocks without interpolation.
if err := hclsimple.Decode(fn, dt, nil, &sc); err != nil {
return nil, err
}
// Set all variables to their default value if defined.
variables := make(map[string]cty.Value)
for _, variable := range sc.Variables {
variables[variable.Name] = cty.StringVal(variable.Default)
}
// Override default with values from environment.
for _, env := range os.Environ() {
parts := strings.SplitN(env, "=", 2)
name, value := parts[0], parts[1]
if _, ok := variables[name]; ok {
variables[name] = cty.StringVal(value)
}
}
functions := make(map[string]function.Function)
for k, v := range stdlibFunctions {
functions[k] = v
}
for k, v := range userFunctions {
functions[k] = v
}
ctx := &hcl.EvalContext{
Variables: variables,
Functions: functions,
}
func ParseHCL(dt []byte) (*Config, error) {
var c Config
if err := hcl.Unmarshal(dt, &c); err != nil {
// Decode with variables and functions.
if err := hclsimple.Decode(fn, dt, ctx, &c); err != nil {
return nil, err
}
return &c, nil
}
func formatHCLError(dt []byte, err error) error {
if err == nil {
return nil
}
diags, ok := err.(hcl.Diagnostics)
if !ok {
return err
}
for _, d := range diags {
if d.Severity != hcl.DiagError {
continue
}
if d.Subject != nil {
src := errdefs.Source{
Info: &pb.SourceInfo{
Filename: d.Subject.Filename,
Data: dt,
},
Ranges: []*pb.Range{toErrRange(d.Subject)},
}
err = errdefs.WithSource(err, src)
break
}
}
return err
}
func toErrRange(in *hcl.Range) *pb.Range {
return &pb.Range{
Start: pb.Position{Line: int32(in.Start.Line), Character: int32(in.Start.Column)},
End: pb.Position{Line: int32(in.End.Line), Character: int32(in.End.Column)},
}
}

View File

@@ -1,13 +1,17 @@
package bake
import (
"os"
"testing"
"github.com/stretchr/testify/require"
)
func TestParseHCL(t *testing.T) {
var dt = []byte(`
t.Parallel()
t.Run("Basic", func(t *testing.T) {
dt := []byte(`
group "default" {
targets = ["db", "webapp"]
}
@@ -40,18 +44,210 @@ func TestParseHCL(t *testing.T) {
}
`)
c, err := ParseHCL(dt)
c, err := ParseHCL(dt, "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Group))
require.Equal(t, []string{"db", "webapp"}, c.Group["default"].Targets)
require.Equal(t, 1, len(c.Groups))
require.Equal(t, "default", c.Groups[0].Name)
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
require.Equal(t, 4, len(c.Target))
require.Equal(t, "./db", *c.Target["db"].Context)
require.Equal(t, 4, len(c.Targets))
require.Equal(t, c.Targets[0].Name, "db")
require.Equal(t, "./db", *c.Targets[0].Context)
require.Equal(t, 1, len(c.Target["webapp"].Args))
require.Equal(t, "123", c.Target["webapp"].Args["buildno"])
require.Equal(t, c.Targets[1].Name, "webapp")
require.Equal(t, 1, len(c.Targets[1].Args))
require.Equal(t, "123", c.Targets[1].Args["buildno"])
require.Equal(t, 2, len(c.Target["cross"].Platforms))
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Target["cross"].Platforms)
require.Equal(t, c.Targets[2].Name, "cross")
require.Equal(t, 2, len(c.Targets[2].Platforms))
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[2].Platforms)
require.Equal(t, c.Targets[3].Name, "webapp-plus")
require.Equal(t, 1, len(c.Targets[3].Args))
require.Equal(t, map[string]string{"IAMCROSS": "true"}, c.Targets[3].Args)
})
t.Run("BasicInJSON", func(t *testing.T) {
dt := []byte(`
{
"group": {
"default": {
"targets": ["db", "webapp"]
}
},
"target": {
"db": {
"context": "./db",
"tags": ["docker.io/tonistiigi/db"]
},
"webapp": {
"context": "./dir",
"dockerfile": "Dockerfile-alternate",
"args": {
"buildno": "123"
}
},
"cross": {
"platforms": [
"linux/amd64",
"linux/arm64"
]
},
"webapp-plus": {
"inherits": ["webapp", "cross"],
"args": {
"IAMCROSS": "true"
}
}
}
}
`)
c, err := ParseHCL(dt, "docker-bake.json")
require.NoError(t, err)
require.Equal(t, 1, len(c.Groups))
require.Equal(t, "default", c.Groups[0].Name)
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
require.Equal(t, 4, len(c.Targets))
require.Equal(t, c.Targets[0].Name, "db")
require.Equal(t, "./db", *c.Targets[0].Context)
require.Equal(t, c.Targets[1].Name, "webapp")
require.Equal(t, 1, len(c.Targets[1].Args))
require.Equal(t, "123", c.Targets[1].Args["buildno"])
require.Equal(t, c.Targets[2].Name, "cross")
require.Equal(t, 2, len(c.Targets[2].Platforms))
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[2].Platforms)
require.Equal(t, c.Targets[3].Name, "webapp-plus")
require.Equal(t, 1, len(c.Targets[3].Args))
require.Equal(t, map[string]string{"IAMCROSS": "true"}, c.Targets[3].Args)
})
t.Run("WithFunctions", func(t *testing.T) {
dt := []byte(`
group "default" {
targets = ["webapp"]
}
target "webapp" {
args = {
buildno = "${add(123, 1)}"
}
}
`)
c, err := ParseHCL(dt, "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Groups))
require.Equal(t, "default", c.Groups[0].Name)
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
require.Equal(t, 1, len(c.Targets))
require.Equal(t, c.Targets[0].Name, "webapp")
require.Equal(t, "124", c.Targets[0].Args["buildno"])
})
t.Run("WithUserDefinedFunctions", func(t *testing.T) {
dt := []byte(`
function "increment" {
params = [number]
result = number + 1
}
group "default" {
targets = ["webapp"]
}
target "webapp" {
args = {
buildno = "${increment(123)}"
}
}
`)
c, err := ParseHCL(dt, "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Groups))
require.Equal(t, "default", c.Groups[0].Name)
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
require.Equal(t, 1, len(c.Targets))
require.Equal(t, c.Targets[0].Name, "webapp")
require.Equal(t, "124", c.Targets[0].Args["buildno"])
})
t.Run("WithVariables", func(t *testing.T) {
dt := []byte(`
variable "BUILD_NUMBER" {
default = "123"
}
group "default" {
targets = ["webapp"]
}
target "webapp" {
args = {
buildno = "${BUILD_NUMBER}"
}
}
`)
c, err := ParseHCL(dt, "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Groups))
require.Equal(t, "default", c.Groups[0].Name)
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
require.Equal(t, 1, len(c.Targets))
require.Equal(t, c.Targets[0].Name, "webapp")
require.Equal(t, "123", c.Targets[0].Args["buildno"])
os.Setenv("BUILD_NUMBER", "456")
c, err = ParseHCL(dt, "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Groups))
require.Equal(t, "default", c.Groups[0].Name)
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
require.Equal(t, 1, len(c.Targets))
require.Equal(t, c.Targets[0].Name, "webapp")
require.Equal(t, "456", c.Targets[0].Args["buildno"])
})
t.Run("WithIncorrectVariables", func(t *testing.T) {
dt := []byte(`
variable "DEFAULT_BUILD_NUMBER" {
default = "1"
}
variable "BUILD_NUMBER" {
default = "${DEFAULT_BUILD_NUMBER}"
}
group "default" {
targets = ["webapp"]
}
target "webapp" {
args = {
buildno = "${BUILD_NUMBER}"
}
}
`)
_, err := ParseHCL(dt, "docker-bake.hcl")
require.Error(t, err)
require.Contains(t, err.Error(), "docker-bake.hcl:7,17-37: Variables not allowed; Variables may not be used here.")
})
}

236
bake/remote.go Normal file
View File

@@ -0,0 +1,236 @@
package bake
import (
"archive/tar"
"bytes"
"context"
"strings"
"github.com/docker/buildx/build"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/util/progress"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
"github.com/pkg/errors"
)
type Input struct {
State *llb.State
URL string
}
func ReadRemoteFiles(ctx context.Context, dis []build.DriverInfo, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
st, filename, ok := detectHttpContext(url)
if !ok {
st, ok = detectGitContext(url)
if !ok {
return nil, nil, errors.Errorf("not url context")
}
}
inp := &Input{State: st, URL: url}
var files []File
var di *build.DriverInfo
for _, d := range dis {
if d.Err == nil {
di = &d
continue
}
}
if di == nil {
return nil, nil, nil
}
c, err := driver.Boot(ctx, di.Driver, pw)
if err != nil {
return nil, nil, err
}
ch, done := progress.NewChannel(pw)
defer func() { <-done }()
_, err = c.Build(ctx, client.SolveOpt{}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
def, err := st.Marshal(ctx)
if err != nil {
return nil, err
}
res, err := c.Solve(ctx, gwclient.SolveRequest{
Definition: def.ToPB(),
})
if err != nil {
return nil, err
}
ref, err := res.SingleRef()
if err != nil {
return nil, err
}
if filename != "" {
files, err = filesFromURLRef(ctx, c, ref, inp, filename, names)
} else {
files, err = filesFromRef(ctx, ref, names)
}
return nil, err
}, ch)
if err != nil {
return nil, nil, err
}
return files, inp, nil
}
func IsRemoteURL(url string) bool {
if _, _, ok := detectHttpContext(url); ok {
return true
}
if _, ok := detectGitContext(url); ok {
return true
}
return false
}
func detectHttpContext(url string) (*llb.State, string, bool) {
if httpPrefix.MatchString(url) {
httpContext := llb.HTTP(url, llb.Filename("context"), llb.WithCustomName("[internal] load remote build context"))
return &httpContext, "context", true
}
return nil, "", false
}
func detectGitContext(ref string) (*llb.State, bool) {
found := false
if httpPrefix.MatchString(ref) && gitURLPathWithFragmentSuffix.MatchString(ref) {
found = true
}
for _, prefix := range []string{"git://", "github.com/", "git@"} {
if strings.HasPrefix(ref, prefix) {
found = true
break
}
}
if !found {
return nil, false
}
parts := strings.SplitN(ref, "#", 2)
branch := ""
if len(parts) > 1 {
branch = parts[1]
}
gitOpts := []llb.GitOption{llb.WithCustomName("[internal] load git source " + ref)}
st := llb.Git(parts[0], branch, gitOpts...)
return &st, true
}
func isArchive(header []byte) bool {
for _, m := range [][]byte{
{0x42, 0x5A, 0x68}, // bzip2
{0x1F, 0x8B, 0x08}, // gzip
{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, // xz
} {
if len(header) < len(m) {
continue
}
if bytes.Equal(m, header[:len(m)]) {
return true
}
}
r := tar.NewReader(bytes.NewBuffer(header))
_, err := r.Next()
return err == nil
}
func filesFromURLRef(ctx context.Context, c gwclient.Client, ref gwclient.Reference, inp *Input, filename string, names []string) ([]File, error) {
stat, err := ref.StatFile(ctx, gwclient.StatRequest{Path: filename})
if err != nil {
return nil, err
}
dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{
Filename: filename,
Range: &gwclient.FileRange{
Length: 1024,
},
})
if err != nil {
return nil, err
}
if isArchive(dt) {
bc := llb.Scratch().File(llb.Copy(inp.State, filename, "/", &llb.CopyInfo{
AttemptUnpack: true,
}))
inp.State = &bc
inp.URL = ""
def, err := bc.Marshal(ctx)
if err != nil {
return nil, err
}
res, err := c.Solve(ctx, gwclient.SolveRequest{
Definition: def.ToPB(),
})
if err != nil {
return nil, err
}
ref, err := res.SingleRef()
if err != nil {
return nil, err
}
return filesFromRef(ctx, ref, names)
}
inp.State = nil
name := inp.URL
inp.URL = ""
if len(dt) > stat.Size() {
if stat.Size() > 1024*512 {
return nil, errors.Errorf("non-archive definition URL bigger than maximum allowed size")
}
dt, err = ref.ReadFile(ctx, gwclient.ReadRequest{
Filename: filename,
})
if err != nil {
return nil, err
}
}
return []File{{Name: name, Data: dt}}, nil
}
func filesFromRef(ctx context.Context, ref gwclient.Reference, names []string) ([]File, error) {
// TODO: auto-remove parent dir in needed
var files []File
isDefault := false
if len(names) == 0 {
isDefault = true
names = defaultFilenames()
}
for _, name := range names {
_, err := ref.StatFile(ctx, gwclient.StatRequest{Path: name})
if err != nil {
if isDefault {
continue
}
return nil, err
}
dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{Filename: name})
if err != nil {
return nil, err
}
files = append(files, File{Name: name, Data: dt})
}
return files, nil
}

View File

@@ -3,6 +3,7 @@ package build
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
@@ -11,6 +12,7 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/platforms"
@@ -19,11 +21,16 @@ import (
"github.com/docker/buildx/util/progress"
clitypes "github.com/docker/cli/cli/config/types"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
dockerclient "github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/urlutil"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/upload/uploadprovider"
"github.com/moby/buildkit/util/entitlements"
"github.com/moby/buildkit/util/progress/progresswriter"
"github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
@@ -55,6 +62,7 @@ type Options struct {
CacheFrom []client.CacheOptionsEntry
CacheTo []client.CacheOptionsEntry
Allow []entitlements.Entitlement
// DockerTarget
}
@@ -62,6 +70,8 @@ type Inputs struct {
ContextPath string
DockerfilePath string
InStream io.Reader
ContextState *llb.State
DockerfileInline string
}
type DriverInfo struct {
@@ -170,8 +180,7 @@ func splitToDriverPairs(availablePlatforms map[string]int, opt map[string]Option
return m
}
func resolveDrivers(ctx context.Context, drivers []DriverInfo, opt map[string]Options, pw progress.Writer) (map[string][]driverPair, []*client.Client, error) {
func resolveDrivers(ctx context.Context, drivers []DriverInfo, auth Auth, opt map[string]Options, pw progress.Writer) (map[string][]driverPair, []*client.Client, error) {
availablePlatforms := map[string]int{}
for i, d := range drivers {
for _, p := range d.Platform {
@@ -276,14 +285,7 @@ func toRepoOnly(in string) (string, error) {
return strings.Join(out, ","), nil
}
func isDefaultMobyDriver(d driver.Driver) bool {
_, ok := d.(interface {
IsDefaultMobyDriver()
})
return ok
}
func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCallback) (solveOpt *client.SolveOpt, release func(), err error) {
func toSolveOpt(ctx context.Context, d driver.Driver, multiDriver bool, opt Options, pw progress.Writer, dl dockerLoadCallback) (solveOpt *client.SolveOpt, release func(), err error) {
defers := make([]func(), 0, 2)
releaseF := func() {
for _, f := range defers {
@@ -298,9 +300,6 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
}()
if opt.ImageIDFile != "" {
if multiDriver || len(opt.Platforms) != 0 {
return nil, nil, errors.Errorf("image ID file cannot be specified when building for multiple platforms")
}
// Avoid leaving a stale file if we eventually fail
if err := os.Remove(opt.ImageIDFile); err != nil && !os.IsNotExist(err) {
return nil, nil, errors.Wrap(err, "removing image ID file")
@@ -329,6 +328,13 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
LocalDirs: map[string]string{},
CacheExports: opt.CacheTo,
CacheImports: opt.CacheFrom,
AllowedEntitlements: opt.Allow,
}
if v, ok := opt.BuildArgs["BUILDKIT_MULTI_PLATFORM"]; ok {
if v, _ := strconv.ParseBool(v); v {
so.FrontendAttrs["multi-platform"] = "true"
}
}
if multiDriver {
@@ -336,15 +342,11 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
so.FrontendAttrs["multi-platform"] = "true"
}
_, isDefaultMobyDriver := d.(interface {
IsDefaultMobyDriver()
})
switch len(opt.Exports) {
case 1:
// valid
case 0:
if isDefaultMobyDriver {
if d.IsMobyDriver() && !noDefaultLoad() {
// backwards compat for docker driver only:
// this ensures the build results in a docker image.
opt.Exports = []client.ExportEntry{{Type: "image", Attrs: map[string]string{}}}
@@ -379,6 +381,15 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
}
}
// cacheonly is a fake exporter to opt out of default behaviors
exports := make([]client.ExportEntry, 0, len(opt.Exports))
for _, e := range opt.Exports {
if e.Type != "cacheonly" {
exports = append(exports, e)
}
}
opt.Exports = exports
// set up exporters
for i, e := range opt.Exports {
if (e.Type == "local" || e.Type == "tar") && opt.ImageIDFile != "" {
@@ -389,7 +400,7 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
}
if e.Type == "docker" {
if e.Output == nil {
if isDefaultMobyDriver {
if d.IsMobyDriver() {
e.Type = "image"
} else {
w, cancel, err := dl(e.Attrs["context"])
@@ -397,17 +408,19 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
return nil, nil, err
}
defers = append(defers, cancel)
opt.Exports[i].Output = w
opt.Exports[i].Output = wrapWriteCloser(w)
}
} else if !d.Features()[driver.DockerExporter] {
return nil, nil, notSupported(d, driver.DockerExporter)
}
}
if e.Type == "image" && isDefaultMobyDriver {
if e.Type == "image" && d.IsMobyDriver() {
opt.Exports[i].Type = "moby"
if e.Attrs["push"] != "" {
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
return nil, nil, errors.Errorf("auto-push is currently not implemented for docker driver")
if ok, _ := strconv.ParseBool(e.Attrs["push-by-digest"]); ok {
return nil, nil, errors.Errorf("push-by-digest is currently not implemented for docker driver, please create a new builder instance")
}
}
}
}
@@ -416,7 +429,7 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
so.Exports = opt.Exports
so.Session = opt.Session
releaseLoad, err := LoadInputs(opt.Inputs, &so)
releaseLoad, err := LoadInputs(ctx, d, opt.Inputs, pw, &so)
if err != nil {
return nil, nil, err
}
@@ -454,6 +467,7 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
switch opt.NetworkMode {
case "host", "none":
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
so.AllowedEntitlements = append(so.AllowedEntitlements, entitlements.EntitlementNetworkHost)
case "", "default":
default:
return nil, nil, errors.Errorf("network mode %q not supported by buildkit", opt.NetworkMode)
@@ -469,7 +483,7 @@ func toSolveOpt(d driver.Driver, multiDriver bool, opt Options, dl dockerLoadCal
return &so, releaseF, nil
}
func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, docker DockerAPI, auth Auth, pw progress.Writer) (resp map[string]*client.SolveResponse, err error) {
func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, docker DockerAPI, auth Auth, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
if len(drivers) == 0 {
return nil, errors.Errorf("driver required for build")
}
@@ -481,13 +495,13 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
var noMobyDriver driver.Driver
for _, d := range drivers {
if !isDefaultMobyDriver(d.Driver) {
if !d.Driver.IsMobyDriver() {
noMobyDriver = d.Driver
break
}
}
if noMobyDriver != nil {
if noMobyDriver != nil && !noDefaultLoad() {
for _, opt := range opt {
if len(opt.Exports) == 0 {
logrus.Warnf("No output specified for %s driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load", noMobyDriver.Factory().Name())
@@ -496,10 +510,8 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
}
}
m, clients, err := resolveDrivers(ctx, drivers, opt, pw)
m, clients, err := resolveDrivers(ctx, drivers, auth, opt, w)
if err != nil {
close(pw.Status())
<-pw.Done()
return nil, err
}
@@ -512,16 +524,19 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
}
}()
mw := progress.NewMultiWriter(pw)
eg, ctx := errgroup.WithContext(ctx)
for k, opt := range opt {
multiDriver := len(m[k]) > 1
hasMobyDriver := false
for i, dp := range m[k] {
d := drivers[dp.driverIndex].Driver
if d.IsMobyDriver() {
hasMobyDriver = true
}
opt.Platforms = dp.platforms
so, release, err := toSolveOpt(d, multiDriver, opt, func(name string) (io.WriteCloser, func(), error) {
return newDockerLoader(ctx, docker, name, mw)
so, release, err := toSolveOpt(ctx, d, multiDriver, opt, w, func(name string) (io.WriteCloser, func(), error) {
return newDockerLoader(ctx, docker, name, w)
})
if err != nil {
return nil, err
@@ -529,6 +544,28 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
defers = append(defers, release)
m[k][i].so = so
}
for _, at := range opt.Session {
if s, ok := at.(interface {
SetLogger(progresswriter.Logger)
}); ok {
s.SetLogger(func(s *client.SolveStatus) {
w.Write(s)
})
}
}
// validate for multi-node push
if hasMobyDriver && multiDriver {
for _, dp := range m[k] {
for _, e := range dp.so.Exports {
if e.Type == "moby" {
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
return nil, errors.Errorf("multi-node push can't currently be performed with the docker driver, please switch to a different driver")
}
}
}
}
}
}
resp = map[string]*client.SolveResponse{}
@@ -537,7 +574,7 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
multiTarget := len(opt) > 1
for k, opt := range opt {
err := func() error {
err := func(k string) error {
opt := opt
dps := m[k]
multiDriver := len(m[k]) > 1
@@ -549,8 +586,7 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
var pushNames string
eg.Go(func() error {
pw := mw.WithPrefix("default", false)
defer close(pw.Status())
pw := progress.WithPrefix(w, "default", false)
wg.Wait()
select {
case <-ctx.Done():
@@ -653,27 +689,43 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
}
func(i int, dp driverPair, so client.SolveOpt) {
pw := mw.WithPrefix(k, multiTarget)
pw := progress.WithPrefix(w, k, multiTarget)
c := clients[dp.driverIndex]
var statusCh chan *client.SolveStatus
if pw != nil {
pw = progress.ResetTime(pw)
statusCh = pw.Status()
eg.Go(func() error {
<-pw.Done()
return pw.Err()
})
}
eg.Go(func() error {
defer wg.Done()
rr, err := c.Solve(ctx, nil, so, statusCh)
ch, done := progress.NewChannel(pw)
defer func() { <-done }()
rr, err := c.Solve(ctx, nil, so, ch)
if err != nil {
return err
}
res[i] = rr
d := drivers[dp.driverIndex].Driver
if d.IsMobyDriver() {
for _, e := range so.Exports {
if e.Type == "moby" && e.Attrs["push"] != "" {
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
pushNames = e.Attrs["name"]
if pushNames == "" {
return errors.Errorf("tag is needed when pushing to registry")
}
pw := progress.ResetTime(pw)
for _, name := range strings.Split(pushNames, ",") {
if err := progress.Wrap(fmt.Sprintf("pushing %s with docker", name), pw.Write, func(l progress.SubLogger) error {
return pushWithMoby(ctx, d, name, l)
}); err != nil {
return err
}
}
}
}
}
}
return nil
})
@@ -681,7 +733,7 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
}
return nil
}()
}(k)
if err != nil {
return nil, err
}
@@ -694,6 +746,86 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, do
return resp, nil
}
func pushWithMoby(ctx context.Context, d driver.Driver, name string, l progress.SubLogger) error {
api := d.Config().DockerAPI
if api == nil {
return errors.Errorf("invalid empty Docker API reference") // should never happen
}
creds, err := imagetools.RegistryAuthForRef(name, d.Config().Auth)
if err != nil {
return err
}
rc, err := api.ImagePush(ctx, name, types.ImagePushOptions{
RegistryAuth: creds,
})
if err != nil {
return err
}
started := map[string]*client.VertexStatus{}
defer func() {
for _, st := range started {
if st.Completed == nil {
now := time.Now()
st.Completed = &now
l.SetStatus(st)
}
}
}()
dec := json.NewDecoder(rc)
var parsedError error
for {
var jm jsonmessage.JSONMessage
if err := dec.Decode(&jm); err != nil {
if parsedError != nil {
return parsedError
}
if err == io.EOF {
break
}
return err
}
if jm.ID != "" {
id := "pushing layer " + jm.ID
st, ok := started[id]
if !ok {
if jm.Progress != nil || jm.Status == "Pushed" {
now := time.Now()
st = &client.VertexStatus{
ID: id,
Started: &now,
}
started[id] = st
} else {
continue
}
}
st.Timestamp = time.Now()
if jm.Progress != nil {
st.Current = jm.Progress.Current
st.Total = jm.Progress.Total
}
if jm.Error != nil {
now := time.Now()
st.Completed = &now
}
if jm.Status == "Pushed" {
now := time.Now()
st.Completed = &now
st.Current = st.Total
}
l.SetStatus(st)
}
if jm.Error != nil {
parsedError = jm.Error
}
}
return nil
}
func createTempDockerfile(r io.Reader) (string, error) {
dir, err := ioutil.TempDir("", "dockerfile")
if err != nil {
@@ -710,7 +842,7 @@ func createTempDockerfile(r io.Reader) (string, error) {
return dir, err
}
func LoadInputs(inp Inputs, target *client.SolveOpt) (func(), error) {
func LoadInputs(ctx context.Context, d driver.Driver, inp Inputs, pw progress.Writer, target *client.SolveOpt) (func(), error) {
if inp.ContextPath == "" {
return nil, errors.New("please specify build context (e.g. \".\" for the current directory)")
}
@@ -726,17 +858,23 @@ func LoadInputs(inp Inputs, target *client.SolveOpt) (func(), error) {
)
switch {
case inp.ContextState != nil:
if target.FrontendInputs == nil {
target.FrontendInputs = make(map[string]llb.State)
}
target.FrontendInputs["context"] = *inp.ContextState
target.FrontendInputs["dockerfile"] = *inp.ContextState
case inp.ContextPath == "-":
if inp.DockerfilePath == "-" {
return nil, errStdinConflict
}
buf := bufio.NewReader(os.Stdin)
buf := bufio.NewReader(inp.InStream)
magic, err := buf.Peek(archiveHeaderSize * 2)
if err != nil && err != io.EOF {
return nil, errors.Wrap(err, "failed to peek context header from STDIN")
}
if !(err == io.EOF && len(magic) == 0) {
if isArchive(magic) {
// stdin is context
up := uploadprovider.New()
@@ -752,12 +890,13 @@ func LoadInputs(inp Inputs, target *client.SolveOpt) (func(), error) {
toRemove = append(toRemove, inp.ContextPath)
target.LocalDirs["context"] = inp.ContextPath
}
}
case isLocalDir(inp.ContextPath):
target.LocalDirs["context"] = inp.ContextPath
switch inp.DockerfilePath {
case "-":
dockerfileReader = os.Stdin
dockerfileReader = inp.InStream
case "":
dockerfileDir = inp.ContextPath
default:
@@ -774,23 +913,41 @@ func LoadInputs(inp Inputs, target *client.SolveOpt) (func(), error) {
return nil, errors.Errorf("unable to prepare context: path %q not found", inp.ContextPath)
}
if inp.DockerfileInline != "" {
dockerfileReader = strings.NewReader(inp.DockerfileInline)
}
if dockerfileReader != nil {
dockerfileDir, err = createTempDockerfile(dockerfileReader)
if err != nil {
return nil, err
}
toRemove = append(toRemove, dockerfileDir)
dockerfileName = "Dockerfile"
target.FrontendAttrs["dockerfilekey"] = "dockerfile"
}
if urlutil.IsURL(inp.DockerfilePath) {
dockerfileDir, err = createTempDockerfileFromURL(ctx, d, inp.DockerfilePath, pw)
if err != nil {
return nil, err
}
toRemove = append(toRemove, dockerfileDir)
dockerfileName = "Dockerfile"
target.FrontendAttrs["dockerfilekey"] = "dockerfile"
delete(target.FrontendInputs, "dockerfile")
}
if dockerfileName == "" {
dockerfileName = "Dockerfile"
}
target.FrontendAttrs["filename"] = dockerfileName
if dockerfileDir != "" {
target.LocalDirs["dockerfile"] = dockerfileDir
dockerfileName = handleLowercaseDockerfile(dockerfileDir, dockerfileName)
}
target.FrontendAttrs["filename"] = dockerfileName
release := func() {
for _, dir := range toRemove {
os.RemoveAll(dir)
@@ -805,40 +962,60 @@ func notSupported(d driver.Driver, f driver.Feature) error {
type dockerLoadCallback func(name string) (io.WriteCloser, func(), error)
func newDockerLoader(ctx context.Context, d DockerAPI, name string, mw *progress.MultiWriter) (io.WriteCloser, func(), error) {
func newDockerLoader(ctx context.Context, d DockerAPI, name string, status progress.Writer) (io.WriteCloser, func(), error) {
c, err := d.DockerAPI(name)
if err != nil {
return nil, nil, err
}
pr, pw := io.Pipe()
started := make(chan struct{})
w := &waitingWriter{
done := make(chan struct{})
ctx, cancel := context.WithCancel(ctx)
var w *waitingWriter
w = &waitingWriter{
PipeWriter: pw,
f: func() {
resp, err := c.ImageLoad(ctx, pr, false)
defer close(done)
if err != nil {
pr.CloseWithError(err)
w.mu.Lock()
w.err = err
w.mu.Unlock()
return
}
prog := mw.WithPrefix("", false)
close(started)
prog := progress.WithPrefix(status, "", false)
progress.FromReader(prog, "importing to docker", resp.Body)
},
started: started,
done: done,
cancel: cancel,
}
return w, func() {
pr.Close()
}, nil
}
func noDefaultLoad() bool {
v, ok := os.LookupEnv("BUILDX_NO_DEFAULT_LOAD")
if !ok {
return false
}
b, err := strconv.ParseBool(v)
if err != nil {
logrus.Warnf("invalid non-bool value for BUILDX_NO_DEFAULT_LOAD: %s", v)
}
return b
}
type waitingWriter struct {
*io.PipeWriter
f func()
once sync.Once
mu sync.Mutex
err error
started chan struct{}
done chan struct{}
cancel func()
}
func (w *waitingWriter) Write(dt []byte) (int, error) {
@@ -850,6 +1027,42 @@ func (w *waitingWriter) Write(dt []byte) (int, error) {
func (w *waitingWriter) Close() error {
err := w.PipeWriter.Close()
<-w.started
<-w.done
if err == nil {
w.mu.Lock()
defer w.mu.Unlock()
return w.err
}
return err
}
// handle https://github.com/moby/moby/pull/10858
func handleLowercaseDockerfile(dir, p string) string {
if filepath.Base(p) != "Dockerfile" {
return p
}
f, err := os.Open(filepath.Dir(filepath.Join(dir, p)))
if err != nil {
return p
}
names, err := f.Readdirnames(-1)
if err != nil {
return p
}
foundLowerCase := false
for _, n := range names {
if n == "Dockerfile" {
return p
}
if n == "dockerfile" {
foundLowerCase = true
}
}
if foundLowerCase {
return filepath.Join(filepath.Dir(p), "dockerfile")
}
return p
}

21
build/entitlements.go Normal file
View File

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

View File

@@ -2,6 +2,7 @@ package build
import (
"encoding/csv"
"io"
"os"
"strings"
@@ -81,7 +82,7 @@ func ParseOutputs(inp []string) ([]client.ExportEntry, error) {
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
return nil, errors.Errorf("output file is required for %s exporter. refusing to write to console", out.Type)
}
out.Output = os.Stdout
out.Output = wrapWriteCloser(os.Stdout)
} else if dest != "" {
fi, err := os.Stat(dest)
if err != nil && !os.IsNotExist(err) {
@@ -94,7 +95,7 @@ func ParseOutputs(inp []string) ([]client.ExportEntry, error) {
if err != nil {
return nil, errors.Errorf("failed to open %s", err)
}
out.Output = f
out.Output = wrapWriteCloser(f)
}
delete(out.Attrs, "dest")
case "registry":
@@ -106,3 +107,9 @@ func ParseOutputs(inp []string) ([]client.ExportEntry, error) {
}
return outs, nil
}
func wrapWriteCloser(wc io.WriteCloser) func(map[string]string) (io.WriteCloser, error) {
return func(map[string]string) (io.WriteCloser, error) {
return wc, nil
}
}

View File

@@ -10,7 +10,7 @@ import (
)
func ParseSecretSpecs(sl []string) (session.Attachable, error) {
fs := make([]secretsprovider.FileSource, 0, len(sl))
fs := make([]secretsprovider.Source, 0, len(sl))
for _, v := range sl {
s, err := parseSecret(v)
if err != nil {
@@ -18,21 +18,21 @@ func ParseSecretSpecs(sl []string) (session.Attachable, error) {
}
fs = append(fs, *s)
}
store, err := secretsprovider.NewFileStore(fs)
store, err := secretsprovider.NewStore(fs)
if err != nil {
return nil, err
}
return secretsprovider.NewSecretProvider(store), nil
}
func parseSecret(value string) (*secretsprovider.FileSource, error) {
func parseSecret(value string) (*secretsprovider.Source, error) {
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return nil, errors.Wrap(err, "failed to parse csv secret")
}
fs := secretsprovider.FileSource{}
fs := secretsprovider.Source{}
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)

71
build/url.go Normal file
View File

@@ -0,0 +1,71 @@
package build
import (
"context"
"io/ioutil"
"path/filepath"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/util/progress"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
"github.com/pkg/errors"
)
func createTempDockerfileFromURL(ctx context.Context, d driver.Driver, url string, pw progress.Writer) (string, error) {
c, err := driver.Boot(ctx, d, pw)
if err != nil {
return "", err
}
var out string
ch, done := progress.NewChannel(pw)
defer func() { <-done }()
_, err = c.Build(ctx, client.SolveOpt{}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
def, err := llb.HTTP(url, llb.Filename("Dockerfile"), llb.WithCustomNamef("[internal] load %s", url)).Marshal(ctx)
if err != nil {
return nil, err
}
res, err := c.Solve(ctx, gwclient.SolveRequest{
Definition: def.ToPB(),
})
if err != nil {
return nil, err
}
ref, err := res.SingleRef()
if err != nil {
return nil, err
}
stat, err := ref.StatFile(ctx, gwclient.StatRequest{
Path: "Dockerfile",
})
if err != nil {
return nil, err
}
if stat.Size() > 512*1024 {
return nil, errors.Errorf("Dockerfile %s bigger than allowed max size", url)
}
dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{
Filename: "Dockerfile",
})
if err != nil {
return nil, err
}
dir, err := ioutil.TempDir("", "buildx")
if err != nil {
return nil, err
}
if err := ioutil.WriteFile(filepath.Join(dir, "Dockerfile"), dt, 0600); err != nil {
return nil, err
}
out = dir
return nil, nil
}, ch)
if err != nil {
return "", err
}
return out, nil
}

View File

@@ -4,20 +4,35 @@ import (
"fmt"
"os"
"github.com/containerd/containerd/pkg/seed"
"github.com/docker/buildx/commands"
"github.com/docker/buildx/version"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/debug"
cliflags "github.com/docker/cli/cli/flags"
"github.com/spf13/cobra"
"github.com/moby/buildkit/solver/errdefs"
"github.com/moby/buildkit/util/stack"
// FIXME: "k8s.io/client-go/plugin/pkg/client/auth/azure" is excluded because of compilation error
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
_ "github.com/docker/buildx/driver/docker"
_ "github.com/docker/buildx/driver/docker-container"
_ "github.com/docker/buildx/driver/kubernetes"
)
var experimental string
func init() {
seed.WithTimeAndRand()
stack.SetVersionInfo(version.Version, version.Revision)
}
func main() {
if os.Getenv("DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND") == "" {
if len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName {
@@ -36,13 +51,42 @@ func main() {
}
}
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
return commands.NewRootCmd("buildx", true, dockerCli)
},
manager.Metadata{
dockerCli, err := command.NewDockerCli()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
p := commands.NewRootCmd("buildx", true, dockerCli)
meta := manager.Metadata{
SchemaVersion: "0.1.0",
Vendor: "Docker Inc.",
Version: version.Version,
Experimental: experimental != "",
})
}
if err := plugin.RunPlugin(dockerCli, p, meta); err != nil {
if sterr, ok := err.(cli.StatusError); ok {
if sterr.Status != "" {
fmt.Fprintln(dockerCli.Err(), sterr.Status)
}
// StatusError should only be used for errors, and all errors should
// have a non-zero exit status, so never exit with 0
if sterr.StatusCode == 0 {
os.Exit(1)
}
os.Exit(sterr.StatusCode)
}
for _, s := range errdefs.Sources(err) {
s.Print(dockerCli.Err())
}
if debug.IsEnabled() {
fmt.Fprintf(dockerCli.Err(), "error: %+v", stack.Formatter(err))
} else {
fmt.Fprintf(dockerCli.Err(), "error: %v\n", err)
}
os.Exit(1)
}
}

1
codecov.yml Normal file
View File

@@ -0,0 +1 @@
comment: false

View File

@@ -1,11 +1,14 @@
package commands
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/docker/buildx/bake"
"github.com/docker/buildx/build"
"github.com/docker/buildx/util/progress"
"github.com/docker/cli/cli/command"
"github.com/moby/buildkit/util/appcontext"
"github.com/pkg/errors"
@@ -19,31 +22,80 @@ type bakeOptions struct {
commonOptions
}
func runBake(dockerCli command.Cli, targets []string, in bakeOptions) error {
func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error) {
ctx := appcontext.Context()
if len(in.files) == 0 {
files, err := defaultFiles()
if err != nil {
return err
var url string
if len(targets) > 0 {
if bake.IsRemoteURL(targets[0]) {
url = targets[0]
targets = targets[1:]
}
if len(files) == 0 {
return errors.Errorf("no docker-compose.yml or docker-bake.hcl found, specify build file with -f/--file")
}
in.files = files
}
if len(targets) == 0 {
targets = []string{"default"}
}
m, err := bake.ReadTargets(ctx, in.files, targets, in.overrides)
overrides := in.overrides
if in.exportPush {
if in.exportLoad {
return errors.Errorf("push and load may not be set together at the moment")
}
overrides = append(overrides, "*.output=type=registry")
} else if in.exportLoad {
overrides = append(overrides, "*.output=type=docker")
}
if in.noCache != nil {
overrides = append(overrides, fmt.Sprintf("*.no-cache=%t", *in.noCache))
}
if in.pull != nil {
overrides = append(overrides, fmt.Sprintf("*.pull=%t", *in.pull))
}
contextPathHash, _ := os.Getwd()
ctx2, cancel := context.WithCancel(context.TODO())
defer cancel()
printer := progress.NewPrinter(ctx2, os.Stderr, in.progress)
defer func() {
if printer != nil {
err1 := printer.Wait()
if err == nil {
err = err1
}
}
}()
dis, err := getInstanceOrDefault(ctx, dockerCli, in.builder, contextPathHash)
if err != nil {
return err
}
var files []bake.File
var inp *bake.Input
if url != "" {
files, inp, err = bake.ReadRemoteFiles(ctx, dis, url, in.files, printer)
} else {
files, err = bake.ReadLocalFiles(in.files)
}
if err != nil {
return err
}
m, err := bake.ReadTargets(ctx, files, targets, overrides)
if err != nil {
return err
}
if in.printOnly {
dt, err := json.MarshalIndent(map[string]map[string]bake.Target{"target": m}, "", " ")
dt, err := json.MarshalIndent(map[string]map[string]*bake.Target{"target": m}, "", " ")
if err != nil {
return err
}
err = printer.Wait()
printer = nil
if err != nil {
return err
}
@@ -51,37 +103,16 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) error {
return nil
}
bo, err := bake.TargetsToBuildOpt(m)
bo, err := bake.TargetsToBuildOpt(m, inp)
if err != nil {
return err
}
return buildTargets(ctx, dockerCli, bo, in.progress)
_, err = build.Build(ctx, dis, bo, dockerAPI(dockerCli), dockerCli.ConfigFile(), printer)
return err
}
func defaultFiles() ([]string, error) {
fns := []string{
"docker-compose.yml", // support app
"docker-compose.yaml", // support app
"docker-bake.json",
"docker-bake.override.json",
"docker-bake.hcl",
"docker-bake.override.hcl",
}
out := make([]string, 0, len(fns))
for _, f := range fns {
if _, err := os.Stat(f); err != nil {
if os.IsNotExist(errors.Cause(err)) {
continue
}
return nil, err
}
out = append(out, f)
}
return out, nil
}
func bakeCmd(dockerCli command.Cli) *cobra.Command {
func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
var options bakeOptions
cmd := &cobra.Command{
@@ -89,6 +120,14 @@ func bakeCmd(dockerCli command.Cli) *cobra.Command {
Aliases: []string{"f"},
Short: "Build from a file",
RunE: func(cmd *cobra.Command, args []string) error {
// reset to nil to avoid override is unset
if !cmd.Flags().Lookup("no-cache").Changed {
options.noCache = nil
}
if !cmd.Flags().Lookup("pull").Changed {
options.pull = nil
}
options.commonOptions.builder = rootOpts.builder
return runBake(dockerCli, args, options)
},
}
@@ -97,9 +136,11 @@ func bakeCmd(dockerCli command.Cli) *cobra.Command {
flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Build definition file")
flags.BoolVar(&options.printOnly, "print", false, "Print the options without building")
flags.StringArrayVar(&options.overrides, "set", nil, "Override target value (eg: target.key=value)")
flags.StringArrayVar(&options.overrides, "set", nil, "Override target value (eg: targetpattern.key=value)")
flags.BoolVar(&options.exportPush, "push", false, "Shorthand for --set=*.output=type=registry")
flags.BoolVar(&options.exportLoad, "load", false, "Shorthand for --set=*.output=type=docker")
commonFlags(&options.commonOptions, flags)
commonBuildFlags(&options.commonOptions, flags)
return cmd
}

View File

@@ -3,6 +3,7 @@ package commands
import (
"context"
"os"
"path/filepath"
"strings"
"github.com/docker/buildx/build"
@@ -14,6 +15,7 @@ import (
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/util/appcontext"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -37,13 +39,12 @@ type buildOptions struct {
extraHosts []string
networkMode string
exportPush bool
exportLoad bool
// unimplemented
squash bool
quiet bool
allow []string
// hidden
// untrusted bool
// ulimits *opts.UlimitOpt
@@ -62,9 +63,12 @@ type buildOptions struct {
}
type commonOptions struct {
noCache bool
builder string
noCache *bool
progress string
pull bool
pull *bool
exportPush bool
exportLoad bool
}
func runBuild(dockerCli command.Cli, in buildOptions) error {
@@ -72,11 +76,20 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
return errors.Errorf("squash currently not implemented")
}
if in.quiet {
return errors.Errorf("quiet currently not implemented")
logrus.Warnf("quiet currently not implemented")
}
ctx := appcontext.Context()
noCache := false
if in.noCache != nil {
noCache = *in.noCache
}
pull := false
if in.pull != nil {
pull = *in.pull
}
opts := build.Options{
Inputs: build.Inputs{
ContextPath: in.contextPath,
@@ -84,10 +97,10 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
InStream: os.Stdin,
},
Tags: in.tags,
Labels: listToMap(in.labels),
BuildArgs: listToMap(in.buildArgs),
Pull: in.pull,
NoCache: in.noCache,
Labels: listToMap(in.labels, false),
BuildArgs: listToMap(in.buildArgs, true),
Pull: pull,
NoCache: noCache,
Target: in.target,
ImageIDFile: in.imageIDFile,
ExtraHosts: in.extraHosts,
@@ -167,24 +180,41 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
}
opts.CacheTo = cacheExports
return buildTargets(ctx, dockerCli, map[string]build.Options{"default": opts}, in.progress)
allow, err := build.ParseEntitlements(in.allow)
if err != nil {
return err
}
opts.Allow = allow
// key string used for kubernetes "sticky" mode
contextPathHash, err := filepath.Abs(in.contextPath)
if err != nil {
contextPathHash = in.contextPath
}
return buildTargets(ctx, dockerCli, map[string]build.Options{"default": opts}, in.progress, contextPathHash, in.builder)
}
func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode string) error {
dis, err := getDefaultDrivers(ctx, dockerCli)
func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode, contextPathHash, instance string) error {
dis, err := getInstanceOrDefault(ctx, dockerCli, instance, contextPathHash)
if err != nil {
return err
}
ctx2, cancel := context.WithCancel(context.TODO())
defer cancel()
pw := progress.NewPrinter(ctx2, os.Stderr, progressMode)
printer := progress.NewPrinter(ctx2, os.Stderr, progressMode)
_, err = build.Build(ctx, dis, opts, dockerAPI(dockerCli), dockerCli.ConfigFile(), printer)
err1 := printer.Wait()
if err == nil {
err = err1
}
_, err = build.Build(ctx, dis, opts, dockerAPI(dockerCli), dockerCli.ConfigFile(), pw)
return err
}
func buildCmd(dockerCli command.Cli) *cobra.Command {
func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
var options buildOptions
cmd := &cobra.Command{
@@ -194,6 +224,7 @@ func buildCmd(dockerCli command.Cli) *cobra.Command {
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
options.contextPath = args[0]
options.builder = rootOpts.builder
return runBuild(dockerCli, options)
},
}
@@ -214,6 +245,8 @@ func buildCmd(dockerCli command.Cli) *cobra.Command {
flags.StringVar(&options.target, "target", "", "Set the target build stage to build.")
flags.StringSliceVar(&options.allow, "allow", []string{}, "Allow extra privileged entitlement, e.g. network.host, security.insecure")
// not implemented
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success")
flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
@@ -271,23 +304,30 @@ func buildCmd(dockerCli command.Cli) *cobra.Command {
flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, "Output destination (format: type=local,dest=path)")
commonFlags(&options.commonOptions, flags)
commonBuildFlags(&options.commonOptions, flags)
return cmd
}
func commonFlags(options *commonOptions, flags *pflag.FlagSet) {
flags.BoolVar(&options.noCache, "no-cache", false, "Do not use cache when building the image")
func commonBuildFlags(options *commonOptions, flags *pflag.FlagSet) {
options.noCache = flags.Bool("no-cache", false, "Do not use cache when building the image")
flags.StringVar(&options.progress, "progress", "auto", "Set type of progress output (auto, plain, tty). Use plain to show container output")
flags.BoolVar(&options.pull, "pull", false, "Always attempt to pull a newer version of the image")
options.pull = flags.Bool("pull", false, "Always attempt to pull a newer version of the image")
}
func listToMap(values []string) map[string]string {
func listToMap(values []string, defaultEnv bool) map[string]string {
result := make(map[string]string, len(values))
for _, value := range values {
kv := strings.SplitN(value, "=", 2)
if len(kv) == 1 {
if defaultEnv {
v, ok := os.LookupEnv(kv[0])
if ok {
result[kv[0]] = v
}
} else {
result[kv[0]] = ""
}
} else {
result[kv[0]] = kv[1]
}

View File

@@ -1,13 +1,16 @@
package commands
import (
"encoding/csv"
"fmt"
"os"
"strings"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/store"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/google/shlex"
"github.com/moby/buildkit/util/appcontext"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -22,6 +25,9 @@ type createOptions struct {
actionAppend bool
actionLeave bool
use bool
flags string
configFile string
driverOpts []string
// upgrade bool // perform upgrade of the driver
}
@@ -107,6 +113,14 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
ng.Driver = driverName
}
var flags []string
if in.flags != "" {
flags, err = shlex.Split(in.flags)
if err != nil {
return errors.Wrap(err, "failed to parse buildkit flags")
}
}
var ep string
if in.actionLeave {
if err := ng.Leave(in.nodeName); err != nil {
@@ -128,7 +142,17 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
return err
}
}
if err := ng.Update(in.nodeName, ep, in.platform, len(args) > 0, in.actionAppend); err != nil {
if in.driver == "kubernetes" {
// naming endpoint to make --append works
ep = fmt.Sprintf("%s://%s?deployment=%s", in.driver, in.name, in.nodeName)
}
m, err := csvToMap(in.driverOpts)
if err != nil {
return err
}
if err := ng.Update(in.nodeName, ep, in.platform, len(args) > 0, in.actionAppend, flags, in.configFile, m); err != nil {
return err
}
}
@@ -154,6 +178,11 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
func createCmd(dockerCli command.Cli) *cobra.Command {
var options createOptions
var drivers []string
for s := range driver.GetFactories() {
drivers = append(drivers, s)
}
cmd := &cobra.Command{
Use: "create [OPTIONS] [CONTEXT|ENDPOINT]",
Short: "Create a new builder instance",
@@ -166,9 +195,12 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags()
flags.StringVar(&options.name, "name", "", "Builder instance name")
flags.StringVar(&options.driver, "driver", "", "Driver to use (eg. docker-container)")
flags.StringVar(&options.driver, "driver", "", fmt.Sprintf("Driver to use (available: %v)", drivers))
flags.StringVar(&options.nodeName, "node", "", "Create/modify node with given name")
flags.StringVar(&options.flags, "buildkitd-flags", "", "Flags for buildkitd daemon")
flags.StringVar(&options.configFile, "config", "", "BuildKit config file")
flags.StringArrayVar(&options.platform, "platform", []string{}, "Fixed platforms for current node")
flags.StringArrayVar(&options.driverOpts, "driver-opt", []string{}, "Options for the driver")
flags.BoolVar(&options.actionAppend, "append", false, "Append a node to builder instead of changing it")
flags.BoolVar(&options.actionLeave, "leave", false, "Remove a node from builder instead of changing it")
@@ -178,3 +210,22 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
return cmd
}
func csvToMap(in []string) (map[string]string, error) {
m := make(map[string]string, len(in))
for _, s := range in {
csvReader := csv.NewReader(strings.NewReader(s))
fields, err := csvReader.Read()
if err != nil {
return nil, err
}
for _, v := range fields {
p := strings.SplitN(v, "=", 2)
if len(p) != 2 {
return nil, errors.Errorf("invalid value %q, expecting k=v", v)
}
m[p[0]] = p[1]
}
}
return m, nil
}

198
commands/diskusage.go Normal file
View File

@@ -0,0 +1,198 @@
package commands
import (
"fmt"
"io"
"os"
"text/tabwriter"
"github.com/docker/buildx/build"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/appcontext"
"github.com/spf13/cobra"
"github.com/tonistiigi/units"
"golang.org/x/sync/errgroup"
)
type duOptions struct {
builder string
filter opts.FilterOpt
verbose bool
}
func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
ctx := appcontext.Context()
pi, err := toBuildkitPruneInfo(opts.filter.Value())
if err != nil {
return err
}
dis, err := getInstanceOrDefault(ctx, dockerCli, opts.builder, "")
if err != nil {
return err
}
for _, di := range dis {
if di.Err != nil {
return err
}
}
out := make([][]*client.UsageInfo, len(dis))
eg, ctx := errgroup.WithContext(ctx)
for i, di := range dis {
func(i int, di build.DriverInfo) {
eg.Go(func() error {
if di.Driver != nil {
c, err := di.Driver.Client(ctx)
if err != nil {
return err
}
du, err := c.DiskUsage(ctx, client.WithFilter(pi.Filter))
if err != nil {
return err
}
out[i] = du
return nil
}
return nil
})
}(i, di)
}
if err := eg.Wait(); err != nil {
return err
}
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
first := true
for _, du := range out {
if du == nil {
continue
}
if opts.verbose {
printVerbose(tw, du)
} else {
if first {
printTableHeader(tw)
first = false
}
for _, di := range du {
printTableRow(tw, di)
}
tw.Flush()
}
}
if opts.filter.Value().Len() == 0 {
printSummary(tw, out)
}
tw.Flush()
return nil
}
func duCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
options := duOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "du",
Short: "Disk usage",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
options.builder = rootOpts.builder
return runDiskUsage(dockerCli, options)
},
}
flags := cmd.Flags()
flags.Var(&options.filter, "filter", "Provide filter values")
flags.BoolVar(&options.verbose, "verbose", false, "Provide a more verbose output")
return cmd
}
func printKV(w io.Writer, k string, v interface{}) {
fmt.Fprintf(w, "%s:\t%v\n", k, v)
}
func printVerbose(tw *tabwriter.Writer, du []*client.UsageInfo) {
for _, di := range du {
printKV(tw, "ID", di.ID)
if di.Parent != "" {
printKV(tw, "Parent", di.Parent)
}
printKV(tw, "Created at", di.CreatedAt)
printKV(tw, "Mutable", di.Mutable)
printKV(tw, "Reclaimable", !di.InUse)
printKV(tw, "Shared", di.Shared)
printKV(tw, "Size", fmt.Sprintf("%.2f", units.Bytes(di.Size)))
if di.Description != "" {
printKV(tw, "Description", di.Description)
}
printKV(tw, "Usage count", di.UsageCount)
if di.LastUsedAt != nil {
printKV(tw, "Last used", di.LastUsedAt)
}
if di.RecordType != "" {
printKV(tw, "Type", di.RecordType)
}
fmt.Fprintf(tw, "\n")
}
tw.Flush()
}
func printTableHeader(tw *tabwriter.Writer) {
fmt.Fprintln(tw, "ID\tRECLAIMABLE\tSIZE\tLAST ACCESSED")
}
func printTableRow(tw *tabwriter.Writer, di *client.UsageInfo) {
id := di.ID
if di.Mutable {
id += "*"
}
size := fmt.Sprintf("%.2f", units.Bytes(di.Size))
if di.Shared {
size += "*"
}
fmt.Fprintf(tw, "%-71s\t%-11v\t%s\t\n", id, !di.InUse, size)
}
func printSummary(tw *tabwriter.Writer, dus [][]*client.UsageInfo) {
total := int64(0)
reclaimable := int64(0)
shared := int64(0)
for _, du := range dus {
for _, di := range du {
if di.Size > 0 {
total += di.Size
if !di.InUse {
reclaimable += di.Size
}
}
if di.Shared {
shared += di.Size
}
}
}
tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
if shared > 0 {
fmt.Fprintf(tw, "Shared:\t%.2f\n", units.Bytes(shared))
fmt.Fprintf(tw, "Private:\t%.2f\n", units.Bytes(total-shared))
}
fmt.Fprintf(tw, "Reclaimable:\t%.2f\n", units.Bytes(reclaimable))
fmt.Fprintf(tw, "Total:\t%.2f\n", units.Bytes(total))
tw.Flush()
}

View File

@@ -30,7 +30,7 @@ func runInspect(dockerCli command.Cli, in inspectOptions, name string) error {
}
if in.raw {
fmt.Printf("%s\n", dt)
fmt.Printf("%s", dt) // avoid newline to keep digest
return nil
}

View File

@@ -23,6 +23,7 @@ import (
type inspectOptions struct {
bootstrap bool
builder string
}
type dinfo struct {
@@ -38,7 +39,7 @@ type nginfo struct {
err error
}
func runInspect(dockerCli command.Cli, in inspectOptions, args []string) error {
func runInspect(dockerCli command.Cli, in inspectOptions) error {
ctx := appcontext.Context()
txn, release, err := getStore(dockerCli)
@@ -49,8 +50,8 @@ func runInspect(dockerCli command.Cli, in inspectOptions, args []string) error {
var ng *store.NodeGroup
if len(args) > 0 {
ng, err = getNodeGroup(txn, dockerCli, args[0])
if in.builder != "" {
ng, err = getNodeGroup(txn, dockerCli, in.builder)
if err != nil {
return err
}
@@ -73,17 +74,19 @@ func runInspect(dockerCli command.Cli, in inspectOptions, args []string) error {
ngi := &nginfo{ng: ng}
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()
err = loadNodeGroupData(timeoutCtx, dockerCli, ngi)
var bootNgi *nginfo
if in.bootstrap {
var ok bool
ok, err = boot(ctx, ngi)
ok, err = boot(ctx, ngi, dockerCli)
if err != nil {
return err
}
bootNgi = ngi
if ok {
ngi = &nginfo{ng: ng}
err = loadNodeGroupData(ctx, dockerCli, ngi)
@@ -112,9 +115,14 @@ func runInspect(dockerCli command.Cli, in inspectOptions, args []string) error {
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
} else if err := ngi.drivers[i].err; err != nil {
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
} else if bootNgi != nil && len(bootNgi.drivers) > i && bootNgi.drivers[i].err != nil {
fmt.Fprintf(w, "Error:\t%s\n", bootNgi.drivers[i].err.Error())
} else {
fmt.Fprintf(w, "Status:\t%s\n", ngi.drivers[i].info.Status)
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platformutil.Format(platformutil.Dedupe(append(n.Platforms, ngi.drivers[i].platforms...))), ", "))
if len(n.Flags) > 0 {
fmt.Fprintf(w, "Flags:\t%s\n", strings.Join(n.Flags, " "))
}
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platformutil.FormatInGroups(n.Platforms, ngi.drivers[i].platforms), ", "))
}
}
}
@@ -124,7 +132,7 @@ func runInspect(dockerCli command.Cli, in inspectOptions, args []string) error {
return nil
}
func inspectCmd(dockerCli command.Cli) *cobra.Command {
func inspectCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
var options inspectOptions
cmd := &cobra.Command{
@@ -132,7 +140,11 @@ func inspectCmd(dockerCli command.Cli) *cobra.Command {
Short: "Inspect current builder instance",
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runInspect(dockerCli, options, args)
options.builder = rootOpts.builder
if len(args) > 0 {
options.builder = args[0]
}
return runInspect(dockerCli, options)
},
}
@@ -145,7 +157,7 @@ func inspectCmd(dockerCli command.Cli) *cobra.Command {
return cmd
}
func boot(ctx context.Context, ngi *nginfo) (bool, error) {
func boot(ctx context.Context, ngi *nginfo, dockerCli command.Cli) (bool, error) {
toBoot := make([]int, 0, len(ngi.drivers))
for i, d := range ngi.drivers {
if d.err != nil || d.di.Err != nil || d.di.Driver == nil || d.info == nil {
@@ -159,25 +171,27 @@ func boot(ctx context.Context, ngi *nginfo) (bool, error) {
return false, nil
}
pw := progress.NewPrinter(context.TODO(), os.Stderr, "auto")
mw := progress.NewMultiWriter(pw)
printer := progress.NewPrinter(context.TODO(), os.Stderr, "auto")
eg, _ := errgroup.WithContext(ctx)
for _, idx := range toBoot {
func(idx int) {
eg.Go(func() error {
pw := mw.WithPrefix(ngi.ng.Nodes[idx].Name, len(toBoot) > 1)
pw := progress.WithPrefix(printer, ngi.ng.Nodes[idx].Name, len(toBoot) > 1)
_, err := driver.Boot(ctx, ngi.drivers[idx].di.Driver, pw)
if err != nil {
ngi.drivers[idx].err = err
}
close(pw.Status())
<-pw.Done()
return nil
})
}(idx)
}
return true, eg.Wait()
err := eg.Wait()
err1 := printer.Wait()
if err == nil {
err = err1
}
return true, err
}

View File

@@ -30,7 +30,7 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
}
defer release()
ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()
ll, err := txn.List()
@@ -126,11 +126,10 @@ func printngi(w io.Writer, ngi *nginfo) {
if d.info != nil {
status = d.info.Status.String()
}
p := append(n.Platforms, d.platforms...)
if err != "" {
fmt.Fprintf(w, " %s\t%s\t%s\n", n.Name, n.Endpoint, err)
} else {
fmt.Fprintf(w, " %s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, strings.Join(platformutil.Format(p), ", "))
fmt.Fprintf(w, " %s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, strings.Join(platformutil.FormatInGroups(n.Platforms, d.platforms), ", "))
}
}
}

197
commands/prune.go Normal file
View File

@@ -0,0 +1,197 @@
package commands
import (
"fmt"
"os"
"strings"
"text/tabwriter"
"time"
"github.com/docker/buildx/build"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/filters"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/appcontext"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/tonistiigi/units"
"golang.org/x/sync/errgroup"
)
type pruneOptions struct {
builder string
all bool
filter opts.FilterOpt
keepStorage opts.MemBytes
force bool
verbose bool
}
const (
normalWarning = `WARNING! This will remove all dangling build cache. Are you sure you want to continue?`
allCacheWarning = `WARNING! This will remove all build cache. Are you sure you want to continue?`
)
func runPrune(dockerCli command.Cli, opts pruneOptions) error {
ctx := appcontext.Context()
pruneFilters := opts.filter.Value()
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
pi, err := toBuildkitPruneInfo(pruneFilters)
if err != nil {
return err
}
warning := normalWarning
if opts.all {
warning = allCacheWarning
}
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
return nil
}
dis, err := getInstanceOrDefault(ctx, dockerCli, opts.builder, "")
if err != nil {
return err
}
for _, di := range dis {
if di.Err != nil {
return err
}
}
ch := make(chan client.UsageInfo)
printed := make(chan struct{})
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
first := true
total := int64(0)
go func() {
defer close(printed)
for du := range ch {
total += du.Size
if opts.verbose {
printVerbose(tw, []*client.UsageInfo{&du})
} else {
if first {
printTableHeader(tw)
first = false
}
printTableRow(tw, &du)
tw.Flush()
}
}
}()
eg, ctx := errgroup.WithContext(ctx)
for _, di := range dis {
func(di build.DriverInfo) {
eg.Go(func() error {
if di.Driver != nil {
c, err := di.Driver.Client(ctx)
if err != nil {
return err
}
popts := []client.PruneOption{
client.WithKeepOpt(pi.KeepDuration, opts.keepStorage.Value()),
client.WithFilter(pi.Filter),
}
if opts.all {
popts = append(popts, client.PruneAll)
}
return c.Prune(ctx, ch, popts...)
}
return nil
})
}(di)
}
if err := eg.Wait(); err != nil {
return err
}
close(ch)
<-printed
tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
fmt.Fprintf(tw, "Total:\t%.2f\n", units.Bytes(total))
tw.Flush()
return nil
}
func pruneCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
options := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "prune",
Short: "Remove build cache ",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
options.builder = rootOpts.builder
return runPrune(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images, not just dangling ones")
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=24h')")
flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")
flags.BoolVar(&options.verbose, "verbose", false, "Provide a more verbose output")
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
return cmd
}
func toBuildkitPruneInfo(f filters.Args) (*client.PruneInfo, error) {
var until time.Duration
untilValues := f.Get("until") // canonical
unusedForValues := f.Get("unused-for") // deprecated synonym for "until" filter
if len(untilValues) > 0 && len(unusedForValues) > 0 {
return nil, errors.Errorf("conflicting filters %q and %q", "until", "unused-for")
}
filterKey := "until"
if len(unusedForValues) > 0 {
filterKey = "unused-for"
}
untilValues = append(untilValues, unusedForValues...)
switch len(untilValues) {
case 0:
// nothing to do
case 1:
var err error
until, err = time.ParseDuration(untilValues[0])
if err != nil {
return nil, errors.Wrapf(err, "%q filter expects a duration (e.g., '24h')", filterKey)
}
default:
return nil, errors.Errorf("filters expect only one value")
}
bkFilter := make([]string, 0, f.Len())
for _, field := range f.Keys() {
values := f.Get(field)
switch len(values) {
case 0:
bkFilter = append(bkFilter, field)
case 1:
if field == "id" {
bkFilter = append(bkFilter, field+"~="+values[0])
} else {
bkFilter = append(bkFilter, field+"=="+values[0])
}
default:
return nil, errors.Errorf("filters expect only one value")
}
}
return &client.PruneInfo{
KeepDuration: until,
Filter: []string{strings.Join(bkFilter, ",")},
}, nil
}

View File

@@ -11,9 +11,10 @@ import (
)
type rmOptions struct {
builder string
}
func runRm(dockerCli command.Cli, in rmOptions, args []string) error {
func runRm(dockerCli command.Cli, in rmOptions) error {
ctx := appcontext.Context()
txn, release, err := getStore(dockerCli)
@@ -22,8 +23,8 @@ func runRm(dockerCli command.Cli, in rmOptions, args []string) error {
}
defer release()
if len(args) > 0 {
ng, err := getNodeGroup(txn, dockerCli, args[0])
if in.builder != "" {
ng, err := getNodeGroup(txn, dockerCli, in.builder)
if err != nil {
return err
}
@@ -49,7 +50,7 @@ func runRm(dockerCli command.Cli, in rmOptions, args []string) error {
return nil
}
func rmCmd(dockerCli command.Cli) *cobra.Command {
func rmCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
var options rmOptions
cmd := &cobra.Command{
@@ -57,7 +58,11 @@ func rmCmd(dockerCli command.Cli) *cobra.Command {
Short: "Remove a builder instance",
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runRm(dockerCli, options, args)
options.builder = rootOpts.builder
if len(args) > 0 {
options.builder = args[0]
}
return runRm(dockerCli, options)
},
}
@@ -65,7 +70,7 @@ func rmCmd(dockerCli command.Cli) *cobra.Command {
}
func stop(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, rm bool) error {
dis, err := driversForNodeGroup(ctx, dockerCli, ng)
dis, err := driversForNodeGroup(ctx, dockerCli, ng, "")
if err != nil {
return err
}
@@ -88,7 +93,7 @@ func stop(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, rm bo
}
func stopCurrent(ctx context.Context, dockerCli command.Cli, rm bool) error {
dis, err := getDefaultDrivers(ctx, dockerCli)
dis, err := getDefaultDrivers(ctx, dockerCli, false, "")
if err != nil {
return err
}

View File

@@ -1,10 +1,13 @@
package commands
import (
"os"
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
"github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Command {
@@ -22,19 +25,32 @@ func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Comman
return cmd
}
type rootOptions struct {
builder string
}
func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
opts := &rootOptions{}
rootFlags(opts, cmd.PersistentFlags())
cmd.AddCommand(
buildCmd(dockerCli),
bakeCmd(dockerCli),
buildCmd(dockerCli, opts),
bakeCmd(dockerCli, opts),
createCmd(dockerCli),
rmCmd(dockerCli),
rmCmd(dockerCli, opts),
lsCmd(dockerCli),
useCmd(dockerCli),
inspectCmd(dockerCli),
stopCmd(dockerCli),
useCmd(dockerCli, opts),
inspectCmd(dockerCli, opts),
stopCmd(dockerCli, opts),
installCmd(dockerCli),
uninstallCmd(dockerCli),
versionCmd(dockerCli),
pruneCmd(dockerCli, opts),
duCmd(dockerCli, opts),
imagetoolscmd.RootCmd(dockerCli),
)
}
func rootFlags(options *rootOptions, flags *pflag.FlagSet) {
flags.StringVar(&options.builder, "builder", os.Getenv("BUILDX_BUILDER"), "Override the configured builder instance")
}

View File

@@ -8,9 +8,10 @@ import (
)
type stopOptions struct {
builder string
}
func runStop(dockerCli command.Cli, in stopOptions, args []string) error {
func runStop(dockerCli command.Cli, in stopOptions) error {
ctx := appcontext.Context()
txn, release, err := getStore(dockerCli)
@@ -19,8 +20,8 @@ func runStop(dockerCli command.Cli, in stopOptions, args []string) error {
}
defer release()
if len(args) > 0 {
ng, err := getNodeGroup(txn, dockerCli, args[0])
if in.builder != "" {
ng, err := getNodeGroup(txn, dockerCli, in.builder)
if err != nil {
return err
}
@@ -41,7 +42,7 @@ func runStop(dockerCli command.Cli, in stopOptions, args []string) error {
return stopCurrent(ctx, dockerCli, false)
}
func stopCmd(dockerCli command.Cli) *cobra.Command {
func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
var options stopOptions
cmd := &cobra.Command{
@@ -49,7 +50,11 @@ func stopCmd(dockerCli command.Cli) *cobra.Command {
Short: "Stop builder instance",
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runStop(dockerCli, options, args)
options.builder = rootOpts.builder
if len(args) > 0 {
options.builder = args[0]
}
return runStop(dockerCli, options)
},
}

View File

@@ -12,21 +12,22 @@ import (
type useOptions struct {
isGlobal bool
isDefault bool
builder string
}
func runUse(dockerCli command.Cli, in useOptions, name string) error {
func runUse(dockerCli command.Cli, in useOptions) error {
txn, release, err := getStore(dockerCli)
if err != nil {
return err
}
defer release()
if _, err := txn.NodeGroupByName(name); err != nil {
if _, err := txn.NodeGroupByName(in.builder); err != nil {
if os.IsNotExist(errors.Cause(err)) {
if name == "default" && name != dockerCli.CurrentContext() {
if in.builder == "default" && in.builder != dockerCli.CurrentContext() {
return errors.Errorf("run `docker context use default` to switch to default context")
}
if name == "default" || name == dockerCli.CurrentContext() {
if in.builder == "default" || in.builder == dockerCli.CurrentContext() {
ep, err := getCurrentEndpoint(dockerCli)
if err != nil {
return err
@@ -41,35 +42,39 @@ func runUse(dockerCli command.Cli, in useOptions, name string) error {
return err
}
for _, l := range list {
if l.Name == name {
return errors.Errorf("run `docker context use %s` to switch to context %s", name, name)
if l.Name == in.builder {
return errors.Errorf("run `docker context use %s` to switch to context %s", in.builder, in.builder)
}
}
}
return errors.Wrapf(err, "failed to find instance %q", name)
return errors.Wrapf(err, "failed to find instance %q", in.builder)
}
ep, err := getCurrentEndpoint(dockerCli)
if err != nil {
return err
}
if err := txn.SetCurrent(ep, name, in.isGlobal, in.isDefault); err != nil {
if err := txn.SetCurrent(ep, in.builder, in.isGlobal, in.isDefault); err != nil {
return err
}
return nil
}
func useCmd(dockerCli command.Cli) *cobra.Command {
func useCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
var options useOptions
cmd := &cobra.Command{
Use: "use [OPTIONS] NAME",
Short: "Set the current builder instance",
Args: cli.ExactArgs(1),
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runUse(dockerCli, options, args[0])
options.builder = rootOpts.builder
if len(args) > 0 {
options.builder = args[0]
}
return runUse(dockerCli, options)
},
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"os"
"path/filepath"
"strings"
"github.com/docker/buildx/build"
"github.com/docker/buildx/driver"
@@ -11,22 +12,37 @@ import (
"github.com/docker/buildx/util/platformutil"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/context/docker"
"github.com/docker/cli/cli/context/kubernetes"
dopts "github.com/docker/cli/opts"
dockerclient "github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
)
// getStore returns current builder instance store
func getStore(dockerCli command.Cli) (*store.Txn, func(), error) {
dir := filepath.Dir(dockerCli.ConfigFile().Filename)
s, err := store.New(dir)
s, err := store.New(getConfigStorePath(dockerCli))
if err != nil {
return nil, nil, err
}
return s.Txn()
}
// getConfigStorePath will look for correct configuration store path;
// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory
// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`)
func getConfigStorePath(dockerCli command.Cli) string {
if buildxConfig := os.Getenv("BUILDX_CONFIG"); buildxConfig != "" {
logrus.Debugf("using config store %q based in \"$BUILDX_CONFIG\" environment variable", buildxConfig)
return buildxConfig
}
buildxConfig := filepath.Join(filepath.Dir(dockerCli.ConfigFile().Filename), "buildx")
logrus.Debugf("using default config store %q", buildxConfig)
return buildxConfig
}
// getCurrentEndpoint returns the current default endpoint value
func getCurrentEndpoint(dockerCli command.Cli) (string, error) {
name := dockerCli.CurrentContext()
@@ -133,7 +149,7 @@ func getNodeGroup(txn *store.Txn, dockerCli command.Cli, name string) (*store.No
}
// driversForNodeGroup returns drivers for a nodegroup instance
func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup) ([]build.DriverInfo, error) {
func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, contextPathHash string) ([]build.DriverInfo, error) {
eg, _ := errgroup.WithContext(ctx)
dis := make([]build.DriverInfo, len(ng.Nodes))
@@ -174,7 +190,37 @@ func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.N
// TODO: replace the following line with dockerclient.WithAPIVersionNegotiation option in clientForEndpoint
dockerapi.NegotiateAPIVersion(ctx)
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi)
contextStore := dockerCli.ContextStore()
var kcc driver.KubeClientConfig
kcc, err = kubernetes.ConfigFromContext(n.Endpoint, contextStore)
if err != nil {
// err is returned if n.Endpoint is non-context name like "unix:///var/run/docker.sock".
// try again with name="default".
// FIXME: n should retain real context name.
kcc, err = kubernetes.ConfigFromContext("default", contextStore)
if err != nil {
logrus.Error(err)
}
}
tryToUseKubeConfigInCluster := false
if kcc == nil {
tryToUseKubeConfigInCluster = true
} else {
if _, err := kcc.ClientConfig(); err != nil {
tryToUseKubeConfigInCluster = true
}
}
if tryToUseKubeConfigInCluster {
kccInCluster := driver.KubeClientConfigInCluster{}
if _, err := kccInCluster.ClientConfig(); err == nil {
logrus.Debug("using kube config in cluster")
kcc = kccInCluster
}
}
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi, dockerCli.ConfigFile(), kcc, n.Flags, n.ConfigFile, assignDriverOptsByDriverInfo(n.DriverOpts, di), contextPathHash)
if err != nil {
di.Err = err
return nil
@@ -192,6 +238,20 @@ func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.N
return dis, nil
}
// pass platform as driver opts to provide for some drive, like kubernetes
func assignDriverOptsByDriverInfo(opts map[string]string, driveInfo build.DriverInfo) map[string]string {
m := map[string]string{}
if len(driveInfo.Platform) > 0 {
m["platform"] = strings.Join(platformutil.Format(driveInfo.Platform), ",")
}
for key := range opts {
m[key] = opts[key]
}
return m
}
// clientForEndpoint returns a docker client for an endpoint
func clientForEndpoint(dockerCli command.Cli, name string) (dockerclient.APIClient, error) {
list, err := dockerCli.ContextStore().List()
@@ -234,24 +294,66 @@ func clientForEndpoint(dockerCli command.Cli, name string) (dockerclient.APIClie
return dockerclient.NewClientWithOpts(clientOpts...)
}
// getDefaultDrivers returns drivers based on current cli config
func getDefaultDrivers(ctx context.Context, dockerCli command.Cli) ([]build.DriverInfo, error) {
func getInstanceOrDefault(ctx context.Context, dockerCli command.Cli, instance, contextPathHash string) ([]build.DriverInfo, error) {
var defaultOnly bool
if instance == "default" && instance != dockerCli.CurrentContext() {
return nil, errors.Errorf("use `docker --context=default buildx` to switch to default context")
}
if instance == "default" || instance == dockerCli.CurrentContext() {
instance = ""
defaultOnly = true
}
list, err := dockerCli.ContextStore().List()
if err != nil {
return nil, err
}
for _, l := range list {
if l.Name == instance {
return nil, errors.Errorf("use `docker --context=%s buildx` to switch to context %s", instance, instance)
}
}
if instance != "" {
return getInstanceByName(ctx, dockerCli, instance, contextPathHash)
}
return getDefaultDrivers(ctx, dockerCli, defaultOnly, contextPathHash)
}
func getInstanceByName(ctx context.Context, dockerCli command.Cli, instance, contextPathHash string) ([]build.DriverInfo, error) {
txn, release, err := getStore(dockerCli)
if err != nil {
return nil, err
}
defer release()
ng, err := txn.NodeGroupByName(instance)
if err != nil {
return nil, err
}
return driversForNodeGroup(ctx, dockerCli, ng, contextPathHash)
}
// getDefaultDrivers returns drivers based on current cli config
func getDefaultDrivers(ctx context.Context, dockerCli command.Cli, defaultOnly bool, contextPathHash string) ([]build.DriverInfo, error) {
txn, release, err := getStore(dockerCli)
if err != nil {
return nil, err
}
defer release()
if !defaultOnly {
ng, err := getCurrentInstance(txn, dockerCli)
if err != nil {
return nil, err
}
if ng != nil {
return driversForNodeGroup(ctx, dockerCli, ng)
return driversForNodeGroup(ctx, dockerCli, ng, contextPathHash)
}
}
d, err := driver.GetDriver(ctx, "buildx_buildkit_default", nil, dockerCli.Client())
d, err := driver.GetDriver(ctx, "buildx_buildkit_default", nil, dockerCli.Client(), dockerCli.ConfigFile(), nil, nil, "", nil, contextPathHash)
if err != nil {
return nil, err
}
@@ -294,7 +396,7 @@ func loadInfoData(ctx context.Context, d *dinfo) error {
func loadNodeGroupData(ctx context.Context, dockerCli command.Cli, ngi *nginfo) error {
eg, _ := errgroup.WithContext(ctx)
dis, err := driversForNodeGroup(ctx, dockerCli, ngi.ng)
dis, err := driversForNodeGroup(ctx, dockerCli, ngi.ng, "")
if err != nil {
return err
}
@@ -312,7 +414,34 @@ func loadNodeGroupData(ctx context.Context, dockerCli command.Cli, ngi *nginfo)
}(&ngi.drivers[i])
}
return eg.Wait()
if eg.Wait(); err != nil {
return err
}
// skip when multi drivers
if len(ngi.drivers) == 1 {
for _, di := range ngi.drivers {
// dynamic nodes are used in Kubernetes driver.
// Kubernetes pods are dynamically mapped to BuildKit Nodes.
if di.info != nil && len(di.info.DynamicNodes) > 0 {
var drivers []dinfo
for i := 0; i < len(di.info.DynamicNodes); i++ {
// all []dinfo share *build.DriverInfo and *driver.Info
diClone := di
if pl := di.info.DynamicNodes[i].Platforms; len(pl) > 0 {
diClone.platforms = pl
}
drivers = append(drivers, di)
}
// not append (remove the static nodes in the store)
ngi.ng.Nodes = di.info.DynamicNodes
ngi.ng.Dynamic = true
ngi.drivers = drivers
return nil
}
}
}
return nil
}
func dockerAPI(dockerCli command.Cli) *api {

View File

@@ -0,0 +1,6 @@
package bkimage
const (
DefaultImage = "moby/buildkit:buildx-stable-1" // TODO: make this verified
DefaultRootlessImage = "moby/buildkit:v0.6.2-rootless"
)

View File

@@ -1,6 +1,8 @@
package docker
import (
"archive/tar"
"bytes"
"context"
"io"
"io/ioutil"
@@ -9,6 +11,8 @@ import (
"time"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/driver/bkimage"
"github.com/docker/buildx/util/imagetools"
"github.com/docker/buildx/util/progress"
"github.com/docker/docker/api/types"
dockertypes "github.com/docker/docker/api/types"
@@ -20,11 +24,20 @@ import (
"github.com/pkg/errors"
)
var buildkitImage = "moby/buildkit:master" // TODO: make this verified and configuratble
type Driver struct {
driver.InitConfig
factory driver.Factory
netMode string
image string
env []string
}
func (d *Driver) IsMobyDriver() bool {
return false
}
func (d *Driver) Config() driver.InitConfig {
return d.InitConfig
}
func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
@@ -40,7 +53,7 @@ func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
if err := d.start(ctx, sub); err != nil {
return err
}
if err := d.wait(ctx); err != nil {
if err := d.wait(ctx, sub); err != nil {
return err
}
return nil
@@ -49,29 +62,67 @@ func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
}
func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
if err := l.Wrap("pulling image "+buildkitImage, func() error {
rc, err := d.DockerAPI.ImageCreate(ctx, buildkitImage, types.ImageCreateOptions{})
imageName := bkimage.DefaultImage
if d.image != "" {
imageName = d.image
}
if err := l.Wrap("pulling image "+imageName, func() error {
ra, err := imagetools.RegistryAuthForRef(imageName, d.Auth)
if err != nil {
return err
}
rc, err := d.DockerAPI.ImageCreate(ctx, imageName, types.ImageCreateOptions{
RegistryAuth: ra,
})
if err != nil {
return err
}
_, err = io.Copy(ioutil.Discard, rc)
return err
}); err != nil {
// image pulling failed, check if it exists in local image store.
// if not, return pulling error. otherwise log it.
_, _, errInspect := d.DockerAPI.ImageInspectWithRaw(ctx, imageName)
if errInspect != nil {
return err
}
l.Wrap("pulling failed, using local image "+imageName, func() error { return nil })
}
cfg := &container.Config{
Image: imageName,
Env: d.env,
}
if d.InitConfig.BuildkitFlags != nil {
cfg.Cmd = d.InitConfig.BuildkitFlags
}
if err := l.Wrap("creating container "+d.Name, func() error {
_, err := d.DockerAPI.ContainerCreate(ctx, &container.Config{
Image: buildkitImage,
}, &container.HostConfig{
hc := &container.HostConfig{
Privileged: true,
}, &network.NetworkingConfig{}, d.Name)
UsernsMode: "host",
}
if d.netMode != "" {
hc.NetworkMode = container.NetworkMode(d.netMode)
}
_, err := d.DockerAPI.ContainerCreate(ctx, cfg, hc, &network.NetworkingConfig{}, nil, d.Name)
if err != nil {
return err
}
if f := d.InitConfig.ConfigFile; f != "" {
buf, err := readFileToTar(f)
if err != nil {
return err
}
if err := d.DockerAPI.CopyToContainer(ctx, d.Name, "/", buf, dockertypes.CopyToContainerOptions{}); err != nil {
return err
}
}
if err := d.start(ctx, l); err != nil {
return err
}
if err := d.wait(ctx); err != nil {
if err := d.wait(ctx, l); err != nil {
return err
}
return nil
@@ -81,17 +132,28 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
return nil
}
func (d *Driver) wait(ctx context.Context) error {
try := 0
func (d *Driver) wait(ctx context.Context, l progress.SubLogger) error {
try := 1
for {
if err := d.run(ctx, []string{"buildctl", "debug", "workers"}); err != nil {
if try > 10 {
bufStdout := &bytes.Buffer{}
bufStderr := &bytes.Buffer{}
if err := d.run(ctx, []string{"buildctl", "debug", "workers"}, bufStdout, bufStderr); err != nil {
if try > 15 {
if err != nil {
d.copyLogs(context.TODO(), l)
if bufStdout.Len() != 0 {
l.Log(1, bufStdout.Bytes())
}
if bufStderr.Len() != 0 {
l.Log(2, bufStderr.Bytes())
}
}
return err
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(time.Duration(100+try*20) * time.Millisecond):
case <-time.After(time.Duration(try*120) * time.Millisecond):
try++
continue
}
@@ -100,6 +162,21 @@ func (d *Driver) wait(ctx context.Context) error {
}
}
func (d *Driver) copyLogs(ctx context.Context, l progress.SubLogger) error {
rc, err := d.DockerAPI.ContainerLogs(ctx, d.Name, types.ContainerLogsOptions{
ShowStdout: true, ShowStderr: true,
})
if err != nil {
return err
}
stdout := &logWriter{logger: l, stream: 1}
stderr := &logWriter{logger: l, stream: 2}
if _, err := stdcopy.StdCopy(stdout, stderr, rc); err != nil {
return err
}
return rc.Close()
}
func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, error) {
execConfig := types.ExecConfig{
Cmd: cmd,
@@ -124,12 +201,12 @@ func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, erro
return execID, resp.Conn, nil
}
func (d *Driver) run(ctx context.Context, cmd []string) error {
func (d *Driver) run(ctx context.Context, cmd []string, stdout, stderr io.Writer) (err error) {
id, conn, err := d.exec(ctx, cmd)
if err != nil {
return err
}
if _, err := io.Copy(ioutil.Discard, conn); err != nil {
if _, err := stdcopy.StdCopy(stdout, stderr, conn); err != nil {
return err
}
conn.Close()
@@ -202,7 +279,7 @@ func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
conn = demuxConn(conn)
return client.New(ctx, "", client.WithDialer(func(string, time.Duration) (net.Conn, error) {
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return conn, nil
}))
}
@@ -224,7 +301,7 @@ func (d *Driver) Features() map[driver.Feature]bool {
func demuxConn(c net.Conn) net.Conn {
pr, pw := io.Pipe()
// TODO: rewrite parser with Reader() to avoid goroutine switch
go stdcopy.StdCopy(pw, os.Stdout, c)
go stdcopy.StdCopy(pw, os.Stderr, c)
return &demux{
Conn: c,
Reader: pr,
@@ -239,3 +316,36 @@ type demux struct {
func (d *demux) Read(dt []byte) (int, error) {
return d.Reader.Read(dt)
}
func readFileToTar(fn string) (*bytes.Buffer, error) {
buf := bytes.NewBuffer(nil)
tw := tar.NewWriter(buf)
dt, err := ioutil.ReadFile(fn)
if err != nil {
return nil, err
}
if err := tw.WriteHeader(&tar.Header{
Name: "/etc/buildkit/buildkitd.toml",
Size: int64(len(dt)),
Mode: 0644,
}); err != nil {
return nil, err
}
if _, err := tw.Write(dt); err != nil {
return nil, err
}
if err := tw.Close(); err != nil {
return nil, err
}
return buf, nil
}
type logWriter struct {
logger progress.SubLogger
stream int
}
func (l *logWriter) Write(dt []byte) (int, error) {
l.logger.Log(l.stream, dt)
return len(dt), nil
}

View File

@@ -2,6 +2,8 @@ package docker
import (
"context"
"fmt"
"strings"
"github.com/docker/buildx/driver"
dockerclient "github.com/docker/docker/client"
@@ -37,8 +39,28 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
if cfg.DockerAPI == nil {
return nil, errors.Errorf("%s driver requires docker API access", f.Name())
}
d := &Driver{factory: f, InitConfig: cfg}
for k, v := range cfg.DriverOpts {
switch {
case k == "network":
d.netMode = v
if v == "host" {
d.InitConfig.BuildkitFlags = append(d.InitConfig.BuildkitFlags, "--allow-insecure-entitlement=network.host")
}
case k == "image":
d.image = v
case strings.HasPrefix(k, "env."):
envName := strings.TrimPrefix(k, "env.")
if envName == "" {
return nil, errors.Errorf("invalid env option %q, expecting env.FOO=bar", k)
}
d.env = append(d.env, fmt.Sprintf("%s=%s", envName, v))
default:
return nil, errors.Errorf("invalid driver option %s for docker-container driver", k)
}
}
return &Driver{factory: f, InitConfig: cfg}, nil
return d, nil
}
func (f *factory) AllowsInstances() bool {

View File

@@ -3,7 +3,6 @@ package docker
import (
"context"
"net"
"time"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/util/progress"
@@ -39,7 +38,7 @@ func (d *Driver) Rm(ctx context.Context, force bool) error {
}
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
return client.New(ctx, "", client.WithDialer(func(string, time.Duration) (net.Conn, error) {
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return d.DockerAPI.DialHijack(ctx, "/grpc", "h2c", nil)
}))
}
@@ -48,7 +47,6 @@ func (d *Driver) Features() map[driver.Feature]bool {
return map[driver.Feature]bool{
driver.OCIExporter: false,
driver.DockerExporter: false,
driver.CacheExport: false,
driver.MultiPlatform: false,
}
@@ -58,4 +56,10 @@ func (d *Driver) Factory() driver.Factory {
return d.factory
}
func (d *Driver) IsDefaultMobyDriver() {}
func (d *Driver) IsMobyDriver() bool {
return true
}
func (d *Driver) Config() driver.InitConfig {
return d.InitConfig
}

View File

@@ -44,6 +44,9 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
if cfg.DockerAPI == nil {
return nil, errors.Errorf("docker driver requires docker API access")
}
if cfg.ConfigFile != "" {
return nil, errors.Errorf("setting config file is not supported for docker driver, use dockerd configuration file")
}
return &Driver{factory: f, InitConfig: cfg}, nil
}

View File

@@ -3,7 +3,9 @@ package driver
import (
"context"
"github.com/docker/buildx/store"
"github.com/docker/buildx/util/progress"
clitypes "github.com/docker/cli/cli/config/types"
"github.com/moby/buildkit/client"
"github.com/pkg/errors"
)
@@ -39,6 +41,12 @@ func (s Status) String() string {
type Info struct {
Status Status
// DynamicNodes must be empty if the actual nodes are statically listed in the store
DynamicNodes []store.Node
}
type Auth interface {
GetAuthConfig(registryHostname string) (clitypes.AuthConfig, error)
}
type Driver interface {
@@ -49,6 +57,8 @@ type Driver interface {
Rm(ctx context.Context, force bool) error
Client(ctx context.Context) (*client.Client, error)
Features() map[Feature]bool
IsMobyDriver() bool
Config() InitConfig
}
func Boot(ctx context.Context, d Driver, pw progress.Writer) (*client.Client, error) {
@@ -63,11 +73,7 @@ func Boot(ctx context.Context, d Driver, pw progress.Writer) (*client.Client, er
if try > 2 {
return nil, errors.Errorf("failed to bootstrap %T driver in attempts", d)
}
if err := d.Bootstrap(ctx, func(s *client.SolveStatus) {
if pw != nil {
pw.Status() <- s
}
}); err != nil {
if err := d.Bootstrap(ctx, pw.Write); err != nil {
return nil, err
}
}

189
driver/kubernetes/driver.go Normal file
View File

@@ -0,0 +1,189 @@
package kubernetes
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/driver/kubernetes/execconn"
"github.com/docker/buildx/driver/kubernetes/manifest"
"github.com/docker/buildx/driver/kubernetes/podchooser"
"github.com/docker/buildx/store"
"github.com/docker/buildx/util/platformutil"
"github.com/docker/buildx/util/progress"
"github.com/moby/buildkit/client"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
clientappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
clientcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
const (
DriverName = "kubernetes"
)
const (
// valid values for driver-opt loadbalance
LoadbalanceRandom = "random"
LoadbalanceSticky = "sticky"
)
type Driver struct {
driver.InitConfig
factory driver.Factory
minReplicas int
deployment *appsv1.Deployment
clientset *kubernetes.Clientset
deploymentClient clientappsv1.DeploymentInterface
podClient clientcorev1.PodInterface
podChooser podchooser.PodChooser
}
func (d *Driver) IsMobyDriver() bool {
return false
}
func (d *Driver) Config() driver.InitConfig {
return d.InitConfig
}
func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
return progress.Wrap("[internal] booting buildkit", l, func(sub progress.SubLogger) error {
_, err := d.deploymentClient.Get(ctx, d.deployment.Name, metav1.GetOptions{})
if err != nil {
// TODO: return err if err != ErrNotFound
_, err = d.deploymentClient.Create(ctx, d.deployment, metav1.CreateOptions{})
if err != nil {
return errors.Wrapf(err, "error while calling deploymentClient.Create for %q", d.deployment.Name)
}
}
return sub.Wrap(
fmt.Sprintf("waiting for %d pods to be ready", d.minReplicas),
func() error {
if err := d.wait(ctx); err != nil {
return err
}
return nil
})
})
}
func (d *Driver) wait(ctx context.Context) error {
// TODO: use watch API
var (
err error
depl *appsv1.Deployment
)
for try := 0; try < 100; try++ {
depl, err = d.deploymentClient.Get(ctx, d.deployment.Name, metav1.GetOptions{})
if err == nil {
if depl.Status.ReadyReplicas >= int32(d.minReplicas) {
return nil
}
err = errors.Errorf("expected %d replicas to be ready, got %d",
d.minReplicas, depl.Status.ReadyReplicas)
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(time.Duration(100+try*20) * time.Millisecond):
}
}
return err
}
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
depl, err := d.deploymentClient.Get(ctx, d.deployment.Name, metav1.GetOptions{})
if err != nil {
// TODO: return err if err != ErrNotFound
return &driver.Info{
Status: driver.Inactive,
}, nil
}
if depl.Status.ReadyReplicas <= 0 {
return &driver.Info{
Status: driver.Stopped,
}, nil
}
pods, err := podchooser.ListRunningPods(ctx, d.podClient, depl)
if err != nil {
return nil, err
}
var dynNodes []store.Node
for _, p := range pods {
node := store.Node{
Name: p.Name,
// Other fields are unset (TODO: detect real platforms)
}
if p.Annotations != nil {
if p, ok := p.Annotations[manifest.AnnotationPlatform]; ok {
ps, err := platformutil.Parse(strings.Split(p, ","))
if err == nil {
node.Platforms = ps
}
}
}
dynNodes = append(dynNodes, node)
}
return &driver.Info{
Status: driver.Running,
DynamicNodes: dynNodes,
}, nil
}
func (d *Driver) Stop(ctx context.Context, force bool) error {
// future version may scale the replicas to zero here
return nil
}
func (d *Driver) Rm(ctx context.Context, force bool) error {
if err := d.deploymentClient.Delete(ctx, d.deployment.Name, metav1.DeleteOptions{}); err != nil {
return errors.Wrapf(err, "error while calling deploymentClient.Delete for %q", d.deployment.Name)
}
return nil
}
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
restClient := d.clientset.CoreV1().RESTClient()
restClientConfig, err := d.KubeClientConfig.ClientConfig()
if err != nil {
return nil, err
}
pod, err := d.podChooser.ChoosePod(ctx)
if err != nil {
return nil, err
}
if len(pod.Spec.Containers) == 0 {
return nil, errors.Errorf("pod %s does not have any container", pod.Name)
}
containerName := pod.Spec.Containers[0].Name
cmd := []string{"buildctl", "dial-stdio"}
conn, err := execconn.ExecConn(restClient, restClientConfig,
pod.Namespace, pod.Name, containerName, cmd)
if err != nil {
return nil, err
}
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return conn, nil
}))
}
func (d *Driver) Factory() driver.Factory {
return d.factory
}
func (d *Driver) Features() map[driver.Feature]bool {
return map[driver.Feature]bool{
driver.OCIExporter: true,
driver.DockerExporter: d.DockerAPI != nil,
driver.CacheExport: true,
driver.MultiPlatform: true, // Untested (needs multiple Driver instances)
}
}

View File

@@ -0,0 +1,135 @@
package execconn
import (
"io"
"net"
"os"
"sync"
"time"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
)
func ExecConn(restClient rest.Interface, restConfig *rest.Config, namespace, pod, container string, cmd []string) (net.Conn, error) {
req := restClient.
Post().
Namespace(namespace).
Resource("pods").
Name(pod).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: container,
Command: cmd,
Stdin: true,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", req.URL())
if err != nil {
return nil, err
}
stdinR, stdinW := io.Pipe()
stdoutR, stdoutW := io.Pipe()
kc := &kubeConn{
stdin: stdinW,
stdout: stdoutR,
localAddr: dummyAddr{network: "dummy", s: "dummy-0"},
remoteAddr: dummyAddr{network: "dummy", s: "dummy-1"},
}
go func() {
serr := exec.Stream(remotecommand.StreamOptions{
Stdin: stdinR,
Stdout: stdoutW,
Stderr: os.Stderr,
Tty: false,
})
if serr != nil {
logrus.Error(serr)
}
}()
return kc, nil
}
type kubeConn struct {
stdin io.WriteCloser
stdout io.ReadCloser
stdioClosedMu sync.Mutex // for stdinClosed and stdoutClosed
stdinClosed bool
stdoutClosed bool
localAddr net.Addr
remoteAddr net.Addr
}
func (c *kubeConn) Write(p []byte) (int, error) {
return c.stdin.Write(p)
}
func (c *kubeConn) Read(p []byte) (int, error) {
return c.stdout.Read(p)
}
func (c *kubeConn) CloseWrite() error {
err := c.stdin.Close()
c.stdioClosedMu.Lock()
c.stdinClosed = true
c.stdioClosedMu.Unlock()
return err
}
func (c *kubeConn) CloseRead() error {
err := c.stdout.Close()
c.stdioClosedMu.Lock()
c.stdoutClosed = true
c.stdioClosedMu.Unlock()
return err
}
func (c *kubeConn) Close() error {
var err error
c.stdioClosedMu.Lock()
stdinClosed := c.stdinClosed
c.stdioClosedMu.Unlock()
if !stdinClosed {
err = c.CloseWrite()
}
c.stdioClosedMu.Lock()
stdoutClosed := c.stdoutClosed
c.stdioClosedMu.Unlock()
if !stdoutClosed {
err = c.CloseRead()
}
return err
}
func (c *kubeConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *kubeConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *kubeConn) SetDeadline(t time.Time) error {
return nil
}
func (c *kubeConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *kubeConn) SetWriteDeadline(t time.Time) error {
return nil
}
type dummyAddr struct {
network string
s string
}
func (d dummyAddr) Network() string {
return d.network
}
func (d dummyAddr) String() string {
return d.s
}

View File

@@ -0,0 +1,165 @@
package kubernetes
import (
"context"
"strconv"
"strings"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/driver/bkimage"
"github.com/docker/buildx/driver/kubernetes/manifest"
"github.com/docker/buildx/driver/kubernetes/podchooser"
"github.com/docker/buildx/util/platformutil"
dockerclient "github.com/docker/docker/client"
"github.com/pkg/errors"
"k8s.io/client-go/kubernetes"
)
const prioritySupported = 40
const priorityUnsupported = 80
func init() {
driver.Register(&factory{})
}
type factory struct {
}
func (*factory) Name() string {
return DriverName
}
func (*factory) Usage() string {
return DriverName
}
func (*factory) Priority(ctx context.Context, api dockerclient.APIClient) int {
if api == nil {
return priorityUnsupported
}
return prioritySupported
}
func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver, error) {
if cfg.KubeClientConfig == nil {
return nil, errors.Errorf("%s driver requires kubernetes API access", DriverName)
}
deploymentName, err := buildxNameToDeploymentName(cfg.Name)
if err != nil {
return nil, err
}
namespace, _, err := cfg.KubeClientConfig.Namespace()
if err != nil {
return nil, errors.Wrap(err, "cannot determine Kubernetes namespace, specify manually")
}
restClientConfig, err := cfg.KubeClientConfig.ClientConfig()
if err != nil {
return nil, err
}
clientset, err := kubernetes.NewForConfig(restClientConfig)
if err != nil {
return nil, err
}
d := &Driver{
factory: f,
InitConfig: cfg,
clientset: clientset,
}
deploymentOpt := &manifest.DeploymentOpt{
Name: deploymentName,
Image: bkimage.DefaultImage,
Replicas: 1,
BuildkitFlags: cfg.BuildkitFlags,
Rootless: false,
}
loadbalance := LoadbalanceSticky
imageOverride := ""
for k, v := range cfg.DriverOpts {
switch k {
case "image":
imageOverride = v
case "namespace":
namespace = v
case "replicas":
deploymentOpt.Replicas, err = strconv.Atoi(v)
if err != nil {
return nil, err
}
case "rootless":
deploymentOpt.Rootless, err = strconv.ParseBool(v)
if err != nil {
return nil, err
}
deploymentOpt.Image = bkimage.DefaultRootlessImage
case "platform":
if v != "" {
platforms, err := platformutil.Parse(strings.Split(v, ","))
if err != nil {
return nil, err
}
deploymentOpt.Platforms = platforms
}
case "nodeselector":
kvs := strings.Split(strings.Trim(v, `"`), ",")
s := map[string]string{}
for i := range kvs {
kv := strings.Split(kvs[i], "=")
if len(kv) == 2 {
s[kv[0]] = kv[1]
}
}
deploymentOpt.NodeSelector = s
case "loadbalance":
switch v {
case LoadbalanceSticky:
case LoadbalanceRandom:
default:
return nil, errors.Errorf("invalid loadbalance %q", v)
}
loadbalance = v
default:
return nil, errors.Errorf("invalid driver option %s for driver %s", k, DriverName)
}
}
if imageOverride != "" {
deploymentOpt.Image = imageOverride
}
d.deployment, err = manifest.NewDeployment(deploymentOpt)
if err != nil {
return nil, err
}
d.minReplicas = deploymentOpt.Replicas
d.deploymentClient = clientset.AppsV1().Deployments(namespace)
d.podClient = clientset.CoreV1().Pods(namespace)
switch loadbalance {
case LoadbalanceSticky:
d.podChooser = &podchooser.StickyPodChooser{
Key: cfg.ContextPathHash,
PodClient: d.podClient,
Deployment: d.deployment,
}
case LoadbalanceRandom:
d.podChooser = &podchooser.RandomPodChooser{
PodClient: d.podClient,
Deployment: d.deployment,
}
}
return d, nil
}
func (f *factory) AllowsInstances() bool {
return true
}
// buildxNameToDeploymentName converts buildx name to Kubernetes Deployment name.
//
// eg. "buildx_buildkit_loving_mendeleev0" -> "loving-mendeleev0"
func buildxNameToDeploymentName(bx string) (string, error) {
// TODO: commands.util.go should not pass "buildx_buildkit_" prefix to drivers
if !strings.HasPrefix(bx, "buildx_buildkit_") {
return "", errors.Errorf("expected a string with \"buildx_buildkit_\", got %q", bx)
}
s := strings.TrimPrefix(bx, "buildx_buildkit_")
s = strings.ReplaceAll(s, "_", "-")
return s, nil
}

View File

@@ -0,0 +1,110 @@
package manifest
import (
"strings"
"github.com/docker/buildx/util/platformutil"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type DeploymentOpt struct {
Namespace string
Name string
Image string
Replicas int
BuildkitFlags []string
Rootless bool
NodeSelector map[string]string
Platforms []v1.Platform
}
const (
containerName = "buildkitd"
AnnotationPlatform = "buildx.docker.com/platform"
)
func NewDeployment(opt *DeploymentOpt) (*appsv1.Deployment, error) {
labels := map[string]string{
"app": opt.Name,
}
annotations := map[string]string{}
replicas := int32(opt.Replicas)
privileged := true
args := opt.BuildkitFlags
if len(opt.Platforms) > 0 {
annotations[AnnotationPlatform] = strings.Join(platformutil.Format(opt.Platforms), ",")
}
d := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: appsv1.SchemeGroupVersion.String(),
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: opt.Namespace,
Name: opt.Name,
Labels: labels,
Annotations: annotations,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
Annotations: annotations,
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: containerName,
Image: opt.Image,
Args: args,
SecurityContext: &corev1.SecurityContext{
Privileged: &privileged,
},
ReadinessProbe: &corev1.Probe{
Handler: corev1.Handler{
Exec: &corev1.ExecAction{
Command: []string{"buildctl", "debug", "workers"},
},
},
},
},
},
},
},
},
}
if opt.Rootless {
if err := toRootless(d); err != nil {
return nil, err
}
}
if len(opt.NodeSelector) > 0 {
d.Spec.Template.Spec.NodeSelector = opt.NodeSelector
}
return d, nil
}
func toRootless(d *appsv1.Deployment) error {
d.Spec.Template.Spec.Containers[0].Args = append(
d.Spec.Template.Spec.Containers[0].Args,
"--oci-worker-no-process-sandbox",
)
d.Spec.Template.Spec.Containers[0].SecurityContext = nil
if d.Spec.Template.ObjectMeta.Annotations == nil {
d.Spec.Template.ObjectMeta.Annotations = make(map[string]string, 2)
}
d.Spec.Template.ObjectMeta.Annotations["container.apparmor.security.beta.kubernetes.io/"+containerName] = "unconfined"
d.Spec.Template.ObjectMeta.Annotations["container.seccomp.security.alpha.kubernetes.io/"+containerName] = "unconfined"
return nil
}

View File

@@ -0,0 +1,97 @@
package podchooser
import (
"context"
"math/rand"
"sort"
"time"
"github.com/serialx/hashring"
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
type PodChooser interface {
ChoosePod(ctx context.Context) (*corev1.Pod, error)
}
type RandomPodChooser struct {
RandSource rand.Source
PodClient clientcorev1.PodInterface
Deployment *appsv1.Deployment
}
func (pc *RandomPodChooser) ChoosePod(ctx context.Context) (*corev1.Pod, error) {
pods, err := ListRunningPods(ctx, pc.PodClient, pc.Deployment)
if err != nil {
return nil, err
}
randSource := pc.RandSource
if randSource == nil {
randSource = rand.NewSource(time.Now().Unix())
}
rnd := rand.New(randSource)
n := rnd.Int() % len(pods)
logrus.Debugf("RandomPodChooser.ChoosePod(): len(pods)=%d, n=%d", len(pods), n)
return pods[n], nil
}
type StickyPodChooser struct {
Key string
PodClient clientcorev1.PodInterface
Deployment *appsv1.Deployment
}
func (pc *StickyPodChooser) ChoosePod(ctx context.Context) (*corev1.Pod, error) {
pods, err := ListRunningPods(ctx, pc.PodClient, pc.Deployment)
if err != nil {
return nil, err
}
var podNames []string
podMap := make(map[string]*corev1.Pod, len(pods))
for _, pod := range pods {
podNames = append(podNames, pod.Name)
podMap[pod.Name] = pod
}
ring := hashring.New(podNames)
chosen, ok := ring.GetNode(pc.Key)
if !ok {
// NOTREACHED
logrus.Errorf("no pod found for key %q", pc.Key)
rpc := &RandomPodChooser{
PodClient: pc.PodClient,
Deployment: pc.Deployment,
}
return rpc.ChoosePod(ctx)
}
return podMap[chosen], nil
}
func ListRunningPods(ctx context.Context, client clientcorev1.PodInterface, depl *appsv1.Deployment) ([]*corev1.Pod, error) {
selector, err := metav1.LabelSelectorAsSelector(depl.Spec.Selector)
if err != nil {
return nil, err
}
listOpts := metav1.ListOptions{
LabelSelector: selector.String(),
}
podList, err := client.List(ctx, listOpts)
if err != nil {
return nil, err
}
var runningPods []*corev1.Pod
for i := range podList.Items {
pod := &podList.Items[i]
if pod.Status.Phase == corev1.PodRunning {
logrus.Debugf("pod runnning: %q", pod.Name)
runningPods = append(runningPods, pod)
}
}
sort.Slice(runningPods, func(i, j int) bool {
return runningPods[i].Name < runningPods[j].Name
})
return runningPods, nil
}

View File

@@ -2,9 +2,15 @@ package driver
import (
"context"
"io/ioutil"
"sort"
"strings"
"sync"
"k8s.io/client-go/rest"
dockerclient "github.com/docker/docker/client"
"github.com/moby/buildkit/client"
"github.com/pkg/errors"
)
@@ -21,12 +27,36 @@ type BuildkitConfig struct {
// Rootless bool
}
type KubeClientConfig interface {
ClientConfig() (*rest.Config, error)
Namespace() (string, bool, error)
}
type KubeClientConfigInCluster struct{}
func (k KubeClientConfigInCluster) ClientConfig() (*rest.Config, error) {
return rest.InClusterConfig()
}
func (k KubeClientConfigInCluster) Namespace() (string, bool, error) {
namespace, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
if err != nil {
return "", false, err
}
return strings.TrimSpace(string(namespace)), true, nil
}
type InitConfig struct {
// This object needs updates to be generic for different drivers
Name string
DockerAPI dockerclient.APIClient
BuildkitConfig BuildkitConfig
Meta map[string]interface{}
KubeClientConfig KubeClientConfig
BuildkitFlags []string
ConfigFile string
DriverOpts map[string]string
Auth Auth
// ContextPathHash can be used for determining pods in the driver instance
ContextPathHash string
}
var drivers map[string]Factory
@@ -71,10 +101,16 @@ func GetFactory(name string, instanceRequired bool) Factory {
return nil
}
func GetDriver(ctx context.Context, name string, f Factory, api dockerclient.APIClient) (Driver, error) {
func GetDriver(ctx context.Context, name string, f Factory, api dockerclient.APIClient, auth Auth, kcc KubeClientConfig, flags []string, config string, do map[string]string, contextPathHash string) (Driver, error) {
ic := InitConfig{
DockerAPI: api,
KubeClientConfig: kcc,
Name: name,
BuildkitFlags: flags,
ConfigFile: config,
DriverOpts: do,
Auth: auth,
ContextPathHash: contextPathHash,
}
if f == nil {
var err error
@@ -83,5 +119,27 @@ func GetDriver(ctx context.Context, name string, f Factory, api dockerclient.API
return nil, err
}
}
return f.New(ctx, ic)
d, err := f.New(ctx, ic)
if err != nil {
return nil, err
}
return &cachedDriver{Driver: d}, nil
}
func GetFactories() map[string]Factory {
return drivers
}
type cachedDriver struct {
Driver
client *client.Client
err error
once sync.Once
}
func (d *cachedDriver) Client(ctx context.Context) (*client.Client, error) {
d.once.Do(func() {
d.client, d.err = d.Driver.Client(ctx)
})
return d.client, d.err
}

96
go.mod
View File

@@ -1,88 +1,68 @@
module github.com/docker/buildx
go 1.13
require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/hcsshim v0.8.6 // indirect
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/bugsnag/bugsnag-go v1.4.1 // indirect
github.com/bugsnag/panicwrap v1.2.0 // indirect
github.com/cenkalti/backoff v2.1.1+incompatible // indirect
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50
github.com/containerd/containerd v1.3.0-0.20190426060238-3a3f0aac8819
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 // indirect
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20 // indirect
github.com/containerd/console v1.0.1
github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 // indirect
github.com/docker/cli v1.14.0-0.20190523191156-ab688a9a79a1
github.com/docker/cli v20.10.0-beta1.0.20201029214301-1d20b15adc38+incompatible
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496 // indirect
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible
github.com/docker/docker v1.14.0-0.20190410063227-3998dffb806f3887f804b813069f59bc14a7f3c1
github.com/docker/docker-credential-helpers v0.6.1 // indirect
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v20.10.0-beta1.0.20201110211921-af34b94a78a1+incompatible
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.0-20170502235133-d466d4f6fd96 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4 // indirect
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-sql-driver/mysql v1.4.1 // indirect
github.com/gofrs/flock v0.7.0
github.com/fvbommel/sortorder v1.0.1 // indirect
github.com/gofrs/flock v0.7.3
github.com/gofrs/uuid v3.2.0+incompatible // indirect
github.com/gogo/protobuf v1.2.1 // indirect
github.com/google/certificate-transparency-go v1.0.21 // indirect
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
github.com/gorilla/mux v1.7.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
github.com/hashicorp/go-version v1.1.0 // indirect
github.com/hashicorp/hcl v1.0.0
github.com/imdario/mergo v0.3.7 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/hashicorp/hcl/v2 v2.6.0
github.com/jinzhu/gorm v1.9.2 // indirect
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
github.com/jinzhu/now v1.0.0 // indirect
github.com/json-iterator/go v1.1.6 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lib/pq v1.0.0 // indirect
github.com/mattn/go-shellwords v1.0.5 // indirect
github.com/mattn/go-sqlite3 v1.10.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19 // indirect
github.com/moby/buildkit v0.5.2-0.20190513182223-f238f1efb04f
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1
github.com/moby/buildkit v0.8.1-0.20201205083753-0af7b1b9c693
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/runtime-spec v1.0.1 // indirect
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v0.8.0 // indirect
github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612 // indirect
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 // indirect
github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be // indirect
github.com/sirupsen/logrus v1.4.0
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.3.2 // indirect
github.com/stretchr/testify v1.3.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.7.1 // indirect
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002
github.com/sirupsen/logrus v1.7.0
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.5.1
github.com/theupdateframework/notary v0.6.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v0.0.0-20160323030313-93e72a773fad // indirect
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/zclconf/go-cty v1.4.0
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/api v0.0.0-20180712090710-2d6f90ab1293 // indirect
k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d // indirect
k8s.io/client-go v2.0.0-alpha.0.0.20180806134042-1f13a808da65+incompatible // indirect
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect
k8s.io/api v0.19.0
k8s.io/apimachinery v0.19.0
k8s.io/client-go v0.19.0
)
replace github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305
replace (
// protobuf: corresponds to containerd (through buildkit)
github.com/golang/protobuf => github.com/golang/protobuf v1.3.5
github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305
// genproto: corresponds to containerd (through buildkit)
google.golang.org/genproto => google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63
)

1447
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -1,56 +1,16 @@
#!/usr/bin/env bash
. $(dirname $0)/util
set -eu
: ${TARGETPLATFORM=$CLI_PLATFORM}
: ${CONTINUOUS_INTEGRATION=}
set -ex
platformFlag=""
if [ -n "$TARGETPLATFORM" ]; then
platformFlag="--platform $TARGETPLATFORM"
fi
progressFlag=""
if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then progressFlag="--progress=plain"; fi
binariesDocker() {
mkdir -p bin/tmp
export DOCKER_BUILDKIT=1
iidfile=$(mktemp -t docker-iidfile.XXXXXXXXXX)
platformFlag=""
if [ -n "$TARGETPLATFORM" ]; then
platformFlag="--build-arg=TARGETPLATFORM=$TARGETPLATFORM"
fi
docker build $platformFlag --target=binaries --iidfile $iidfile --force-rm .
iid=$(cat $iidfile)
containerID=$(docker create $iid copy)
docker cp $containerID:/ bin/tmp
mv bin/tmp/build* bin/
rm -rf bin/tmp
docker rm $containerID
docker rmi -f $iid
rm -f $iidfile
}
binaries() {
platformFlag=""
if [ ! -z "$TARGETPLATFORM" ]; then
platformFlag="--frontend-opt=platform=$TARGETPLATFORM"
fi
buildctl build $progressFlag --frontend=dockerfile.v0 \
--local context=. --local dockerfile=. \
--frontend-opt target=binaries $platformFlag \
--output type=local,dest=./bin/
}
case $buildmode in
"buildkit")
binaries
;;
"docker-buildkit")
binariesDocker
;;
*)
echo "buildctl or docker with buildkit support is required"
exit 1
;;
esac
buildxCmd build $platformFlag \
--target "binaries" \
--output "type=local,dest=./bin/" \
.

38
hack/build_ci_first_pass Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
TYP=$1
. $(dirname $0)/util
set -e
usage() {
echo "usage: ./hack/build_ci_first_pass <binaries>"
exit 1
}
if [ -z "$TYP" ]; then
usage
fi
importCacheFlags=""
exportCacheFlags=""
if [ "$GITHUB_ACTIONS" = "true" ]; then
if [ -n "$cacheRefFrom" ]; then
importCacheFlags="--cache-from=type=local,src=$cacheRefFrom"
fi
if [ -n "$cacheRefTo" ]; then
exportCacheFlags="--cache-to=type=local,dest=$cacheRefTo"
fi
fi
case $TYP in
"binaries")
buildxCmd build $importCacheFlags $exportCacheFlags \
--target "binaries" \
$currentcontext
;;
*)
echo >&2 "Unknown type $TYP"
exit 1
;;
esac

View File

@@ -1,20 +1,24 @@
#!/usr/bin/env bash
. $(dirname $0)/util
set -e
: ${TARGETPLATFORM=linux/amd64,linux/arm/v7,linux/arm64,darwin/amd64,windows/amd64,linux/ppc64le,linux/s390x}
: ${CONTINUOUS_INTEGRATION=}
: ${EXPORT_LOCAL=}
set -ex
importCacheFlags=""
if [ "$GITHUB_ACTIONS" = "true" ]; then
if [ -n "$cacheRefFrom" ]; then
importCacheFlags="--cache-from=type=local,src=$cacheRefFrom"
fi
fi
exportFlag=""
if [ -n "$EXPORT_LOCAL" ]; then
exportFlag="--output=type=local,dest=$EXPORT_LOCAL"
fi
progressFlag=""
if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then progressFlag="--progress=plain";
fi
buildctl build $progressFlag --frontend=dockerfile.v0 --local context=. --local dockerfile=. --opt platform=$TARGETPLATFORM $exportFlag --opt target=binaries
buildxCmd build $importCacheFlags $exportFlag \
--target "binaries" \
--platform "$TARGETPLATFORM" \
$currentcontext

View File

@@ -10,7 +10,7 @@ if [ -n "$TMUX_ENTRYPOINT" ]; then
tmux new-window
tmux a -t demo
else
( $dockerdCmd 2>/var/log/dockerd.log & )
( $dockerdCmd &>/var/log/dockerd.log & )
exec ash
fi

View File

@@ -1,10 +1,12 @@
# syntax=docker/dockerfile:1.0-experimental
FROM golang:1.12-alpine
RUN apk add --no-cache git
FROM golang:1.13-alpine
RUN apk add --no-cache git yamllint
RUN go get -u gopkg.in/alecthomas/gometalinter.v1 \
&& mv /go/bin/gometalinter.v1 /go/bin/gometalinter \
&& gometalinter --install
WORKDIR /go/src/github.com/docker/buildx
RUN --mount=target=/go/src/github.com/docker/buildx \
gometalinter --config=gometalinter.json ./...
RUN --mount=target=/go/src/github.com/docker/buildx \
yamllint -c .yamllint.yml --strict .

View File

@@ -1,5 +1,5 @@
# syntax = docker/dockerfile:1.0-experimental
FROM golang:1.12-alpine AS vendored
FROM golang:1.13-alpine AS vendored
RUN apk add --no-cache git rsync
WORKDIR /src
RUN --mount=target=/context \

View File

@@ -1,37 +1,6 @@
#!/usr/bin/env bash
. $(dirname $0)/util
set -eu -o pipefail -x
set -eu
: ${CONTINUOUS_INTEGRATION=}
progressFlag=""
if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then progressFlag="--progress=plain"; fi
lintDocker() {
export DOCKER_BUILDKIT=1
iidfile=$(mktemp -t docker-iidfile.XXXXXXXXXX)
docker build --iidfile $iidfile -f ./hack/dockerfiles/lint.Dockerfile --force-rm .
iid=$(cat $iidfile)
docker rmi $iid
rm -f $iidfile
}
lint() {
buildctl build $progressFlag --frontend=dockerfile.v0 \
--local context=. --local dockerfile=. \
--frontend-opt filename=./hack/dockerfiles/lint.Dockerfile
}
case $buildmode in
"buildkit")
lint
;;
"docker-buildkit")
lintDocker
;;
*)
echo "buildctl or docker with buildkit support is required"
exit 1
;;
esac
buildxCmd build --file ./hack/dockerfiles/lint.Dockerfile .

View File

@@ -3,14 +3,10 @@
TAG=$1
OUT=$2
. $(dirname $0)/util
set -eu -o pipefail
: ${PLATFORMS=linux/amd64}
: ${CONTINUOUS_INTEGRATION=}
progressFlag=""
if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then progressFlag="--progress=plain"; fi
usage() {
echo "usage: ./hack/release <tag> <out>"
@@ -21,12 +17,15 @@ if [ -z "$TAG" ] || [ -z "$OUT" ]; then
usage
fi
importCacheFlags=""
if [[ -n "$cacheRefFrom" ]] && [[ "$cacheType" = "local" ]]; then
for ref in $cacheRefFrom; do
importCacheFlags="$importCacheFlags--cache-from=type=local,src=$ref "
done
fi
set -x
buildctl build $progressFlag --frontend=dockerfile.v0 \
--local context=. --local dockerfile=. \
--opt target=release \
--opt platform=$PLATFORMS \
--exporter local \
--exporter-opt output=$OUT
buildxCmd build $importCacheFlags \
--target "release" \
--platform "$PLATFORMS" \
--output "type=local,dest=$OUT" \
$currentcontext

View File

@@ -3,51 +3,45 @@
. $(dirname $0)/util
set -eu -o pipefail
: ${CONTINUOUS_INTEGRATION=}
: ${BUILDX_NOCACHE=}
: ${TEST_COVERAGE=}
progressFlag=""
if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then progressFlag="--progress=plain"; fi
importCacheFlags=""
if [ -n "$cacheRefFrom" ]; then
if [ "$cacheType" = "local" ]; then
for ref in $cacheRefFrom; do
importCacheFlags="$importCacheFlags--cache-from=type=local,src=$ref "
done
fi
fi
iid="buildx-tests"
iidfile=$(mktemp -t docker-iidfile.XXXXXXXXXX)
set -x
case $buildmode in
"buildkit")
tmpfile=$(mktemp -t docker-iidfile.XXXXXXXXXX)
buildctl build $progressFlag --frontend=dockerfile.v0 \
--local context=. --local dockerfile=. \
--frontend-opt target=integration-tests \
--output type=docker,name=$iid,dest=$tmpfile
docker load -i $tmpfile
rm $tmpfile
;;
"docker-buildkit")
export DOCKER_BUILDKIT=1
docker build --iidfile $iidfile --target integration-tests --force-rm .
iid=$(cat $iidfile)
;;
*)
echo "docker with buildkit support is required"
exit 1
;;
esac
cacheVolume="buildx-cache"
if ! docker inspect "$cacheVolume" 2>&1 >/dev/null ; then
cacheVolume=$(docker create --name=buildx-cache -v /root/.cache -v /go/pkg/mod alpine)
coverageVol=""
coverageFlags=""
if [ "$TEST_COVERAGE" = "1" ]; then
covdir="$(pwd)/coverage"
mkdir -p "$covdir"
coverageVol="-v $covdir:/coverage"
coverageFlags="-coverprofile=/coverage/coverage.txt -covermode=atomic"
fi
docker run --rm -v /tmp --volumes-from=$cacheVolume --privileged $iid go test ${TESTFLAGS:--v} ${TESTPKGS:-./...}
buildxCmd build $importCacheFlags \
--target "integration-tests" \
--output "type=docker,name=$iid" \
$currentcontext
cacheVolume="buildx-cache"
if ! docker inspect "$cacheVolume" > /dev/null 2>&1; then
cacheVolume=$(docker create --name=buildx-cache -v /root/.cache -v /go/pkg/mod alpine)
fi
docker run --rm -v /tmp $coverageVol --volumes-from=$cacheVolume --privileged $iid go test $coverageFlags ${TESTFLAGS:--v} ${TESTPKGS:-./...}
if [ -n "$BUILDX_NOCACHE" ]; then
docker rm -v $cacheVolume
fi
case $buildmode in
"docker-buildkit")
rm "$iidfile"
docker rmi $iid
;;
esac
rm "$iidfile"
docker rmi $iid

View File

@@ -1,45 +1,16 @@
#!/usr/bin/env bash
. $(dirname $0)/util
set -eu -o pipefail -x
set -eu
: ${CONTINUOUS_INTEGRATION=}
output=$(mktemp -d -t buildx-output.XXXXXXXXXX)
progressFlag=""
if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then progressFlag="--progress=plain"; fi
buildxCmd build \
--target "update" \
--output "type=local,dest=$output" \
--file "./hack/dockerfiles/vendor.Dockerfile" \
.
case $buildmode in
"buildkit")
output=$(mktemp -d -t buildctl-output.XXXXXXXXXX)
buildctl build $progressFlag --frontend=dockerfile.v0 --local context=. --local dockerfile=. \
--frontend-opt target=update \
--frontend-opt filename=./hack/dockerfiles/vendor.Dockerfile \
--output type=local,dest=$output
rm -rf ./vendor
cp -R "$output/out/" .
rm -rf $output
;;
*)
iidfile=$(mktemp -t docker-iidfile.XXXXXXXXXX)
case $buildmode in
"docker-buildkit")
export DOCKER_BUILDKIT=1
docker build --iidfile $iidfile -f ./hack/dockerfiles/vendor.Dockerfile --target update --force-rm .
;;
*)
echo "buildctl or docker with buildkit support is required"
exit 1
;;
esac
iid=$(cat $iidfile)
cid=$(docker create $iid noop)
rm -rf ./vendor
docker cp $cid:/out/go.mod .
docker cp $cid:/out/go.sum .
docker cp $cid:/out/vendor .
docker rm $cid
rm -f $iidfile
;;
esac
rm -rf ./vendor
cp -R "$output"/out/* .
rm -rf $output

View File

@@ -1,32 +1,66 @@
#!/usr/bin/env sh
: ${CI=}
: ${PREFER_BUILDCTL=}
: ${PREFER_LEGACY=}
: ${CLI_PLATFORM=}
: ${GITHUB_ACTIONS=}
: ${CACHEDIR_FROM=}
: ${CACHEDIR_TO=}
newerEqualThan() { # $1=minimum wanted version $2=actual-version
[ "$1" = "$(printf "$1\n$2" | sort -V | head -n 1)" ]
}
buildmode="legacy"
if [ "$PREFER_BUILDCTL" = "1" ]; then
buildmode="buildkit";
else
serverVersion=$(docker info --format '{{.ServerVersion}}')
experimental=$(docker info --format '{{.ExperimentalBuild}}')
if [ "$PREFER_LEGACY" != "1" ] && ( newerEqualThan "18.09" $serverVersion || \
( newerEqualThan "18.06" $serverVersion && [ "true" = "$experimental" ] ) || \
[ "$DOCKER_BUILDKIT" = "1" ]); then
buildmode="docker-buildkit";
fi
echo >&2 "WARNING: PREFER_BUILDCTL is no longer supported. Ignoring."
fi
if [ "$PREFER_LEGACY" = "1" ]; then
echo >&2 "WARNING: PREFER_LEGACY is no longer supported. Ignoring."
fi
progressFlag=""
if [ "$CI" = "true" ]; then
progressFlag="--progress=plain"
fi
buildxCmd() {
if docker buildx version >/dev/null 2>&1; then
set -x
docker buildx "$@" $progressFlag
elif buildx version >/dev/null 2>&1; then
set -x
buildx "$@" $progressFlag
elif docker version >/dev/null 2>&1; then
set -x
DOCKER_BUILDKIT=1 docker "$@" $progressFlag
else
echo >&2 "ERROR: Please enable DOCKER_BUILDKIT or install standalone buildx"
exit 1
fi
}
if [ -z "$CLI_PLATFORM" ]; then
rawos=$(uname -s)
if [ "$rawos" = "Darwin" ]; then
CLI_PLATFORM="darwin/amd64"
elif uname -s | grep MINGW 2>&1 >/dev/null ; then
if [ "$(uname -s)" = "Darwin" ]; then
arch="$(uname -m)"
if [ "$arch" = "x86_64" ]; then
arch="amd64"
fi
CLI_PLATFORM="darwin/$arch"
elif uname -s | grep MINGW > /dev/null 2>&1 ; then
CLI_PLATFORM="windows/amd64"
fi
fi
cacheType=""
cacheRefFrom=""
cacheRefTo=""
currentref=""
if [ "$GITHUB_ACTIONS" = "true" ]; then
currentref="git://github.com/$GITHUB_REPOSITORY#$GITHUB_REF"
cacheType="local"
cacheRefFrom="$CACHEDIR_FROM"
cacheRefTo="$CACHEDIR_TO"
fi
currentcontext="."
if [ -n "$currentref" ]; then
currentcontext="--build-arg BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 $currentref"
fi

View File

@@ -1,35 +1,15 @@
#!/usr/bin/env sh
set -eu
: ${CONTINUOUS_INTEGRATION=}
: ${DOCKER_BUILDKIT=}
progressFlag=""
if [ "$CONTINUOUS_INTEGRATION" = "true" ]; then progressFlag="--progress=plain"; fi
case ${1:-} in
'')
'')
. $(dirname $0)/util
case $buildmode in
"buildkit")
buildctl build $progressFlag --frontend=dockerfile.v0 --local context=. --local dockerfile=. --frontend-opt filename=./hack/dockerfiles/vendor.Dockerfile --frontend-opt target=validate
buildxCmd build \
--target validate \
--file ./hack/dockerfiles/vendor.Dockerfile \
.
;;
"docker-buildkit")
export DOCKER_BUILDKIT=1
iidfile=$(mktemp -t docker-iidfile.XXXXXXXXXX)
docker build --iidfile $iidfile -f ./hack/dockerfiles/vendor.Dockerfile --target validate --force-rm . || exit 1
iid=$(cat $iidfile)
docker rmi $iid
rm -f $iidfile
;;
*)
echo "buildkit support is required"
exit 1
;;
esac
;;
check)
check)
status="$(git status --porcelain -- go.mod go.sum vendor 2>/dev/null)"
diffs=$(echo "$status" | grep -v '^[RAD] ' || true)
if [ "$diffs" ]; then

View File

@@ -13,15 +13,22 @@ type NodeGroup struct {
Name string
Driver string
Nodes []Node
Dynamic bool
}
type Node struct {
Name string
Endpoint string
Platforms []specs.Platform
Flags []string
ConfigFile string
DriverOpts map[string]string
}
func (ng *NodeGroup) Leave(name string) error {
if ng.Dynamic {
return errors.New("dynamic node group does not support Leave")
}
i := ng.findNode(name)
if i == -1 {
return errors.Errorf("node %q not found for %s", name, ng.Name)
@@ -33,7 +40,10 @@ func (ng *NodeGroup) Leave(name string) error {
return nil
}
func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpointsSet bool, actionAppend bool) error {
func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpointsSet bool, actionAppend bool, flags []string, configFile string, do map[string]string) error {
if ng.Dynamic {
return errors.New("dynamic node group does not support Update")
}
i := ng.findNode(name)
if i == -1 && !actionAppend {
if len(ng.Nodes) > 0 {
@@ -55,6 +65,9 @@ func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpoints
if len(platforms) > 0 {
n.Platforms = pp
}
if flags != nil {
n.Flags = flags
}
ng.Nodes[i] = n
if err := ng.validateDuplicates(endpoint, i); err != nil {
return err
@@ -75,6 +88,9 @@ func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpoints
Name: name,
Endpoint: endpoint,
Platforms: pp,
ConfigFile: configFile,
Flags: flags,
DriverOpts: do,
}
ng.Nodes = append(ng.Nodes, n)

View File

@@ -11,16 +11,16 @@ func TestNodeGroupUpdate(t *testing.T) {
t.Parallel()
ng := &NodeGroup{}
err := ng.Update("foo", "foo0", []string{"linux/amd64"}, true, false)
err := ng.Update("foo", "foo0", []string{"linux/amd64"}, true, false, []string{"--debug"}, "", nil)
require.NoError(t, err)
err = ng.Update("foo1", "foo1", []string{"linux/arm64", "linux/arm/v7"}, true, true)
err = ng.Update("foo1", "foo1", []string{"linux/arm64", "linux/arm/v7"}, true, true, nil, "", nil)
require.NoError(t, err)
require.Equal(t, len(ng.Nodes), 2)
// update
err = ng.Update("foo", "foo2", []string{"linux/amd64", "linux/arm"}, true, false)
err = ng.Update("foo", "foo2", []string{"linux/amd64", "linux/arm"}, true, false, nil, "", nil)
require.NoError(t, err)
require.Equal(t, len(ng.Nodes), 2)
@@ -28,9 +28,11 @@ func TestNodeGroupUpdate(t *testing.T) {
require.Equal(t, []string{"linux/arm64"}, platformutil.Format(ng.Nodes[1].Platforms))
require.Equal(t, "foo2", ng.Nodes[0].Endpoint)
require.Equal(t, []string{"--debug"}, ng.Nodes[0].Flags)
require.Equal(t, []string(nil), ng.Nodes[1].Flags)
// duplicate endpoint
err = ng.Update("foo1", "foo2", nil, true, false)
err = ng.Update("foo1", "foo2", nil, true, false, nil, "", nil)
require.Error(t, err)
require.Contains(t, err.Error(), "duplicate endpoint")

View File

@@ -15,7 +15,6 @@ import (
)
func New(root string) (*Store, error) {
root = filepath.Join(root, "buildx")
if err := os.MkdirAll(filepath.Join(root, "instances"), 0700); err != nil {
return nil, err
}

View File

@@ -3,6 +3,8 @@ package imagetools
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"io"
"net/http"
@@ -107,3 +109,26 @@ func toCredentialsFunc(a Auth) func(string) (string, string, error) {
return ac.Username, ac.Password, nil
}
}
func RegistryAuthForRef(ref string, a Auth) (string, error) {
if a == nil {
return "", nil
}
r, err := parseRef(ref)
if err != nil {
return "", err
}
host := reference.Domain(r)
if host == "docker.io" {
host = "https://index.docker.io/v1/"
}
ac, err := a.GetAuthConfig(host)
if err != nil {
return "", err
}
buf, err := json.Marshal(ac)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(buf), nil
}

View File

@@ -53,6 +53,27 @@ func Dedupe(in []specs.Platform) []specs.Platform {
return out
}
func FormatInGroups(gg ...[]specs.Platform) []string {
m := map[string]struct{}{}
out := make([]string, 0, len(gg))
for i, g := range gg {
for _, p := range g {
p := platforms.Normalize(p)
key := platforms.Format(p)
if _, ok := m[key]; ok {
continue
}
m[key] = struct{}{}
v := platforms.Format(p)
if i == 0 {
v += "*"
}
out = append(out, v)
}
}
return out
}
func Format(in []specs.Platform) []string {
if len(in) == 0 {
return nil

View File

@@ -11,7 +11,6 @@ import (
)
func FromReader(w Writer, name string, rc io.ReadCloser) {
status := w.Status()
dgst := digest.FromBytes([]byte(identity.NewID()))
tm := time.Now()
@@ -21,9 +20,9 @@ func FromReader(w Writer, name string, rc io.ReadCloser) {
Started: &tm,
}
status <- &client.SolveStatus{
w.Write(&client.SolveStatus{
Vertexes: []*client.Vertex{&vtx},
}
})
_, err := io.Copy(ioutil.Discard, rc)
@@ -33,8 +32,7 @@ func FromReader(w Writer, name string, rc io.ReadCloser) {
if err != nil {
vtx2.Error = err.Error()
}
status <- &client.SolveStatus{
w.Write(&client.SolveStatus{
Vertexes: []*client.Vertex{&vtx2},
}
close(status)
})
}

View File

@@ -1,101 +1,32 @@
package progress
import (
"context"
"strings"
"sync"
"github.com/moby/buildkit/client"
"golang.org/x/sync/errgroup"
)
type MultiWriter struct {
w Writer
eg *errgroup.Group
once sync.Once
ready chan struct{}
}
func (mw *MultiWriter) WithPrefix(pfx string, force bool) Writer {
in := make(chan *client.SolveStatus)
out := mw.w.Status()
p := &prefixed{
main: mw.w,
in: in,
func WithPrefix(w Writer, pfx string, force bool) Writer {
return &prefixed{
main: w,
pfx: pfx,
force: force,
}
mw.eg.Go(func() error {
mw.once.Do(func() {
close(mw.ready)
})
for {
select {
case v, ok := <-in:
if ok {
if force {
for _, v := range v.Vertexes {
v.Name = addPrefix(pfx, v.Name)
}
}
out <- v
} else {
return nil
}
case <-mw.Done():
return mw.Err()
}
}
})
return p
}
func (mw *MultiWriter) Done() <-chan struct{} {
return mw.w.Done()
}
func (mw *MultiWriter) Err() error {
return mw.w.Err()
}
func (mw *MultiWriter) Status() chan *client.SolveStatus {
return nil
}
type prefixed struct {
main Writer
in chan *client.SolveStatus
pfx string
force bool
}
func (p *prefixed) Done() <-chan struct{} {
return p.main.Done()
}
func (p *prefixed) Err() error {
return p.main.Err()
}
func (p *prefixed) Status() chan *client.SolveStatus {
return p.in
}
func NewMultiWriter(pw Writer) *MultiWriter {
if pw == nil {
return nil
func (p *prefixed) Write(v *client.SolveStatus) {
if p.force {
for _, v := range v.Vertexes {
v.Name = addPrefix(p.pfx, v.Name)
}
eg, _ := errgroup.WithContext(context.TODO())
ready := make(chan struct{})
go func() {
<-ready
eg.Wait()
close(pw.Status())
}()
return &MultiWriter{
w: pw,
eg: eg,
ready: ready,
}
p.main.Write(v)
}
func addPrefix(pfx, name string) string {

View File

@@ -9,32 +9,27 @@ import (
"github.com/moby/buildkit/util/progress/progressui"
)
type printer struct {
type Printer struct {
status chan *client.SolveStatus
done <-chan struct{}
err error
}
func (p *printer) Done() <-chan struct{} {
return p.done
}
func (p *printer) Err() error {
func (p *Printer) Wait() error {
close(p.status)
<-p.done
return p.err
}
func (p *printer) Status() chan *client.SolveStatus {
if p == nil {
return nil
}
return p.status
func (p *Printer) Write(s *client.SolveStatus) {
p.status <- s
}
func NewPrinter(ctx context.Context, out *os.File, mode string) Writer {
func NewPrinter(ctx context.Context, out console.File, mode string) *Printer {
statusCh := make(chan *client.SolveStatus)
doneCh := make(chan struct{})
pw := &printer{
pw := &Printer{
status: statusCh,
done: doneCh,
}

View File

@@ -13,6 +13,7 @@ type Logger func(*client.SolveStatus)
type SubLogger interface {
Wrap(name string, fn func() error) error
Log(stream int, dt []byte)
SetStatus(*client.VertexStatus)
}
func Wrap(name string, l Logger, fn func(SubLogger) error) (err error) {
@@ -88,3 +89,10 @@ func (sl *subLogger) Log(stream int, dt []byte) {
}},
})
}
func (sl *subLogger) SetStatus(st *client.VertexStatus) {
st.Vertex = sl.dgst
sl.logger(&client.SolveStatus{
Statuses: []*client.VertexStatus{st},
})
}

View File

@@ -7,17 +7,10 @@ import (
)
func ResetTime(in Writer) Writer {
w := &pw{Writer: in, status: make(chan *client.SolveStatus), tm: time.Now()}
go func() {
for {
select {
case <-in.Done():
return
case st, ok := <-w.status:
if !ok {
close(in.Status())
return
}
return &pw{Writer: in, status: make(chan *client.SolveStatus), tm: time.Now()}
}
func (w *pw) Write(st *client.SolveStatus) {
if w.diff == nil {
for _, v := range st.Vertexes {
if v.Started != nil {
@@ -52,11 +45,7 @@ func ResetTime(in Writer) Writer {
v.Timestamp = v.Timestamp.Add(-*w.diff)
}
}
in.Status() <- st
}
}
}()
return w
w.Writer.Write(st)
}
type pw struct {

View File

@@ -9,13 +9,10 @@ import (
)
type Writer interface {
Done() <-chan struct{}
Err() error
Status() chan *client.SolveStatus
Write(*client.SolveStatus)
}
func Write(w Writer, name string, f func() error) {
status := w.Status()
dgst := digest.FromBytes([]byte(identity.NewID()))
tm := time.Now()
@@ -25,9 +22,9 @@ func Write(w Writer, name string, f func() error) {
Started: &tm,
}
status <- &client.SolveStatus{
w.Write(&client.SolveStatus{
Vertexes: []*client.Vertex{&vtx},
}
})
err := f()
@@ -37,7 +34,23 @@ func Write(w Writer, name string, f func() error) {
if err != nil {
vtx2.Error = err.Error()
}
status <- &client.SolveStatus{
w.Write(&client.SolveStatus{
Vertexes: []*client.Vertex{&vtx2},
}
})
}
func NewChannel(w Writer) (chan *client.SolveStatus, chan struct{}) {
ch := make(chan *client.SolveStatus)
done := make(chan struct{})
go func() {
for {
v, ok := <-ch
if !ok {
close(done)
return
}
w.Write(v)
}
}()
return ch, done
}

202
vendor/cloud.google.com/go/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

518
vendor/cloud.google.com/go/compute/metadata/metadata.go generated vendored Normal file
View File

@@ -0,0 +1,518 @@
// Copyright 2014 Google LLC
//
// 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 metadata provides access to Google Compute Engine (GCE)
// metadata and API service accounts.
//
// This package is a wrapper around the GCE metadata service,
// as documented at https://developers.google.com/compute/docs/metadata.
package metadata // import "cloud.google.com/go/compute/metadata"
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"runtime"
"strings"
"sync"
"time"
)
const (
// metadataIP is the documented metadata server IP address.
metadataIP = "169.254.169.254"
// metadataHostEnv is the environment variable specifying the
// GCE metadata hostname. If empty, the default value of
// metadataIP ("169.254.169.254") is used instead.
// This is variable name is not defined by any spec, as far as
// I know; it was made up for the Go package.
metadataHostEnv = "GCE_METADATA_HOST"
userAgent = "gcloud-golang/0.1"
)
type cachedValue struct {
k string
trim bool
mu sync.Mutex
v string
}
var (
projID = &cachedValue{k: "project/project-id", trim: true}
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
instID = &cachedValue{k: "instance/id", trim: true}
)
var defaultClient = &Client{hc: &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
},
}}
// NotDefinedError is returned when requested metadata is not defined.
//
// The underlying string is the suffix after "/computeMetadata/v1/".
//
// This error is not returned if the value is defined to be the empty
// string.
type NotDefinedError string
func (suffix NotDefinedError) Error() string {
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
}
func (c *cachedValue) get(cl *Client) (v string, err error) {
defer c.mu.Unlock()
c.mu.Lock()
if c.v != "" {
return c.v, nil
}
if c.trim {
v, err = cl.getTrimmed(c.k)
} else {
v, err = cl.Get(c.k)
}
if err == nil {
c.v = v
}
return
}
var (
onGCEOnce sync.Once
onGCE bool
)
// OnGCE reports whether this process is running on Google Compute Engine.
func OnGCE() bool {
onGCEOnce.Do(initOnGCE)
return onGCE
}
func initOnGCE() {
onGCE = testOnGCE()
}
func testOnGCE() bool {
// The user explicitly said they're on GCE, so trust them.
if os.Getenv(metadataHostEnv) != "" {
return true
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
resc := make(chan bool, 2)
// Try two strategies in parallel.
// See https://github.com/googleapis/google-cloud-go/issues/194
go func() {
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
req.Header.Set("User-Agent", userAgent)
res, err := defaultClient.hc.Do(req.WithContext(ctx))
if err != nil {
resc <- false
return
}
defer res.Body.Close()
resc <- res.Header.Get("Metadata-Flavor") == "Google"
}()
go func() {
addrs, err := net.LookupHost("metadata.google.internal")
if err != nil || len(addrs) == 0 {
resc <- false
return
}
resc <- strsContains(addrs, metadataIP)
}()
tryHarder := systemInfoSuggestsGCE()
if tryHarder {
res := <-resc
if res {
// The first strategy succeeded, so let's use it.
return true
}
// Wait for either the DNS or metadata server probe to
// contradict the other one and say we are running on
// GCE. Give it a lot of time to do so, since the system
// info already suggests we're running on a GCE BIOS.
timer := time.NewTimer(5 * time.Second)
defer timer.Stop()
select {
case res = <-resc:
return res
case <-timer.C:
// Too slow. Who knows what this system is.
return false
}
}
// There's no hint from the system info that we're running on
// GCE, so use the first probe's result as truth, whether it's
// true or false. The goal here is to optimize for speed for
// users who are NOT running on GCE. We can't assume that
// either a DNS lookup or an HTTP request to a blackholed IP
// address is fast. Worst case this should return when the
// metaClient's Transport.ResponseHeaderTimeout or
// Transport.Dial.Timeout fires (in two seconds).
return <-resc
}
// systemInfoSuggestsGCE reports whether the local system (without
// doing network requests) suggests that we're running on GCE. If this
// returns true, testOnGCE tries a bit harder to reach its metadata
// server.
func systemInfoSuggestsGCE() bool {
if runtime.GOOS != "linux" {
// We don't have any non-Linux clues available, at least yet.
return false
}
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
name := strings.TrimSpace(string(slurp))
return name == "Google" || name == "Google Compute Engine"
}
// Subscribe calls Client.Subscribe on the default client.
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
return defaultClient.Subscribe(suffix, fn)
}
// Get calls Client.Get on the default client.
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
// ProjectID returns the current instance's project ID string.
func ProjectID() (string, error) { return defaultClient.ProjectID() }
// NumericProjectID returns the current instance's numeric project ID.
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
// InternalIP returns the instance's primary internal IP address.
func InternalIP() (string, error) { return defaultClient.InternalIP() }
// ExternalIP returns the instance's primary external (public) IP address.
func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
// Email calls Client.Email on the default client.
func Email(serviceAccount string) (string, error) { return defaultClient.Email(serviceAccount) }
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func Hostname() (string, error) { return defaultClient.Hostname() }
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
// InstanceID returns the current VM's numeric instance ID.
func InstanceID() (string, error) { return defaultClient.InstanceID() }
// InstanceName returns the current VM's instance ID string.
func InstanceName() (string, error) { return defaultClient.InstanceName() }
// Zone returns the current VM's zone, such as "us-central1-b".
func Zone() (string, error) { return defaultClient.Zone() }
// InstanceAttributes calls Client.InstanceAttributes on the default client.
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
// ProjectAttributes calls Client.ProjectAttributes on the default client.
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
func InstanceAttributeValue(attr string) (string, error) {
return defaultClient.InstanceAttributeValue(attr)
}
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
func ProjectAttributeValue(attr string) (string, error) {
return defaultClient.ProjectAttributeValue(attr)
}
// Scopes calls Client.Scopes on the default client.
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
func strsContains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}
// A Client provides metadata.
type Client struct {
hc *http.Client
}
// NewClient returns a Client that can be used to fetch metadata.
// Returns the client that uses the specified http.Client for HTTP requests.
// If nil is specified, returns the default client.
func NewClient(c *http.Client) *Client {
if c == nil {
return defaultClient
}
return &Client{hc: c}
}
// getETag returns a value from the metadata service as well as the associated ETag.
// This func is otherwise equivalent to Get.
func (c *Client) getETag(suffix string) (value, etag string, err error) {
// Using a fixed IP makes it very difficult to spoof the metadata service in
// a container, which is an important use-case for local testing of cloud
// deployments. To enable spoofing of the metadata service, the environment
// variable GCE_METADATA_HOST is first inspected to decide where metadata
// requests shall go.
host := os.Getenv(metadataHostEnv)
if host == "" {
// Using 169.254.169.254 instead of "metadata" here because Go
// binaries built with the "netgo" tag and without cgo won't
// know the search suffix for "metadata" is
// ".google.internal", and this IP address is documented as
// being stable anyway.
host = metadataIP
}
u := "http://" + host + "/computeMetadata/v1/" + suffix
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return "", "", err
}
req.Header.Set("Metadata-Flavor", "Google")
req.Header.Set("User-Agent", userAgent)
res, err := c.hc.Do(req)
if err != nil {
return "", "", err
}
defer res.Body.Close()
if res.StatusCode == http.StatusNotFound {
return "", "", NotDefinedError(suffix)
}
all, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", err
}
if res.StatusCode != 200 {
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
}
return string(all), res.Header.Get("Etag"), nil
}
// Get returns a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
//
// If the GCE_METADATA_HOST environment variable is not defined, a default of
// 169.254.169.254 will be used instead.
//
// If the requested metadata is not defined, the returned error will
// be of type NotDefinedError.
func (c *Client) Get(suffix string) (string, error) {
val, _, err := c.getETag(suffix)
return val, err
}
func (c *Client) getTrimmed(suffix string) (s string, err error) {
s, err = c.Get(suffix)
s = strings.TrimSpace(s)
return
}
func (c *Client) lines(suffix string) ([]string, error) {
j, err := c.Get(suffix)
if err != nil {
return nil, err
}
s := strings.Split(strings.TrimSpace(j), "\n")
for i := range s {
s[i] = strings.TrimSpace(s[i])
}
return s, nil
}
// ProjectID returns the current instance's project ID string.
func (c *Client) ProjectID() (string, error) { return projID.get(c) }
// NumericProjectID returns the current instance's numeric project ID.
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
// InstanceID returns the current VM's numeric instance ID.
func (c *Client) InstanceID() (string, error) { return instID.get(c) }
// InternalIP returns the instance's primary internal IP address.
func (c *Client) InternalIP() (string, error) {
return c.getTrimmed("instance/network-interfaces/0/ip")
}
// Email returns the email address associated with the service account.
// The account may be empty or the string "default" to use the instance's
// main account.
func (c *Client) Email(serviceAccount string) (string, error) {
if serviceAccount == "" {
serviceAccount = "default"
}
return c.getTrimmed("instance/service-accounts/" + serviceAccount + "/email")
}
// ExternalIP returns the instance's primary external (public) IP address.
func (c *Client) ExternalIP() (string, error) {
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
}
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func (c *Client) Hostname() (string, error) {
return c.getTrimmed("instance/hostname")
}
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func (c *Client) InstanceTags() ([]string, error) {
var s []string
j, err := c.Get("instance/tags")
if err != nil {
return nil, err
}
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
return nil, err
}
return s, nil
}
// InstanceName returns the current VM's instance ID string.
func (c *Client) InstanceName() (string, error) {
return c.getTrimmed("instance/name")
}
// Zone returns the current VM's zone, such as "us-central1-b".
func (c *Client) Zone() (string, error) {
zone, err := c.getTrimmed("instance/zone")
// zone is of the form "projects/<projNum>/zones/<zoneName>".
if err != nil {
return "", err
}
return zone[strings.LastIndex(zone, "/")+1:], nil
}
// InstanceAttributes returns the list of user-defined attributes,
// assigned when initially creating a GCE VM instance. The value of an
// attribute can be obtained with InstanceAttributeValue.
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
// ProjectAttributes returns the list of user-defined attributes
// applying to the project as a whole, not just this VM. The value of
// an attribute can be obtained with ProjectAttributeValue.
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
// InstanceAttributeValue returns the value of the provided VM
// instance attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// InstanceAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
return c.Get("instance/attributes/" + attr)
}
// ProjectAttributeValue returns the value of the provided
// project attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// ProjectAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
return c.Get("project/attributes/" + attr)
}
// Scopes returns the service account scopes for the given account.
// The account may be empty or the string "default" to use the instance's
// main account.
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
if serviceAccount == "" {
serviceAccount = "default"
}
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
}
// Subscribe subscribes to a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
// The suffix may contain query parameters.
//
// Subscribe calls fn with the latest metadata value indicated by the provided
// suffix. If the metadata value is deleted, fn is called with the empty string
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
// is deleted. Subscribe returns the error value returned from the last call to
// fn, which may be nil when ok == false.
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
const failedSubscribeSleep = time.Second * 5
// First check to see if the metadata value exists at all.
val, lastETag, err := c.getETag(suffix)
if err != nil {
return err
}
if err := fn(val, true); err != nil {
return err
}
ok := true
if strings.ContainsRune(suffix, '?') {
suffix += "&wait_for_change=true&last_etag="
} else {
suffix += "?wait_for_change=true&last_etag="
}
for {
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
if err != nil {
if _, deleted := err.(NotDefinedError); !deleted {
time.Sleep(failedSubscribeSleep)
continue // Retry on other errors.
}
ok = false
}
lastETag = etag
if err := fn(val, ok); err != nil || !ok {
return err
}
}
}
// Error contains an error response from the server.
type Error struct {
// Code is the HTTP response status code.
Code int
// Message is the server response message.
Message string
}
func (e *Error) Error() string {
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
}

View File

@@ -16,6 +16,7 @@ import (
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
type atomicBool int32
@@ -79,6 +80,7 @@ type win32File struct {
wg sync.WaitGroup
wgLock sync.RWMutex
closing atomicBool
socket bool
readDeadline deadlineHandler
writeDeadline deadlineHandler
}
@@ -109,7 +111,13 @@ func makeWin32File(h syscall.Handle) (*win32File, error) {
}
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
return makeWin32File(h)
// If we return the result of makeWin32File directly, it can result in an
// interface-wrapped nil, rather than a nil interface value.
f, err := makeWin32File(h)
if err != nil {
return nil, err
}
return f, nil
}
// closeHandle closes the resources associated with a Win32 handle
@@ -190,6 +198,10 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er
if f.closing.isSet() {
err = ErrFileClosed
}
} else if err != nil && f.socket {
// err is from Win32. Query the overlapped structure to get the winsock error.
var bytes, flags uint32
err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
}
case <-timeout:
cancelIoEx(f.handle, &c.o)
@@ -265,6 +277,10 @@ func (f *win32File) Flush() error {
return syscall.FlushFileBuffers(f.handle)
}
func (f *win32File) Fd() uintptr {
return uintptr(f.handle)
}
func (d *deadlineHandler) set(deadline time.Time) error {
d.setLock.Lock()
defer d.setLock.Unlock()

9
vendor/github.com/Microsoft/go-winio/go.mod generated vendored Normal file
View File

@@ -0,0 +1,9 @@
module github.com/Microsoft/go-winio
go 1.12
require (
github.com/pkg/errors v0.8.1
github.com/sirupsen/logrus v1.4.1
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3
)

18
vendor/github.com/Microsoft/go-winio/go.sum generated vendored Normal file
View File

@@ -0,0 +1,18 @@
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/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

305
vendor/github.com/Microsoft/go-winio/hvsock.go generated vendored Normal file
View File

@@ -0,0 +1,305 @@
package winio
import (
"fmt"
"io"
"net"
"os"
"syscall"
"time"
"unsafe"
"github.com/Microsoft/go-winio/pkg/guid"
)
//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
const (
afHvSock = 34 // AF_HYPERV
socketError = ^uintptr(0)
)
// An HvsockAddr is an address for a AF_HYPERV socket.
type HvsockAddr struct {
VMID guid.GUID
ServiceID guid.GUID
}
type rawHvsockAddr struct {
Family uint16
_ uint16
VMID guid.GUID
ServiceID guid.GUID
}
// Network returns the address's network name, "hvsock".
func (addr *HvsockAddr) Network() string {
return "hvsock"
}
func (addr *HvsockAddr) String() string {
return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID)
}
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
func VsockServiceID(port uint32) guid.GUID {
g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3")
g.Data1 = port
return g
}
func (addr *HvsockAddr) raw() rawHvsockAddr {
return rawHvsockAddr{
Family: afHvSock,
VMID: addr.VMID,
ServiceID: addr.ServiceID,
}
}
func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) {
addr.VMID = raw.VMID
addr.ServiceID = raw.ServiceID
}
// HvsockListener is a socket listener for the AF_HYPERV address family.
type HvsockListener struct {
sock *win32File
addr HvsockAddr
}
// HvsockConn is a connected socket of the AF_HYPERV address family.
type HvsockConn struct {
sock *win32File
local, remote HvsockAddr
}
func newHvSocket() (*win32File, error) {
fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1)
if err != nil {
return nil, os.NewSyscallError("socket", err)
}
f, err := makeWin32File(fd)
if err != nil {
syscall.Close(fd)
return nil, err
}
f.socket = true
return f, nil
}
// ListenHvsock listens for connections on the specified hvsock address.
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) {
l := &HvsockListener{addr: *addr}
sock, err := newHvSocket()
if err != nil {
return nil, l.opErr("listen", err)
}
sa := addr.raw()
err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa)))
if err != nil {
return nil, l.opErr("listen", os.NewSyscallError("socket", err))
}
err = syscall.Listen(sock.handle, 16)
if err != nil {
return nil, l.opErr("listen", os.NewSyscallError("listen", err))
}
return &HvsockListener{sock: sock, addr: *addr}, nil
}
func (l *HvsockListener) opErr(op string, err error) error {
return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err}
}
// Addr returns the listener's network address.
func (l *HvsockListener) Addr() net.Addr {
return &l.addr
}
// Accept waits for the next connection and returns it.
func (l *HvsockListener) Accept() (_ net.Conn, err error) {
sock, err := newHvSocket()
if err != nil {
return nil, l.opErr("accept", err)
}
defer func() {
if sock != nil {
sock.Close()
}
}()
c, err := l.sock.prepareIo()
if err != nil {
return nil, l.opErr("accept", err)
}
defer l.sock.wg.Done()
// AcceptEx, per documentation, requires an extra 16 bytes per address.
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
var addrbuf [addrlen * 2]byte
var bytes uint32
err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o)
_, err = l.sock.asyncIo(c, nil, bytes, err)
if err != nil {
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
}
conn := &HvsockConn{
sock: sock,
}
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
sock = nil
return conn, nil
}
// Close closes the listener, causing any pending Accept calls to fail.
func (l *HvsockListener) Close() error {
return l.sock.Close()
}
/* Need to finish ConnectEx handling
func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) {
sock, err := newHvSocket()
if err != nil {
return nil, err
}
defer func() {
if sock != nil {
sock.Close()
}
}()
c, err := sock.prepareIo()
if err != nil {
return nil, err
}
defer sock.wg.Done()
var bytes uint32
err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o)
_, err = sock.asyncIo(ctx, c, nil, bytes, err)
if err != nil {
return nil, err
}
conn := &HvsockConn{
sock: sock,
remote: *addr,
}
sock = nil
return conn, nil
}
*/
func (conn *HvsockConn) opErr(op string, err error) error {
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
}
func (conn *HvsockConn) Read(b []byte) (int, error) {
c, err := conn.sock.prepareIo()
if err != nil {
return 0, conn.opErr("read", err)
}
defer conn.sock.wg.Done()
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
var flags, bytes uint32
err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil)
n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err)
if err != nil {
if _, ok := err.(syscall.Errno); ok {
err = os.NewSyscallError("wsarecv", err)
}
return 0, conn.opErr("read", err)
} else if n == 0 {
err = io.EOF
}
return n, err
}
func (conn *HvsockConn) Write(b []byte) (int, error) {
t := 0
for len(b) != 0 {
n, err := conn.write(b)
if err != nil {
return t + n, err
}
t += n
b = b[n:]
}
return t, nil
}
func (conn *HvsockConn) write(b []byte) (int, error) {
c, err := conn.sock.prepareIo()
if err != nil {
return 0, conn.opErr("write", err)
}
defer conn.sock.wg.Done()
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
var bytes uint32
err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil)
n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err)
if err != nil {
if _, ok := err.(syscall.Errno); ok {
err = os.NewSyscallError("wsasend", err)
}
return 0, conn.opErr("write", err)
}
return n, err
}
// Close closes the socket connection, failing any pending read or write calls.
func (conn *HvsockConn) Close() error {
return conn.sock.Close()
}
func (conn *HvsockConn) shutdown(how int) error {
err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD)
if err != nil {
return os.NewSyscallError("shutdown", err)
}
return nil
}
// CloseRead shuts down the read end of the socket.
func (conn *HvsockConn) CloseRead() error {
err := conn.shutdown(syscall.SHUT_RD)
if err != nil {
return conn.opErr("close", err)
}
return nil
}
// CloseWrite shuts down the write end of the socket, notifying the other endpoint that
// no more data will be written.
func (conn *HvsockConn) CloseWrite() error {
err := conn.shutdown(syscall.SHUT_WR)
if err != nil {
return conn.opErr("close", err)
}
return nil
}
// LocalAddr returns the local address of the connection.
func (conn *HvsockConn) LocalAddr() net.Addr {
return &conn.local
}
// RemoteAddr returns the remote address of the connection.
func (conn *HvsockConn) RemoteAddr() net.Addr {
return &conn.remote
}
// SetDeadline implements the net.Conn SetDeadline method.
func (conn *HvsockConn) SetDeadline(t time.Time) error {
conn.SetReadDeadline(t)
conn.SetWriteDeadline(t)
return nil
}
// SetReadDeadline implements the net.Conn SetReadDeadline method.
func (conn *HvsockConn) SetReadDeadline(t time.Time) error {
return conn.sock.SetReadDeadline(t)
}
// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
func (conn *HvsockConn) SetWriteDeadline(t time.Time) error {
return conn.sock.SetWriteDeadline(t)
}

View File

@@ -182,13 +182,14 @@ func (s pipeAddress) String() string {
}
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) {
func tryDialPipe(ctx context.Context, path *string, access uint32) (syscall.Handle, error) {
for {
select {
case <-ctx.Done():
return syscall.Handle(0), ctx.Err()
default:
h, err := createFile(*path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
h, err := createFile(*path, access, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
if err == nil {
return h, nil
}
@@ -197,7 +198,7 @@ func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) {
}
// Wait 10 msec and try again. This is a rather simplistic
// view, as we always try each 10 milliseconds.
time.Sleep(time.Millisecond * 10)
time.Sleep(10 * time.Millisecond)
}
}
}
@@ -210,7 +211,7 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
if timeout != nil {
absTimeout = time.Now().Add(*timeout)
} else {
absTimeout = time.Now().Add(time.Second * 2)
absTimeout = time.Now().Add(2 * time.Second)
}
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
conn, err := DialPipeContext(ctx, path)
@@ -223,9 +224,15 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
// DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
// cancellation or timeout.
func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
return DialPipeAccess(ctx, path, syscall.GENERIC_READ|syscall.GENERIC_WRITE)
}
// DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx`
// cancellation or timeout.
func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) {
var err error
var h syscall.Handle
h, err = tryDialPipe(ctx, &path)
h, err = tryDialPipe(ctx, &path, access)
if err != nil {
return nil, err
}

235
vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go generated vendored Normal file
View File

@@ -0,0 +1,235 @@
// Package guid provides a GUID type. The backing structure for a GUID is
// identical to that used by the golang.org/x/sys/windows GUID type.
// There are two main binary encodings used for a GUID, the big-endian encoding,
// and the Windows (mixed-endian) encoding. See here for details:
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
package guid
import (
"crypto/rand"
"crypto/sha1"
"encoding"
"encoding/binary"
"fmt"
"strconv"
"golang.org/x/sys/windows"
)
// Variant specifies which GUID variant (or "type") of the GUID. It determines
// how the entirety of the rest of the GUID is interpreted.
type Variant uint8
// The variants specified by RFC 4122.
const (
// VariantUnknown specifies a GUID variant which does not conform to one of
// the variant encodings specified in RFC 4122.
VariantUnknown Variant = iota
VariantNCS
VariantRFC4122
VariantMicrosoft
VariantFuture
)
// Version specifies how the bits in the GUID were generated. For instance, a
// version 4 GUID is randomly generated, and a version 5 is generated from the
// hash of an input string.
type Version uint8
var _ = (encoding.TextMarshaler)(GUID{})
var _ = (encoding.TextUnmarshaler)(&GUID{})
// GUID represents a GUID/UUID. It has the same structure as
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
// that type. It is defined as its own type so that stringification and
// marshaling can be supported. The representation matches that used by native
// Windows code.
type GUID windows.GUID
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
func NewV4() (GUID, error) {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
return GUID{}, err
}
g := FromArray(b)
g.setVersion(4) // Version 4 means randomly generated.
g.setVariant(VariantRFC4122)
return g, nil
}
// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing)
// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name,
// and the sample code treats it as a series of bytes, so we do the same here.
//
// Some implementations, such as those found on Windows, treat the name as a
// big-endian UTF16 stream of bytes. If that is desired, the string can be
// encoded as such before being passed to this function.
func NewV5(namespace GUID, name []byte) (GUID, error) {
b := sha1.New()
namespaceBytes := namespace.ToArray()
b.Write(namespaceBytes[:])
b.Write(name)
a := [16]byte{}
copy(a[:], b.Sum(nil))
g := FromArray(a)
g.setVersion(5) // Version 5 means generated from a string.
g.setVariant(VariantRFC4122)
return g, nil
}
func fromArray(b [16]byte, order binary.ByteOrder) GUID {
var g GUID
g.Data1 = order.Uint32(b[0:4])
g.Data2 = order.Uint16(b[4:6])
g.Data3 = order.Uint16(b[6:8])
copy(g.Data4[:], b[8:16])
return g
}
func (g GUID) toArray(order binary.ByteOrder) [16]byte {
b := [16]byte{}
order.PutUint32(b[0:4], g.Data1)
order.PutUint16(b[4:6], g.Data2)
order.PutUint16(b[6:8], g.Data3)
copy(b[8:16], g.Data4[:])
return b
}
// FromArray constructs a GUID from a big-endian encoding array of 16 bytes.
func FromArray(b [16]byte) GUID {
return fromArray(b, binary.BigEndian)
}
// ToArray returns an array of 16 bytes representing the GUID in big-endian
// encoding.
func (g GUID) ToArray() [16]byte {
return g.toArray(binary.BigEndian)
}
// FromWindowsArray constructs a GUID from a Windows encoding array of bytes.
func FromWindowsArray(b [16]byte) GUID {
return fromArray(b, binary.LittleEndian)
}
// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows
// encoding.
func (g GUID) ToWindowsArray() [16]byte {
return g.toArray(binary.LittleEndian)
}
func (g GUID) String() string {
return fmt.Sprintf(
"%08x-%04x-%04x-%04x-%012x",
g.Data1,
g.Data2,
g.Data3,
g.Data4[:2],
g.Data4[2:])
}
// FromString parses a string containing a GUID and returns the GUID. The only
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
// format.
func FromString(s string) (GUID, error) {
if len(s) != 36 {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
var g GUID
data1, err := strconv.ParseUint(s[0:8], 16, 32)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data1 = uint32(data1)
data2, err := strconv.ParseUint(s[9:13], 16, 16)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data2 = uint16(data2)
data3, err := strconv.ParseUint(s[14:18], 16, 16)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data3 = uint16(data3)
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
v, err := strconv.ParseUint(s[x:x+2], 16, 8)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data4[i] = uint8(v)
}
return g, nil
}
func (g *GUID) setVariant(v Variant) {
d := g.Data4[0]
switch v {
case VariantNCS:
d = (d & 0x7f)
case VariantRFC4122:
d = (d & 0x3f) | 0x80
case VariantMicrosoft:
d = (d & 0x1f) | 0xc0
case VariantFuture:
d = (d & 0x0f) | 0xe0
case VariantUnknown:
fallthrough
default:
panic(fmt.Sprintf("invalid variant: %d", v))
}
g.Data4[0] = d
}
// Variant returns the GUID variant, as defined in RFC 4122.
func (g GUID) Variant() Variant {
b := g.Data4[0]
if b&0x80 == 0 {
return VariantNCS
} else if b&0xc0 == 0x80 {
return VariantRFC4122
} else if b&0xe0 == 0xc0 {
return VariantMicrosoft
} else if b&0xe0 == 0xe0 {
return VariantFuture
}
return VariantUnknown
}
func (g *GUID) setVersion(v Version) {
g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12)
}
// Version returns the GUID version, as defined in RFC 4122.
func (g GUID) Version() Version {
return Version((g.Data3 & 0xF000) >> 12)
}
// MarshalText returns the textual representation of the GUID.
func (g GUID) MarshalText() ([]byte, error) {
return []byte(g.String()), nil
}
// UnmarshalText takes the textual representation of a GUID, and unmarhals it
// into this GUID.
func (g *GUID) UnmarshalText(text []byte) error {
g2, err := FromString(string(text))
if err != nil {
return err
}
*g = g2
return nil
}

View File

@@ -1,3 +1,3 @@
package winio
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go

151
vendor/github.com/Microsoft/go-winio/vhd/vhd.go generated vendored Normal file
View File

@@ -0,0 +1,151 @@
// +build windows
package vhd
import "syscall"
//go:generate go run mksyscall_windows.go -output zvhd.go vhd.go
//sys createVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) [failretval != 0] = VirtDisk.CreateVirtualDisk
//sys openVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, flags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (err error) [failretval != 0] = VirtDisk.OpenVirtualDisk
//sys detachVirtualDisk(handle syscall.Handle, flags uint32, providerSpecificFlags uint32) (err error) [failretval != 0] = VirtDisk.DetachVirtualDisk
type virtualStorageType struct {
DeviceID uint32
VendorID [16]byte
}
type (
createVirtualDiskFlag uint32
VirtualDiskAccessMask uint32
VirtualDiskFlag uint32
)
const (
// Flags for creating a VHD (not exported)
createVirtualDiskFlagNone createVirtualDiskFlag = 0
createVirtualDiskFlagFullPhysicalAllocation createVirtualDiskFlag = 1
createVirtualDiskFlagPreventWritesToSourceDisk createVirtualDiskFlag = 2
createVirtualDiskFlagDoNotCopyMetadataFromParent createVirtualDiskFlag = 4
// Access Mask for opening a VHD
VirtualDiskAccessNone VirtualDiskAccessMask = 0
VirtualDiskAccessAttachRO VirtualDiskAccessMask = 65536
VirtualDiskAccessAttachRW VirtualDiskAccessMask = 131072
VirtualDiskAccessDetach VirtualDiskAccessMask = 262144
VirtualDiskAccessGetInfo VirtualDiskAccessMask = 524288
VirtualDiskAccessCreate VirtualDiskAccessMask = 1048576
VirtualDiskAccessMetaOps VirtualDiskAccessMask = 2097152
VirtualDiskAccessRead VirtualDiskAccessMask = 851968
VirtualDiskAccessAll VirtualDiskAccessMask = 4128768
VirtualDiskAccessWritable VirtualDiskAccessMask = 3276800
// Flags for opening a VHD
OpenVirtualDiskFlagNone VirtualDiskFlag = 0
OpenVirtualDiskFlagNoParents VirtualDiskFlag = 0x1
OpenVirtualDiskFlagBlankFile VirtualDiskFlag = 0x2
OpenVirtualDiskFlagBootDrive VirtualDiskFlag = 0x4
OpenVirtualDiskFlagCachedIO VirtualDiskFlag = 0x8
OpenVirtualDiskFlagCustomDiffChain VirtualDiskFlag = 0x10
OpenVirtualDiskFlagParentCachedIO VirtualDiskFlag = 0x20
OpenVirtualDiskFlagVhdSetFileOnly VirtualDiskFlag = 0x40
OpenVirtualDiskFlagIgnoreRelativeParentLocator VirtualDiskFlag = 0x80
OpenVirtualDiskFlagNoWriteHardening VirtualDiskFlag = 0x100
)
type createVersion2 struct {
UniqueID [16]byte // GUID
MaximumSize uint64
BlockSizeInBytes uint32
SectorSizeInBytes uint32
ParentPath *uint16 // string
SourcePath *uint16 // string
OpenFlags uint32
ParentVirtualStorageType virtualStorageType
SourceVirtualStorageType virtualStorageType
ResiliencyGUID [16]byte // GUID
}
type createVirtualDiskParameters struct {
Version uint32 // Must always be set to 2
Version2 createVersion2
}
type openVersion2 struct {
GetInfoOnly int32 // bool but 4-byte aligned
ReadOnly int32 // bool but 4-byte aligned
ResiliencyGUID [16]byte // GUID
}
type openVirtualDiskParameters struct {
Version uint32 // Must always be set to 2
Version2 openVersion2
}
// CreateVhdx will create a simple vhdx file at the given path using default values.
func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error {
var (
defaultType virtualStorageType
handle syscall.Handle
)
parameters := createVirtualDiskParameters{
Version: 2,
Version2: createVersion2{
MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024,
BlockSizeInBytes: blockSizeInMb * 1024 * 1024,
},
}
if err := createVirtualDisk(
&defaultType,
path,
uint32(VirtualDiskAccessNone),
nil,
uint32(createVirtualDiskFlagNone),
0,
&parameters,
nil,
&handle); err != nil {
return err
}
if err := syscall.CloseHandle(handle); err != nil {
return err
}
return nil
}
// DetachVhd detaches a mounted container layer vhd found at `path`.
func DetachVhd(path string) error {
handle, err := OpenVirtualDisk(
path,
VirtualDiskAccessNone,
OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator)
if err != nil {
return err
}
defer syscall.CloseHandle(handle)
return detachVirtualDisk(handle, 0, 0)
}
// OpenVirtualDisk obtains a handle to a VHD opened with supplied access mask and flags.
func OpenVirtualDisk(path string, accessMask VirtualDiskAccessMask, flag VirtualDiskFlag) (syscall.Handle, error) {
var (
defaultType virtualStorageType
handle syscall.Handle
)
parameters := openVirtualDiskParameters{Version: 2}
if err := openVirtualDisk(
&defaultType,
path,
uint32(accessMask),
uint32(flag),
&parameters,
&handle); err != nil {
return 0, err
}
return handle, nil
}

99
vendor/github.com/Microsoft/go-winio/vhd/zvhd.go generated vendored Normal file
View File

@@ -0,0 +1,99 @@
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
package vhd
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modVirtDisk = windows.NewLazySystemDLL("VirtDisk.dll")
procCreateVirtualDisk = modVirtDisk.NewProc("CreateVirtualDisk")
procOpenVirtualDisk = modVirtDisk.NewProc("OpenVirtualDisk")
procDetachVirtualDisk = modVirtDisk.NewProc("DetachVirtualDisk")
)
func createVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(path)
if err != nil {
return
}
return _createVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, securityDescriptor, flags, providerSpecificFlags, parameters, o, handle)
}
func _createVirtualDisk(virtualStorageType *virtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) {
r1, _, e1 := syscall.Syscall9(procCreateVirtualDisk.Addr(), 9, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(flags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(handle)))
if r1 != 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func openVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, flags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(path)
if err != nil {
return
}
return _openVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, flags, parameters, handle)
}
func _openVirtualDisk(virtualStorageType *virtualStorageType, path *uint16, virtualDiskAccessMask uint32, flags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (err error) {
r1, _, e1 := syscall.Syscall6(procOpenVirtualDisk.Addr(), 6, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(flags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle)))
if r1 != 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func detachVirtualDisk(handle syscall.Handle, flags uint32, providerSpecificFlags uint32) (err error) {
r1, _, e1 := syscall.Syscall(procDetachVirtualDisk.Addr(), 3, uintptr(handle), uintptr(flags), uintptr(providerSpecificFlags))
if r1 != 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}

View File

@@ -38,6 +38,7 @@ func errnoErr(e syscall.Errno) error {
var (
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
modntdll = windows.NewLazySystemDLL("ntdll.dll")
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
@@ -45,6 +46,7 @@ var (
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procCreateFileW = modkernel32.NewProc("CreateFileW")
@@ -73,6 +75,7 @@ var (
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
procBackupRead = modkernel32.NewProc("BackupRead")
procBackupWrite = modkernel32.NewProc("BackupWrite")
procbind = modws2_32.NewProc("bind")
)
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
@@ -124,6 +127,24 @@ func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err erro
return
}
func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
var _p0 uint32
if wait {
_p0 = 1
} else {
_p0 = 0
}
r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
@@ -527,3 +548,15 @@ func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, p
}
return
}
func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
if r1 == socketError {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}

3
vendor/github.com/Microsoft/hcsshim/CODEOWNERS generated vendored Normal file
View File

@@ -0,0 +1,3 @@
* @microsoft/containerplat
/hcn/* @nagiesek

View File

@@ -12,7 +12,7 @@ plugins = ["grpc", "fieldpath"]
# Paths that should be treated as include roots in relation to the vendor
# directory. These will be calculated with the vendor directory nearest the
# target package.
packages = ["github.com/gogo/protobuf", "github.com/gogo/googleapis"]
packages = ["github.com/gogo/protobuf"]
# Paths that will be added untouched to the end of the includes. We use
# `/usr/local/include` to pickup the common install location of protobuf.
@@ -29,58 +29,25 @@ plugins = ["grpc", "fieldpath"]
"google/protobuf/field_mask.proto" = "github.com/gogo/protobuf/types"
"google/protobuf/timestamp.proto" = "github.com/gogo/protobuf/types"
"google/protobuf/duration.proto" = "github.com/gogo/protobuf/types"
"google/rpc/status.proto" = "github.com/gogo/googleapis/google/rpc"
"github/containerd/cgroups/stats/v1/metrics.proto" = "github.com/containerd/cgroups/stats/v1"
[[overrides]]
prefixes = ["github.com/containerd/containerd/api/events"]
plugins = ["fieldpath"] # disable grpc for this package
[[overrides]]
prefixes = ["github.com/containerd/containerd/api/services/ttrpc/events/v1"]
plugins = ["ttrpc", "fieldpath"]
[[overrides]]
# enable ttrpc and disable fieldpath and grpc for the shim
prefixes = ["github.com/containerd/containerd/runtime/v1/shim/v1", "github.com/containerd/containerd/runtime/v2/task"]
prefixes = ["github.com/Microsoft/hcsshim/internal/shimdiag"]
plugins = ["ttrpc"]
# Aggregrate the API descriptors to lock down API changes.
[[descriptors]]
prefix = "github.com/containerd/containerd/api"
target = "api/next.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
"gogoproto/gogo.proto"
]
# Lock down runhcs config
# Lock down runc config
[[descriptors]]
prefix = "github.com/containerd/containerd/runtime/linux/runctypes"
target = "runtime/linux/runctypes/next.pb.txt"
prefix = "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
target = "cmd/containerd-shim-runhcs-v1/options/next.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
"gogoproto/gogo.proto"
]
[[descriptors]]
prefix = "github.com/containerd/containerd/runtime/v2/runc/options"
target = "runtime/v2/runc/options/next.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
"gogoproto/gogo.proto"
]
[[descriptors]]
prefix = "github.com/containerd/containerd/runtime/v2/runhcs/options"
target = "runtime/v2/runhcs/options/next.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
"gogoproto/gogo.proto"
]
[[descriptors]]
prefix = "github.com/containerd/containerd/windows/hcsshimtypes"
target = "windows/hcsshimtypes/next.pb.txt"
prefix = "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats"
target = "cmd/containerd-shim-runhcs-v1/stats/next.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
"gogoproto/gogo.proto"

View File

@@ -2,7 +2,7 @@
[![Build status](https://ci.appveyor.com/api/projects/status/nbcw28mnkqml0loa/branch/master?svg=true)](https://ci.appveyor.com/project/WindowsVirtualization/hcsshim/branch/master)
This package contains the Golang interface for using the Windows [Host Compute Service](https://blogs.technet.microsoft.com/virtualization/2017/01/27/introducing-the-host-compute-service-hcs/) (HCS) to launch and manage [Windows Containers](https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/). It also contains other helpers and functions for managing Windows Containers such as the Golang interface for the Host Network Service (HNS).
This package contains the Golang interface for using the Windows [Host Compute Service](https://techcommunity.microsoft.com/t5/containers/introducing-the-host-compute-service-hcs/ba-p/382332) (HCS) to launch and manage [Windows Containers](https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/). It also contains other helpers and functions for managing Windows Containers such as the Golang interface for the Host Network Service (HNS).
It is primarily used in the [Moby Project](https://github.com/moby/moby), but it can be freely used by other projects as well.
@@ -16,6 +16,11 @@ When you submit a pull request, a CLA-bot will automatically determine whether y
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
We also ask that contributors [sign their commits](https://git-scm.com/docs/git-commit) using `git commit -s` or `git commit --signoff` to certify they either authored the work themselves or otherwise have permission to use it in this project.
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

View File

@@ -6,24 +6,40 @@ clone_folder: c:\gopath\src\github.com\Microsoft\hcsshim
environment:
GOPATH: c:\gopath
PATH: C:\mingw-w64\x86_64-7.2.0-posix-seh-rt_v5-rev1\mingw64\bin;%GOPATH%\bin;C:\gometalinter-2.0.12-windows-amd64;%PATH%
PATH: "%GOPATH%\\bin;C:\\gometalinter-2.0.12-windows-amd64;%PATH%"
stack: go 1.11
stack: go 1.13.4
build_script:
- appveyor DownloadFile https://github.com/alecthomas/gometalinter/releases/download/v2.0.12/gometalinter-2.0.12-windows-amd64.zip
- 7z x gometalinter-2.0.12-windows-amd64.zip -y -oC:\ > NUL
- gometalinter.exe --config .gometalinter.json ./...
- go build ./cmd/wclayer
- go build ./cmd/containerd-shim-runhcs-v1
- go build ./cmd/runhcs
- go build ./cmd/tar2ext4
- go build ./cmd/wclayer
- go build ./cmd/device-util
- go build ./internal/tools/grantvmgroupaccess
- go build ./internal/tools/uvmboot
- go build ./internal/tools/zapdir
- go test -v ./... -tags admin
- go test -c ./test/functional/ -tags functional
- go test -c ./test/runhcs/ -tags integration
- cd test
- go test -v ./internal -tags admin
- go test -c ./containerd-shim-runhcs-v1/ -tags functional
- go test -c ./cri-containerd/ -tags functional
- go test -c ./functional/ -tags functional
- go test -c ./runhcs/ -tags functional
artifacts:
- path: 'wclayer.exe'
- path: 'containerd-shim-runhcs-v1.exe'
- path: 'runhcs.exe'
- path: 'tar2ext4.exe'
- path: 'functional.test.exe'
- path: 'runhcs.test.exe'
- path: 'device-util.exe'
- path: 'wclayer.exe'
- path: 'grantvmgroupaccess.exe'
- path: 'uvmboot.exe'
- path: 'zapdir.exe'
- path: './test/containerd-shim-runhcs-v1.test.exe'
- path: './test/cri-containerd.test.exe'
- path: './test/functional.test.exe'
- path: './test/runhcs.test.exe'

View File

@@ -1,8 +1,10 @@
package hcsshim
import (
"context"
"fmt"
"os"
"sync"
"time"
"github.com/Microsoft/hcsshim/internal/hcs"
@@ -53,6 +55,9 @@ type ResourceModificationRequestResponse = schema1.ResourceModificationRequestRe
type container struct {
system *hcs.System
waitOnce sync.Once
waitErr error
waitCh chan struct{}
}
// createComputeSystemAdditionalJSON is read from the environment at initialisation
@@ -71,61 +76,87 @@ func CreateContainer(id string, c *ContainerConfig) (Container, error) {
return nil, fmt.Errorf("failed to merge additional JSON '%s': %s", createContainerAdditionalJSON, err)
}
system, err := hcs.CreateComputeSystem(id, fullConfig)
system, err := hcs.CreateComputeSystem(context.Background(), id, fullConfig)
if err != nil {
return nil, err
}
return &container{system}, err
return &container{system: system}, err
}
// OpenContainer opens an existing container by ID.
func OpenContainer(id string) (Container, error) {
system, err := hcs.OpenComputeSystem(id)
system, err := hcs.OpenComputeSystem(context.Background(), id)
if err != nil {
return nil, err
}
return &container{system}, err
return &container{system: system}, err
}
// GetContainers gets a list of the containers on the system that match the query
func GetContainers(q ComputeSystemQuery) ([]ContainerProperties, error) {
return hcs.GetComputeSystems(q)
return hcs.GetComputeSystems(context.Background(), q)
}
// Start synchronously starts the container.
func (container *container) Start() error {
return convertSystemError(container.system.Start(), container)
return convertSystemError(container.system.Start(context.Background()), container)
}
// Shutdown requests a container shutdown, but it may not actually be shutdown until Wait() succeeds.
func (container *container) Shutdown() error {
return convertSystemError(container.system.Shutdown(), container)
err := container.system.Shutdown(context.Background())
if err != nil {
return convertSystemError(err, container)
}
return &ContainerError{Container: container, Err: ErrVmcomputeOperationPending, Operation: "hcsshim::ComputeSystem::Shutdown"}
}
// Terminate requests a container terminate, but it may not actually be terminated until Wait() succeeds.
func (container *container) Terminate() error {
return convertSystemError(container.system.Terminate(), container)
err := container.system.Terminate(context.Background())
if err != nil {
return convertSystemError(err, container)
}
return &ContainerError{Container: container, Err: ErrVmcomputeOperationPending, Operation: "hcsshim::ComputeSystem::Terminate"}
}
// Waits synchronously waits for the container to shutdown or terminate.
func (container *container) Wait() error {
return convertSystemError(container.system.Wait(), container)
err := container.system.Wait()
if err == nil {
err = container.system.ExitError()
}
return convertSystemError(err, container)
}
// WaitTimeout synchronously waits for the container to terminate or the duration to elapse. It
// returns false if timeout occurs.
func (container *container) WaitTimeout(t time.Duration) error {
return convertSystemError(container.system.WaitTimeout(t), container)
func (container *container) WaitTimeout(timeout time.Duration) error {
container.waitOnce.Do(func() {
container.waitCh = make(chan struct{})
go func() {
container.waitErr = container.Wait()
close(container.waitCh)
}()
})
t := time.NewTimer(timeout)
defer t.Stop()
select {
case <-t.C:
return &ContainerError{Container: container, Err: ErrTimeout, Operation: "hcsshim::ComputeSystem::Wait"}
case <-container.waitCh:
return container.waitErr
}
}
// Pause pauses the execution of a container.
func (container *container) Pause() error {
return convertSystemError(container.system.Pause(), container)
return convertSystemError(container.system.Pause(context.Background()), container)
}
// Resume resumes the execution of a container.
func (container *container) Resume() error {
return convertSystemError(container.system.Resume(), container)
return convertSystemError(container.system.Resume(context.Background()), container)
}
// HasPendingUpdates returns true if the container has updates pending to install
@@ -135,7 +166,7 @@ func (container *container) HasPendingUpdates() (bool, error) {
// Statistics returns statistics for the container. This is a legacy v1 call
func (container *container) Statistics() (Statistics, error) {
properties, err := container.system.Properties(schema1.PropertyTypeStatistics)
properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeStatistics)
if err != nil {
return Statistics{}, convertSystemError(err, container)
}
@@ -145,7 +176,7 @@ func (container *container) Statistics() (Statistics, error) {
// ProcessList returns an array of ProcessListItems for the container. This is a legacy v1 call
func (container *container) ProcessList() ([]ProcessListItem, error) {
properties, err := container.system.Properties(schema1.PropertyTypeProcessList)
properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeProcessList)
if err != nil {
return nil, convertSystemError(err, container)
}
@@ -155,7 +186,7 @@ func (container *container) ProcessList() ([]ProcessListItem, error) {
// This is a legacy v1 call
func (container *container) MappedVirtualDisks() (map[int]MappedVirtualDiskController, error) {
properties, err := container.system.Properties(schema1.PropertyTypeMappedVirtualDisk)
properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeMappedVirtualDisk)
if err != nil {
return nil, convertSystemError(err, container)
}
@@ -165,20 +196,20 @@ func (container *container) MappedVirtualDisks() (map[int]MappedVirtualDiskContr
// CreateProcess launches a new process within the container.
func (container *container) CreateProcess(c *ProcessConfig) (Process, error) {
p, err := container.system.CreateProcess(c)
p, err := container.system.CreateProcess(context.Background(), c)
if err != nil {
return nil, convertSystemError(err, container)
}
return &process{p}, nil
return &process{p: p.(*hcs.Process)}, nil
}
// OpenProcess gets an interface to an existing process within the container.
func (container *container) OpenProcess(pid int) (Process, error) {
p, err := container.system.OpenProcess(pid)
p, err := container.system.OpenProcess(context.Background(), pid)
if err != nil {
return nil, convertSystemError(err, container)
}
return &process{p}, nil
return &process{p: p}, nil
}
// Close cleans up any state associated with the container but does not terminate or wait for it.
@@ -188,5 +219,5 @@ func (container *container) Close() error {
// Modify the System
func (container *container) Modify(config *ResourceModificationRequestResponse) error {
return convertSystemError(container.system.Modify(config), container)
return convertSystemError(container.system.Modify(context.Background(), config), container)
}

35
vendor/github.com/Microsoft/hcsshim/go.mod generated vendored Normal file
View File

@@ -0,0 +1,35 @@
module github.com/Microsoft/hcsshim
go 1.13
require (
github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1
github.com/containerd/containerd v1.3.2
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc // indirect
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 // indirect
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd
github.com/gogo/protobuf v1.3.1
github.com/golang/protobuf v1.3.2 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2 // indirect
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f // indirect
github.com/opencontainers/runtime-spec v1.0.2
github.com/pkg/errors v0.9.1
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7 // indirect
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.4.0 // indirect
github.com/urfave/cli v1.22.2
go.opencensus.io v0.22.0
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 // indirect
golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 // indirect
google.golang.org/grpc v1.23.1
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
gotest.tools v2.2.0+incompatible // indirect
)

157
vendor/github.com/Microsoft/hcsshim/go.sum generated vendored Normal file
View File

@@ -0,0 +1,157 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab h1:9pygWVFqbY9lPxM0peffumuVDyMuIMzNLyO9uFjJuQo=
github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59 h1:qWj4qVYZ95vLWwqyNJCQg7rDsG5wPdze0UaPolH7DUk=
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1 h1:uict5mhHFTzKLUCufdSLym7z/J0CbBJT59lYbP9wtbg=
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA=
github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 h1:PUD50EuOMkXVcpBIA/R95d56duJR9VxhwncsFbNnxW4=
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3 h1:esQOJREg8nw8aXj6uCN5dfW5cKUBiEJ/+nni1Q/D/sw=
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de h1:dlfGmNcE3jDAecLqwKPMNX6nk2qh1c1Vg1/YTzpOOF4=
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd h1:JNn81o/xG+8NEo3bC/vx9pbi/g2WI8mtP2/nXzu297Y=
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2 h1:QhPf3A2AZW3tTGvHPg0TA+CR3oHbVLlXUhlghqISp1I=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f h1:a969LJ4IQFwRHYqonHtUDMSh9i54WcKggeEkQ3fZMl4=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7 h1:hhvfGDVThBnd4kYisSFmYuHYeUhglxcwag7FhVPH9zM=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 h1:KaQtG+aDELoNmXYas3TVkGNYRuq8JQ1aa7LJt8EXVyo=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479 h1:LhLiKguPgZL+Tglay4GhVtfF0kb8cvOJ0dHTCBO8YNI=
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -39,11 +39,21 @@ func HNSListEndpointRequest() ([]HNSEndpoint, error) {
// HotAttachEndpoint makes a HCS Call to attach the endpoint to the container
func HotAttachEndpoint(containerID string, endpointID string) error {
endpoint, err := GetHNSEndpointByID(endpointID)
isAttached, err := endpoint.IsAttached(containerID)
if isAttached {
return err
}
return modifyNetworkEndpoint(containerID, endpointID, Add)
}
// HotDetachEndpoint makes a HCS Call to detach the endpoint from the container
func HotDetachEndpoint(containerID string, endpointID string) error {
endpoint, err := GetHNSEndpointByID(endpointID)
isAttached, err := endpoint.IsAttached(containerID)
if !isAttached {
return err
}
return modifyNetworkEndpoint(containerID, endpointID, Remove)
}

View File

@@ -21,8 +21,11 @@ const (
OutboundNat = hns.OutboundNat
ExternalLoadBalancer = hns.ExternalLoadBalancer
Route = hns.Route
Proxy = hns.Proxy
)
type ProxyPolicy = hns.ProxyPolicy
type NatPolicy = hns.NatPolicy
type QosPolicy = hns.QosPolicy

View File

@@ -0,0 +1,83 @@
package cow
import (
"context"
"io"
"github.com/Microsoft/hcsshim/internal/schema1"
hcsschema "github.com/Microsoft/hcsshim/internal/schema2"
)
// Process is the interface for an OS process running in a container or utility VM.
type Process interface {
// Close releases resources associated with the process and closes the
// writer and readers returned by Stdio. Depending on the implementation,
// this may also terminate the process.
Close() error
// CloseStdin causes the process's stdin handle to receive EOF/EPIPE/whatever
// is appropriate to indicate that no more data is available.
CloseStdin(ctx context.Context) error
// Pid returns the process ID.
Pid() int
// Stdio returns the stdio streams for a process. These may be nil if a stream
// was not requested during CreateProcess.
Stdio() (_ io.Writer, _ io.Reader, _ io.Reader)
// ResizeConsole resizes the virtual terminal associated with the process.
ResizeConsole(ctx context.Context, width, height uint16) error
// Kill sends a SIGKILL or equivalent signal to the process and returns whether
// the signal was delivered. It does not wait for the process to terminate.
Kill(ctx context.Context) (bool, error)
// Signal sends a signal to the process and returns whether the signal was
// delivered. The input is OS specific (either
// guestrequest.SignalProcessOptionsWCOW or
// guestrequest.SignalProcessOptionsLCOW). It does not wait for the process
// to terminate.
Signal(ctx context.Context, options interface{}) (bool, error)
// Wait waits for the process to complete, or for a connection to the process to be
// terminated by some error condition (including calling Close).
Wait() error
// ExitCode returns the exit code of the process. Returns an error if the process is
// not running.
ExitCode() (int, error)
}
// ProcessHost is the interface for creating processes.
type ProcessHost interface {
// CreateProcess creates a process. The configuration is host specific
// (either hcsschema.ProcessParameters or lcow.ProcessParameters).
CreateProcess(ctx context.Context, config interface{}) (Process, error)
// OS returns the host's operating system, "linux" or "windows".
OS() string
// IsOCI specifies whether this is an OCI-compliant process host. If true,
// then the configuration passed to CreateProcess should have an OCI process
// spec (or nil if this is the initial process in an OCI container).
// Otherwise, it should have the HCS-specific process parameters.
IsOCI() bool
}
// Container is the interface for container objects, either running on the host or
// in a utility VM.
type Container interface {
ProcessHost
// Close releases the resources associated with the container. Depending on
// the implementation, this may also terminate the container.
Close() error
// ID returns the container ID.
ID() string
// Properties returns the requested container properties targeting a V1 schema container.
Properties(ctx context.Context, types ...schema1.PropertyType) (*schema1.ContainerProperties, error)
// PropertiesV2 returns the requested container properties targeting a V2 schema container.
PropertiesV2(ctx context.Context, types ...hcsschema.PropertyType) (*hcsschema.Properties, error)
// Start starts a container.
Start(ctx context.Context) error
// Shutdown sends a shutdown request to the container (but does not wait for
// the shutdown to complete).
Shutdown(ctx context.Context) error
// Terminate sends a terminate request to the container (but does not wait
// for the terminate to complete).
Terminate(ctx context.Context) error
// Wait waits for the container to terminate, or for the connection to the
// container to be terminated by some error condition (including calling
// Close).
Wait() error
}

View File

@@ -1,100 +0,0 @@
package guestrequest
import (
"github.com/Microsoft/hcsshim/internal/schema2"
)
// Arguably, many of these (at least CombinedLayers) should have been generated
// by swagger.
//
// This will also change package name due to an inbound breaking change.
// This class is used by a modify request to add or remove a combined layers
// structure in the guest. For windows, the GCS applies a filter in ContainerRootPath
// using the specified layers as the parent content. Ignores property ScratchPath
// since the container path is already the scratch path. For linux, the GCS unions
// the specified layers and ScratchPath together, placing the resulting union
// filesystem at ContainerRootPath.
type CombinedLayers struct {
ContainerRootPath string `json:"ContainerRootPath,omitempty"`
Layers []hcsschema.Layer `json:"Layers,omitempty"`
ScratchPath string `json:"ScratchPath,omitempty"`
}
// Defines the schema for hosted settings passed to GCS and/or OpenGCS
// SCSI. Scratch space for remote file-system commands, or R/W layer for containers
type LCOWMappedVirtualDisk struct {
MountPath string `json:"MountPath,omitempty"` // /tmp/scratch for an LCOW utility VM being used as a service VM
Lun uint8 `json:"Lun,omitempty"`
Controller uint8 `json:"Controller,omitempty"`
ReadOnly bool `json:"ReadOnly,omitempty"`
}
type WCOWMappedVirtualDisk struct {
ContainerPath string `json:"ContainerPath,omitempty"`
Lun int32 `json:"Lun,omitempty"`
}
type LCOWMappedDirectory struct {
MountPath string `json:"MountPath,omitempty"`
Port int32 `json:"Port,omitempty"`
ShareName string `json:"ShareName,omitempty"` // If empty not using ANames (not currently supported)
ReadOnly bool `json:"ReadOnly,omitempty"`
}
// Read-only layers over VPMem
type LCOWMappedVPMemDevice struct {
DeviceNumber uint32 `json:"DeviceNumber,omitempty"`
MountPath string `json:"MountPath,omitempty"` // /tmp/pN
}
type LCOWNetworkAdapter struct {
NamespaceID string `json:",omitempty"`
ID string `json:",omitempty"`
MacAddress string `json:",omitempty"`
IPAddress string `json:",omitempty"`
PrefixLength uint8 `json:",omitempty"`
GatewayAddress string `json:",omitempty"`
DNSSuffix string `json:",omitempty"`
DNSServerList string `json:",omitempty"`
EnableLowMetric bool `json:",omitempty"`
EncapOverhead uint16 `json:",omitempty"`
}
type ResourceType string
const (
// These are constants for v2 schema modify guest requests.
ResourceTypeMappedDirectory ResourceType = "MappedDirectory"
ResourceTypeMappedVirtualDisk ResourceType = "MappedVirtualDisk"
ResourceTypeNetwork ResourceType = "Network"
ResourceTypeNetworkNamespace ResourceType = "NetworkNamespace"
ResourceTypeCombinedLayers ResourceType = "CombinedLayers"
ResourceTypeVPMemDevice ResourceType = "VPMemDevice"
)
// GuestRequest is for modify commands passed to the guest.
type GuestRequest struct {
RequestType string `json:"RequestType,omitempty"`
ResourceType ResourceType `json:"ResourceType,omitempty"`
Settings interface{} `json:"Settings,omitempty"`
}
type NetworkModifyRequest struct {
AdapterId string `json:"AdapterId,omitempty"`
RequestType string `json:"RequestType,omitempty"`
Settings interface{} `json:"Settings,omitempty"`
}
type RS4NetworkModifyRequest struct {
AdapterInstanceId string `json:"AdapterInstanceId,omitempty"`
RequestType string `json:"RequestType,omitempty"`
Settings interface{} `json:"Settings,omitempty"`
}
// SignalProcessOptions is the options passed to either WCOW or LCOW
// to signal a given process.
type SignalProcessOptions struct {
Signal int `json:,omitempty`
}

View File

@@ -1,69 +0,0 @@
package guid
import (
"crypto/rand"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
)
var _ = (json.Marshaler)(&GUID{})
var _ = (json.Unmarshaler)(&GUID{})
type GUID [16]byte
func New() GUID {
g := GUID{}
_, err := io.ReadFull(rand.Reader, g[:])
if err != nil {
panic(err)
}
return g
}
func (g GUID) String() string {
return fmt.Sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x-%02x", g[3], g[2], g[1], g[0], g[5], g[4], g[7], g[6], g[8:10], g[10:])
}
func FromString(s string) GUID {
if len(s) != 36 {
panic(fmt.Sprintf("invalid GUID length: %d", len(s)))
}
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
panic("invalid GUID format")
}
indexOrder := [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34,
}
byteOrder := [16]int{
3, 2, 1, 0,
5, 4,
7, 6,
8, 9,
10, 11, 12, 13, 14, 15,
}
var g GUID
for i, x := range indexOrder {
b, err := strconv.ParseInt(s[x:x+2], 16, 16)
if err != nil {
panic(err)
}
g[byteOrder[i]] = byte(b)
}
return g
}
func (g GUID) MarshalJSON() ([]byte, error) {
return json.Marshal(g.String())
}
func (g *GUID) UnmarshalJSON(data []byte) error {
*g = FromString(strings.Trim(string(data), "\""))
return nil
}

View File

@@ -1,10 +1,13 @@
package hcs
import (
"fmt"
"sync"
"syscall"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/Microsoft/hcsshim/internal/logfields"
"github.com/Microsoft/hcsshim/internal/vmcompute"
"github.com/sirupsen/logrus"
)
@@ -40,35 +43,83 @@ var (
)
type hcsNotification uint32
func (hn hcsNotification) String() string {
switch hn {
case hcsNotificationSystemExited:
return "SystemExited"
case hcsNotificationSystemCreateCompleted:
return "SystemCreateCompleted"
case hcsNotificationSystemStartCompleted:
return "SystemStartCompleted"
case hcsNotificationSystemPauseCompleted:
return "SystemPauseCompleted"
case hcsNotificationSystemResumeCompleted:
return "SystemResumeCompleted"
case hcsNotificationSystemCrashReport:
return "SystemCrashReport"
case hcsNotificationSystemSiloJobCreated:
return "SystemSiloJobCreated"
case hcsNotificationSystemSaveCompleted:
return "SystemSaveCompleted"
case hcsNotificationSystemRdpEnhancedModeStateChanged:
return "SystemRdpEnhancedModeStateChanged"
case hcsNotificationSystemShutdownFailed:
return "SystemShutdownFailed"
case hcsNotificationSystemGetPropertiesCompleted:
return "SystemGetPropertiesCompleted"
case hcsNotificationSystemModifyCompleted:
return "SystemModifyCompleted"
case hcsNotificationSystemCrashInitiated:
return "SystemCrashInitiated"
case hcsNotificationSystemGuestConnectionClosed:
return "SystemGuestConnectionClosed"
case hcsNotificationProcessExited:
return "ProcessExited"
case hcsNotificationInvalid:
return "Invalid"
case hcsNotificationServiceDisconnect:
return "ServiceDisconnect"
default:
return fmt.Sprintf("Unknown: %d", hn)
}
}
type notificationChannel chan error
type notifcationWatcherContext struct {
channels notificationChannels
handle hcsCallback
handle vmcompute.HcsCallback
systemID string
processID int
}
type notificationChannels map[hcsNotification]notificationChannel
func newChannels() notificationChannels {
func newSystemChannels() notificationChannels {
channels := make(notificationChannels)
for _, notif := range []hcsNotification{
hcsNotificationServiceDisconnect,
hcsNotificationSystemExited,
hcsNotificationSystemCreateCompleted,
hcsNotificationSystemStartCompleted,
hcsNotificationSystemPauseCompleted,
hcsNotificationSystemResumeCompleted,
} {
channels[notif] = make(notificationChannel, 1)
}
return channels
}
channels[hcsNotificationSystemExited] = make(notificationChannel, 1)
channels[hcsNotificationSystemCreateCompleted] = make(notificationChannel, 1)
channels[hcsNotificationSystemStartCompleted] = make(notificationChannel, 1)
channels[hcsNotificationSystemPauseCompleted] = make(notificationChannel, 1)
channels[hcsNotificationSystemResumeCompleted] = make(notificationChannel, 1)
channels[hcsNotificationProcessExited] = make(notificationChannel, 1)
channels[hcsNotificationServiceDisconnect] = make(notificationChannel, 1)
channels[hcsNotificationSystemCrashReport] = make(notificationChannel, 1)
channels[hcsNotificationSystemSiloJobCreated] = make(notificationChannel, 1)
channels[hcsNotificationSystemSaveCompleted] = make(notificationChannel, 1)
channels[hcsNotificationSystemRdpEnhancedModeStateChanged] = make(notificationChannel, 1)
channels[hcsNotificationSystemShutdownFailed] = make(notificationChannel, 1)
channels[hcsNotificationSystemGetPropertiesCompleted] = make(notificationChannel, 1)
channels[hcsNotificationSystemModifyCompleted] = make(notificationChannel, 1)
channels[hcsNotificationSystemCrashInitiated] = make(notificationChannel, 1)
channels[hcsNotificationSystemGuestConnectionClosed] = make(notificationChannel, 1)
func newProcessChannels() notificationChannels {
channels := make(notificationChannels)
for _, notif := range []hcsNotification{
hcsNotificationServiceDisconnect,
hcsNotificationProcessExited,
} {
channels[notif] = make(notificationChannel, 1)
}
return channels
}
@@ -92,12 +143,17 @@ func notificationWatcher(notificationType hcsNotification, callbackNumber uintpt
return 0
}
log := logrus.WithFields(logrus.Fields{
"notification-type": notificationType.String(),
"system-id": context.systemID,
})
if context.processID != 0 {
log.Data[logfields.ProcessID] = context.processID
}
log.Debug("HCS notification")
if channel, ok := context.channels[notificationType]; ok {
channel <- result
} else {
logrus.WithFields(logrus.Fields{
"notification-type": notificationType,
}).Warn("Received a callback of an unsupported type")
}
return 0

View File

@@ -1,7 +0,0 @@
package hcs
import "C"
// This import is needed to make the library compile as CGO because HCSSHIM
// only works with CGO due to callbacks from HCS comming back from a C thread
// which is not supported without CGO. See https://github.com/golang/go/issues/10973

View File

@@ -1,14 +1,14 @@
package hcs
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"syscall"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/Microsoft/hcsshim/internal/logfields"
"github.com/sirupsen/logrus"
"github.com/Microsoft/hcsshim/internal/log"
)
var (
@@ -117,17 +117,11 @@ func (ev *ErrorEvent) String() string {
return evs
}
func processHcsResult(resultp *uint16) []ErrorEvent {
if resultp != nil {
resultj := interop.ConvertAndFreeCoTaskMemString(resultp)
logrus.WithField(logfields.JSON, resultj).
Debug("HCS Result")
func processHcsResult(ctx context.Context, resultJSON string) []ErrorEvent {
if resultJSON != "" {
result := &hcsResult{}
if err := json.Unmarshal([]byte(resultj), result); err != nil {
logrus.WithFields(logrus.Fields{
logfields.JSON: resultj,
logrus.ErrorKey: err,
}).Warning("Could not unmarshal HCS result")
if err := json.Unmarshal([]byte(resultJSON), result); err != nil {
log.G(ctx).WithError(err).Warning("Could not unmarshal HCS result")
return nil
}
return result.ErrorEvents
@@ -141,6 +135,8 @@ type HcsError struct {
Events []ErrorEvent
}
var _ net.Error = &HcsError{}
func (e *HcsError) Error() string {
s := e.Op + ": " + e.Err.Error()
for _, ev := range e.Events {
@@ -149,6 +145,16 @@ func (e *HcsError) Error() string {
return s
}
func (e *HcsError) Temporary() bool {
err, ok := e.Err.(net.Error)
return ok && err.Temporary()
}
func (e *HcsError) Timeout() bool {
err, ok := e.Err.(net.Error)
return ok && err.Timeout()
}
// ProcessError is an error encountered in HCS during an operation on a Process object
type ProcessError struct {
SystemID string
@@ -158,6 +164,8 @@ type ProcessError struct {
Events []ErrorEvent
}
var _ net.Error = &ProcessError{}
// SystemError is an error encountered in HCS during an operation on a Container object
type SystemError struct {
ID string
@@ -167,6 +175,8 @@ type SystemError struct {
Events []ErrorEvent
}
var _ net.Error = &SystemError{}
func (e *SystemError) Error() string {
s := e.Op + " " + e.ID + ": " + e.Err.Error()
for _, ev := range e.Events {
@@ -178,6 +188,16 @@ func (e *SystemError) Error() string {
return s
}
func (e *SystemError) Temporary() bool {
err, ok := e.Err.(net.Error)
return ok && err.Temporary()
}
func (e *SystemError) Timeout() bool {
err, ok := e.Err.(net.Error)
return ok && err.Timeout()
}
func makeSystemError(system *System, op string, extra string, err error, events []ErrorEvent) error {
// Don't double wrap errors
if _, ok := err.(*SystemError); ok {
@@ -200,6 +220,16 @@ func (e *ProcessError) Error() string {
return s
}
func (e *ProcessError) Temporary() bool {
err, ok := e.Err.(net.Error)
return ok && err.Temporary()
}
func (e *ProcessError) Timeout() bool {
err, ok := e.Err.(net.Error)
return ok && err.Timeout()
}
func makeProcessError(process *Process, op string, err error, events []ErrorEvent) error {
// Don't double wrap errors
if _, ok := err.(*ProcessError); ok {
@@ -242,6 +272,9 @@ func IsPending(err error) bool {
// IsTimeout returns a boolean indicating whether the error is caused by
// a timeout waiting for the operation to complete.
func IsTimeout(err error) bool {
if err, ok := err.(net.Error); ok && err.Timeout() {
return true
}
err = getInnerError(err)
return err == ErrTimeout
}
@@ -272,6 +305,13 @@ func IsNotSupported(err error) bool {
err == ErrVmcomputeUnknownMessage
}
// IsOperationInvalidState returns true when err is caused by
// `ErrVmcomputeOperationInvalidState`.
func IsOperationInvalidState(err error) bool {
err = getInnerError(err)
return err == ErrVmcomputeOperationInvalidState
}
func getInnerError(err error) error {
switch pe := err.(type) {
case nil:
@@ -285,3 +325,12 @@ func getInnerError(err error) error {
}
return err
}
func getOperationLogResult(err error) (string, error) {
switch err {
case nil:
return "Success", nil
default:
return "Error", err
}
}

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