Compare commits

...

95 Commits

Author SHA1 Message Date
Tõnis Tiigi 0bed0b5653
Merge pull request #3242 from rrjjvv/new-bakefile-env-var
Allow bake files to be specified via environment variable
2025-06-25 08:54:04 -07:00
CrazyMax b034cff8c2
Merge pull request #3268 from thaJeztah/bump_engine
vendor: github.com/docker/docker, github.com/docker/cli v28.3.0
2025-06-25 09:38:40 +02:00
CrazyMax fdb0ebc6cb
Merge pull request #3252 from thaJeztah/docker_28.3
Dockerfile: update to docker v28.3.0
2025-06-25 09:26:55 +02:00
Sebastiaan van Stijn 25a9ad6abd
vendor: github.com/docker/cli v28.3.0
full diff: https://github.com/docker/cli/compare/v28.2.2...v28.3.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-25 09:21:46 +02:00
Sebastiaan van Stijn a11757121a
vendor: github.com/docker/docker v28.3.0
full diff: https://github.com/docker/docker/compare/v28.2.2...v28.3.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-25 09:17:40 +02:00
Sebastiaan van Stijn 7a05ca4547
Dockerfile: update to docker v28.3.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-25 09:06:20 +02:00
Tõnis Tiigi 63bb3db985
Merge pull request #3264 from crazy-max/fix-args-history
history: fix required args for inspect attachment command
2025-06-24 11:13:05 -07:00
Tõnis Tiigi fba5d5e554
Merge pull request #3265 from crazy-max/update-govulncheck
dockerfile: update govulncheck to v1.1.4
2025-06-24 11:12:20 -07:00
CrazyMax 179aad79b5
history: fix required args for inspect attachment command
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-06-24 16:06:54 +02:00
Roberto Villarreal d44ffb4bd4 Display source of bake definitions when read from environment
While it would make sense to add "from file" to complement "from env,"
 (in the common case of `--file` or using the default), it wouldn't
 provide any real value.

A simpler solution would have been looking for the existence of the
variable at the point where printing happens.  It felt wrong
duplicating the logic.  Executing the same logic (if it was extracted)
wouldn't be as bad, but still not ideal.

A 'correct' solution would be to explicitly track the source of each
definition, which would be clearer and more future-proof.  It didn't
seem like this feature warranted that amount of engineering (with no
known features that might make use of it).

This implementation seemed like a fair compromise; none of the functions
 are exported, and all have only one caller.

I also considered converting prefixing environment values with `env://`
so they could be thought of (and processed like) `cmd://` values.  I
didn't think it would be viewed as a good solution.

Co-authored-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
Signed-off-by: Roberto Villarreal <rrjjvv@yahoo.com>
2025-06-24 00:32:37 -06:00
Tõnis Tiigi 4c1e7b2119
Merge pull request #3258 from crazy-max/docs-fix-history-attachment
docs: fix history inspect attachment examples
2025-06-23 16:48:09 -07:00
CrazyMax 2d3a9ef229
dockerfile: update govulncheck to v1.1.4
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-06-23 09:56:45 +02:00
CrazyMax ec45eb6ebc
docs: fix history inspect attachment examples
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-06-23 09:40:13 +02:00
CrazyMax e9b6a01aef
Merge pull request #3259 from crazy-max/build-metadata-provenance-02
build: fix buildx.build.provenance metadata
2025-06-23 09:23:37 +02:00
Tõnis Tiigi c48ccdee36
Merge pull request #3262 from crazy-max/buildkit-0.23.1
dockerfile: update buildkit to 0.23.1
2025-06-20 13:11:18 -07:00
CrazyMax 22f776f664
Merge pull request #3253 from samifruit514/master
driver kubernetes: allow to work in a Memory mount to speed up things
2025-06-20 16:13:19 +02:00
CrazyMax 8da4f0fe64
dockerfile: update buildkit to 0.23.1
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-06-20 11:49:38 +02:00
CrazyMax 2588b66fd9
build: fix buildx.build.provenance metadata
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-06-19 18:47:27 +02:00
CrazyMax 931e714919
vendor: github.com/moby/buildkit 9b91d20
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-06-19 18:47:27 +02:00
Samuel Archambault d5f914a263 driver kubernetes: allow to work in a Memory mount to speed up things
Signed-off-by: Samuel Archambault <samuel.archambault@getmaintainx.com>
2025-06-18 14:49:54 -04:00
Tõnis Tiigi d09eb752a5
Merge pull request #3256 from jsternberg/buildkit-bump
dockerfile: update buildkit to 0.23.0
2025-06-17 17:42:39 -07:00
Jonathan A. Sternberg 3c2decea38
dockerfile: update buildkit to 0.23.0
Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-17 13:29:22 -05:00
Tõnis Tiigi 18041a5855
Merge pull request #3254 from crazy-max/buildkit-0.23.0
vendor: update buildkit v0.23.0
2025-06-17 08:32:22 -07:00
CrazyMax 96ebe9d9a9
vendor: update buildkit v0.23.0
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-06-17 15:31:32 +02:00
Tõnis Tiigi 08dd378b59
Merge pull request #3249 from tonistiigi/update-buildkit-v0.23.0-rc2
vendor: update buildkit v0.23.0-rc2
2025-06-16 14:31:08 -07:00
Tonis Tiigi cb29cd0efb
vendor: update buildkit v0.23.0-rc2
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2025-06-16 13:42:31 -07:00
Tõnis Tiigi 99f1c4b15c
Merge pull request #3245 from crazy-max/history-slsa-check
history: slsa v1 support
2025-06-16 10:53:26 -07:00
Tõnis Tiigi 77e4a88781
Merge pull request #3248 from jsternberg/printer-bake-wait-fix
progress: ensure bake waits for progress to finish printing on error conditions
2025-06-16 10:52:50 -07:00
Jonathan A. Sternberg 7660acf9c7
progress: ensure bake waits for progress to finish printing on error conditions
Some minor fixes to the printer and how bake invokes it. Bake previously
had a race condition that could result in the display not updating on an
error condition, but it was much rarer because the channel communication
was much closer. The refactor added a proxy for the status channel so
there was more of an opportunity to surface the race condition.

When bake exits with an error when reading the bakefiles, it doesn't
wait for the printer to finish so it is possible for the printer to
update the display after an error is printed. This adds an extra `Wait`
in a defer to make sure the printer is finished.

`Wait` has also been fixed to allow it to be called multiple times and
have the same behavior. Previously, it only waited for the done channel
once so only the first wait would block.

The `onclose` method is now called every time the display is paused or
stopped. That was the previous behavior and it's been restored here.

The display only gets refreshed if we aren't exiting. There's no point
in initializing another display if we're about to exit.

The metric writer attached to the printer was erroneously removed. It is
now assigned properly.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-16 12:24:04 -05:00
Tõnis Tiigi 03737f11bc
Merge pull request #3244 from crazy-max/bake-extra-hosts-multi-ip
bake: multi ips support for extra hosts
2025-06-16 09:21:39 -07:00
CrazyMax 4a22b92775
history: slsa v1 support
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-06-16 16:23:20 +02:00
CrazyMax ba782f195b
Merge pull request #3236 from docker/dependabot/github_actions/softprops/action-gh-release-2.3.2
build(deps): bump softprops/action-gh-release from 2.2.2 to 2.3.2
2025-06-16 13:38:29 +02:00
CrazyMax 989978a42b
bake: multi ips support for extra hosts
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-06-16 11:55:17 +02:00
Roberto Villarreal cb54ddb9fe Allow bake files to be specified via environment variable
The environment variable `BUILDX_BAKE_FILE` (and optional variable
`BUILDX_BAKE_FILE_SEPARATOR`) can be used to specify one or more bake
files (similar to `compose`).  This is mutually exclusive with`--file`
(which takes precedence).

This is done very early to ensure the values are treated just like
`--file`, e.g., participate in telemetry.  This includes leaving
relative paths as-is, which deviates from `compose` (which makes them
absolute).

Signed-off-by: Roberto Villarreal <rrjjvv@yahoo.com>
2025-06-16 00:08:54 -06:00
Tõnis Tiigi eb43f4c237
Merge pull request #3183 from crazy-max/modernize-fix
hack: modernize-fix bake target
2025-06-13 15:39:07 -07:00
Tõnis Tiigi 43e2f27cac
Merge pull request #3240 from jsternberg/remove-debugcmd-package
commands: remove debug package in commands
2025-06-13 11:46:37 -07:00
Jonathan A. Sternberg 7f5ff6b797
commands: remove debug package in commands
The package just causes the entire flow to be more complicated as build
has to pretend it doesn't know about debug options and the debugger has
to pretend it doesn't know about the build.

This abstraction has been difficult when integrating a DAP command into
this same workflow so I don't think this abstraction has much of a
value.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-13 09:32:35 -05:00
Tõnis Tiigi 32e9bfcba8
Merge pull request #3237 from jsternberg/vendor-update
vendor: github.com/moby/buildkit v0.23.0-rc1
2025-06-11 14:47:39 -07:00
Jonathan A. Sternberg e1adeee898
vendor: github.com/moby/buildkit v0.23.0-rc1
Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-11 16:29:31 -05:00
Tõnis Tiigi 1e969978aa
Merge pull request #3234 from crazy-max/bake-add-host
bake: extra-hosts support
2025-06-11 12:50:34 -07:00
dependabot[bot] 640541cefa
build(deps): bump softprops/action-gh-release from 2.2.2 to 2.3.2
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.2.2 to 2.3.2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](da05d55257...72f2c25fcb)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 2.3.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-11 18:13:37 +00:00
CrazyMax b514ed45fb
bake: extra-hosts support
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-06-11 19:38:01 +02:00
Tõnis Tiigi 1b4bd20e6f
Merge pull request #3233 from tonistiigi/imagetools-registrytoken
imagetools: support registrytoken auth in docker config
2025-06-11 09:07:19 -07:00
Tonis Tiigi da426ecd3a
imagetools: support registrytoken auth in docker config
This is not supported by the Authorizer from containerd and
needs to be added manually. Build authentication happens through
BuildKit session that already supports this.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2025-06-10 23:20:08 -07:00
Tonis Tiigi 10618d4c73
imagetools: move auth function to separate file
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2025-06-10 22:04:56 -07:00
Tõnis Tiigi 52b5d0862f
Merge pull request #3224 from jsternberg/evaluate-handler
build: change build handler to evaluate instead of onresult
2025-06-10 11:07:31 -07:00
Tõnis Tiigi d1e22e5fc3
Merge pull request #3228 from tonistiigi/hack-link-gold
lint: fix linter error on arm64
2025-06-10 10:37:36 -07:00
Jonathan A. Sternberg 38cf84346c
build: change build handler to evaluate instead of onresult
This changes the build handler to customize the behavior of evaluate
rather than onresult and also simplifies the `ResultHandle`. The
`ResultHandle` is now only valid within the gateway callback and can be
used to start containers from the handler.

`Evaluate` now executes inside of the gateway callback rather than
having a separate implementation that executes or re-invokes the build.
This keeps the gateway callback session open until the debugger has
returned.

The `ErrReload` for monitor has now been moved into the `build` package
and been renamed to `ErrRestart`. This is because it restarts the build
so the name makes a bit more sense. The actual use of this functionality
is still tied to the monitor reload.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-10 11:48:41 -05:00
Jonathan A. Sternberg 34e59ca1bd
progress: fix progress writer pause and unpause to prevent panics
This changes the progress printer's pause and unpause implementation to
be reentrant to prevent race conditions and it also allows the status
updates to be buffered when the display is paused.

The previous implementation mixed the pause implementation with the
finish implementation and could cause a send on closed channel panic
because it could close the status channel before it had finished being
used. Now, the status channel is not closed.

When the display is enabled, the status channel will be forwarded to an
internal channel that is used to display the updates. When the display
is paused, the status channel will have the statuses buffered in memory
to be sent when the progress display is resumed.

The `Unpause` method has also been renamed to `Resume`.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-09 14:07:52 -05:00
Tonis Tiigi 2706e2f429
lint: fix linter error on arm64
Something has changed in golang or alpine requiring gold linker by
default. In future this could be updated to clang/lld instead, eg.
by just calling xx.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2025-06-09 10:59:23 -07:00
Tõnis Tiigi 02ab492cac
Merge pull request #3226 from ArthurFlag/ENGDOCS-2699-build-list-and-explain-accepted-schemes
docs: restructure examples for context
2025-06-06 11:54:37 -07:00
Tõnis Tiigi b8d8c7b1a6
Merge pull request #3227 from crazy-max/hcl-merge-tests
bake: hcl merged tests
2025-06-06 11:52:59 -07:00
ArthurFlag dc6ec35e1d
docs: restructure examples for context
Signed-off-by: ArthurFlag <arthur.flageul@docker.com>
2025-06-06 17:22:22 +02:00
CrazyMax 3f49ee5a90
bake: hcl merged tests
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-06-06 17:16:56 +02:00
Tõnis Tiigi c45185fde0
Merge pull request #3222 from jsternberg/controller-remove-final
controller: remove remaining parts of the controller
2025-06-05 10:18:40 -07:00
Jonathan A. Sternberg 1d7cda1232
controller: remove remaining parts of the controller
Removes all references to the controller and moves the remaining
sections of code to other packages.

Processes has been moved to monitor where it is used and the data
structs have been removed so buildflags is used directly. The controller
build function has been moved to the commands package.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-05 11:57:03 -05:00
Tõnis Tiigi fb916a960c
Merge pull request #3214 from tonistiigi/internal-codes
cmd: custom exit codes for internal, resource and canceled errors
2025-06-05 09:11:19 -07:00
Tõnis Tiigi 60b1eda2df
Merge pull request #3220 from jsternberg/monitor-driven-build
monitor: move remaining controller functionality into monitor
2025-06-04 13:50:24 -07:00
Jonathan A. Sternberg 8f2604b6b4
monitor: move remaining controller functionality into monitor
This creates a `Monitor` type that keeps the global state between
monitor invocations and allows the monitor to exist during the build so
it can be utilized for callbacks.

The result handler is now registered with the monitor during the build
and `Run` will use the result if it is present and the configuration
intends the monitor to be invoked with the given result.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-04 15:27:24 -05:00
Tõnis Tiigi bb5b5e37e8
Merge pull request #3219 from jsternberg/monitor-reload-refactor
monitor: refactor how reload works
2025-06-04 13:26:48 -07:00
Jonathan A. Sternberg 21ebf82c99
monitor: refactor how reload works
The build now happens in a loop and the monitor is run after every
build. The monitor can return `ErrReload` to signal to the main thread
that it should reload the build result.

This will be used in the future to move the monitor into a callback
rather than as a separate existence. It allows the monitor to not
control the build itself which now makes it possible to completely
remove the controller.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-04 15:06:31 -05:00
Tõnis Tiigi d61853bbb3
Merge pull request #3213 from jsternberg/build-refactors
build: refactor some of the build functions into smaller utility functions
2025-06-03 14:43:26 -07:00
Jonathan A. Sternberg 65e46cc6af
commands: simplify passing stdin to the build when the monitor is configured
The monitor needs stdin to run and isn't compatible with loading a
context or dockerfile from stdin. We already disallow this combination
and, with the removal of the remote controller, there's no way to use
stdin during the build when invoke is configured.

This just removes the extra code to allow forwarding stdin to the build
when the monitor is configured to simplify that section of code.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-03 15:41:23 -05:00
Jonathan A. Sternberg 6a0f5610e3
controller: remove the controller interface
The controller interface is removed and the local controller is used for
only the initial build, invoke, and rebuilds.

Process control has been moved to the monitor.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-03 15:41:23 -05:00
Jonathan A. Sternberg e78aa98c92
build: refactor some of the build functions into smaller utility functions
Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-03 15:41:22 -05:00
CrazyMax e6ff731323
Merge pull request #3216 from jsternberg/keep-storage-deprecation-notice
commands: update deprecation notice for keep-storage
2025-06-02 16:58:58 +02:00
Jonathan A. Sternberg 9bd1ba2f5c
commands: update deprecation notice for keep-storage
The `--keep-storage` flag was changed to `--reserved-space`. Before it was
changed to that name, it was changed to `--max-storage`. This flag never
made it into a release as the name was changed before release, but the
update to the flag in buildx forgot to update the deprecation notice.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-02 09:35:39 -05:00
CrazyMax f90170965a
Merge pull request #3207 from rrjjvv/show-var-types
Show types during variable list operation
2025-06-02 09:09:18 +02:00
Tonis Tiigi b3e37e899f
cmd: custom exit codes for internal, resource and canceled errors
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2025-05-30 11:12:24 -07:00
Tõnis Tiigi a04b7d8689
Merge pull request #3212 from thaJeztah/bump_engine
vendor: github.com/docker/docker, docker/cli v28.2.2
2025-05-30 10:49:29 -07:00
Tõnis Tiigi 52bf4bf7ce
Merge pull request #3210 from thaJeztah/dockerfile_bump_docker
Dockerfile: update to docker v28.2.2
2025-05-30 10:49:08 -07:00
Sebastiaan van Stijn 13031cc2ca
vendor: github.com/docker/docker, docker/cli v28.2.2
no changes in vendored file, just version update

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-30 17:30:09 +02:00
Sebastiaan van Stijn 46fae59e2e
Dockerfile: update to docker v28.2.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-30 17:24:23 +02:00
Roberto Villarreal b40b2caf1a Show types during variable list operation
If a type was explicitly provided, it will be displayed in the variable
listing.  Inferred type names are not displayed, as they likely would
not match the user's intent.

Previously only `string` and `bool` default values were displayed in the
 listing.  All default values, regardless of type, are now displayed.

Signed-off-by: Roberto Villarreal <rrjjvv@yahoo.com>
2025-05-29 17:36:46 -06:00
Tõnis Tiigi 1436f93aa1
Merge pull request #3194 from thaJeztah/bump_engine
vendor: github.com/docker/docker, github.com/docker/cli v28.2.1
2025-05-29 16:05:26 -07:00
Sebastiaan van Stijn 99d82e6cea
vendor: github.com/docker/cli v28.2.1
full diff: https://github.com/docker/cli/compare/v28.1.1...v28.2.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-29 09:36:54 +02:00
Sebastiaan van Stijn bc620fcc71
vendor: github.com/docker/docker v28.2.1
full diff: https://github.com/docker/docker/compare/v28.1.1...v28.2.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-29 09:34:27 +02:00
CrazyMax e3c6618db2
Merge pull request #3201 from jsternberg/remove-generated-files
hack: remove code generation related to generated files
2025-05-23 11:14:45 +02:00
Tõnis Tiigi 542bda49f2
Merge pull request #3188 from crazy-max/buildkit-0.22
dockerfile: update buildkit to 0.22.0
2025-05-22 15:33:45 -07:00
Jonathan A. Sternberg 781a3f117a
hack: remove code generation related to generated files
With the removal of the protobuf for the controller, there are no longer
any generated files. Remove the makefile targets and the associated
dockerfiles and bake targets.

This wasn't being included in CI because it wasn't part of the
`validate` target.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-05-22 14:59:42 -05:00
CrazyMax 614cc880dd
dockerfile: update buildkit to 0.22.0
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-05-21 16:55:34 +02:00
CrazyMax dfad6e0b1f
Merge pull request #3189 from rrjjvv/var-typing-docs
Add variable typing to reference docs
2025-05-21 16:34:59 +02:00
CrazyMax 776dbd4086
Merge pull request #3198 from rrjjvv/var-typing-no-value-fix
Consider typed, value-less variables to have `null` value
2025-05-21 16:34:42 +02:00
Tõnis Tiigi 75f1d5e26b
Merge pull request #3199 from crazy-max/buildkit-0.22.0
vendor: github.com/moby/buildkit v0.22.0
2025-05-21 07:26:25 -07:00
CrazyMax 291c353575
bake: TestEmptyVariable
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-05-21 16:15:30 +02:00
CrazyMax a11bb4985c
vendor: github.com/moby/buildkit v0.22.0
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-05-21 14:57:25 +02:00
Roberto Villarreal cfeca919a9 Add variable typing to reference docs
This documents the variable typing introduced in #3167.

Signed-off-by: Roberto Villarreal <rrjjvv@yahoo.com>
2025-05-20 13:49:27 -06:00
Roberto Villarreal 3c0f5c5c21 Consider typed, value-less variables to have `null` value
A variable with a type but no default value or override resulted in an
empty string.  This matches the legacy behavior of untyped variables,
but does not make sense when using types (an empty string is itself a
type violation for everything except `string`).  All variables defined
with a type but with no value are now a typed `null`.

A variable explicitly typed `any` was previously treated as if the
typing was omitted; with no defined value or override, that resulted in
an empty string.  The `any` type is now distinguished from an omitted
type; these variables, with no default or override, are also `null`.

In other respects, the behavior of `any` is unchanged and largely
behaves as if the type was omitted.  It's not clear whether it should be
 supported, let alone how it should behave, so these tests were removed.
It's being treated as undefined behavior.

Signed-off-by: Roberto Villarreal <rrjjvv@yahoo.com>
2025-05-20 13:09:13 -06:00
CrazyMax ea2b7020a4
Merge pull request #3193 from crazy-max/buildkit-0.22.0-rc2
vendor: github.com/moby/buildkit v0.22.0-rc2
2025-05-19 17:29:11 +02:00
CrazyMax 5ba7d7eb4f
vendor: github.com/moby/buildkit v0.22.0-rc2
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-05-19 17:14:59 +02:00
CrazyMax 95ac2b4d09
Merge pull request #3192 from crazy-max/update-cli-docs-tool
vendor: github.com/docker/cli-docs-tool v0.10.0
2025-05-19 16:23:50 +02:00
CrazyMax 934cca3ab1
vendor: github.com/docker/cli-docs-tool v0.10.0
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-05-19 15:54:56 +02:00
CrazyMax 6e562e9ede
Merge pull request #3191 from glours/bump-compose-go-v2.6.3
bump compose-go to v2.6.3
2025-05-19 15:05:34 +02:00
Guillaume Lours 51b8646c44
bump compose-go to v2.6.3
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-05-19 14:49:51 +02:00
CrazyMax c1209acb27
hack: modernize-fix bake target
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-05-13 18:22:24 +02:00
1440 changed files with 67444 additions and 38356 deletions

5
.github/labeler.yml vendored
View File

@ -48,11 +48,6 @@ area/cli:
- cmd/**
- commands/**
# Add 'area/controller' label to changes in the controller
area/controller:
- changed-files:
- any-glob-to-any-file: 'controller/**'
# Add 'area/docs' label to markdown files in the docs folder
area/docs:
- changed-files:

View File

@ -54,9 +54,9 @@ jobs:
- master
- latest
- buildx-stable-1
- v0.23.1
- v0.22.0
- v0.21.1
- v0.20.2
- v0.19.0
worker:
- docker-container
- remote
@ -535,7 +535,7 @@ jobs:
-
name: GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:

View File

@ -5,13 +5,13 @@ ARG ALPINE_VERSION=3.21
ARG XX_VERSION=1.6.1
# for testing
ARG DOCKER_VERSION=28.1.1
ARG DOCKER_VERSION=28.3.0
ARG DOCKER_VERSION_ALT_27=27.5.1
ARG DOCKER_VERSION_ALT_26=26.1.3
ARG DOCKER_CLI_VERSION=${DOCKER_VERSION}
ARG GOTESTSUM_VERSION=v1.12.0
ARG REGISTRY_VERSION=3.0.0
ARG BUILDKIT_VERSION=v0.21.1
ARG BUILDKIT_VERSION=v0.23.1
ARG UNDOCK_VERSION=0.9.0
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx

View File

@ -8,7 +8,7 @@ endif
export BUILDX_CMD ?= docker buildx
BAKE_TARGETS := binaries binaries-cross lint lint-gopls validate-vendor validate-docs validate-authors validate-generated-files
BAKE_TARGETS := binaries binaries-cross lint lint-gopls validate-vendor validate-docs validate-authors
.PHONY: all
all: binaries
@ -35,7 +35,7 @@ release:
./hack/release
.PHONY: validate-all
validate-all: lint test validate-vendor validate-docs validate-generated-files
validate-all: lint test validate-vendor validate-docs
.PHONY: test
test:
@ -68,7 +68,3 @@ authors:
.PHONY: mod-outdated
mod-outdated:
$(BUILDX_CMD) bake mod-outdated
.PHONY: generated-files
generated-files:
$(BUILDX_CMD) bake update-generated-files

View File

@ -79,7 +79,6 @@ Area or component of the project affected. Please note that the table below may
| `area/checks` | Any | `checks` |
| `area/ci` | Any | Project CI |
| `area/cli` | Any | `cli` |
| `area/controller` | Any | `controller` |
| `area/debug` | Any | `debug` |
| `area/dependencies` | Any | Project dependencies |
| `area/dockerfile` | Any | `dockerfile` |

View File

@ -4,6 +4,7 @@ import (
"context"
"encoding"
"encoding/json"
"fmt"
"io"
"maps"
"os"
@ -19,7 +20,6 @@ import (
composecli "github.com/compose-spec/compose-go/v2/cli"
"github.com/docker/buildx/bake/hclparser"
"github.com/docker/buildx/build"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/platformutil"
"github.com/docker/buildx/util/progress"
@ -727,6 +727,7 @@ type Target struct {
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional" cty:"ulimits"`
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
ExtraHosts map[string]*string `json:"extra-hosts,omitempty" hcl:"extra-hosts,optional" cty:"extra-hosts"`
// IMPORTANT: if you add more fields here, do not forget to update newOverrides/AddOverrides and docs/bake-reference.md.
// linked is a private field to mark a target used as a linked one
@ -765,6 +766,14 @@ func (t *Target) MarshalJSON() ([]byte, error) {
}
}
tgt.ExtraHosts = maps.Clone(t.ExtraHosts)
for k, v := range t.ExtraHosts {
if v != nil {
escaped := esc(*v)
tgt.ExtraHosts[k] = &escaped
}
}
return json.Marshal(tgt)
}
@ -895,6 +904,15 @@ func (t *Target) Merge(t2 *Target) {
if t2.Entitlements != nil { // merge
t.Entitlements = append(t.Entitlements, t2.Entitlements...)
}
for k, v := range t2.ExtraHosts {
if v == nil {
continue
}
if t.ExtraHosts == nil {
t.ExtraHosts = map[string]*string{}
}
t.ExtraHosts[k] = v
}
t.Inherits = append(t.Inherits, t2.Inherits...)
}
@ -1083,6 +1101,14 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
return errors.Errorf("invalid value %s for boolean key load", value)
}
t.Outputs = setLoadOverride(t.Outputs, load)
case "extra-hosts":
if len(keys) != 2 {
return errors.Errorf("invalid format for extra-hosts, expecting extra-hosts.<hostname>=<ip>")
}
if t.ExtraHosts == nil {
t.ExtraHosts = map[string]*string{}
}
t.ExtraHosts[keys[1]] = &value
default:
return errors.Errorf("unknown key: %s", keys[0])
}
@ -1405,6 +1431,14 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
}
}
var extraHosts []string
for k, v := range t.ExtraHosts {
if v == nil {
continue
}
extraHosts = append(extraHosts, fmt.Sprintf("%s=%s", k, *v))
}
bo := &build.Options{
Inputs: bi,
Tags: t.Tags,
@ -1416,6 +1450,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
NetworkMode: networkMode,
Linked: t.linked,
ShmSize: *shmSize,
ExtraHosts: extraHosts,
}
platforms, err := platformutil.Parse(t.Platforms)
@ -1439,20 +1474,19 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
})
}
}
secrets = secrets.Normalize()
bo.SecretSpecs = secrets.ToPB()
secretAttachment, err := controllerapi.CreateSecrets(bo.SecretSpecs)
bo.SecretSpecs = secrets.Normalize()
secretAttachment, err := build.CreateSecrets(bo.SecretSpecs)
if err != nil {
return nil, err
}
bo.Session = append(bo.Session, secretAttachment)
bo.SSHSpecs = t.SSH.ToPB()
bo.SSHSpecs = t.SSH
if len(bo.SSHSpecs) == 0 && buildflags.IsGitSSH(bi.ContextPath) || (inp != nil && buildflags.IsGitSSH(inp.URL)) {
bo.SSHSpecs = []*controllerapi.SSH{{ID: "default"}}
bo.SSHSpecs = []*buildflags.SSH{{ID: "default"}}
}
sshAttachment, err := controllerapi.CreateSSH(bo.SSHSpecs)
sshAttachment, err := build.CreateSSH(bo.SSHSpecs)
if err != nil {
return nil, err
}
@ -1469,13 +1503,13 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
}
if t.CacheFrom != nil {
bo.CacheFrom = controllerapi.CreateCaches(t.CacheFrom.ToPB())
bo.CacheFrom = build.CreateCaches(t.CacheFrom)
}
if t.CacheTo != nil {
bo.CacheTo = controllerapi.CreateCaches(t.CacheTo.ToPB())
bo.CacheTo = build.CreateCaches(t.CacheTo)
}
bo.Exports, bo.ExportsLocalPathsTemporary, err = controllerapi.CreateExports(t.Outputs.ToPB())
bo.Exports, bo.ExportsLocalPathsTemporary, err = build.CreateExports(t.Outputs)
if err != nil {
return nil, err
}
@ -1490,7 +1524,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
}
}
bo.Attests = controllerapi.CreateAttestations(t.Attest.ToPB())
bo.Attests = t.Attest.ToMap()
bo.SourcePolicy, err = build.ReadSourcePolicy()
if err != nil {

View File

@ -27,6 +27,9 @@ target "webDEP" {
no-cache = true
shm-size = "128m"
ulimits = ["nofile=1024:1024"]
extra-hosts = {
my_hostname = "8.8.8.8"
}
}
target "webapp" {
@ -64,6 +67,7 @@ target "webapp" {
require.Equal(t, true, *m["webapp"].NoCache)
require.Equal(t, "128m", *m["webapp"].ShmSize)
require.Equal(t, []string{"nofile=1024:1024"}, m["webapp"].Ulimits)
require.Equal(t, map[string]*string{"my_hostname": ptrstr("8.8.8.8")}, m["webapp"].ExtraHosts)
require.Nil(t, m["webapp"].Pull)
require.Equal(t, 1, len(g))

View File

@ -122,6 +122,14 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf
}
}
extraHosts := map[string]*string{}
if s.Build.ExtraHosts != nil {
for k, v := range s.Build.ExtraHosts {
vv := strings.Join(v, ",")
extraHosts[k] = &vv
}
}
var ssh []*buildflags.SSH
for _, bkey := range s.Build.SSH {
sshkey := composeToBuildkitSSH(bkey)
@ -180,6 +188,7 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf
Secrets: secrets,
ShmSize: shmSize,
Ulimits: ulimits,
ExtraHosts: extraHosts,
}
if err = t.composeExtTarget(s.Build.Extensions); err != nil {
return nil, err

View File

@ -32,6 +32,10 @@ services:
- type=local,src=path/to/cache
cache_to:
- type=local,dest=path/to/cache
extra_hosts:
- "somehost:162.242.195.82"
- "somehost:162.242.195.83"
- "myhostv6:::1"
ssh:
- key=/path/to/key
- default
@ -76,6 +80,7 @@ secrets:
require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"])
require.Equal(t, []string{"type=local,src=path/to/cache"}, stringify(c.Targets[1].CacheFrom))
require.Equal(t, []string{"type=local,dest=path/to/cache"}, stringify(c.Targets[1].CacheTo))
require.Equal(t, map[string]*string{"myhostv6": ptrstr("::1"), "somehost": ptrstr("162.242.195.82,162.242.195.83")}, c.Targets[1].ExtraHosts)
require.Equal(t, "none", *c.Targets[1].NetworkMode)
require.Equal(t, []string{"default", "key=/path/to/key"}, stringify(c.Targets[1].SSH))
require.Equal(t, []string{

View File

@ -8,7 +8,7 @@ import (
"testing"
"github.com/docker/buildx/build"
"github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/osutil"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/util/entitlements"
@ -264,7 +264,7 @@ func TestValidateEntitlements(t *testing.T) {
{
name: "SSHMissing",
opt: build.Options{
SSHSpecs: []*pb.SSH{
SSHSpecs: []*buildflags.SSH{
{
ID: "test",
},
@ -296,7 +296,7 @@ func TestValidateEntitlements(t *testing.T) {
{
name: "SecretFromSubFile",
opt: build.Options{
SecretSpecs: []*pb.Secret{
SecretSpecs: []*buildflags.Secret{
{
FilePath: filepath.Join(dir1, "subfile"),
},
@ -309,7 +309,7 @@ func TestValidateEntitlements(t *testing.T) {
{
name: "SecretFromEscapeLink",
opt: build.Options{
SecretSpecs: []*pb.Secret{
SecretSpecs: []*buildflags.Secret{
{
FilePath: escapeLink,
},
@ -325,7 +325,7 @@ func TestValidateEntitlements(t *testing.T) {
{
name: "SecretFromEscapeLinkAllowRoot",
opt: build.Options{
SecretSpecs: []*pb.Secret{
SecretSpecs: []*buildflags.Secret{
{
FilePath: escapeLink,
},
@ -352,7 +352,7 @@ func TestValidateEntitlements(t *testing.T) {
{
name: "SecretFromEscapeLinkAllowAny",
opt: build.Options{
SecretSpecs: []*pb.Secret{
SecretSpecs: []*buildflags.Secret{
{
FilePath: escapeLink,
},

View File

@ -423,6 +423,63 @@ func TestHCLNullVariables(t *testing.T) {
require.Equal(t, ptrstr("bar"), c.Targets[0].Args["foo"])
}
func TestHCLTypedNullVariables(t *testing.T) {
types := []string{
"any",
"string", "number", "bool",
"list(string)", "set(string)", "map(string)",
"tuple([string])", "object({val: string})",
}
for _, varType := range types {
tName := fmt.Sprintf("variable typed %q with null default remains null", varType)
t.Run(tName, func(t *testing.T) {
dt := fmt.Sprintf(`
variable "FOO" {
type = %s
default = null
}
target "default" {
args = {
foo = equal(FOO, null)
}
}`, varType)
c, err := ParseFile([]byte(dt), "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Targets))
require.Equal(t, "true", *c.Targets[0].Args["foo"])
})
}
}
func TestHCLTypedValuelessVariables(t *testing.T) {
types := []string{
"any",
"string", "number", "bool",
"list(string)", "set(string)", "map(string)",
"tuple([string])", "object({val: string})",
}
for _, varType := range types {
tName := fmt.Sprintf("variable typed %q with no default is null", varType)
t.Run(tName, func(t *testing.T) {
dt := fmt.Sprintf(`
variable "FOO" {
type = %s
}
target "default" {
args = {
foo = equal(FOO, null)
}
}`, varType)
c, err := ParseFile([]byte(dt), "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Targets))
require.Equal(t, "true", *c.Targets[0].Args["foo"])
})
}
}
func TestJSONNullVariables(t *testing.T) {
dt := []byte(`{
"variable": {
@ -1565,6 +1622,20 @@ target "two" {
require.Equal(t, map[string]*string{"b": ptrstr("pre-jkl")}, c.Targets[1].Args)
}
func TestEmptyVariable(t *testing.T) {
dt := []byte(`
variable "FOO" {}
target "default" {
args = {
foo = equal(FOO, "")
}
}`)
c, err := ParseFile(dt, "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Targets))
require.Equal(t, "true", *c.Targets[0].Args["foo"])
}
func TestEmptyVariableJSON(t *testing.T) {
dt := []byte(`{
"variable": {
@ -1877,19 +1948,6 @@ func TestTypedVarOverrides(t *testing.T) {
override: `"hello"`,
wantValue: `"hello"`,
},
{
name: "any",
varType: "any",
override: "[1,2]",
wantValue: "[1,2]",
},
{
name: "any never convert to complex types",
varType: "any",
override: "[1,2]",
argValue: "length(FOO)",
wantErrorMsg: "collection must be a list",
},
{
name: "proper CSV list of strings",
varType: "list(string)",
@ -2090,19 +2148,6 @@ func TestTypedVarOverrides_JSON(t *testing.T) {
override: `"hello"`,
wantValue: "hello",
},
{
name: "any",
varType: "any",
override: "[1,2]",
wantValue: "[1,2]",
},
{
name: "any never convert to complex types",
varType: "any",
override: "[1,2]",
argValue: "length(FOO)",
wantErrorMsg: "collection must be a list",
},
{
name: "list of strings",
varType: "list(string)",
@ -2313,6 +2358,7 @@ func TestJSONOverridePriority(t *testing.T) {
dt := []byte(`
variable "foo" {
type = number
default = 101
}
target "default" {
@ -2325,8 +2371,7 @@ func TestJSONOverridePriority(t *testing.T) {
c, err := ParseFile(dt, "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Targets))
// a variable with no value has always resulted in an empty string
require.Equal(t, "", *c.Targets[0].Args["bar"])
require.Equal(t, "101", *c.Targets[0].Args["bar"])
t.Setenv("foo_JSON", "42")
c, err = ParseFile(dt, "docker-bake.hcl")

View File

@ -38,6 +38,9 @@ type variable struct {
Validations []*variableValidation `json:"validation,omitempty" hcl:"validation,block"`
Body hcl.Body `json:"-" hcl:",body"`
Remain hcl.Body `json:"-" hcl:",remain"`
// the type described by Type if it was specified
constraint *cty.Type
}
type variableValidation struct {
@ -282,7 +285,7 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
}
var diags hcl.Diagnostics
varType := cty.DynamicPseudoType
varType, typeSpecified := cty.DynamicPseudoType, false
def, ok := p.attrs[name]
if !ok {
vr, ok := p.vars[name]
@ -295,12 +298,16 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
if diags.HasErrors() {
return diags
}
typeSpecified = !varType.Equals(cty.DynamicPseudoType) || hcl.ExprAsKeyword(vr.Type) == "any"
if typeSpecified {
vr.constraint = &varType
}
}
if def == nil {
// lack of specified value is considered to have an empty string value,
// but any overrides get type checked
if _, ok, _ := p.valueHasOverride(name, false); !ok {
// Lack of specified value, when untyped is considered to have an empty string value.
// A typed variable with no value will result in (typed) nil.
if _, ok, _ := p.valueHasOverride(name, false); !ok && !typeSpecified {
vv := cty.StringVal("")
v = &vv
return
@ -322,9 +329,6 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
}
}
// Not entirely true... this doesn't differentiate between a user that specified 'any'
// and a user that specified nothing. But the result is the same; both are treated as strings.
typeSpecified := !varType.Equals(cty.DynamicPseudoType)
envv, hasEnv, jsonEnv := p.valueHasOverride(name, typeSpecified)
_, isVar := p.vars[name]
@ -676,6 +680,7 @@ func (p *parser) validateVariables(vars map[string]*variable, ectx *hcl.EvalCont
type Variable struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Type string `json:"type,omitempty"`
Value *string `json:"value,omitempty"`
}
@ -805,13 +810,31 @@ func Parse(b hcl.Body, opt Opt, val any) (*ParseMeta, hcl.Diagnostics) {
Name: p.vars[k].Name,
Description: p.vars[k].Description,
}
tc := p.vars[k].constraint
if tc != nil {
v.Type = tc.FriendlyNameForConstraint()
}
if vv := p.ectx.Variables[k]; !vv.IsNull() {
var s string
switch vv.Type() {
case cty.String:
s = vv.AsString()
case cty.Bool:
s = strconv.FormatBool(vv.True())
switch {
case tc != nil:
if bs, err := ctyjson.Marshal(vv, *tc); err == nil {
s = string(bs)
// untyped strings were always unquoted, so be consistent with typed strings as well
if tc.Equals(cty.String) {
s = strings.Trim(s, "\"")
}
}
case vv.Type().IsPrimitiveType():
// all primitives can convert to string, so error should never occur
if val, err := convert.Convert(vv, cty.String); err == nil {
s = val.AsString()
}
default:
// must be an (inferred) tuple or object
if bs, err := ctyjson.Marshal(vv, vv.Type()); err == nil {
s = string(bs)
}
}
v.Value = &s
}

View File

@ -1,8 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Forked from https://github.com/hashicorp/hcl/blob/4679383728fe331fc8a6b46036a27b8f818d9bc0/merged.go
package hclparser
import (

View File

@ -0,0 +1,687 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclparser
import (
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/hcl/v2"
)
func TestMergedBodiesContent(t *testing.T) {
tests := []struct {
Bodies []hcl.Body
Schema *hcl.BodySchema
Want *hcl.BodyContent
DiagCount int
}{
{
[]hcl.Body{},
&hcl.BodySchema{},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
},
0,
},
{
[]hcl.Body{},
&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "name",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
},
0,
},
{
[]hcl.Body{},
&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "name",
Required: true,
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
},
1,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
HasAttributes: []string{"name"},
},
},
&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "name",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{
"name": {
Name: "name",
},
},
},
0,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
Name: "first",
HasAttributes: []string{"name"},
},
&testMergedBodiesVictim{
Name: "second",
HasAttributes: []string{"name"},
},
},
&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "name",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{
"name": {
Name: "name",
NameRange: hcl.Range{Filename: "second"},
},
},
},
1,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
Name: "first",
HasAttributes: []string{"name"},
},
&testMergedBodiesVictim{
Name: "second",
HasAttributes: []string{"age"},
},
},
&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "name",
},
{
Name: "age",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{
"name": {
Name: "name",
NameRange: hcl.Range{Filename: "first"},
},
"age": {
Name: "age",
NameRange: hcl.Range{Filename: "second"},
},
},
},
0,
},
{
[]hcl.Body{},
&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "pizza",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
},
0,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
HasBlocks: map[string]int{
"pizza": 1,
},
},
},
&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "pizza",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
Blocks: hcl.Blocks{
{
Type: "pizza",
},
},
},
0,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
HasBlocks: map[string]int{
"pizza": 2,
},
},
},
&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "pizza",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
Blocks: hcl.Blocks{
{
Type: "pizza",
},
{
Type: "pizza",
},
},
},
0,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
Name: "first",
HasBlocks: map[string]int{
"pizza": 1,
},
},
&testMergedBodiesVictim{
Name: "second",
HasBlocks: map[string]int{
"pizza": 1,
},
},
},
&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "pizza",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
Blocks: hcl.Blocks{
{
Type: "pizza",
DefRange: hcl.Range{Filename: "first"},
},
{
Type: "pizza",
DefRange: hcl.Range{Filename: "second"},
},
},
},
0,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
Name: "first",
},
&testMergedBodiesVictim{
Name: "second",
HasBlocks: map[string]int{
"pizza": 2,
},
},
},
&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "pizza",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
Blocks: hcl.Blocks{
{
Type: "pizza",
DefRange: hcl.Range{Filename: "second"},
},
{
Type: "pizza",
DefRange: hcl.Range{Filename: "second"},
},
},
},
0,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
Name: "first",
HasBlocks: map[string]int{
"pizza": 2,
},
},
&testMergedBodiesVictim{
Name: "second",
},
},
&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "pizza",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
Blocks: hcl.Blocks{
{
Type: "pizza",
DefRange: hcl.Range{Filename: "first"},
},
{
Type: "pizza",
DefRange: hcl.Range{Filename: "first"},
},
},
},
0,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
Name: "first",
},
&testMergedBodiesVictim{
Name: "second",
},
},
&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "pizza",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
},
0,
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
merged := MergeBodies(test.Bodies)
got, diags := merged.Content(test.Schema)
if len(diags) != test.DiagCount {
t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
for _, diag := range diags {
t.Logf(" - %s", diag)
}
}
if !reflect.DeepEqual(got, test.Want) {
t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want))
}
})
}
}
func TestMergeBodiesPartialContent(t *testing.T) {
tests := []struct {
Bodies []hcl.Body
Schema *hcl.BodySchema
WantContent *hcl.BodyContent
WantRemain hcl.Body
DiagCount int
}{
{
[]hcl.Body{},
&hcl.BodySchema{},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
},
mergedBodies{},
0,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
Name: "first",
HasAttributes: []string{"name", "age"},
},
},
&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "name",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{
"name": {
Name: "name",
NameRange: hcl.Range{Filename: "first"},
},
},
},
mergedBodies{
&testMergedBodiesVictim{
Name: "first",
HasAttributes: []string{"age"},
},
},
0,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
Name: "first",
HasAttributes: []string{"name", "age"},
},
&testMergedBodiesVictim{
Name: "second",
HasAttributes: []string{"name", "pizza"},
},
},
&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "name",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{
"name": {
Name: "name",
NameRange: hcl.Range{Filename: "second"},
},
},
},
mergedBodies{
&testMergedBodiesVictim{
Name: "first",
HasAttributes: []string{"age"},
},
&testMergedBodiesVictim{
Name: "second",
HasAttributes: []string{"pizza"},
},
},
1,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
Name: "first",
HasAttributes: []string{"name", "age"},
},
&testMergedBodiesVictim{
Name: "second",
HasAttributes: []string{"pizza", "soda"},
},
},
&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "name",
},
{
Name: "soda",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{
"name": {
Name: "name",
NameRange: hcl.Range{Filename: "first"},
},
"soda": {
Name: "soda",
NameRange: hcl.Range{Filename: "second"},
},
},
},
mergedBodies{
&testMergedBodiesVictim{
Name: "first",
HasAttributes: []string{"age"},
},
&testMergedBodiesVictim{
Name: "second",
HasAttributes: []string{"pizza"},
},
},
0,
},
{
[]hcl.Body{
&testMergedBodiesVictim{
Name: "first",
HasBlocks: map[string]int{
"pizza": 1,
},
},
&testMergedBodiesVictim{
Name: "second",
HasBlocks: map[string]int{
"pizza": 1,
"soda": 2,
},
},
},
&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "pizza",
},
},
},
&hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
Blocks: hcl.Blocks{
{
Type: "pizza",
DefRange: hcl.Range{Filename: "first"},
},
{
Type: "pizza",
DefRange: hcl.Range{Filename: "second"},
},
},
},
mergedBodies{
&testMergedBodiesVictim{
Name: "first",
HasAttributes: []string{},
HasBlocks: map[string]int{},
},
&testMergedBodiesVictim{
Name: "second",
HasAttributes: []string{},
HasBlocks: map[string]int{
"soda": 2,
},
},
},
0,
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
merged := MergeBodies(test.Bodies)
got, gotRemain, diags := merged.PartialContent(test.Schema)
if len(diags) != test.DiagCount {
t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
for _, diag := range diags {
t.Logf(" - %s", diag)
}
}
if !reflect.DeepEqual(got, test.WantContent) {
t.Errorf("wrong content result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.WantContent))
}
if !reflect.DeepEqual(gotRemain, test.WantRemain) {
t.Errorf("wrong remaining result\ngot: %s\nwant: %s", spew.Sdump(gotRemain), spew.Sdump(test.WantRemain))
}
})
}
}
type testMergedBodiesVictim struct {
Name string
HasAttributes []string
HasBlocks map[string]int
DiagCount int
}
func (v *testMergedBodiesVictim) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
c, _, d := v.PartialContent(schema)
return c, d
}
func (v *testMergedBodiesVictim) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
remain := &testMergedBodiesVictim{
Name: v.Name,
HasAttributes: []string{},
}
hasAttrs := map[string]struct{}{}
for _, n := range v.HasAttributes {
hasAttrs[n] = struct{}{}
var found bool
for _, attrS := range schema.Attributes {
if n == attrS.Name {
found = true
break
}
}
if !found {
remain.HasAttributes = append(remain.HasAttributes, n)
}
}
content := &hcl.BodyContent{
Attributes: map[string]*hcl.Attribute{},
}
rng := hcl.Range{
Filename: v.Name,
}
for _, attrS := range schema.Attributes {
_, has := hasAttrs[attrS.Name]
if has {
content.Attributes[attrS.Name] = &hcl.Attribute{
Name: attrS.Name,
NameRange: rng,
}
}
}
if v.HasBlocks != nil {
for _, blockS := range schema.Blocks {
num := v.HasBlocks[blockS.Type]
for range num {
content.Blocks = append(content.Blocks, &hcl.Block{
Type: blockS.Type,
DefRange: rng,
})
}
}
remain.HasBlocks = map[string]int{}
for n := range v.HasBlocks {
var found bool
for _, blockS := range schema.Blocks {
if blockS.Type == n {
found = true
break
}
}
if !found {
remain.HasBlocks[n] = v.HasBlocks[n]
}
}
}
diags := make(hcl.Diagnostics, v.DiagCount)
for i := range diags {
diags[i] = &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Fake diagnostic %d", i),
Detail: "For testing only.",
Context: &rng,
}
}
return content, remain, diags
}
func (v *testMergedBodiesVictim) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
attrs := make(map[string]*hcl.Attribute)
rng := hcl.Range{
Filename: v.Name,
}
for _, name := range v.HasAttributes {
attrs[name] = &hcl.Attribute{
Name: name,
NameRange: rng,
}
}
diags := make(hcl.Diagnostics, v.DiagCount)
for i := range diags {
diags[i] = &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Fake diagnostic %d", i),
Detail: "For testing only.",
Context: &rng,
}
}
return attrs, diags
}
func (v *testMergedBodiesVictim) MissingItemRange() hcl.Range {
return hcl.Range{
Filename: v.Name,
}
}

View File

@ -7,9 +7,10 @@ import (
"os"
"strings"
"github.com/docker/buildx/build"
"github.com/docker/buildx/builder"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/progress"
"github.com/docker/go-units"
"github.com/moby/buildkit/client"
@ -33,27 +34,27 @@ func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, name
st, ok := dockerui.DetectGitContext(url, false)
if ok {
if ssh, err := controllerapi.CreateSSH([]*controllerapi.SSH{{
if ssh, err := build.CreateSSH([]*buildflags.SSH{{
ID: "default",
Paths: strings.Split(os.Getenv("BUILDX_BAKE_GIT_SSH"), ","),
}}); err == nil {
sessions = append(sessions, ssh)
}
var gitAuthSecrets []*controllerapi.Secret
var gitAuthSecrets []*buildflags.Secret
if _, ok := os.LookupEnv("BUILDX_BAKE_GIT_AUTH_TOKEN"); ok {
gitAuthSecrets = append(gitAuthSecrets, &controllerapi.Secret{
gitAuthSecrets = append(gitAuthSecrets, &buildflags.Secret{
ID: llb.GitAuthTokenKey,
Env: "BUILDX_BAKE_GIT_AUTH_TOKEN",
})
}
if _, ok := os.LookupEnv("BUILDX_BAKE_GIT_AUTH_HEADER"); ok {
gitAuthSecrets = append(gitAuthSecrets, &controllerapi.Secret{
gitAuthSecrets = append(gitAuthSecrets, &buildflags.Secret{
ID: llb.GitAuthHeaderKey,
Env: "BUILDX_BAKE_GIT_AUTH_HEADER",
})
}
if len(gitAuthSecrets) > 0 {
if secrets, err := controllerapi.CreateSecrets(gitAuthSecrets); err == nil {
if secrets, err := build.CreateSecrets(gitAuthSecrets); err == nil {
sessions = append(sessions, secrets)
}
}

View File

@ -19,8 +19,8 @@ import (
"github.com/containerd/containerd/v2/core/images"
"github.com/distribution/reference"
"github.com/docker/buildx/builder"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/desktop"
"github.com/docker/buildx/util/dockerutil"
@ -59,6 +59,8 @@ const (
printLintFallbackImage = "docker/dockerfile:1.8.1@sha256:e87caa74dcb7d46cd820352bfea12591f3dba3ddc4285e19c7dcd13359f7cefd"
)
var ErrRestart = errors.New("build: restart")
type Options struct {
Inputs Inputs
@ -78,8 +80,8 @@ type Options struct {
NoCacheFilter []string
Platforms []ocispecs.Platform
Pull bool
SecretSpecs []*controllerapi.Secret
SSHSpecs []*controllerapi.SSH
SecretSpecs buildflags.Secrets
SSHSpecs []*buildflags.SSH
ShmSize opts.MemBytes
Tags []string
Target string
@ -138,75 +140,68 @@ func filterAvailableNodes(nodes []builder.Node) ([]builder.Node, error) {
return nil, err
}
func toRepoOnly(in string) (string, error) {
m := map[string]struct{}{}
p := strings.Split(in, ",")
for _, pp := range p {
n, err := reference.ParseNormalizedNamed(pp)
if err != nil {
return "", err
}
m[n.Name()] = struct{}{}
}
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
return strings.Join(out, ","), nil
}
func Build(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
return BuildWithResultHandler(ctx, nodes, opts, docker, cfg, w, nil)
}
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultHandle)) (resp map[string]*client.SolveResponse, err error) {
if len(nodes) == 0 {
return nil, errors.Errorf("driver required for build")
}
nodes, err = filterAvailableNodes(nodes)
if err != nil {
return nil, errors.Wrapf(err, "no valid drivers found")
}
var noMobyDriver *driver.DriverHandle
// findNonMobyDriver returns the first non-moby based driver.
func findNonMobyDriver(nodes []builder.Node) *driver.DriverHandle {
for _, n := range nodes {
if !n.Driver.IsMobyDriver() {
noMobyDriver = n.Driver
break
return n.Driver
}
}
return nil
}
// warnOnNoOutput will check if the given nodes and options would result in an output
// and prints a warning if it would not.
func warnOnNoOutput(ctx context.Context, nodes []builder.Node, opts map[string]Options) {
// Return immediately if default load is explicitly disabled or a call
// function is used.
if noDefaultLoad() || !noCallFunc(opts) {
return
}
// Find the first non-moby driver and return if it either doesn't exist
// or if the driver has default load enabled.
noMobyDriver := findNonMobyDriver(nodes)
if noMobyDriver == nil || noMobyDriver.Features(ctx)[driver.DefaultLoad] {
return
}
// Produce a warning describing the targets affected.
var noOutputTargets []string
for name, opt := range opts {
if !opt.Linked && len(opt.Exports) == 0 {
noOutputTargets = append(noOutputTargets, name)
}
}
if noMobyDriver != nil && !noDefaultLoad() && noCallFunc(opts) {
var noOutputTargets []string
for name, opt := range opts {
if noMobyDriver.Features(ctx)[driver.DefaultLoad] {
continue
}
if !opt.Linked && len(opt.Exports) == 0 {
noOutputTargets = append(noOutputTargets, name)
}
}
if len(noOutputTargets) > 0 {
var warnNoOutputBuf bytes.Buffer
warnNoOutputBuf.WriteString("No output specified ")
if len(noOutputTargets) == 1 && noOutputTargets[0] == "default" {
warnNoOutputBuf.WriteString(fmt.Sprintf("with %s driver", noMobyDriver.Factory().Name()))
} else {
warnNoOutputBuf.WriteString(fmt.Sprintf("for %s target(s) with %s driver", strings.Join(noOutputTargets, ", "), noMobyDriver.Factory().Name()))
}
logrus.Warnf("%s. 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", warnNoOutputBuf.String())
}
if len(noOutputTargets) == 0 {
return
}
drivers, err := resolveDrivers(ctx, nodes, opts, w)
if err != nil {
return nil, err
var warnNoOutputBuf bytes.Buffer
warnNoOutputBuf.WriteString("No output specified ")
if len(noOutputTargets) == 1 && noOutputTargets[0] == "default" {
warnNoOutputBuf.WriteString(fmt.Sprintf("with %s driver", noMobyDriver.Factory().Name()))
} else {
warnNoOutputBuf.WriteString(fmt.Sprintf("for %s target(s) with %s driver", strings.Join(noOutputTargets, ", "), noMobyDriver.Factory().Name()))
}
logrus.Warnf("%s. 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", warnNoOutputBuf.String())
}
func newBuildRequests(ctx context.Context, docker *dockerutil.Client, cfg *confutil.Config, drivers map[string][]*resolvedNode, w progress.Writer, opts map[string]Options) (_ map[string][]*reqForNode, _ func(), retErr error) {
reqForNodes := make(map[string][]*reqForNode)
eg, ctx := errgroup.WithContext(ctx)
var releasers []func()
releaseAll := func() {
for _, fn := range releasers {
fn()
}
}
defer func() {
if retErr != nil {
releaseAll()
}
}()
for k, opt := range opts {
multiDriver := len(drivers[k]) > 1
@ -226,17 +221,17 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
opt.Platforms = np.platforms
gatewayOpts, err := np.BuildOpts(ctx)
if err != nil {
return nil, err
return nil, nil, err
}
localOpt := opt
so, release, err := toSolveOpt(ctx, np.Node(), multiDriver, &localOpt, gatewayOpts, cfg, w, docker)
opts[k] = localOpt
if err != nil {
return nil, err
return nil, nil, err
}
defer release()
releasers = append(releasers, release)
if err := saveLocalState(so, k, opt, np.Node(), cfg); err != nil {
return nil, err
return nil, nil, err
}
addGitAttrs(so)
reqn = append(reqn, &reqForNode{
@ -261,15 +256,17 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
for _, e := range np.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")
return nil, nil, errors.Errorf("multi-node push can't currently be performed with the docker driver, please switch to a different driver")
}
}
}
}
}
}
return reqForNodes, releaseAll, nil
}
// validate that all links between targets use same drivers
func validateTargetLinks(reqForNodes map[string][]*reqForNode, drivers map[string][]*resolvedNode, opts map[string]Options) error {
for name := range opts {
dps := reqForNodes[name]
for i, dp := range dps {
@ -279,8 +276,9 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
k2 := strings.TrimPrefix(v, "target:")
dps2, ok := drivers[k2]
if !ok {
return nil, errors.Errorf("failed to find target %s for context %s", k2, strings.TrimPrefix(k, "context:")) // should be validated before already
return errors.Errorf("failed to find target %s for context %s", k2, strings.TrimPrefix(k, "context:")) // should be validated before already
}
var found bool
for _, dp2 := range dps2 {
if dp2.driverIndex == dp.driverIndex {
@ -289,12 +287,67 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
}
}
if !found {
return nil, errors.Errorf("failed to use %s as context %s for %s because targets build with different drivers", k2, strings.TrimPrefix(k, "context:"), name)
return errors.Errorf("failed to use %s as context %s for %s because targets build with different drivers", k2, strings.TrimPrefix(k, "context:"), name)
}
}
}
}
}
return nil
}
func toRepoOnly(in string) (string, error) {
m := map[string]struct{}{}
p := strings.Split(in, ",")
for _, pp := range p {
n, err := reference.ParseNormalizedNamed(pp)
if err != nil {
return "", err
}
m[n.Name()] = struct{}{}
}
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
return strings.Join(out, ","), nil
}
type Handler struct {
Evaluate func(ctx context.Context, c gateway.Client, res *gateway.Result) error
}
func Build(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
return BuildWithResultHandler(ctx, nodes, opts, docker, cfg, w, nil)
}
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer, bh *Handler) (resp map[string]*client.SolveResponse, err error) {
if len(nodes) == 0 {
return nil, errors.Errorf("driver required for build")
}
nodes, err = filterAvailableNodes(nodes)
if err != nil {
return nil, errors.Wrapf(err, "no valid drivers found")
}
warnOnNoOutput(ctx, nodes, opts)
drivers, err := resolveDrivers(ctx, nodes, opts, w)
if err != nil {
return nil, err
}
eg, ctx := errgroup.WithContext(ctx)
reqForNodes, release, err := newBuildRequests(ctx, docker, cfg, drivers, w, opts)
if err != nil {
return nil, err
}
defer release()
// validate that all links between targets use same drivers
if err := validateTargetLinks(reqForNodes, drivers, opts); err != nil {
return nil, err
}
sharedSessions, err := detectSharedMounts(ctx, reqForNodes)
if err != nil {
@ -311,7 +364,6 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
for k, opt := range opts {
err := func(k string) (err error) {
opt := opt
dps := drivers[k]
multiDriver := len(drivers[k]) > 1
@ -429,9 +481,14 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
ch, done := progress.NewChannel(pw)
defer func() { <-done }()
cc := c
var callRes map[string][]byte
buildFunc := func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
var (
callRes map[string][]byte
frontendErr error
)
buildFunc := func(ctx context.Context, c gateway.Client) (_ *gateway.Result, retErr error) {
// Capture the error from this build function.
defer catchFrontendError(&retErr, &frontendErr)
if opt.CallFunc != nil {
if _, ok := req.FrontendOpt["frontend.caps"]; !ok {
req.FrontendOpt["frontend.caps"] = "moby.buildkit.frontend.subrequests+forward"
@ -441,19 +498,11 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
req.FrontendOpt["requestid"] = "frontend." + opt.CallFunc.Name
}
res, err := c.Solve(ctx, req)
res, err := solve(ctx, c, req)
if err != nil {
req, ok := fallbackPrintError(err, req)
if ok {
res2, err2 := c.Solve(ctx, req)
if err2 != nil {
return nil, err
}
res = res2
} else {
return nil, err
}
return nil, err
}
if opt.CallFunc != nil {
callRes = res.Metadata
}
@ -461,41 +510,27 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
rKey := resultKey(dp.driverIndex, k)
results.Set(rKey, res)
if children, ok := childTargets[rKey]; ok && len(children) > 0 {
// wait for the child targets to register their LLB before evaluating
_, err := results.Get(ctx, children...)
if err != nil {
if children := childTargets[rKey]; len(children) > 0 {
if err := waitForChildren(ctx, bh, c, res, results, children); err != nil {
return nil, err
}
// we need to wait until the child targets have completed before we can release
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
return res.EachRef(func(ref gateway.Reference) error {
return ref.Evaluate(ctx)
})
})
eg.Go(func() error {
_, err := results.Get(ctx, children...)
return err
})
if err := eg.Wait(); err != nil {
} else if bh != nil && bh.Evaluate != nil {
if err := bh.Evaluate(ctx, c, res); err != nil {
return nil, err
}
}
return res, nil
}
buildRef := fmt.Sprintf("%s/%s/%s", node.Builder, node.Name, so.Ref)
var rr *client.SolveResponse
if resultHandleFunc != nil {
var resultHandle *ResultHandle
resultHandle, rr, err = NewResultHandle(ctx, cc, *so, "buildx", buildFunc, ch)
resultHandleFunc(dp.driverIndex, resultHandle)
} else {
span, ctx := tracing.StartSpan(ctx, "build")
rr, err = c.Build(ctx, *so, "buildx", buildFunc, ch)
tracing.FinishWithError(span, err)
span, ctx := tracing.StartSpan(ctx, "build")
rr, err := c.Build(ctx, *so, "buildx", buildFunc, ch)
if errors.Is(frontendErr, ErrRestart) {
err = ErrRestart
}
tracing.FinishWithError(span, err)
if !so.Internal && desktop.BuildBackendEnabled() && node.Driver.HistoryAPISupported(ctx) {
if err != nil {
return &desktop.ErrorWithBuildRef{
@ -1146,3 +1181,52 @@ func ReadSourcePolicy() (*spb.Policy, error) {
return &pol, nil
}
func solve(ctx context.Context, c gateway.Client, req gateway.SolveRequest) (*gateway.Result, error) {
res, err := c.Solve(ctx, req)
if err != nil {
req, ok := fallbackPrintError(err, req)
if ok {
res2, err2 := c.Solve(ctx, req)
if err2 != nil {
return nil, err
}
res = res2
} else {
return nil, err
}
}
return res, nil
}
func waitForChildren(ctx context.Context, bh *Handler, c gateway.Client, res *gateway.Result, results *waitmap.Map, children []string) error {
// wait for the child targets to register their LLB before evaluating
_, err := results.Get(ctx, children...)
if err != nil {
return err
}
// we need to wait until the child targets have completed before we can release
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
if bh != nil && bh.Evaluate != nil {
return bh.Evaluate(ctx, c, res)
}
return res.EachRef(func(ref gateway.Reference) error {
return ref.Evaluate(ctx)
})
})
eg.Go(func() error {
_, err := results.Get(ctx, children...)
return err
})
return eg.Wait()
}
func catchFrontendError(retErr, frontendErr *error) {
*frontendErr = *retErr
if errors.Is(*retErr, ErrRestart) {
// Overwrite the sentinel error with a more user friendly message.
// This gets stored only in the return error.
*retErr = errors.New("build restarted by client")
}
}

View File

@ -8,12 +8,41 @@ import (
"sync/atomic"
"syscall"
controllerapi "github.com/docker/buildx/controller/pb"
gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type InvokeConfig struct {
Entrypoint []string
Cmd []string
NoCmd bool
Env []string
User string
NoUser bool
Cwd string
NoCwd bool
Tty bool
Rollback bool
Initial bool
SuspendOn SuspendOn
}
func (cfg *InvokeConfig) NeedsDebug(err error) bool {
return cfg.SuspendOn.DebugEnabled(err)
}
type SuspendOn int
const (
SuspendError SuspendOn = iota
SuspendAlways
)
func (s SuspendOn) DebugEnabled(err error) bool {
return err != nil || s == SuspendAlways
}
type Container struct {
cancelOnce sync.Once
containerCancel func(error)
@ -24,29 +53,21 @@ type Container struct {
resultCtx *ResultHandle
}
func NewContainer(ctx context.Context, resultCtx *ResultHandle, cfg *controllerapi.InvokeConfig) (*Container, error) {
func NewContainer(ctx context.Context, resultCtx *ResultHandle, cfg *InvokeConfig) (*Container, error) {
mainCtx := ctx
ctrCh := make(chan *Container)
errCh := make(chan error)
ctrCh := make(chan *Container, 1)
errCh := make(chan error, 1)
go func() {
err := resultCtx.build(func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
ctx, cancel := context.WithCancelCause(ctx)
go func() {
<-mainCtx.Done()
cancel(errors.WithStack(context.Canceled))
}()
containerCfg, err := resultCtx.getContainerConfig(cfg)
if err != nil {
return nil, err
}
err := func() error {
containerCtx, containerCancel := context.WithCancelCause(ctx)
defer containerCancel(errors.WithStack(context.Canceled))
bkContainer, err := c.NewContainer(containerCtx, containerCfg)
bkContainer, err := resultCtx.NewContainer(containerCtx, cfg)
if err != nil {
return nil, err
return err
}
releaseCh := make(chan struct{})
container := &Container{
containerCancel: containerCancel,
@ -63,8 +84,8 @@ func NewContainer(ctx context.Context, resultCtx *ResultHandle, cfg *controllera
ctrCh <- container
<-container.releaseCh
return nil, bkContainer.Release(ctx)
})
return bkContainer.Release(ctx)
}()
if err != nil {
errCh <- err
}
@ -97,7 +118,7 @@ func (c *Container) markUnavailable() {
c.isUnavailable.Store(true)
}
func (c *Container) Exec(ctx context.Context, cfg *controllerapi.InvokeConfig, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
func (c *Container) Exec(ctx context.Context, cfg *InvokeConfig, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
if isInit := c.initStarted.CompareAndSwap(false, true); isInit {
defer func() {
// container can't be used after init exits
@ -112,7 +133,7 @@ func (c *Container) Exec(ctx context.Context, cfg *controllerapi.InvokeConfig, s
return err
}
func exec(ctx context.Context, resultCtx *ResultHandle, cfg *controllerapi.InvokeConfig, ctr gateway.Container, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
func exec(ctx context.Context, resultCtx *ResultHandle, cfg *InvokeConfig, ctr gateway.Container, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
processCfg, err := resultCtx.getProcessConfig(cfg, stdin, stdout, stderr)
if err != nil {
return err

View File

@ -4,6 +4,7 @@ import (
"bytes"
"context"
"io"
"maps"
"os"
"path/filepath"
"slices"
@ -11,12 +12,15 @@ import (
"strings"
"syscall"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/containerd/console"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/plugins/content/local"
"github.com/containerd/platforms"
"github.com/distribution/reference"
"github.com/docker/buildx/builder"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/osutil"
@ -26,6 +30,9 @@ import (
"github.com/moby/buildkit/client/ociindex"
gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/secrets/secretsprovider"
"github.com/moby/buildkit/session/sshforward/sshprovider"
"github.com/moby/buildkit/session/upload/uploadprovider"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/apicaps"
@ -659,3 +666,221 @@ type fs struct {
}
var _ fsutil.FS = &fs{}
func CreateSSH(ssh []*buildflags.SSH) (session.Attachable, error) {
configs := make([]sshprovider.AgentConfig, 0, len(ssh))
for _, ssh := range ssh {
cfg := sshprovider.AgentConfig{
ID: ssh.ID,
Paths: slices.Clone(ssh.Paths),
}
configs = append(configs, cfg)
}
return sshprovider.NewSSHAgentProvider(configs)
}
func CreateSecrets(secrets []*buildflags.Secret) (session.Attachable, error) {
fs := make([]secretsprovider.Source, 0, len(secrets))
for _, secret := range secrets {
fs = append(fs, secretsprovider.Source{
ID: secret.ID,
FilePath: secret.FilePath,
Env: secret.Env,
})
}
store, err := secretsprovider.NewStore(fs)
if err != nil {
return nil, err
}
return secretsprovider.NewSecretProvider(store), nil
}
func CreateExports(entries []*buildflags.ExportEntry) ([]client.ExportEntry, []string, error) {
var outs []client.ExportEntry
var localPaths []string
if len(entries) == 0 {
return nil, nil, nil
}
var stdoutUsed bool
for _, entry := range entries {
if entry.Type == "" {
return nil, nil, errors.Errorf("type is required for output")
}
out := client.ExportEntry{
Type: entry.Type,
Attrs: map[string]string{},
}
maps.Copy(out.Attrs, entry.Attrs)
supportFile := false
supportDir := false
switch out.Type {
case client.ExporterLocal:
supportDir = true
case client.ExporterTar:
supportFile = true
case client.ExporterOCI, client.ExporterDocker:
tar, err := strconv.ParseBool(out.Attrs["tar"])
if err != nil {
tar = true
}
supportFile = tar
supportDir = !tar
case "registry":
out.Type = client.ExporterImage
out.Attrs["push"] = "true"
}
if supportDir {
if entry.Destination == "" {
return nil, nil, errors.Errorf("dest is required for %s exporter", out.Type)
}
if entry.Destination == "-" {
return nil, nil, errors.Errorf("dest cannot be stdout for %s exporter", out.Type)
}
fi, err := os.Stat(entry.Destination)
if err != nil && !os.IsNotExist(err) {
return nil, nil, errors.Wrapf(err, "invalid destination directory: %s", entry.Destination)
}
if err == nil && !fi.IsDir() {
return nil, nil, errors.Errorf("destination directory %s is a file", entry.Destination)
}
out.OutputDir = entry.Destination
localPaths = append(localPaths, entry.Destination)
}
if supportFile {
if entry.Destination == "" && out.Type != client.ExporterDocker {
entry.Destination = "-"
}
if entry.Destination == "-" {
if stdoutUsed {
return nil, nil, errors.Errorf("multiple outputs configured to write to stdout")
}
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
return nil, nil, errors.Errorf("dest file is required for %s exporter. refusing to write to console", out.Type)
}
out.Output = wrapWriteCloser(os.Stdout)
stdoutUsed = true
} else if entry.Destination != "" {
fi, err := os.Stat(entry.Destination)
if err != nil && !os.IsNotExist(err) {
return nil, nil, errors.Wrapf(err, "invalid destination file: %s", entry.Destination)
}
if err == nil && fi.IsDir() {
return nil, nil, errors.Errorf("destination file %s is a directory", entry.Destination)
}
f, err := os.Create(entry.Destination)
if err != nil {
return nil, nil, errors.Errorf("failed to open %s", err)
}
out.Output = wrapWriteCloser(f)
localPaths = append(localPaths, entry.Destination)
}
}
outs = append(outs, out)
}
return outs, localPaths, nil
}
func wrapWriteCloser(wc io.WriteCloser) func(map[string]string) (io.WriteCloser, error) {
return func(map[string]string) (io.WriteCloser, error) {
return wc, nil
}
}
func CreateCaches(entries []*buildflags.CacheOptionsEntry) []client.CacheOptionsEntry {
var outs []client.CacheOptionsEntry
if len(entries) == 0 {
return nil
}
for _, entry := range entries {
out := client.CacheOptionsEntry{
Type: entry.Type,
Attrs: map[string]string{},
}
maps.Copy(out.Attrs, entry.Attrs)
addGithubToken(&out)
addAwsCredentials(&out)
if !isActive(&out) {
continue
}
outs = append(outs, out)
}
return outs
}
func addGithubToken(ci *client.CacheOptionsEntry) {
if ci.Type != "gha" {
return
}
version, ok := ci.Attrs["version"]
if !ok {
// https://github.com/actions/toolkit/blob/2b08dc18f261b9fdd978b70279b85cbef81af8bc/packages/cache/src/internal/config.ts#L19
if v, ok := os.LookupEnv("ACTIONS_CACHE_SERVICE_V2"); ok {
if b, err := strconv.ParseBool(v); err == nil && b {
version = "2"
}
}
}
if _, ok := ci.Attrs["token"]; !ok {
if v, ok := os.LookupEnv("ACTIONS_RUNTIME_TOKEN"); ok {
ci.Attrs["token"] = v
}
}
if _, ok := ci.Attrs["url_v2"]; !ok && version == "2" {
// https://github.com/actions/toolkit/blob/2b08dc18f261b9fdd978b70279b85cbef81af8bc/packages/cache/src/internal/config.ts#L34-L35
if v, ok := os.LookupEnv("ACTIONS_RESULTS_URL"); ok {
ci.Attrs["url_v2"] = v
}
}
if _, ok := ci.Attrs["url"]; !ok {
// https://github.com/actions/toolkit/blob/2b08dc18f261b9fdd978b70279b85cbef81af8bc/packages/cache/src/internal/config.ts#L28-L33
if v, ok := os.LookupEnv("ACTIONS_CACHE_URL"); ok {
ci.Attrs["url"] = v
} else if v, ok := os.LookupEnv("ACTIONS_RESULTS_URL"); ok {
ci.Attrs["url"] = v
}
}
}
func addAwsCredentials(ci *client.CacheOptionsEntry) {
if ci.Type != "s3" {
return
}
_, okAccessKeyID := ci.Attrs["access_key_id"]
_, okSecretAccessKey := ci.Attrs["secret_access_key"]
// If the user provides access_key_id, secret_access_key, do not override the session token.
if okAccessKeyID && okSecretAccessKey {
return
}
ctx := context.TODO()
awsConfig, err := awsconfig.LoadDefaultConfig(ctx)
if err != nil {
return
}
credentials, err := awsConfig.Credentials.Retrieve(ctx)
if err != nil {
return
}
if !okAccessKeyID && credentials.AccessKeyID != "" {
ci.Attrs["access_key_id"] = credentials.AccessKeyID
}
if !okSecretAccessKey && credentials.SecretAccessKey != "" {
ci.Attrs["secret_access_key"] = credentials.SecretAccessKey
}
if _, ok := ci.Attrs["session_token"]; !ok && credentials.SessionToken != "" {
ci.Attrs["session_token"] = credentials.SessionToken
}
}
func isActive(ce *client.CacheOptionsEntry) bool {
// Always active if not gha.
if ce.Type != "gha" {
return true
}
return ce.Attrs["token"] != "" && (ce.Attrs["url"] != "" || ce.Attrs["url_v2"] != "")
}

40
build/opt_test.go Normal file
View File

@ -0,0 +1,40 @@
package build
import (
"testing"
"github.com/docker/buildx/util/buildflags"
"github.com/moby/buildkit/client"
"github.com/stretchr/testify/require"
)
func TestCacheOptions_DerivedVars(t *testing.T) {
t.Setenv("ACTIONS_RUNTIME_TOKEN", "sensitive_token")
t.Setenv("ACTIONS_CACHE_URL", "https://cache.github.com")
t.Setenv("AWS_ACCESS_KEY_ID", "definitely_dont_look_here")
t.Setenv("AWS_SECRET_ACCESS_KEY", "hackers_please_dont_steal")
t.Setenv("AWS_SESSION_TOKEN", "not_a_mitm_attack")
cacheFrom, err := buildflags.ParseCacheEntry([]string{"type=gha", "type=s3,region=us-west-2,bucket=my_bucket,name=my_image"})
require.NoError(t, err)
require.Equal(t, []client.CacheOptionsEntry{
{
Type: "gha",
Attrs: map[string]string{
"token": "sensitive_token",
"url": "https://cache.github.com",
},
},
{
Type: "s3",
Attrs: map[string]string{
"region": "us-west-2",
"bucket": "my_bucket",
"name": "my_image",
"access_key_id": "definitely_dont_look_here",
"secret_access_key": "hackers_please_dont_steal",
"session_token": "not_a_mitm_attack",
},
},
}, CreateCaches(cacheFrom))
}

View File

@ -13,6 +13,7 @@ import (
"github.com/containerd/containerd/v2/core/content/proxy"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/progress"
slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
controlapi "github.com/moby/buildkit/api/services/control"
"github.com/moby/buildkit/client"
provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types"
@ -22,15 +23,6 @@ import (
"golang.org/x/sync/errgroup"
)
type provenancePredicate struct {
Builder *provenanceBuilder `json:"builder,omitempty"`
provenancetypes.ProvenancePredicate
}
type provenanceBuilder struct {
ID string `json:"id,omitempty"`
}
func setRecordProvenance(ctx context.Context, c *client.Client, sr *client.SolveResponse, ref string, mode confutil.MetadataProvenanceMode, pw progress.Writer) error {
if mode == confutil.MetadataProvenanceModeDisabled {
return nil
@ -69,7 +61,7 @@ func fetchProvenance(ctx context.Context, c *client.Client, ref string, mode con
continue
}
if ev.Record.Result != nil {
desc := lookupProvenance(ev.Record.Result)
desc, predicateType := lookupProvenance(ev.Record.Result)
if desc == nil {
continue
}
@ -78,7 +70,7 @@ func fetchProvenance(ctx context.Context, c *client.Client, ref string, mode con
if err != nil {
return errors.Wrapf(err, "failed to load provenance blob from build record")
}
prv, err := encodeProvenance(dt, mode)
prv, err := encodeProvenance(dt, predicateType, mode)
if err != nil {
return err
}
@ -92,7 +84,7 @@ func fetchProvenance(ctx context.Context, c *client.Client, ref string, mode con
})
} else if ev.Record.Results != nil {
for platform, res := range ev.Record.Results {
desc := lookupProvenance(res)
desc, predicateType := lookupProvenance(res)
if desc == nil {
continue
}
@ -101,7 +93,7 @@ func fetchProvenance(ctx context.Context, c *client.Client, ref string, mode con
if err != nil {
return errors.Wrapf(err, "failed to load provenance blob from build record")
}
prv, err := encodeProvenance(dt, mode)
prv, err := encodeProvenance(dt, predicateType, mode)
if err != nil {
return err
}
@ -119,7 +111,7 @@ func fetchProvenance(ctx context.Context, c *client.Client, ref string, mode con
return out, eg.Wait()
}
func lookupProvenance(res *controlapi.BuildResultInfo) *ocispecs.Descriptor {
func lookupProvenance(res *controlapi.BuildResultInfo) (*ocispecs.Descriptor, string) {
for _, a := range res.Attestations {
if a.MediaType == "application/vnd.in-toto+json" && strings.HasPrefix(a.Annotations["in-toto.io/predicate-type"], "https://slsa.dev/provenance/") {
return &ocispecs.Descriptor{
@ -127,27 +119,29 @@ func lookupProvenance(res *controlapi.BuildResultInfo) *ocispecs.Descriptor {
Size: a.Size,
MediaType: a.MediaType,
Annotations: a.Annotations,
}
}, a.Annotations["in-toto.io/predicate-type"]
}
}
return nil
return nil, ""
}
func encodeProvenance(dt []byte, mode confutil.MetadataProvenanceMode) (string, error) {
var prv provenancePredicate
if err := json.Unmarshal(dt, &prv); err != nil {
func encodeProvenance(dt []byte, predicateType string, mode confutil.MetadataProvenanceMode) (string, error) {
var pred *provenancetypes.ProvenancePredicateSLSA02
if predicateType == slsa1.PredicateSLSAProvenance {
var pred1 *provenancetypes.ProvenancePredicateSLSA1
if err := json.Unmarshal(dt, &pred1); err != nil {
return "", errors.Wrapf(err, "failed to unmarshal provenance")
}
pred = pred1.ConvertToSLSA02()
} else if err := json.Unmarshal(dt, &pred); err != nil {
return "", errors.Wrapf(err, "failed to unmarshal provenance")
}
if prv.Builder != nil && prv.Builder.ID == "" {
// reset builder if id is empty
prv.Builder = nil
}
if mode == confutil.MetadataProvenanceModeMin {
// reset fields for minimal provenance
prv.BuildConfig = nil
prv.Metadata = nil
pred.BuildConfig = nil
pred.Metadata = nil
}
dtprv, err := json.Marshal(prv)
dtprv, err := json.Marshal(pred)
if err != nil {
return "", errors.Wrapf(err, "failed to marshal provenance")
}

View File

@ -7,260 +7,41 @@ import (
"io"
"sync"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/solver/errdefs"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/solver/result"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
)
// NewResultHandle makes a call to client.Build, additionally returning a
// opaque ResultHandle alongside the standard response and error.
// NewResultHandle stores a gateway client, gateway result, and the error from
// an evaluate call if it is present.
//
// This ResultHandle can be used to execute additional build steps in the same
// context as the build occurred, which can allow easy debugging of build
// failures and successes.
//
// If the returned ResultHandle is not nil, the caller must call Done() on it.
func NewResultHandle(ctx context.Context, cc *client.Client, opt client.SolveOpt, product string, buildFunc gateway.BuildFunc, ch chan *client.SolveStatus) (*ResultHandle, *client.SolveResponse, error) {
// Create a new context to wrap the original, and cancel it when the
// caller-provided context is cancelled.
//
// We derive the context from the background context so that we can forbid
// cancellation of the build request after <-done is closed (which we do
// before returning the ResultHandle).
baseCtx := ctx
ctx, cancel := context.WithCancelCause(context.Background())
done := make(chan struct{})
go func() {
select {
case <-baseCtx.Done():
cancel(baseCtx.Err())
case <-done:
// Once done is closed, we've recorded a ResultHandle, so we
// shouldn't allow cancelling the underlying build request anymore.
}
}()
// Create a new channel to forward status messages to the original.
//
// We do this so that we can discard status messages after the main portion
// of the build is complete. This is necessary for the solve error case,
// where the original gateway is kept open until the ResultHandle is
// closed - we don't want progress messages from operations in that
// ResultHandle to display after this function exits.
//
// Additionally, callers should wait for the progress channel to be closed.
// If we keep the session open and never close the progress channel, the
// caller will likely hang.
baseCh := ch
ch = make(chan *client.SolveStatus)
go func() {
for {
s, ok := <-ch
if !ok {
return
}
select {
case <-baseCh:
// base channel is closed, discard status messages
default:
baseCh <- s
}
}
}()
defer close(baseCh)
var resp *client.SolveResponse
var respErr error
var respHandle *ResultHandle
go func() {
defer func() { cancel(errors.WithStack(context.Canceled)) }() // ensure no dangling processes
var res *gateway.Result
var err error
resp, err = cc.Build(ctx, opt, product, func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
var err error
res, err = buildFunc(ctx, c)
if res != nil && err == nil {
// Force evaluation of the build result (otherwise, we likely
// won't get a solve error)
def, err2 := getDefinition(ctx, res)
if err2 != nil {
return nil, err2
}
res, err = evalDefinition(ctx, c, def)
}
if err != nil {
// Scenario 1: we failed to evaluate a node somewhere in the
// build graph.
//
// In this case, we construct a ResultHandle from this
// original Build session, and return it alongside the original
// build error. We then need to keep the gateway session open
// until the caller explicitly closes the ResultHandle.
var se *errdefs.SolveError
if errors.As(err, &se) {
respHandle = &ResultHandle{
done: make(chan struct{}),
solveErr: se,
gwClient: c,
gwCtx: ctx,
}
respErr = err // return original error to preserve stacktrace
close(done)
// Block until the caller closes the ResultHandle.
select {
case <-respHandle.done:
case <-ctx.Done():
}
}
}
return res, err
}, ch)
if respHandle != nil {
return
}
if err != nil {
// Something unexpected failed during the build, we didn't succeed,
// but we also didn't make it far enough to create a ResultHandle.
respErr = err
close(done)
return
}
// Scenario 2: we successfully built the image with no errors.
//
// In this case, the original gateway session has now been closed
// since the Build has been completed. So, we need to create a new
// gateway session to populate the ResultHandle. To do this, we
// need to re-evaluate the target result, in this new session. This
// should be instantaneous since the result should be cached.
def, err := getDefinition(ctx, res)
if err != nil {
respErr = err
close(done)
return
}
// NOTE: ideally this second connection should be lazily opened
opt := opt
opt.Ref = ""
opt.Exports = nil
opt.CacheExports = nil
opt.Internal = true
_, respErr = cc.Build(ctx, opt, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
res, err := evalDefinition(ctx, c, def)
if err != nil {
// This should probably not happen, since we've previously
// successfully evaluated the same result with no issues.
return nil, errors.Wrap(err, "inconsistent solve result")
}
respHandle = &ResultHandle{
done: make(chan struct{}),
res: res,
gwClient: c,
gwCtx: ctx,
}
close(done)
// Block until the caller closes the ResultHandle.
select {
case <-respHandle.done:
case <-ctx.Done():
}
return nil, context.Cause(ctx)
}, nil)
if respHandle != nil {
return
}
close(done)
}()
// Block until the other thread signals that it's completed the build.
select {
case <-done:
case <-baseCtx.Done():
if respErr == nil {
respErr = baseCtx.Err()
}
func NewResultHandle(ctx context.Context, c gateway.Client, res *gateway.Result, err error) *ResultHandle {
rCtx := &ResultHandle{
res: res,
gwClient: c,
}
return respHandle, resp, respErr
}
// getDefinition converts a gateway result into a collection of definitions for
// each ref in the result.
func getDefinition(ctx context.Context, res *gateway.Result) (*result.Result[*pb.Definition], error) {
return result.ConvertResult(res, func(ref gateway.Reference) (*pb.Definition, error) {
st, err := ref.ToState()
if err != nil {
return nil, err
}
def, err := st.Marshal(ctx)
if err != nil {
return nil, err
}
return def.ToPB(), nil
})
}
// evalDefinition performs the reverse of getDefinition, converting a
// collection of definitions into a gateway result.
func evalDefinition(ctx context.Context, c gateway.Client, defs *result.Result[*pb.Definition]) (*gateway.Result, error) {
// force evaluation of all targets in parallel
results := make(map[*pb.Definition]*gateway.Result)
resultsMu := sync.Mutex{}
eg, egCtx := errgroup.WithContext(ctx)
defs.EachRef(func(def *pb.Definition) error {
eg.Go(func() error {
res, err := c.Solve(egCtx, gateway.SolveRequest{
Evaluate: true,
Definition: def,
})
if err != nil {
return err
}
resultsMu.Lock()
results[def] = res
resultsMu.Unlock()
return nil
})
if err != nil && !errors.As(err, &rCtx.solveErr) {
return nil
})
if err := eg.Wait(); err != nil {
return nil, err
}
res, _ := result.ConvertResult(defs, func(def *pb.Definition) (gateway.Reference, error) {
if res, ok := results[def]; ok {
return res.Ref, nil
}
return nil, nil
})
return res, nil
return rCtx
}
// ResultHandle is a build result with the client that built it.
type ResultHandle struct {
res *gateway.Result
solveErr *errdefs.SolveError
done chan struct{}
doneOnce sync.Once
gwClient gateway.Client
gwCtx context.Context
doneOnce sync.Once
cleanups []func()
cleanupsMu sync.Mutex
@ -275,9 +56,6 @@ func (r *ResultHandle) Done() {
for _, f := range cleanups {
f()
}
close(r.done)
<-r.gwCtx.Done()
})
}
@ -287,12 +65,15 @@ func (r *ResultHandle) registerCleanup(f func()) {
r.cleanupsMu.Unlock()
}
func (r *ResultHandle) build(buildFunc gateway.BuildFunc) (err error) {
_, err = buildFunc(r.gwCtx, r.gwClient)
return err
func (r *ResultHandle) NewContainer(ctx context.Context, cfg *InvokeConfig) (gateway.Container, error) {
req, err := r.getContainerConfig(cfg)
if err != nil {
return nil, err
}
return r.gwClient.NewContainer(ctx, req)
}
func (r *ResultHandle) getContainerConfig(cfg *controllerapi.InvokeConfig) (containerCfg gateway.NewContainerRequest, _ error) {
func (r *ResultHandle) getContainerConfig(cfg *InvokeConfig) (containerCfg gateway.NewContainerRequest, _ error) {
if r.res != nil && r.solveErr == nil {
logrus.Debugf("creating container from successful build")
ccfg, err := containerConfigFromResult(r.res, cfg)
@ -311,7 +92,7 @@ func (r *ResultHandle) getContainerConfig(cfg *controllerapi.InvokeConfig) (cont
return containerCfg, nil
}
func (r *ResultHandle) getProcessConfig(cfg *controllerapi.InvokeConfig, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) (_ gateway.StartRequest, err error) {
func (r *ResultHandle) getProcessConfig(cfg *InvokeConfig, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) (_ gateway.StartRequest, err error) {
processCfg := newStartRequest(stdin, stdout, stderr)
if r.res != nil && r.solveErr == nil {
logrus.Debugf("creating container from successful build")
@ -327,7 +108,7 @@ func (r *ResultHandle) getProcessConfig(cfg *controllerapi.InvokeConfig, stdin i
return processCfg, nil
}
func containerConfigFromResult(res *gateway.Result, cfg *controllerapi.InvokeConfig) (*gateway.NewContainerRequest, error) {
func containerConfigFromResult(res *gateway.Result, cfg *InvokeConfig) (*gateway.NewContainerRequest, error) {
if cfg.Initial {
return nil, errors.Errorf("starting from the container from the initial state of the step is supported only on the failed steps")
}
@ -352,7 +133,7 @@ func containerConfigFromResult(res *gateway.Result, cfg *controllerapi.InvokeCon
}, nil
}
func populateProcessConfigFromResult(req *gateway.StartRequest, res *gateway.Result, cfg *controllerapi.InvokeConfig) error {
func populateProcessConfigFromResult(req *gateway.StartRequest, res *gateway.Result, cfg *InvokeConfig) error {
imgData := res.Metadata[exptypes.ExporterImageConfigKey]
var img *ocispecs.Image
if len(imgData) > 0 {
@ -403,7 +184,7 @@ func populateProcessConfigFromResult(req *gateway.StartRequest, res *gateway.Res
return nil
}
func containerConfigFromError(solveErr *errdefs.SolveError, cfg *controllerapi.InvokeConfig) (*gateway.NewContainerRequest, error) {
func containerConfigFromError(solveErr *errdefs.SolveError, cfg *InvokeConfig) (*gateway.NewContainerRequest, error) {
exec, err := execOpFromError(solveErr)
if err != nil {
return nil, err
@ -431,7 +212,7 @@ func containerConfigFromError(solveErr *errdefs.SolveError, cfg *controllerapi.I
}, nil
}
func populateProcessConfigFromError(req *gateway.StartRequest, solveErr *errdefs.SolveError, cfg *controllerapi.InvokeConfig) error {
func populateProcessConfigFromError(req *gateway.StartRequest, solveErr *errdefs.SolveError, cfg *InvokeConfig) error {
exec, err := execOpFromError(solveErr)
if err != nil {
return err

View File

@ -77,24 +77,30 @@ func toBuildkitExtraHosts(ctx context.Context, inp []string, nodeDriver *driver.
}
// If the IP Address is a "host-gateway", replace this value with the
// IP address provided by the worker's label.
var ips []string
if ip == mobyHostGatewayName {
hgip, err := nodeDriver.HostGatewayIP(ctx)
if err != nil {
return "", errors.Wrap(err, "unable to derive the IP value for host-gateway")
}
ip = hgip.String()
ips = append(ips, hgip.String())
} else {
// If the address is enclosed in square brackets, extract it (for IPv6, but
// permit it for IPv4 as well; we don't know the address family here, but it's
// unambiguous).
if len(ip) > 2 && ip[0] == '[' && ip[len(ip)-1] == ']' {
ip = ip[1 : len(ip)-1]
}
if net.ParseIP(ip) == nil {
return "", errors.Errorf("invalid host %s", h)
for _, v := range strings.Split(ip, ",") {
// If the address is enclosed in square brackets, extract it
// (for IPv6, but permit it for IPv4 as well; we don't know the
// address family here, but it's unambiguous).
if len(v) > 2 && v[0] == '[' && v[len(v)-1] == ']' {
v = v[1 : len(v)-1]
}
if net.ParseIP(v) == nil {
return "", errors.Errorf("invalid host %s", h)
}
ips = append(ips, v)
}
}
hosts = append(hosts, host+"="+ip)
for _, v := range ips {
hosts = append(hosts, host+"="+v)
}
}
return strings.Join(hosts, ","), nil
}

View File

@ -72,6 +72,11 @@ func TestToBuildkitExtraHosts(t *testing.T) {
doc: "IPv6 localhost, non-canonical, eq sep",
input: []string{`ipv6local=0:0:0:0:0:0:0:1`},
},
{
doc: "Multi IPs",
input: []string{`myhost=162.242.195.82,162.242.195.83`},
expectedOut: `myhost=162.242.195.82,myhost=162.242.195.83`,
},
{
doc: "IPv6 localhost, non-canonical, eq sep, brackets",
input: []string{`ipv6local=[0:0:0:0:0:0:0:1]`},

View File

@ -14,10 +14,12 @@ import (
"github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/debug"
"github.com/moby/buildkit/solver/errdefs"
solvererrdefs "github.com/moby/buildkit/solver/errdefs"
"github.com/moby/buildkit/util/grpcerrors"
"github.com/moby/buildkit/util/stack"
"github.com/pkg/errors"
"go.opentelemetry.io/otel"
"google.golang.org/grpc/codes"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
@ -99,7 +101,7 @@ func main() {
os.Exit(sterr.StatusCode)
}
for _, s := range errdefs.Sources(err) {
for _, s := range solvererrdefs.Sources(err) {
s.Print(cmd.Err())
}
if debug.IsEnabled() {
@ -113,5 +115,19 @@ func main() {
ebr.Print(cmd.Err())
}
os.Exit(1)
exitCode := 1
switch grpcerrors.Code(err) {
case codes.Internal:
exitCode = 100 // https://github.com/square/exit/blob/v1.3.0/exit.go#L70
case codes.ResourceExhausted:
exitCode = 102
case codes.Canceled:
exitCode = 130
default:
if errors.Is(err, context.Canceled) {
exitCode = 130
}
}
os.Exit(exitCode)
}

View File

@ -22,7 +22,6 @@ import (
"github.com/docker/buildx/bake/hclparser"
"github.com/docker/buildx/build"
"github.com/docker/buildx/builder"
"github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/localstate"
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/cobrautil/completion"
@ -41,6 +40,11 @@ import (
"go.opentelemetry.io/otel/attribute"
)
const (
bakeEnvFileSeparator = "BUILDX_BAKE_PATH_SEPARATOR"
bakeEnvFilePath = "BUILDX_BAKE_FILE"
)
type bakeOptions struct {
files []string
overrides []string
@ -63,7 +67,7 @@ type bakeOptions struct {
listVars bool
}
func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) {
func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags, filesFromEnv bool) (err error) {
mp := dockerCli.MeterProvider()
ctx, end, err := tracing.TraceCurrentCommand(ctx, append([]string{"bake"}, targets...),
@ -163,7 +167,13 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
attributes := bakeMetricAttributes(dockerCli, driverType, url, cmdContext, targets, &in)
progressMode := progressui.DisplayMode(cFlags.progress)
var printer *progress.Printer
defer func() {
if printer != nil {
printer.Wait()
}
}()
makePrinter := func() error {
var err error
@ -181,7 +191,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
return err
}
files, inp, err := readBakeFiles(ctx, nodes, url, in.files, dockerCli.In(), printer)
files, inp, err := readBakeFiles(ctx, nodes, url, in.files, dockerCli.In(), printer, filesFromEnv)
if err != nil {
return err
}
@ -348,7 +358,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
continue
}
pf := &pb.CallFunc{
pf := &buildflags.CallFunc{
Name: req.CallFunc.Name,
Format: req.CallFunc.Format,
IgnoreStatus: req.CallFunc.IgnoreStatus,
@ -453,6 +463,15 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
Aliases: []string{"f"},
Short: "Build from a file",
RunE: func(cmd *cobra.Command, args []string) error {
filesFromEnv := false
if len(options.files) == 0 {
envFiles, err := bakeEnvFiles(os.LookupEnv)
if err != nil {
return err
}
options.files = envFiles
filesFromEnv = true
}
// reset to nil to avoid override is unset
if !cmd.Flags().Lookup("no-cache").Changed {
cFlags.noCache = nil
@ -470,7 +489,7 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
options.builder = rootOpts.builder
options.metadataFile = cFlags.metadataFile
// Other common flags (noCache, pull and progress) are processed in runBake function.
return runBake(cmd.Context(), dockerCli, args, options, cFlags)
return runBake(cmd.Context(), dockerCli, args, options, cFlags, filesFromEnv)
},
ValidArgsFunction: completion.BakeTargets(options.files),
}
@ -505,6 +524,37 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
return cmd
}
func bakeEnvFiles(lookup func(string string) (string, bool)) ([]string, error) {
sep, _ := lookup(bakeEnvFileSeparator)
if sep == "" {
sep = string(os.PathListSeparator)
}
f, ok := lookup(bakeEnvFilePath)
if ok {
return cleanPaths(strings.Split(f, sep))
}
return []string{}, nil
}
func cleanPaths(p []string) ([]string, error) {
var paths []string
for _, f := range p {
f = strings.TrimSpace(f)
if f == "" {
continue
}
if f == "-" {
paths = append(paths, f)
continue
}
if _, err := os.Stat(f); err != nil {
return nil, err
}
paths = append(paths, f)
}
return paths, nil
}
func saveLocalStateGroup(dockerCli command.Cli, in bakeOptions, targets []string, bo map[string]build.Options) error {
l, err := localstate.New(confutil.NewConfig(dockerCli))
if err != nil {
@ -554,7 +604,7 @@ func bakeArgs(args []string) (url, cmdContext string, targets []string) {
return url, cmdContext, targets
}
func readBakeFiles(ctx context.Context, nodes []builder.Node, url string, names []string, stdin io.Reader, pw progress.Writer) (files []bake.File, inp *bake.Input, err error) {
func readBakeFiles(ctx context.Context, nodes []builder.Node, url string, names []string, stdin io.Reader, pw progress.Writer, filesFromEnv bool) (files []bake.File, inp *bake.Input, err error) {
var lnames []string // local
var rnames []string // remote
var anames []string // both
@ -579,7 +629,11 @@ func readBakeFiles(ctx context.Context, nodes []builder.Node, url string, names
if len(lnames) > 0 || url == "" {
var lfiles []bake.File
progress.Wrap("[internal] load local bake definitions", pw.Write, func(sub progress.SubLogger) error {
where := ""
if filesFromEnv {
where = " from " + bakeEnvFilePath + " env"
}
progress.Wrap("[internal] load local bake definitions"+where, pw.Write, func(sub progress.SubLogger) error {
if url != "" {
lfiles, err = bake.ReadLocalFiles(lnames, stdin, sub)
} else {
@ -663,7 +717,7 @@ func printVars(w io.Writer, format string, vars []*hclparser.Variable) error {
tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
defer tw.Flush()
tw.Write([]byte("VARIABLE\tVALUE\tDESCRIPTION\n"))
tw.Write([]byte("VARIABLE\tTYPE\tVALUE\tDESCRIPTION\n"))
for _, v := range vars {
var value string
@ -672,7 +726,7 @@ func printVars(w io.Writer, format string, vars []*hclparser.Variable) error {
} else {
value = "<null>"
}
fmt.Fprintf(tw, "%s\t%s\t%s\n", v.Name, value, v.Description)
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", v.Name, v.Type, value, v.Description)
}
return nil
}

View File

@ -20,12 +20,6 @@ import (
"github.com/containerd/console"
"github.com/docker/buildx/build"
"github.com/docker/buildx/builder"
"github.com/docker/buildx/commands/debug"
"github.com/docker/buildx/controller"
cbuild "github.com/docker/buildx/controller/build"
"github.com/docker/buildx/controller/control"
controllererrors "github.com/docker/buildx/controller/errdefs"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/monitor"
"github.com/docker/buildx/store"
"github.com/docker/buildx/store/storeutil"
@ -33,9 +27,10 @@ import (
"github.com/docker/buildx/util/cobrautil"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/desktop"
"github.com/docker/buildx/util/ioset"
"github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/metricutil"
"github.com/docker/buildx/util/osutil"
"github.com/docker/buildx/util/platformutil"
"github.com/docker/buildx/util/progress"
"github.com/docker/buildx/util/tracing"
"github.com/docker/cli/cli"
@ -48,8 +43,10 @@ import (
"github.com/moby/buildkit/frontend/subrequests/lint"
"github.com/moby/buildkit/frontend/subrequests/outline"
"github.com/moby/buildkit/frontend/subrequests/targets"
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/solver/errdefs"
solverpb "github.com/moby/buildkit/solver/pb"
sourcepolicy "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/moby/buildkit/util/grpcerrors"
"github.com/moby/buildkit/util/progress/progressui"
"github.com/moby/sys/atomicwriter"
@ -107,7 +104,7 @@ type buildOptions struct {
invokeConfig *invokeConfig
}
func (o *buildOptions) toControllerOptions() (*cbuild.Options, error) {
func (o *buildOptions) toOptions() (*BuildOptions, error) {
var err error
buildArgs, err := listToMap(o.buildArgs, true)
@ -120,7 +117,7 @@ func (o *buildOptions) toControllerOptions() (*cbuild.Options, error) {
return nil, err
}
opts := cbuild.Options{
opts := BuildOptions{
Allow: o.allow,
Annotations: o.annotations,
BuildArgs: buildArgs,
@ -135,7 +132,7 @@ func (o *buildOptions) toControllerOptions() (*cbuild.Options, error) {
ShmSize: int64(o.shmSize),
Tags: o.tags,
Target: o.target,
Ulimits: dockerUlimitToControllerUlimit(o.ulimits),
Ulimits: o.ulimits,
Builder: o.builder,
NoCache: o.noCache,
Pull: o.pull,
@ -182,17 +179,15 @@ func (o *buildOptions) toControllerOptions() (*cbuild.Options, error) {
}
}
cacheFrom, err := buildflags.ParseCacheEntry(o.cacheFrom)
opts.CacheFrom, err = buildflags.ParseCacheEntry(o.cacheFrom)
if err != nil {
return nil, err
}
opts.CacheFrom = cacheFrom.ToPB()
cacheTo, err := buildflags.ParseCacheEntry(o.cacheTo)
opts.CacheTo, err = buildflags.ParseCacheEntry(o.cacheTo)
if err != nil {
return nil, err
}
opts.CacheTo = cacheTo.ToPB()
opts.Secrets, err = buildflags.ParseSecretSpecs(o.secrets)
if err != nil {
@ -296,7 +291,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
end(err)
}()
opts, err := options.toControllerOptions()
opts, err := options.toOptions()
if err != nil {
return err
}
@ -353,14 +348,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
}
done := timeBuildCommand(mp, attributes)
var resp *client.SolveResponse
var inputs *build.Inputs
var retErr error
if confutil.IsExperimental() {
resp, inputs, retErr = runControllerBuild(ctx, dockerCli, opts, options, printer)
} else {
resp, inputs, retErr = runBasicBuild(ctx, dockerCli, opts, printer)
}
resp, inputs, retErr := runBuildWithOptions(ctx, dockerCli, opts, options, printer)
if err := printer.Wait(); retErr == nil {
retErr = err
@ -418,134 +406,41 @@ func getImageID(resp map[string]string) string {
return dgst
}
func runBasicBuild(ctx context.Context, dockerCli command.Cli, opts *cbuild.Options, printer *progress.Printer) (*client.SolveResponse, *build.Inputs, error) {
resp, res, dfmap, err := cbuild.RunBuild(ctx, dockerCli, opts, dockerCli.In(), printer, false)
if res != nil {
res.Done()
}
return resp, dfmap, err
}
func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *cbuild.Options, options buildOptions, printer *progress.Printer) (*client.SolveResponse, *build.Inputs, error) {
func runBuildWithOptions(ctx context.Context, dockerCli command.Cli, opts *BuildOptions, options buildOptions, printer *progress.Printer) (_ *client.SolveResponse, _ *build.Inputs, retErr error) {
if options.invokeConfig != nil && (options.dockerfileName == "-" || options.contextPath == "-") {
// stdin must be usable for monitor
return nil, nil, errors.Errorf("Dockerfile or context from stdin is not supported with invoke")
}
c := controller.NewController(ctx, dockerCli)
defer func() {
if err := c.Close(); err != nil {
logrus.Warnf("failed to close server connection %v", err)
}
}()
// NOTE: buildx server has the current working directory different from the client
// so we need to resolve paths to abosolute ones in the client.
opts, err := cbuild.ResolveOptionPaths(opts)
if err != nil {
return nil, nil, err
}
var ref string
var retErr error
var resp *client.SolveResponse
var inputs *build.Inputs
var f *ioset.SingleForwarder
var pr io.ReadCloser
var pw io.WriteCloser
var (
in io.ReadCloser
m *monitor.Monitor
bh build.Handler
)
if options.invokeConfig == nil {
pr = dockerCli.In()
in = dockerCli.In()
} else {
f = ioset.NewSingleForwarder()
f.SetReader(dockerCli.In())
pr, pw = io.Pipe()
f.SetWriter(pw, func() io.WriteCloser {
pw.Close() // propagate EOF
logrus.Debug("propagating stdin close")
return nil
})
m = monitor.New(&options.invokeConfig.InvokeConfig, dockerCli.In(), os.Stdout, os.Stderr, printer)
defer m.Close()
bh = m.Handler()
}
resp, inputs, err = c.Build(ctx, opts, pr, printer)
if err != nil {
var be *controllererrors.BuildError
if errors.As(err, &be) {
retErr = err
// We can proceed to monitor
} else {
for {
resp, inputs, err := RunBuild(ctx, dockerCli, opts, in, printer, &bh)
if err != nil {
if errors.Is(err, build.ErrRestart) {
retErr = nil
continue
}
return nil, nil, errors.Wrapf(err, "failed to build")
}
return resp, inputs, err
}
if options.invokeConfig != nil {
if err := pw.Close(); err != nil {
logrus.Debug("failed to close stdin pipe writer")
}
if err := pr.Close(); err != nil {
logrus.Debug("failed to close stdin pipe reader")
}
}
if options.invokeConfig != nil && options.invokeConfig.needsDebug(retErr) {
// Print errors before launching monitor
if err := printError(retErr, printer); err != nil {
logrus.Warnf("failed to print error information: %v", err)
}
pr2, pw2 := io.Pipe()
f.SetWriter(pw2, func() io.WriteCloser {
pw2.Close() // propagate EOF
return nil
})
monitorBuildResult, err := options.invokeConfig.runDebug(ctx, ref, opts, c, pr2, os.Stdout, os.Stderr, printer)
if err := pw2.Close(); err != nil {
logrus.Debug("failed to close monitor stdin pipe reader")
}
if err != nil {
logrus.Warnf("failed to run monitor: %v", err)
}
if monitorBuildResult != nil {
// Update return values with the last build result from monitor
resp, retErr = monitorBuildResult.Resp, monitorBuildResult.Err
}
} else {
if err := c.Close(); err != nil {
logrus.Warnf("close error: %v", err)
}
}
return resp, inputs, retErr
}
func printError(err error, printer *progress.Printer) error {
if err == nil {
return nil
}
if err := printer.Pause(); err != nil {
return err
}
defer printer.Unpause()
for _, s := range errdefs.Sources(err) {
s.Print(os.Stderr)
}
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
return nil
}
func newDebuggableBuild(dockerCli command.Cli, rootOpts *rootOptions) debug.DebuggableCmd {
return &debuggableBuild{dockerCli: dockerCli, rootOpts: rootOpts}
}
type debuggableBuild struct {
dockerCli command.Cli
rootOpts *rootOptions
}
func (b *debuggableBuild) NewDebugger(cfg *debug.DebugConfig) *cobra.Command {
return buildCmd(b.dockerCli, b.rootOpts, cfg)
}
func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.DebugConfig) *cobra.Command {
func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debugOptions) *cobra.Command {
cFlags := &commonFlags{}
options := &buildOptions{}
@ -832,21 +727,6 @@ func listToMap(values []string, defaultEnv bool) (map[string]string, error) {
return result, nil
}
func dockerUlimitToControllerUlimit(u *dockeropts.UlimitOpt) *controllerapi.UlimitOpt {
if u == nil {
return nil
}
values := make(map[string]*controllerapi.Ulimit)
for _, u := range u.GetList() {
values[u.Name] = &controllerapi.Ulimit{
Name: u.Name,
Hard: u.Hard,
Soft: u.Soft,
}
}
return &controllerapi.UlimitOpt{Values: values}
}
func printWarnings(w io.Writer, warnings []client.VertexWarning, mode progressui.DisplayMode) {
if len(warnings) == 0 || mode == progressui.QuietMode || mode == progressui.RawJSONMode {
return
@ -886,7 +766,7 @@ func printWarnings(w io.Writer, warnings []client.VertexWarning, mode progressui
}
}
func printResult(w io.Writer, f *controllerapi.CallFunc, res map[string]string, target string, inp *build.Inputs) (int, error) {
func printResult(w io.Writer, f *buildflags.CallFunc, res map[string]string, target string, inp *build.Inputs) (int, error) {
switch f.Name {
case "outline":
return 0, printValue(w, outline.PrintOutline, outline.SubrequestsOutlineDefinition.Version, f.Format, res)
@ -987,37 +867,22 @@ func printValue(w io.Writer, printer callFunc, version string, format string, re
}
type invokeConfig struct {
controllerapi.InvokeConfig
onFlag string
build.InvokeConfig
invokeFlag string
}
func (cfg *invokeConfig) needsDebug(retErr error) bool {
switch cfg.onFlag {
case "always":
return true
case "error":
return retErr != nil
default:
return cfg.invokeFlag != ""
}
}
func (cfg *invokeConfig) runDebug(ctx context.Context, ref string, options *cbuild.Options, c control.BuildxController, stdin io.ReadCloser, stdout io.WriteCloser, stderr console.File, progress *progress.Printer) (*monitor.MonitorBuildResult, error) {
con := console.Current()
if err := con.SetRaw(); err != nil {
// TODO: run disconnect in build command (on error case)
if err := c.Close(); err != nil {
logrus.Warnf("close error: %v", err)
}
return nil, errors.Errorf("failed to configure terminal: %v", err)
}
defer con.Reset()
return monitor.RunMonitor(ctx, ref, options, &cfg.InvokeConfig, c, stdin, stdout, stderr, progress)
}
func (cfg *invokeConfig) parseInvokeConfig(invoke, on string) error {
cfg.onFlag = on
switch on {
case "always":
cfg.SuspendOn = build.SuspendAlways
case "error":
cfg.SuspendOn = build.SuspendError
default:
if invoke != "" {
cfg.SuspendOn = build.SuspendAlways
}
}
cfg.invokeFlag = invoke
cfg.Tty = true
cfg.NoCmd = true
@ -1139,3 +1004,209 @@ func otelErrorType(err error) string {
}
return name
}
const defaultTargetName = "default"
type BuildOptions struct {
ContextPath string
DockerfileName string
CallFunc *buildflags.CallFunc
NamedContexts map[string]string
Allow []string
Attests buildflags.Attests
BuildArgs map[string]string
CacheFrom []*buildflags.CacheOptionsEntry
CacheTo []*buildflags.CacheOptionsEntry
CgroupParent string
Exports []*buildflags.ExportEntry
ExtraHosts []string
Labels map[string]string
NetworkMode string
NoCacheFilter []string
Platforms []string
Secrets buildflags.Secrets
ShmSize int64
SSH []*buildflags.SSH
Tags []string
Target string
Ulimits *dockeropts.UlimitOpt
Builder string
NoCache bool
Pull bool
ExportPush bool
ExportLoad bool
SourcePolicy *sourcepolicy.Policy
Ref string
GroupRef string
Annotations []string
ProvenanceResponseMode string
}
// RunBuild runs the specified build and returns the result.
func RunBuild(ctx context.Context, dockerCli command.Cli, in *BuildOptions, inStream io.Reader, progress progress.Writer, bh *build.Handler) (*client.SolveResponse, *build.Inputs, error) {
if in.NoCache && len(in.NoCacheFilter) > 0 {
return nil, nil, errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together")
}
contexts := map[string]build.NamedContext{}
for name, path := range in.NamedContexts {
contexts[name] = build.NamedContext{Path: path}
}
opts := build.Options{
Inputs: build.Inputs{
ContextPath: in.ContextPath,
DockerfilePath: in.DockerfileName,
InStream: build.NewSyncMultiReader(inStream),
NamedContexts: contexts,
},
Ref: in.Ref,
BuildArgs: in.BuildArgs,
CgroupParent: in.CgroupParent,
ExtraHosts: in.ExtraHosts,
Labels: in.Labels,
NetworkMode: in.NetworkMode,
NoCache: in.NoCache,
NoCacheFilter: in.NoCacheFilter,
Pull: in.Pull,
ShmSize: dockeropts.MemBytes(in.ShmSize),
Tags: in.Tags,
Target: in.Target,
Ulimits: in.Ulimits,
GroupRef: in.GroupRef,
ProvenanceResponseMode: confutil.ParseMetadataProvenance(in.ProvenanceResponseMode),
}
platforms, err := platformutil.Parse(in.Platforms)
if err != nil {
return nil, nil, err
}
opts.Platforms = platforms
dockerConfig := dockerCli.ConfigFile()
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
ConfigFile: dockerConfig,
}))
secrets, err := build.CreateSecrets(in.Secrets)
if err != nil {
return nil, nil, err
}
opts.Session = append(opts.Session, secrets)
sshSpecs := in.SSH
if len(sshSpecs) == 0 && buildflags.IsGitSSH(in.ContextPath) {
sshSpecs = append(sshSpecs, &buildflags.SSH{ID: "default"})
}
ssh, err := build.CreateSSH(sshSpecs)
if err != nil {
return nil, nil, err
}
opts.Session = append(opts.Session, ssh)
outputs, _, err := build.CreateExports(in.Exports)
if err != nil {
return nil, nil, err
}
if in.ExportPush {
var pushUsed bool
for i := range outputs {
if outputs[i].Type == client.ExporterImage {
outputs[i].Attrs["push"] = "true"
pushUsed = true
}
}
if !pushUsed {
outputs = append(outputs, client.ExportEntry{
Type: client.ExporterImage,
Attrs: map[string]string{
"push": "true",
},
})
}
}
if in.ExportLoad {
var loadUsed bool
for i := range outputs {
if outputs[i].Type == client.ExporterDocker {
if _, ok := outputs[i].Attrs["dest"]; !ok {
loadUsed = true
break
}
}
}
if !loadUsed {
outputs = append(outputs, client.ExportEntry{
Type: client.ExporterDocker,
Attrs: map[string]string{},
})
}
}
annotations, err := buildflags.ParseAnnotations(in.Annotations)
if err != nil {
return nil, nil, errors.Wrap(err, "parse annotations")
}
for _, o := range outputs {
for k, v := range annotations {
o.Attrs[k.String()] = v
}
}
opts.Exports = outputs
opts.CacheFrom = build.CreateCaches(in.CacheFrom)
opts.CacheTo = build.CreateCaches(in.CacheTo)
opts.Attests = in.Attests.ToMap()
opts.SourcePolicy = in.SourcePolicy
allow, err := buildflags.ParseEntitlements(in.Allow)
if err != nil {
return nil, nil, err
}
opts.Allow = allow
if in.CallFunc != nil {
opts.CallFunc = &build.CallFunc{
Name: in.CallFunc.Name,
Format: in.CallFunc.Format,
IgnoreStatus: in.CallFunc.IgnoreStatus,
}
}
// key string used for kubernetes "sticky" mode
contextPathHash, err := filepath.Abs(in.ContextPath)
if err != nil {
contextPathHash = in.ContextPath
}
b, err := builder.New(dockerCli,
builder.WithName(in.Builder),
builder.WithContextPathHash(contextPathHash),
)
if err != nil {
return nil, nil, err
}
if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil {
return nil, nil, errors.Wrapf(err, "failed to update builder last activity time")
}
nodes, err := b.LoadNodes(ctx)
if err != nil {
return nil, nil, err
}
var inputs *build.Inputs
buildOptions := map[string]build.Options{defaultTargetName: opts}
resp, err := build.BuildWithResultHandler(ctx, nodes, buildOptions, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), progress, bh)
err = wrapBuildError(err, false)
if err != nil {
return nil, nil, err
}
if i, ok := buildOptions[defaultTargetName]; ok {
inputs = &i.Inputs
}
return resp[defaultTargetName], inputs, nil
}

34
commands/debug.go Normal file
View File

@ -0,0 +1,34 @@
package commands
import (
"github.com/docker/buildx/util/cobrautil"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
)
type debugOptions struct {
// InvokeFlag is a flag to configure the launched debugger and the commaned executed on the debugger.
InvokeFlag string
// OnFlag is a flag to configure the timing of launching the debugger.
OnFlag string
}
func debugCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
var options debugOptions
cmd := &cobra.Command{
Use: "debug",
Short: "Start debugger",
}
cobrautil.MarkCommandExperimental(cmd)
flags := cmd.Flags()
flags.StringVar(&options.InvokeFlag, "invoke", "", "Launch a monitor with executing specified command")
flags.StringVar(&options.OnFlag, "on", "error", "When to launch the monitor ([always, error])")
cobrautil.MarkFlagsExperimental(flags, "invoke", "on")
cmd.AddCommand(buildCmd(dockerCli, rootOpts, &options))
return cmd
}

View File

@ -1,82 +0,0 @@
package debug
import (
"context"
"os"
"github.com/containerd/console"
"github.com/docker/buildx/controller"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/monitor"
"github.com/docker/buildx/util/cobrautil"
"github.com/docker/buildx/util/progress"
"github.com/docker/cli/cli/command"
"github.com/moby/buildkit/util/progress/progressui"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
// DebugConfig is a user-specified configuration for the debugger.
type DebugConfig struct {
// InvokeFlag is a flag to configure the launched debugger and the commaned executed on the debugger.
InvokeFlag string
// OnFlag is a flag to configure the timing of launching the debugger.
OnFlag string
}
// DebuggableCmd is a command that supports debugger with recognizing the user-specified DebugConfig.
type DebuggableCmd interface {
// NewDebugger returns the new *cobra.Command with support for the debugger with recognizing DebugConfig.
NewDebugger(*DebugConfig) *cobra.Command
}
func RootCmd(dockerCli command.Cli, children ...DebuggableCmd) *cobra.Command {
var progressMode string
var options DebugConfig
cmd := &cobra.Command{
Use: "debug",
Short: "Start debugger",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
printer, err := progress.NewPrinter(context.TODO(), os.Stderr, progressui.DisplayMode(progressMode))
if err != nil {
return err
}
ctx := context.TODO()
c := controller.NewController(ctx, dockerCli)
defer func() {
if err := c.Close(); err != nil {
logrus.Warnf("failed to close server connection %v", err)
}
}()
con := console.Current()
if err := con.SetRaw(); err != nil {
return errors.Errorf("failed to configure terminal: %v", err)
}
_, err = monitor.RunMonitor(ctx, "", nil, &controllerapi.InvokeConfig{
Tty: true,
}, c, dockerCli.In(), os.Stdout, os.Stderr, printer)
con.Reset()
return err
},
}
cobrautil.MarkCommandExperimental(cmd)
flags := cmd.Flags()
flags.StringVar(&options.InvokeFlag, "invoke", "", "Launch a monitor with executing specified command")
flags.StringVar(&options.OnFlag, "on", "error", "When to launch the monitor ([always, error])")
flags.StringVar(&progressMode, "progress", "auto", `Set type of progress output ("auto", "plain", "tty", "rawjson") for the monitor. Use plain to show container output`)
cobrautil.MarkFlagsExperimental(flags, "invoke", "on")
for _, c := range children {
cmd.AddCommand(c.NewDebugger(&options))
}
return cmd
}

View File

@ -426,23 +426,32 @@ workers0:
}
provIndex := slices.IndexFunc(attachments, func(a attachment) bool {
return descrType(a.descr) == slsa02.PredicateSLSAProvenance
return strings.HasPrefix(descrType(a.descr), "https://slsa.dev/provenance/")
})
if provIndex != -1 {
prov := attachments[provIndex]
predType := descrType(prov.descr)
dt, err := content.ReadBlob(ctx, store, prov.descr)
if err != nil {
return errors.Errorf("failed to read provenance %s: %v", prov.descr.Digest, err)
}
var pred provenancetypes.ProvenancePredicate
if err := json.Unmarshal(dt, &pred); err != nil {
var pred *provenancetypes.ProvenancePredicateSLSA1
if predType == slsa02.PredicateSLSAProvenance {
var pred02 *provenancetypes.ProvenancePredicateSLSA02
if err := json.Unmarshal(dt, &pred02); err != nil {
return errors.Errorf("failed to unmarshal provenance %s: %v", prov.descr.Digest, err)
}
pred = pred02.ConvertToSLSA1()
} else if err := json.Unmarshal(dt, &pred); err != nil {
return errors.Errorf("failed to unmarshal provenance %s: %v", prov.descr.Digest, err)
}
for _, m := range pred.Materials {
out.Materials = append(out.Materials, materialOutput{
URI: m.URI,
Digests: digestSetToDigests(m.Digest),
})
if pred != nil {
for _, m := range pred.BuildDefinition.ResolvedDependencies {
out.Materials = append(out.Materials, materialOutput{
URI: m.URI,
Digests: digestSetToDigests(m.Digest),
})
}
}
}
@ -836,6 +845,7 @@ func ociDesc(in *controlapi.Descriptor) ocispecs.Descriptor {
Annotations: in.Annotations,
}
}
func descrType(desc ocispecs.Descriptor) string {
if typ, ok := desc.Annotations["in-toto.io/predicate-type"]; ok {
return typ

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/cli/cli/command"
intoto "github.com/in-toto/in-toto-golang/in_toto"
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
"github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
@ -76,25 +77,30 @@ func runAttachment(ctx context.Context, dockerCli command.Cli, opts attachmentOp
return err
}
typ := opts.typ
switch typ {
types := make(map[string]struct{})
switch opts.typ {
case "index":
typ = ocispecs.MediaTypeImageIndex
types[ocispecs.MediaTypeImageIndex] = struct{}{}
case "manifest":
typ = ocispecs.MediaTypeImageManifest
types[ocispecs.MediaTypeImageManifest] = struct{}{}
case "image":
typ = ocispecs.MediaTypeImageConfig
types[ocispecs.MediaTypeImageConfig] = struct{}{}
case "provenance":
typ = slsa02.PredicateSLSAProvenance
types[slsa1.PredicateSLSAProvenance] = struct{}{}
types[slsa02.PredicateSLSAProvenance] = struct{}{}
case "sbom":
typ = intoto.PredicateSPDX
types[intoto.PredicateSPDX] = struct{}{}
default:
if opts.typ != "" {
types[opts.typ] = struct{}{}
}
}
for _, a := range attachments {
if opts.platform != "" && (a.platform == nil || platforms.FormatAll(*a.platform) != opts.platform) {
continue
}
if typ != "" && descrType(a.descr) != typ {
if _, ok := types[descrType(a.descr)]; opts.typ != "" && !ok {
continue
}
ra, err := store.ReaderAt(ctx, a.descr)
@ -112,9 +118,9 @@ func attachmentCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
var options attachmentOptions
cmd := &cobra.Command{
Use: "attachment [OPTIONS] REF [DIGEST]",
Use: "attachment [OPTIONS] [REF [DIGEST]]",
Short: "Inspect a build record attachment",
Args: cobra.RangeArgs(1, 2),
Args: cobra.MaximumNArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
options.ref = args[0]

View File

@ -182,7 +182,7 @@ func pruneCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
flags.Var(&options.reservedSpace, "keep-storage", "Amount of disk space to keep for cache")
flags.MarkDeprecated("keep-storage", "keep-storage flag has been changed to max-storage")
flags.MarkDeprecated("keep-storage", "keep-storage flag has been changed to reserved-space")
return cmd
}

View File

@ -4,7 +4,6 @@ import (
"fmt"
"os"
debugcmd "github.com/docker/buildx/commands/debug"
historycmd "github.com/docker/buildx/commands/history"
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
"github.com/docker/buildx/util/cobrautil/completion"
@ -120,9 +119,7 @@ func addCommands(cmd *cobra.Command, opts *rootOptions, dockerCli command.Cli) {
historycmd.RootCmd(cmd, dockerCli, historycmd.RootOptions{Builder: &opts.builder}),
)
if confutil.IsExperimental() {
cmd.AddCommand(debugcmd.RootCmd(dockerCli,
newDebuggableBuild(dockerCli, opts),
))
cmd.AddCommand(debugCmd(dockerCli, opts))
}
cmd.RegisterFlagCompletionFunc( //nolint:errcheck

View File

@ -1,288 +0,0 @@
package build
import (
"context"
"io"
"path/filepath"
"strings"
"sync"
"github.com/docker/buildx/build"
"github.com/docker/buildx/builder"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/store"
"github.com/docker/buildx/store/storeutil"
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/platformutil"
"github.com/docker/buildx/util/progress"
"github.com/docker/cli/cli/command"
dockeropts "github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/util/grpcerrors"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
)
const defaultTargetName = "default"
// RunBuild runs the specified build and returns the result.
//
// NOTE: When an error happens during the build and this function acquires the debuggable *build.ResultHandle,
// this function returns it in addition to the error (i.e. it does "return nil, res, err"). The caller can
// inspect the result and debug the cause of that error.
func RunBuild(ctx context.Context, dockerCli command.Cli, in *Options, inStream io.Reader, progress progress.Writer, generateResult bool) (*client.SolveResponse, *build.ResultHandle, *build.Inputs, error) {
if in.NoCache && len(in.NoCacheFilter) > 0 {
return nil, nil, nil, errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together")
}
contexts := map[string]build.NamedContext{}
for name, path := range in.NamedContexts {
contexts[name] = build.NamedContext{Path: path}
}
opts := build.Options{
Inputs: build.Inputs{
ContextPath: in.ContextPath,
DockerfilePath: in.DockerfileName,
InStream: build.NewSyncMultiReader(inStream),
NamedContexts: contexts,
},
Ref: in.Ref,
BuildArgs: in.BuildArgs,
CgroupParent: in.CgroupParent,
ExtraHosts: in.ExtraHosts,
Labels: in.Labels,
NetworkMode: in.NetworkMode,
NoCache: in.NoCache,
NoCacheFilter: in.NoCacheFilter,
Pull: in.Pull,
ShmSize: dockeropts.MemBytes(in.ShmSize),
Tags: in.Tags,
Target: in.Target,
Ulimits: controllerUlimitOpt2DockerUlimit(in.Ulimits),
GroupRef: in.GroupRef,
ProvenanceResponseMode: confutil.ParseMetadataProvenance(in.ProvenanceResponseMode),
}
platforms, err := platformutil.Parse(in.Platforms)
if err != nil {
return nil, nil, nil, err
}
opts.Platforms = platforms
dockerConfig := dockerCli.ConfigFile()
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
ConfigFile: dockerConfig,
}))
secrets, err := controllerapi.CreateSecrets(in.Secrets)
if err != nil {
return nil, nil, nil, err
}
opts.Session = append(opts.Session, secrets)
sshSpecs := in.SSH
if len(sshSpecs) == 0 && buildflags.IsGitSSH(in.ContextPath) {
sshSpecs = append(sshSpecs, &controllerapi.SSH{ID: "default"})
}
ssh, err := controllerapi.CreateSSH(sshSpecs)
if err != nil {
return nil, nil, nil, err
}
opts.Session = append(opts.Session, ssh)
outputs, _, err := controllerapi.CreateExports(in.Exports)
if err != nil {
return nil, nil, nil, err
}
if in.ExportPush {
var pushUsed bool
for i := range outputs {
if outputs[i].Type == client.ExporterImage {
outputs[i].Attrs["push"] = "true"
pushUsed = true
}
}
if !pushUsed {
outputs = append(outputs, client.ExportEntry{
Type: client.ExporterImage,
Attrs: map[string]string{
"push": "true",
},
})
}
}
if in.ExportLoad {
var loadUsed bool
for i := range outputs {
if outputs[i].Type == client.ExporterDocker {
if _, ok := outputs[i].Attrs["dest"]; !ok {
loadUsed = true
break
}
}
}
if !loadUsed {
outputs = append(outputs, client.ExportEntry{
Type: client.ExporterDocker,
Attrs: map[string]string{},
})
}
}
annotations, err := buildflags.ParseAnnotations(in.Annotations)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "parse annotations")
}
for _, o := range outputs {
for k, v := range annotations {
o.Attrs[k.String()] = v
}
}
opts.Exports = outputs
opts.CacheFrom = controllerapi.CreateCaches(in.CacheFrom)
opts.CacheTo = controllerapi.CreateCaches(in.CacheTo)
opts.Attests = controllerapi.CreateAttestations(in.Attests)
opts.SourcePolicy = in.SourcePolicy
allow, err := buildflags.ParseEntitlements(in.Allow)
if err != nil {
return nil, nil, nil, err
}
opts.Allow = allow
if in.CallFunc != nil {
opts.CallFunc = &build.CallFunc{
Name: in.CallFunc.Name,
Format: in.CallFunc.Format,
IgnoreStatus: in.CallFunc.IgnoreStatus,
}
}
// key string used for kubernetes "sticky" mode
contextPathHash, err := filepath.Abs(in.ContextPath)
if err != nil {
contextPathHash = in.ContextPath
}
// TODO: this should not be loaded this side of the controller api
b, err := builder.New(dockerCli,
builder.WithName(in.Builder),
builder.WithContextPathHash(contextPathHash),
)
if err != nil {
return nil, nil, nil, err
}
if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil {
return nil, nil, nil, errors.Wrapf(err, "failed to update builder last activity time")
}
nodes, err := b.LoadNodes(ctx)
if err != nil {
return nil, nil, nil, err
}
var inputs *build.Inputs
buildOptions := map[string]build.Options{defaultTargetName: opts}
resp, res, err := buildTargets(ctx, dockerCli, nodes, buildOptions, progress, generateResult)
err = wrapBuildError(err, false)
if err != nil {
// NOTE: buildTargets can return *build.ResultHandle even on error.
return nil, res, nil, err
}
if i, ok := buildOptions[defaultTargetName]; ok {
inputs = &i.Inputs
}
return resp, res, inputs, nil
}
// buildTargets runs the specified build and returns the result.
//
// NOTE: When an error happens during the build and this function acquires the debuggable *build.ResultHandle,
// this function returns it in addition to the error (i.e. it does "return nil, res, err"). The caller can
// inspect the result and debug the cause of that error.
func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.Node, opts map[string]build.Options, progress progress.Writer, generateResult bool) (*client.SolveResponse, *build.ResultHandle, error) {
var res *build.ResultHandle
var resp map[string]*client.SolveResponse
var err error
if generateResult {
var mu sync.Mutex
var idx int
resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), progress, func(driverIndex int, gotRes *build.ResultHandle) {
mu.Lock()
defer mu.Unlock()
if res == nil || driverIndex < idx {
idx, res = driverIndex, gotRes
}
})
} else {
resp, err = build.Build(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), progress)
}
if err != nil {
return nil, res, err
}
return resp[defaultTargetName], res, err
}
func wrapBuildError(err error, bake bool) error {
if err == nil {
return nil
}
st, ok := grpcerrors.AsGRPCStatus(err)
if ok {
if st.Code() == codes.Unimplemented && strings.Contains(st.Message(), "unsupported frontend capability moby.buildkit.frontend.contexts") {
msg := "current frontend does not support --build-context."
if bake {
msg = "current frontend does not support defining additional contexts for targets."
}
msg += " Named contexts are supported since Dockerfile v1.4. Use #syntax directive in Dockerfile or update to latest BuildKit."
return &wrapped{err, msg}
}
}
return err
}
type wrapped struct {
err error
msg string
}
func (w *wrapped) Error() string {
return w.msg
}
func (w *wrapped) Unwrap() error {
return w.err
}
func updateLastActivity(dockerCli command.Cli, ng *store.NodeGroup) error {
txn, release, err := storeutil.GetStore(dockerCli)
if err != nil {
return err
}
defer release()
return txn.UpdateLastActivity(ng)
}
func controllerUlimitOpt2DockerUlimit(u *controllerapi.UlimitOpt) *dockeropts.UlimitOpt {
if u == nil {
return nil
}
values := make(map[string]*container.Ulimit)
for k, v := range u.Values {
values[k] = &container.Ulimit{
Name: v.Name,
Hard: v.Hard,
Soft: v.Soft,
}
}
return dockeropts.NewUlimitOpt(&values)
}

View File

@ -1,216 +0,0 @@
package build
import (
"path/filepath"
"strings"
"github.com/docker/buildx/controller/pb"
sourcepolicy "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/moby/buildkit/util/gitutil"
)
type Options struct {
ContextPath string
DockerfileName string
CallFunc *pb.CallFunc
NamedContexts map[string]string
Allow []string
Attests []*pb.Attest
BuildArgs map[string]string
CacheFrom []*pb.CacheOptionsEntry
CacheTo []*pb.CacheOptionsEntry
CgroupParent string
Exports []*pb.ExportEntry
ExtraHosts []string
Labels map[string]string
NetworkMode string
NoCacheFilter []string
Platforms []string
Secrets []*pb.Secret
ShmSize int64
SSH []*pb.SSH
Tags []string
Target string
Ulimits *pb.UlimitOpt
Builder string
NoCache bool
Pull bool
ExportPush bool
ExportLoad bool
SourcePolicy *sourcepolicy.Policy
Ref string
GroupRef string
Annotations []string
ProvenanceResponseMode string
}
// ResolveOptionPaths resolves all paths contained in BuildOptions
// and replaces them to absolute paths.
func ResolveOptionPaths(options *Options) (_ *Options, err error) {
localContext := false
if options.ContextPath != "" && options.ContextPath != "-" {
if !isRemoteURL(options.ContextPath) {
localContext = true
options.ContextPath, err = filepath.Abs(options.ContextPath)
if err != nil {
return nil, err
}
}
}
if options.DockerfileName != "" && options.DockerfileName != "-" {
if localContext && !isHTTPURL(options.DockerfileName) {
options.DockerfileName, err = filepath.Abs(options.DockerfileName)
if err != nil {
return nil, err
}
}
}
var contexts map[string]string
for k, v := range options.NamedContexts {
if isRemoteURL(v) || strings.HasPrefix(v, "docker-image://") {
// url prefix, this is a remote path
} else if p, ok := strings.CutPrefix(v, "oci-layout://"); ok {
// oci layout prefix, this is a local path
p, err = filepath.Abs(p)
if err != nil {
return nil, err
}
v = "oci-layout://" + p
} else {
// no prefix, assume local path
v, err = filepath.Abs(v)
if err != nil {
return nil, err
}
}
if contexts == nil {
contexts = make(map[string]string)
}
contexts[k] = v
}
options.NamedContexts = contexts
var cacheFrom []*pb.CacheOptionsEntry
for _, co := range options.CacheFrom {
switch co.Type {
case "local":
var attrs map[string]string
for k, v := range co.Attrs {
if attrs == nil {
attrs = make(map[string]string)
}
switch k {
case "src":
p := v
if p != "" {
p, err = filepath.Abs(p)
if err != nil {
return nil, err
}
}
attrs[k] = p
default:
attrs[k] = v
}
}
co.Attrs = attrs
cacheFrom = append(cacheFrom, co)
default:
cacheFrom = append(cacheFrom, co)
}
}
options.CacheFrom = cacheFrom
var cacheTo []*pb.CacheOptionsEntry
for _, co := range options.CacheTo {
switch co.Type {
case "local":
var attrs map[string]string
for k, v := range co.Attrs {
if attrs == nil {
attrs = make(map[string]string)
}
switch k {
case "dest":
p := v
if p != "" {
p, err = filepath.Abs(p)
if err != nil {
return nil, err
}
}
attrs[k] = p
default:
attrs[k] = v
}
}
co.Attrs = attrs
cacheTo = append(cacheTo, co)
default:
cacheTo = append(cacheTo, co)
}
}
options.CacheTo = cacheTo
var exports []*pb.ExportEntry
for _, e := range options.Exports {
if e.Destination != "" && e.Destination != "-" {
e.Destination, err = filepath.Abs(e.Destination)
if err != nil {
return nil, err
}
}
exports = append(exports, e)
}
options.Exports = exports
var secrets []*pb.Secret
for _, s := range options.Secrets {
if s.FilePath != "" {
s.FilePath, err = filepath.Abs(s.FilePath)
if err != nil {
return nil, err
}
}
secrets = append(secrets, s)
}
options.Secrets = secrets
var ssh []*pb.SSH
for _, s := range options.SSH {
var ps []string
for _, pt := range s.Paths {
p := pt
if p != "" {
p, err = filepath.Abs(p)
if err != nil {
return nil, err
}
}
ps = append(ps, p)
}
s.Paths = ps
ssh = append(ssh, s)
}
options.SSH = ssh
return options, nil
}
// isHTTPURL returns true if the provided str is an HTTP(S) URL by checking if it
// has a http:// or https:// scheme. No validation is performed to verify if the
// URL is well-formed.
func isHTTPURL(str string) bool {
return strings.HasPrefix(str, "https://") || strings.HasPrefix(str, "http://")
}
func isRemoteURL(c string) bool {
if isHTTPURL(c) {
return true
}
if _, err := gitutil.ParseGitRef(c); err == nil {
return true
}
return false
}

View File

@ -1,249 +0,0 @@
package build
import (
"os"
"path/filepath"
"testing"
"github.com/docker/buildx/controller/pb"
"github.com/stretchr/testify/require"
)
func TestResolvePaths(t *testing.T) {
tmpwd, err := os.MkdirTemp("", "testresolvepaths")
require.NoError(t, err)
defer os.Remove(tmpwd)
require.NoError(t, os.Chdir(tmpwd))
tests := []struct {
name string
options *Options
want *Options
}{
{
name: "contextpath",
options: &Options{ContextPath: "test"},
want: &Options{ContextPath: filepath.Join(tmpwd, "test")},
},
{
name: "contextpath-cwd",
options: &Options{ContextPath: "."},
want: &Options{ContextPath: tmpwd},
},
{
name: "contextpath-dash",
options: &Options{ContextPath: "-"},
want: &Options{ContextPath: "-"},
},
{
name: "contextpath-ssh",
options: &Options{ContextPath: "git@github.com:docker/buildx.git"},
want: &Options{ContextPath: "git@github.com:docker/buildx.git"},
},
{
name: "dockerfilename",
options: &Options{DockerfileName: "test", ContextPath: "."},
want: &Options{DockerfileName: filepath.Join(tmpwd, "test"), ContextPath: tmpwd},
},
{
name: "dockerfilename-dash",
options: &Options{DockerfileName: "-", ContextPath: "."},
want: &Options{DockerfileName: "-", ContextPath: tmpwd},
},
{
name: "dockerfilename-remote",
options: &Options{DockerfileName: "test", ContextPath: "git@github.com:docker/buildx.git"},
want: &Options{DockerfileName: "test", ContextPath: "git@github.com:docker/buildx.git"},
},
{
name: "contexts",
options: &Options{NamedContexts: map[string]string{
"a": "test1", "b": "test2",
"alpine": "docker-image://alpine@sha256:0123456789", "project": "https://github.com/myuser/project.git",
}},
want: &Options{NamedContexts: map[string]string{
"a": filepath.Join(tmpwd, "test1"), "b": filepath.Join(tmpwd, "test2"),
"alpine": "docker-image://alpine@sha256:0123456789", "project": "https://github.com/myuser/project.git",
}},
},
{
name: "cache-from",
options: &Options{
CacheFrom: []*pb.CacheOptionsEntry{
{
Type: "local",
Attrs: map[string]string{"src": "test"},
},
{
Type: "registry",
Attrs: map[string]string{"ref": "user/app"},
},
},
},
want: &Options{
CacheFrom: []*pb.CacheOptionsEntry{
{
Type: "local",
Attrs: map[string]string{"src": filepath.Join(tmpwd, "test")},
},
{
Type: "registry",
Attrs: map[string]string{"ref": "user/app"},
},
},
},
},
{
name: "cache-to",
options: &Options{
CacheTo: []*pb.CacheOptionsEntry{
{
Type: "local",
Attrs: map[string]string{"dest": "test"},
},
{
Type: "registry",
Attrs: map[string]string{"ref": "user/app"},
},
},
},
want: &Options{
CacheTo: []*pb.CacheOptionsEntry{
{
Type: "local",
Attrs: map[string]string{"dest": filepath.Join(tmpwd, "test")},
},
{
Type: "registry",
Attrs: map[string]string{"ref": "user/app"},
},
},
},
},
{
name: "exports",
options: &Options{
Exports: []*pb.ExportEntry{
{
Type: "local",
Destination: "-",
},
{
Type: "local",
Destination: "test1",
},
{
Type: "tar",
Destination: "test3",
},
{
Type: "oci",
Destination: "-",
},
{
Type: "docker",
Destination: "test4",
},
{
Type: "image",
Attrs: map[string]string{"push": "true"},
},
},
},
want: &Options{
Exports: []*pb.ExportEntry{
{
Type: "local",
Destination: "-",
},
{
Type: "local",
Destination: filepath.Join(tmpwd, "test1"),
},
{
Type: "tar",
Destination: filepath.Join(tmpwd, "test3"),
},
{
Type: "oci",
Destination: "-",
},
{
Type: "docker",
Destination: filepath.Join(tmpwd, "test4"),
},
{
Type: "image",
Attrs: map[string]string{"push": "true"},
},
},
},
},
{
name: "secrets",
options: &Options{
Secrets: []*pb.Secret{
{
FilePath: "test1",
},
{
ID: "val",
Env: "a",
},
{
ID: "test",
FilePath: "test3",
},
},
},
want: &Options{
Secrets: []*pb.Secret{
{
FilePath: filepath.Join(tmpwd, "test1"),
},
{
ID: "val",
Env: "a",
},
{
ID: "test",
FilePath: filepath.Join(tmpwd, "test3"),
},
},
},
},
{
name: "ssh",
options: &Options{
SSH: []*pb.SSH{
{
ID: "default",
Paths: []string{"test1", "test2"},
},
{
ID: "a",
Paths: []string{"test3"},
},
},
},
want: &Options{
SSH: []*pb.SSH{
{
ID: "default",
Paths: []string{filepath.Join(tmpwd, "test1"), filepath.Join(tmpwd, "test2")},
},
{
ID: "a",
Paths: []string{filepath.Join(tmpwd, "test3")},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ResolveOptionPaths(tt.options)
require.NoError(t, err)
require.Equal(t, tt.want, got)
})
}
}

View File

@ -1,26 +0,0 @@
package control
import (
"context"
"io"
"github.com/docker/buildx/build"
cbuild "github.com/docker/buildx/controller/build"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/controller/processes"
"github.com/docker/buildx/util/progress"
"github.com/moby/buildkit/client"
)
type BuildxController interface {
Build(ctx context.Context, options *cbuild.Options, in io.ReadCloser, progress progress.Writer) (resp *client.SolveResponse, inputs *build.Inputs, err error)
// Invoke starts an IO session into the specified process.
// If pid doesn't match to any running processes, it starts a new process with the specified config.
// If there is no container running or InvokeConfig.Rollback is specified, the process will start in a newly created container.
// NOTE: If needed, in the future, we can split this API into three APIs (NewContainer, NewProcess and Attach).
Invoke(ctx context.Context, pid string, options *controllerapi.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error
Close() error
ListProcesses(ctx context.Context) (infos []*processes.ProcessInfo, retErr error)
DisconnectProcess(ctx context.Context, pid string) error
Inspect(ctx context.Context) *cbuild.Options
}

View File

@ -1,13 +0,0 @@
package controller
import (
"context"
"github.com/docker/buildx/controller/control"
"github.com/docker/buildx/controller/local"
"github.com/docker/cli/cli/command"
)
func NewController(ctx context.Context, dockerCli command.Cli) control.BuildxController {
return local.NewLocalBuildxController(ctx, dockerCli)
}

View File

@ -1,20 +0,0 @@
package errdefs
type BuildError struct {
err error
}
func (e *BuildError) Unwrap() error {
return e.err
}
func (e *BuildError) Error() string {
return e.err.Error()
}
func WrapBuild(err error) error {
if err == nil {
return nil
}
return &BuildError{err: err}
}

View File

@ -1,117 +0,0 @@
package local
import (
"context"
"io"
"sync/atomic"
"github.com/docker/buildx/build"
cbuild "github.com/docker/buildx/controller/build"
"github.com/docker/buildx/controller/control"
controllererrors "github.com/docker/buildx/controller/errdefs"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/controller/processes"
"github.com/docker/buildx/util/ioset"
"github.com/docker/buildx/util/progress"
"github.com/docker/cli/cli/command"
"github.com/moby/buildkit/client"
"github.com/pkg/errors"
)
func NewLocalBuildxController(ctx context.Context, dockerCli command.Cli) control.BuildxController {
return &localController{
dockerCli: dockerCli,
processes: processes.NewManager(),
}
}
type buildConfig struct {
// TODO: these two structs should be merged
// Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113279719
resultCtx *build.ResultHandle
buildOptions *cbuild.Options
}
type localController struct {
dockerCli command.Cli
buildConfig buildConfig
processes *processes.Manager
buildOnGoing atomic.Bool
}
func (b *localController) Build(ctx context.Context, options *cbuild.Options, in io.ReadCloser, progress progress.Writer) (*client.SolveResponse, *build.Inputs, error) {
if !b.buildOnGoing.CompareAndSwap(false, true) {
return nil, nil, errors.New("build ongoing")
}
defer b.buildOnGoing.Store(false)
resp, res, dockerfileMappings, buildErr := cbuild.RunBuild(ctx, b.dockerCli, options, in, progress, true)
// NOTE: RunBuild can return *build.ResultHandle even on error.
if res != nil {
b.buildConfig = buildConfig{
resultCtx: res,
buildOptions: options,
}
if buildErr != nil {
buildErr = controllererrors.WrapBuild(buildErr)
}
}
if buildErr != nil {
return nil, nil, buildErr
}
return resp, dockerfileMappings, nil
}
func (b *localController) ListProcesses(ctx context.Context) (infos []*processes.ProcessInfo, retErr error) {
return b.processes.ListProcesses(), nil
}
func (b *localController) DisconnectProcess(ctx context.Context, pid string) error {
return b.processes.DeleteProcess(pid)
}
func (b *localController) cancelRunningProcesses() {
b.processes.CancelRunningProcesses()
}
func (b *localController) Invoke(ctx context.Context, pid string, cfg *controllerapi.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error {
proc, ok := b.processes.Get(pid)
if !ok {
// Start a new process.
if b.buildConfig.resultCtx == nil {
return errors.New("no build result is registered")
}
var err error
proc, err = b.processes.StartProcess(pid, b.buildConfig.resultCtx, cfg)
if err != nil {
return err
}
}
// Attach containerIn to this process
ioCancelledCh := make(chan struct{})
proc.ForwardIO(&ioset.In{Stdin: ioIn, Stdout: ioOut, Stderr: ioErr}, func(error) { close(ioCancelledCh) })
select {
case <-ioCancelledCh:
return errors.Errorf("io cancelled")
case err := <-proc.Done():
return err
case <-ctx.Done():
return context.Cause(ctx)
}
}
func (b *localController) Close() error {
b.cancelRunningProcesses()
if b.buildConfig.resultCtx != nil {
b.buildConfig.resultCtx.Done()
}
// TODO: cancel ongoing builds?
return nil
}
func (b *localController) Inspect(ctx context.Context) *cbuild.Options {
return b.buildConfig.buildOptions
}

View File

@ -1,26 +0,0 @@
package pb
type Attest struct {
Type string
Disabled bool
Attrs string
}
func CreateAttestations(attests []*Attest) map[string]*string {
result := map[string]*string{}
for _, attest := range attests {
// ignore duplicates
if _, ok := result[attest.Type]; ok {
continue
}
if attest.Disabled {
result[attest.Type] = nil
continue
}
attrs := attest.Attrs
result[attest.Type] = &attrs
}
return result
}

View File

@ -1,28 +0,0 @@
package pb
import (
"maps"
"github.com/moby/buildkit/client"
)
type CacheOptionsEntry struct {
Type string
Attrs map[string]string
}
func CreateCaches(entries []*CacheOptionsEntry) []client.CacheOptionsEntry {
var outs []client.CacheOptionsEntry
if len(entries) == 0 {
return nil
}
for _, entry := range entries {
out := client.CacheOptionsEntry{
Type: entry.Type,
Attrs: map[string]string{},
}
maps.Copy(out.Attrs, entry.Attrs)
outs = append(outs, out)
}
return outs
}

View File

@ -1,114 +0,0 @@
package pb
import (
"io"
"maps"
"os"
"strconv"
"github.com/containerd/console"
"github.com/moby/buildkit/client"
"github.com/pkg/errors"
)
type ExportEntry struct {
Type string
Attrs map[string]string
Destination string
}
func CreateExports(entries []*ExportEntry) ([]client.ExportEntry, []string, error) {
var outs []client.ExportEntry
var localPaths []string
if len(entries) == 0 {
return nil, nil, nil
}
var stdoutUsed bool
for _, entry := range entries {
if entry.Type == "" {
return nil, nil, errors.Errorf("type is required for output")
}
out := client.ExportEntry{
Type: entry.Type,
Attrs: map[string]string{},
}
maps.Copy(out.Attrs, entry.Attrs)
supportFile := false
supportDir := false
switch out.Type {
case client.ExporterLocal:
supportDir = true
case client.ExporterTar:
supportFile = true
case client.ExporterOCI, client.ExporterDocker:
tar, err := strconv.ParseBool(out.Attrs["tar"])
if err != nil {
tar = true
}
supportFile = tar
supportDir = !tar
case "registry":
out.Type = client.ExporterImage
out.Attrs["push"] = "true"
}
if supportDir {
if entry.Destination == "" {
return nil, nil, errors.Errorf("dest is required for %s exporter", out.Type)
}
if entry.Destination == "-" {
return nil, nil, errors.Errorf("dest cannot be stdout for %s exporter", out.Type)
}
fi, err := os.Stat(entry.Destination)
if err != nil && !os.IsNotExist(err) {
return nil, nil, errors.Wrapf(err, "invalid destination directory: %s", entry.Destination)
}
if err == nil && !fi.IsDir() {
return nil, nil, errors.Errorf("destination directory %s is a file", entry.Destination)
}
out.OutputDir = entry.Destination
localPaths = append(localPaths, entry.Destination)
}
if supportFile {
if entry.Destination == "" && out.Type != client.ExporterDocker {
entry.Destination = "-"
}
if entry.Destination == "-" {
if stdoutUsed {
return nil, nil, errors.Errorf("multiple outputs configured to write to stdout")
}
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
return nil, nil, errors.Errorf("dest file is required for %s exporter. refusing to write to console", out.Type)
}
out.Output = wrapWriteCloser(os.Stdout)
stdoutUsed = true
} else if entry.Destination != "" {
fi, err := os.Stat(entry.Destination)
if err != nil && !os.IsNotExist(err) {
return nil, nil, errors.Wrapf(err, "invalid destination file: %s", entry.Destination)
}
if err == nil && fi.IsDir() {
return nil, nil, errors.Errorf("destination file %s is a directory", entry.Destination)
}
f, err := os.Create(entry.Destination)
if err != nil {
return nil, nil, errors.Errorf("failed to open %s", err)
}
out.Output = wrapWriteCloser(f)
localPaths = append(localPaths, entry.Destination)
}
}
outs = append(outs, out)
}
return outs, localPaths, 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

@ -1,40 +0,0 @@
package pb
import (
"fmt"
"strings"
)
type CallFunc struct {
Name string
Format string
IgnoreStatus bool
}
func (x *CallFunc) String() string {
var elems []string
if x.Name != "" {
elems = append(elems, fmt.Sprintf("Name:%q", x.Name))
}
if x.Format != "" {
elems = append(elems, fmt.Sprintf("Format:%q", x.Format))
}
if x.IgnoreStatus {
elems = append(elems, fmt.Sprintf("IgnoreStatus:%v", x.IgnoreStatus))
}
return strings.Join(elems, " ")
}
type InvokeConfig struct {
Entrypoint []string
Cmd []string
NoCmd bool
Env []string
User string
NoUser bool
Cwd string
NoCwd bool
Tty bool
Rollback bool
Initial bool
}

View File

@ -1,28 +0,0 @@
package pb
import (
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/secrets/secretsprovider"
)
type Secret struct {
ID string
FilePath string
Env string
}
func CreateSecrets(secrets []*Secret) (session.Attachable, error) {
fs := make([]secretsprovider.Source, 0, len(secrets))
for _, secret := range secrets {
fs = append(fs, secretsprovider.Source{
ID: secret.ID,
FilePath: secret.FilePath,
Env: secret.Env,
})
}
store, err := secretsprovider.NewStore(fs)
if err != nil {
return nil, err
}
return secretsprovider.NewSecretProvider(store), nil
}

View File

@ -1,25 +0,0 @@
package pb
import (
"slices"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/sshforward/sshprovider"
)
type SSH struct {
ID string
Paths []string
}
func CreateSSH(ssh []*SSH) (session.Attachable, error) {
configs := make([]sshprovider.AgentConfig, 0, len(ssh))
for _, ssh := range ssh {
cfg := sshprovider.AgentConfig{
ID: ssh.ID,
Paths: slices.Clone(ssh.Paths),
}
configs = append(configs, cfg)
}
return sshprovider.NewSSHAgentProvider(configs)
}

View File

@ -1,11 +0,0 @@
package pb
type UlimitOpt struct {
Values map[string]*Ulimit
}
type Ulimit struct {
Name string
Hard int64
Soft int64
}

View File

@ -73,6 +73,13 @@ target "lint-gopls" {
target = "gopls-analyze"
}
target "modernize-fix" {
inherits = ["_common"]
dockerfile = "./hack/dockerfiles/lint.Dockerfile"
target = "modernize-fix"
output = ["."]
}
target "validate-vendor" {
inherits = ["_common"]
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
@ -98,13 +105,6 @@ target "validate-authors" {
output = ["type=cacheonly"]
}
target "validate-generated-files" {
inherits = ["_common"]
dockerfile = "./hack/dockerfiles/generated-files.Dockerfile"
target = "validate"
output = ["type=cacheonly"]
}
target "update-vendor" {
inherits = ["_common"]
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
@ -130,13 +130,6 @@ target "update-authors" {
output = ["."]
}
target "update-generated-files" {
inherits = ["_common"]
dockerfile = "./hack/dockerfiles/generated-files.Dockerfile"
target = "update"
output = ["."]
}
target "mod-outdated" {
inherits = ["_common"]
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"

View File

@ -227,6 +227,8 @@ The following table shows the complete list of attributes that you can assign to
| [`description`](#targetdescription) | String | Description of a target |
| [`dockerfile-inline`](#targetdockerfile-inline) | String | Inline Dockerfile string |
| [`dockerfile`](#targetdockerfile) | String | Dockerfile location |
| [`entitlements`](#targetentitlements) | List | Permissions that the build process requires to run |
| [`extra-hosts`](#targetextra-hosts) | List | Customs host-to-IP mapping |
| [`inherits`](#targetinherits) | List | Inherit attributes from other targets |
| [`labels`](#targetlabels) | Map | Metadata for images |
| [`matrix`](#targetmatrix) | Map | Define a set of variables that forks a target into multiple targets. |
@ -583,6 +585,20 @@ target "integration-tests" {
Entitlements are enabled with a two-step process. First, a target must declare the entitlements it requires. Secondly, when invoking the `bake` command, the user must grant the entitlements by passing the `--allow` flag or confirming the entitlements when prompted in an interactive terminal. This is to ensure that the user is aware of the possibly insecure permissions they are granting to the build process.
### `target.extra-hosts`
Use the `extra-hosts` attribute to define customs host-to-IP mapping for the
target. This has the same effect as passing a [`--add-host`][add-host] flag to
the build command.
```hcl
target "default" {
extra-hosts = {
my_hostname = "8.8.8.8"
}
}
```
### `target.inherits`
A target can inherit attributes from other targets.
@ -1081,6 +1097,7 @@ or interpolate them in attribute values in your Bake file.
```hcl
variable "TAG" {
type = string
default = "latest"
}
@ -1102,6 +1119,206 @@ overriding the default `latest` value shown in the previous example.
$ TAG=dev docker buildx bake webapp-dev
```
Variables can also be assigned an explicit type.
If provided, it will be used to validate the default value (if set), as well as any overrides.
This is particularly useful when using complex types which are intended to be overridden.
The previous example could be expanded to apply an arbitrary series of tags.
```hcl
variable "TAGS" {
default = ["latest"]
type = list(string)
}
target "webapp-dev" {
dockerfile = "Dockerfile.webapp"
tags = [for tag in TAGS: "docker.io/username/webapp:${tag}"]
}
```
This example shows how to generate three tags without changing the file
or using custom functions/parsing:
```console
$ TAGS=dev,latest,2 docker buildx bake webapp-dev
```
### Variable typing
The following primitive types are available:
* `string`
* `number`
* `bool`
The type is expressed like a keyword; it must be expressed as a literal:
```hcl
variable "OK" {
type = string
}
# cannot be an actual string
variable "BAD" {
type = "string"
}
# cannot be the result of an expression
variable "ALSO_BAD" {
type = lower("string")
}
```
Specifying primitive types can be valuable to show intent (especially when a default is not provided),
but bake will generally behave as expected without explicit typing.
Complex types are expressed with "type constructors"; they are:
* `tuple([<type>,...])`
* `list(<type>)`
* `set(<type>)`
* `map(<type>)`
* `object({<attr>=<type>},...})`
The following are examples of each of those, as well as how the (optional) default value would be expressed:
```hcl
# structured way to express "1.2.3-alpha"
variable "MY_VERSION" {
type = tuple([number, number, number, string])
default = [1, 2, 3, "alpha"]
}
# JDK versions used in a matrix build
variable "JDK_VERSIONS" {
type = list(number)
default = [11, 17, 21]
}
# better way to express the previous example; this will also
# enforce set semantics and allow use of set-based functions
variable "JDK_VERSIONS" {
type = set(number)
default = [11, 17, 21]
}
# with the help of lookup(), translate a 'feature' to a tag
variable "FEATURE_TO_NAME" {
type = map(string)
default = {featureA = "slim", featureB = "tiny"}
}
# map a branch name to a registry location
variable "PUSH_DESTINATION" {
type = object({branch = string, registry = string})
default = {branch = "main", registry = "prod-registry.invalid.com"}
}
# make the previous example more useful with composition
variable "PUSH_DESTINATIONS" {
type = list(object({branch = string, registry = string}))
default = [
{branch = "develop", registry = "test-registry.invalid.com"},
{branch = "main", registry = "prod-registry.invalid.com"},
]
}
```
Note that in each example, the default value would be valid even if typing was not present.
If typing was omitted, the first three would all be considered `tuple`;
you would be restricted to functions that operate on `tuple` and, for example, not be able to add elements.
Similarly, the third and fourth would both be considered `object`, with the limits and semantics of that type.
In short, in the absence of a type, any value delimited with `[]` is a `tuple`
and value delimited with `{}` is an `object`.
Explicit typing for complex types not only opens up the ability to use functions applicable to that specialized type,
but is also a precondition for providing overrides.
> [!NOTE]
> See [HCL Type Expressions][typeexpr] page for more details.
### Overriding variables
As mentioned in the [intro to variables](#variable), primitive types (`string`, `number`, and `bool`)
can be overridden without typing and will generally behave as expected.
(When explicit typing is not provided, a variable is assumed to be primitive when the default value lacks `{}` or `[]` delimiters;
a variable with neither typing nor a default value is treated as `string`.)
Naturally, these same overrides can be used alongside explicit typing too;
they may help in edge cases where you want `VAR=true` to be a `string`, where without typing,
it may be a `string` or a `bool` depending on how/where it's used.
Overriding a variable with a complex type can only be done when the type is provided.
This is still done via environment variables, but the values can be provided via CSV or JSON.
#### CSV overrides
This is considered the canonical method and is well suited to interactive usage.
It is assumed that `list` and `set` will be the most common complex type,
as well as the most common complex type designed to be overridden.
Thus, there is full CSV support for `list` and `set`
(and `tuple`; despite being considered a structural type, it is more like a collection type in this regard).
There is limited support for `map` and `object` and no support for composite types;
for these advanced cases, an alternative mechanism [using JSON](#json-overrides) is available.
#### JSON overrides
Overrides can also be provided via JSON.
This is the only method available for providing some complex types and may be convenient if overrides are already JSON
(for example, if they come from a JSON API).
It can also be used when dealing with values are difficult or impossible to specify using CSV (e.g., values containing quotes or commas).
To use JSON, simply append `_JSON` to the variable name.
In this contrived example, CSV cannot handle the second value; despite being a supported CSV type, JSON must be used:
```hcl
variable "VALS" {
type = list(string)
default = ["some", "list"]
}
```
```console
$ cat data.json
["hello","with,comma","with\"quote"]
$ VALS_JSON=$(< data.json) docker buildx bake
# CSV equivalent, though the second value cannot be expressed at all
$ VALS='hello,"with""quote"' docker buildx bake
```
This example illustrates some precedence and usage rules:
```hcl
variable "FOO" {
type = string
default = "foo"
}
variable "FOO_JSON" {
type = string
default = "foo"
}
```
The variable `FOO` can *only* be overridden using CSV because `FOO_JSON`, which would typically used for a JSON override,
is already a defined variable.
Since `FOO_JSON` is an actual variable, setting that environment variable would be expected to a CSV value.
A JSON override *is* possible for this variable, using environment variable `FOO_JSON_JSON`.
```Console
# These three are all equivalent, setting variable FOO=bar
$ FOO=bar docker buildx bake <...>
$ FOO='bar' docker buildx bake <...>
$ FOO="bar" docker buildx bake <...>
# Sets *only* variable FOO_JSON; FOO is untouched
$ FOO_JSON=bar docker buildx bake <...>
# This also sets FOO_JSON, but will fail due to not being valid JSON
$ FOO_JSON_JSON=bar docker buildx bake <...>
# These are all equivalent
$ cat data.json
"bar"
$ FOO_JSON_JSON=$(< data.json) docker buildx bake <...>
$ FOO_JSON_JSON='"bar"' docker buildx bake <...>
$ FOO_JSON=bar docker buildx bake <...>
# This results in setting two different variables, both specified as CSV (FOO=bar and FOO_JSON="baz")
$ FOO=bar FOO_JSON='"baz"' docker buildx bake <...>
# These refer to the same variable with FOO_JSON_JSON having precedence and read as JSON (FOO_JSON=baz)
$ FOO_JSON=bar FOO_JSON_JSON='"baz"' docker buildx bake <...>
```
### Built-in variables
The following variables are built-ins that you can use with Bake without having
@ -1221,6 +1438,7 @@ target "webapp-dev" {
<!-- external links -->
[add-host]: https://docs.docker.com/reference/cli/docker/buildx/build/#add-host
[attestations]: https://docs.docker.com/build/attestations/
[bake_stdlib]: https://github.com/docker/buildx/blob/master/bake/hclparser/stdlib.go
[build-arg]: https://docs.docker.com/reference/cli/docker/image/build/#build-arg
@ -1239,4 +1457,5 @@ target "webapp-dev" {
[ssh]: https://docs.docker.com/reference/cli/docker/buildx/build/#ssh
[tag]: https://docs.docker.com/reference/cli/docker/image/build/#tag
[target]: https://docs.docker.com/reference/cli/docker/image/build/#target
[typeexpr]: https://github.com/hashicorp/hcl/tree/main/ext/typeexpr
[userfunc]: https://github.com/hashicorp/hcl/tree/main/ext/userfunc

View File

@ -26,7 +26,6 @@ Arguments available after `buildx debug build` are the same as the normal `build
```console
$ docker buildx debug --invoke /bin/sh build .
[+] Building 4.2s (19/19) FINISHED
=> [internal] connecting to local controller 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 32B 0.0s
=> [internal] load .dockerignore 0.0s
@ -68,7 +67,6 @@ If you want to start a debug session when a build fails, you can use
```console
$ docker buildx debug --on=error build .
[+] Building 4.2s (19/19) FINISHED
=> [internal] connecting to local controller 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 32B 0.0s
=> [internal] load .dockerignore 0.0s
@ -94,7 +92,6 @@ can use `buildx debug` command to start a debug session.
```
$ docker buildx debug
[+] Building 4.2s (19/19) FINISHED
=> [internal] connecting to local controller 0.0s
(buildx)
```
@ -125,41 +122,3 @@ Available commands are:
rollback re-runs the interactive container with the step's rootfs contents
```
## Build controllers
Debugging is performed using a buildx "controller", which provides a high-level
abstraction to perform builds. By default, the local controller is used for a
more stable experience which runs all builds in-process. However, you can also
use the remote controller to detach the build process from the CLI.
To detach the build process from the CLI, you can use the `--detach=true` flag with
the build command.
```console
$ docker buildx debug --invoke /bin/sh build --detach=true .
```
If you start a debugging session using the `--invoke` flag with a detached
build, then you can attach to it using the `buildx debug` command to
immediately enter the monitor mode.
```console
$ docker buildx debug
[+] Building 0.0s (1/1) FINISHED
=> [internal] connecting to remote controller
(buildx) list
ID CURRENT_SESSION
xfe1162ovd9def8yapb4ys66t false
(buildx) attach xfe1162ovd9def8yapb4ys66t
Attached to process "". Press Ctrl-a-c to switch to the new container
(buildx) ps
PID CURRENT_SESSION COMMAND
3ug8iqaufiwwnukimhqqt06jz false [sh]
(buildx) attach 3ug8iqaufiwwnukimhqqt06jz
Attached to process "3ug8iqaufiwwnukimhqqt06jz". Press Ctrl-a-c to switch to the new container
(buildx) Switched IO
/ # ls
bin etc lib mnt proc run srv tmp var
dev home media opt root sbin sys usr work
/ #
```

View File

@ -143,6 +143,11 @@ Use the `-f` / `--file` option to specify the build definition file to use.
The file can be an HCL, JSON or Compose file. If multiple files are specified,
all are read and the build configurations are combined.
Alternatively, the environment variable `BUILDX_BAKE_FILE` can be used to specify the build definition to use.
This is mutually exclusive with `-f` / `--file`; if both are specified, the environment variable is ignored.
Multiple definitions can be specified by separating them with the system's path separator
(typically `;` on Windows and `:` elsewhere), but can be changed with `BUILDX_BAKE_PATH_SEPARATOR`.
You can pass the names of the targets to build, to build only specific target(s).
The following example builds the `db` and `webapp-release` targets that are
defined in the `docker-bake.dev.hcl` file:
@ -198,12 +203,15 @@ To list variables:
```console
$ docker buildx bake --list=variables
VARIABLE VALUE DESCRIPTION
REGISTRY docker.io/username Registry and namespace
IMAGE_NAME my-app Image name
GO_VERSION <null>
VARIABLE TYPE VALUE DESCRIPTION
REGISTRY string docker.io/username Registry and namespace
IMAGE_NAME string my-app Image name
GO_VERSION <null>
DEBUG bool false Add debug symbols
```
Variable types will be shown when set using the `type` property in the Bake file.
By default, the output of `docker buildx bake --list` is presented in a table
format. Alternatively, you can use a long-form CSV syntax and specify a
`format` attribute to output the list in JSON.
@ -363,6 +371,7 @@ You can override the following fields:
* `context`
* `dockerfile`
* `entitlements`
* `extra-hosts`
* `labels`
* `load`
* `no-cache`

View File

@ -240,13 +240,15 @@ Learn more about the built-in build arguments in the [Dockerfile reference docs]
Define additional build context with specified contents. In Dockerfile the context can be accessed when `FROM name` or `--from=name` is used.
When Dockerfile defines a stage with the same name it is overwritten.
The value can be a local source directory, [local OCI layout compliant directory](https://github.com/opencontainers/image-spec/blob/main/image-layout.md), container image (with docker-image:// prefix), Git or HTTP URL.
The value can be a:
Replace `alpine:latest` with a pinned one:
- local source directory
- [local OCI layout compliant directory](https://github.com/opencontainers/image-spec/blob/main/image-layout.md)
- container image
- Git URL
- HTTP URL
```console
$ docker buildx build --build-context alpine=docker-image://alpine@sha256:0123456789 .
```
#### <a name="local-path"></a> Use a local path
Expose a secondary local source directory:
@ -255,6 +257,16 @@ $ docker buildx build --build-context project=path/to/project/source .
# docker buildx build --build-context project=https://github.com/myuser/project.git .
```
#### <a name="docker-image"></a> Use a container image
Use the `docker-image://` scheme.
Replace `alpine:latest` with a pinned one:
```console
$ docker buildx build --build-context alpine=docker-image://alpine@sha256:0123456789 .
```
```dockerfile
# syntax=docker/dockerfile:1
FROM alpine
@ -263,7 +275,10 @@ COPY --from=project myfile /
#### <a name="source-oci-layout"></a> Use an OCI layout directory as build context
Source an image from a local [OCI layout compliant directory](https://github.com/opencontainers/image-spec/blob/main/image-layout.md),
Use the `oci-layout:///` scheme.
Source an image from a local
[OCI layout compliant directory](https://github.com/opencontainers/image-spec/blob/main/image-layout.md),
either by tag, or by digest:
```console
@ -281,7 +296,6 @@ FROM foo
```
The OCI layout directory must be compliant with the [OCI layout specification](https://github.com/opencontainers/image-spec/blob/main/image-layout.md).
You can reference an image in the layout using either tags, or the exact digest.
### <a name="builder"></a> Override the configured builder instance (--builder)

View File

@ -12,13 +12,12 @@ Start debugger (EXPERIMENTAL)
### Options
| Name | Type | Default | Description |
|:----------------|:---------|:--------|:--------------------------------------------------------------------------------------------------------------------|
| `--builder` | `string` | | Override the configured builder instance |
| `-D`, `--debug` | `bool` | | Enable debug logging |
| `--invoke` | `string` | | Launch a monitor with executing specified command (EXPERIMENTAL) |
| `--on` | `string` | `error` | When to launch the monitor ([always, error]) (EXPERIMENTAL) |
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`, `rawjson`) for the monitor. Use plain to show container output |
| Name | Type | Default | Description |
|:----------------|:---------|:--------|:-----------------------------------------------------------------|
| `--builder` | `string` | | Override the configured builder instance |
| `-D`, `--debug` | `bool` | | Enable debug logging |
| `--invoke` | `string` | | Launch a monitor with executing specified command (EXPERIMENTAL) |
| `--on` | `string` | `error` | When to launch the monitor ([always, error]) (EXPERIMENTAL) |
<!---MARKER_GEN_END-->

View File

@ -1,7 +1,7 @@
# docker buildx history inspect attachment
```text
docker buildx history inspect attachment [OPTIONS] REF [DIGEST]
docker buildx history inspect attachment [OPTIONS] [REF [DIGEST]]
```
<!---MARKER_GEN_START-->
@ -9,12 +9,12 @@ Inspect a build record attachment
### Options
| Name | Type | Default | Description |
|:------------------|:---------|:--------|:-----------------------------------------|
| `--builder` | `string` | | Override the configured builder instance |
| `-D`, `--debug` | `bool` | | Enable debug logging |
| `--platform` | `string` | | Platform of attachment |
| [`--type`](#type) | `string` | | Type of attachment |
| Name | Type | Default | Description |
|:--------------------------|:---------|:--------|:-----------------------------------------|
| `--builder` | `string` | | Override the configured builder instance |
| `-D`, `--debug` | `bool` | | Enable debug logging |
| [`--platform`](#platform) | `string` | | Platform of attachment |
| [`--type`](#type) | `string` | | Type of attachment |
<!---MARKER_GEN_END-->
@ -27,48 +27,160 @@ platform-specific.
## Examples
### <a name="type"></a> Inspect a provenance attachment from a build (--type)
Supported types include `provenance` and `sbom`.
### <a name="platform"></a> Inspect an attachment by platform (--platform)
```console
$ docker buildx history inspect attachment qu2gsuo8ejqrwdfii23xkkckt --type provenance
$ docker buildx history inspect attachment --platform linux/amd64
{
"_type": "https://slsa.dev/provenance/v0.2",
"buildDefinition": {
"buildType": "https://build.docker.com/BuildKit@v1",
"externalParameters": {
"target": "app",
"platforms": ["linux/amd64"]
}
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:814e63f06465bc78123775714e4df1ebdda37e6403e0b4f481df74947c047163",
"size": 600
},
"runDetails": {
"builder": "docker",
"by": "ci@docker.com"
}
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:36537f3920ae948ce3e12b4ae34c21190280e6e7d58eeabde0dff3fdfb43b6b0",
"size": 21664137
}
]
}
```
### Inspect a SBOM for linux/amd64
### <a name="type"></a> Inspect an attachment by type (--type)
Supported types include:
* `index`
* `manifest`
* `image`
* `provenance`
* `sbom`
#### Index
```console
$ docker buildx history inspect attachment ^0 \
--type sbom \
--platform linux/amd64
$ docker buildx history inspect attachment --type index
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"version": 1,
"components": [
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"type": "library",
"name": "alpine",
"version": "3.18.2"
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:a194e24f47dc6d0e65992c09577b9bc4e7bd0cd5cc4f81e7738918f868aa397b",
"size": 481,
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:49e40223d6a96ea0667a12737fd3dde004cf217eb48cb28c9191288cd44c6ace",
"size": 839,
"annotations": {
"vnd.docker.reference.digest": "sha256:a194e24f47dc6d0e65992c09577b9bc4e7bd0cd5cc4f81e7738918f868aa397b",
"vnd.docker.reference.type": "attestation-manifest"
},
"platform": {
"architecture": "unknown",
"os": "unknown"
}
}
]
}
```
#### Manifest
```console
$ docker buildx history inspect attachment --type manifest
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:814e63f06465bc78123775714e4df1ebdda37e6403e0b4f481df74947c047163",
"size": 600
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:36537f3920ae948ce3e12b4ae34c21190280e6e7d58eeabde0dff3fdfb43b6b0",
"size": 21664137
}
]
}
```
#### Provenance
```console
$ docker buildx history inspect attachment --type provenance
{
"builder": {
"id": ""
},
"buildType": "https://mobyproject.org/buildkit@v1",
"materials": [
{
"uri": "pkg:docker/docker/dockerfile@1",
"digest": {
"sha256": "9ba7531bd80fb0a858632727cf7a112fbfd19b17e94c4e84ced81e24ef1a0dbc"
}
},
{
"uri": "pkg:docker/golang@1.19.4-alpine?platform=linux%2Farm64",
"digest": {
"sha256": "a9b24b67dc83b3383d22a14941c2b2b2ca6a103d805cac6820fd1355943beaf1"
}
}
],
"invocation": {
"configSource": {
"entryPoint": "Dockerfile"
},
"parameters": {
"frontend": "gateway.v0",
"args": {
"cmdline": "docker/dockerfile:1",
"source": "docker/dockerfile:1",
"target": "binaries"
},
"locals": [
{
"name": "context"
},
{
"name": "dockerfile"
}
]
},
"environment": {
"platform": "linux/arm64"
}
},
"metadata": {
"buildInvocationID": "c4a87v0sxhliuewig10gnsb6v",
"buildStartedOn": "2022-12-16T08:26:28.651359794Z",
"buildFinishedOn": "2022-12-16T08:26:29.625483253Z",
"reproducible": false,
"completeness": {
"parameters": true,
"environment": true,
"materials": false
},
"https://mobyproject.org/buildkit@v1#metadata": {
"vcs": {
"revision": "a9ba846486420e07d30db1107411ac3697ecab68",
"source": "git@github.com:<org>/<repo>.git"
}
}
}
}
```
### Inspect an attachment by digest
You can inspect an attachment directly using its digset, which you can get from

View File

@ -12,6 +12,7 @@ import (
"sync/atomic"
"time"
cerrdefs "github.com/containerd/errdefs"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/driver/bkimage"
"github.com/docker/buildx/util/confutil"
@ -23,7 +24,6 @@ import (
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/stdcopy"
"github.com/moby/buildkit/client"
@ -70,7 +70,7 @@ 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.DockerAPI.ContainerInspect(ctx, d.Name)
if err != nil {
if errdefs.IsNotFound(err) {
if cerrdefs.IsNotFound(err) {
return d.create(ctx, sub)
}
return err
@ -183,7 +183,7 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
}
}
_, err := d.DockerAPI.ContainerCreate(ctx, cfg, hc, &network.NetworkingConfig{}, nil, d.Name)
if err != nil && !errdefs.IsConflict(err) {
if err != nil && !cerrdefs.IsConflict(err) {
return err
}
if err == nil {
@ -310,7 +310,7 @@ func (d *Driver) start(ctx context.Context) error {
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
ctn, err := d.DockerAPI.ContainerInspect(ctx, d.Name)
if err != nil {
if errdefs.IsNotFound(err) {
if cerrdefs.IsNotFound(err) {
return &driver.Info{
Status: driver.Inactive,
}, nil

View File

@ -286,6 +286,10 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
if v != "" {
deploymentOpt.Qemu.Image = v
}
case "buildkit-root-volume-memory":
if v != "" {
deploymentOpt.BuildKitRootVolumeMemory = v
}
case "default-load":
defaultLoad, err = strconv.ParseBool(v)
if err != nil {

View File

@ -32,6 +32,7 @@ type DeploymentOpt struct {
// files mounted at /etc/buildkitd
ConfigFiles map[string][]byte
BuildKitRootVolumeMemory string
Rootless bool
NodeSelector map[string]string
CustomAnnotations map[string]string
@ -50,6 +51,8 @@ const (
containerName = "buildkitd"
AnnotationPlatform = "buildx.docker.com/platform"
LabelApp = "app"
rootVolumeName = "buildkit-memory"
rootVolumePath = "/var/lib/buildkit"
)
type ErrReservedAnnotationPlatform struct{}
@ -247,6 +250,26 @@ func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, c []*corev1.Config
d.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceEphemeralStorage] = limEphemeralStorage
}
if opt.BuildKitRootVolumeMemory != "" {
buildKitRootVolumeMemory, err := resource.ParseQuantity(opt.BuildKitRootVolumeMemory)
if err != nil {
return nil, nil, err
}
d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, corev1.Volume{
Name: rootVolumeName,
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{
Medium: "Memory",
SizeLimit: &buildKitRootVolumeMemory,
},
},
})
d.Spec.Template.Spec.Containers[0].VolumeMounts = append(d.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
Name: rootVolumeName,
MountPath: rootVolumePath,
})
}
return
}

83
go.mod
View File

@ -6,9 +6,9 @@ require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/Microsoft/go-winio v0.6.2
github.com/aws/aws-sdk-go-v2/config v1.27.27
github.com/compose-spec/compose-go/v2 v2.6.2
github.com/containerd/console v1.0.4
github.com/containerd/containerd/v2 v2.0.5
github.com/compose-spec/compose-go/v2 v2.6.3
github.com/containerd/console v1.0.5
github.com/containerd/containerd/v2 v2.1.1
github.com/containerd/continuity v0.4.5
github.com/containerd/errdefs v1.0.0
github.com/containerd/log v0.1.0
@ -16,9 +16,9 @@ require (
github.com/creack/pty v1.1.24
github.com/davecgh/go-spew v1.1.1
github.com/distribution/reference v0.6.0
github.com/docker/cli v28.1.1+incompatible
github.com/docker/cli-docs-tool v0.9.0
github.com/docker/docker v28.1.1+incompatible
github.com/docker/cli v28.3.0+incompatible
github.com/docker/cli-docs-tool v0.10.0
github.com/docker/docker v28.3.0+incompatible
github.com/docker/go-units v0.5.0
github.com/gofrs/flock v0.12.1
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
@ -26,9 +26,9 @@ require (
github.com/hashicorp/go-cty-funcs v0.0.0-20250210171435-dda779884a9f
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/hcl/v2 v2.23.0
github.com/in-toto/in-toto-golang v0.5.0
github.com/in-toto/in-toto-golang v0.9.0
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/moby/buildkit v0.22.0-rc1
github.com/moby/buildkit v0.23.0-rc1.0.20250618182037-9b91d20367db // master
github.com/moby/go-archive v0.1.0
github.com/moby/sys/atomicwriter v0.1.0
github.com/moby/sys/mountinfo v0.7.2
@ -44,33 +44,32 @@ require (
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0
github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117
github.com/zclconf/go-cty v1.16.2
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0
go.opentelemetry.io/otel v1.31.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0
go.opentelemetry.io/otel v1.35.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0
go.opentelemetry.io/otel/metric v1.31.0
go.opentelemetry.io/otel/sdk v1.31.0
go.opentelemetry.io/otel/trace v1.31.0
go.opentelemetry.io/otel/metric v1.35.0
go.opentelemetry.io/otel/sdk v1.35.0
go.opentelemetry.io/otel/trace v1.35.0
golang.org/x/mod v0.24.0
golang.org/x/sync v0.13.0
golang.org/x/sys v0.32.0
golang.org/x/sync v0.14.0
golang.org/x/sys v0.33.0
golang.org/x/term v0.31.0
golang.org/x/text v0.24.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38
google.golang.org/grpc v1.69.4
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a
google.golang.org/grpc v1.72.2
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1
google.golang.org/protobuf v1.35.2
google.golang.org/protobuf v1.36.6
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.31.2
k8s.io/apimachinery v0.31.2
k8s.io/client-go v0.31.2
k8s.io/api v0.32.3
k8s.io/apimachinery v0.32.3
k8s.io/client-go v0.32.3
)
require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-cidr v1.0.1 // indirect
@ -88,7 +87,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/containerd/containerd/api v1.8.0 // indirect
github.com/containerd/containerd/api v1.9.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
@ -102,9 +101,9 @@ require (
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
@ -112,10 +111,9 @@ require (
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
@ -140,7 +138,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
@ -151,28 +149,29 @@ require (
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.32.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

205
go.sum
View File

@ -1,7 +1,5 @@
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 h1:dIScnXFlF784X79oi7MzVT6GWqr/W1uUt0pB5CsDs9M=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2/go.mod h1:gCLVsLfv1egrcZu+GoJATN5ts75F2s62ih/457eWzOw=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@ -9,8 +7,8 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg=
github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y=
github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=
github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok=
github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
@ -64,16 +62,16 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/compose-spec/compose-go/v2 v2.6.2 h1:31uZNNLeRrKjtUCc56CzPpPykW1Tm6SxLn4gx9Jjzqw=
github.com/compose-spec/compose-go/v2 v2.6.2/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA=
github.com/compose-spec/compose-go/v2 v2.6.3 h1:zfW1Qp605ESySyth/zR+6yLr55XE0AiOAUlZLHKMoW0=
github.com/compose-spec/compose-go/v2 v2.6.3/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0=
github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc=
github.com/containerd/containerd/v2 v2.0.5 h1:2vg/TjUXnaohAxiHnthQg8K06L9I4gdYEMcOLiMc8BQ=
github.com/containerd/containerd/v2 v2.0.5/go.mod h1:Qqo0UN43i2fX1FLkrSTCg6zcHNfjN7gEnx3NPRZI+N0=
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0=
github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI=
github.com/containerd/containerd/v2 v2.1.1 h1:znnkm7Ajz8lg8BcIPMhc/9yjBRN3B+OkNKqKisKfwwM=
github.com/containerd/containerd/v2 v2.1.1/go.mod h1:zIfkQj4RIodclYQkX7GSSswSwgP8d/XxDOtOAoSDIGU=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
@ -84,8 +82,8 @@ github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/nydus-snapshotter v0.15.0 h1:RqZRs1GPeM6T3wmuxJV9u+2Rg4YETVMwTmiDeX+iWC8=
github.com/containerd/nydus-snapshotter v0.15.0/go.mod h1:biq0ijpeZe0I5yZFSJyHzFSjjRZQ7P7y/OuHyd7hYOw=
github.com/containerd/nydus-snapshotter v0.15.2 h1:qsHI4M+Wwrf6Jr4eBqhNx8qh+YU0dSiJ+WPmcLFWNcg=
github.com/containerd/nydus-snapshotter v0.15.2/go.mod h1:FfwH2KBkNYoisK/e+KsmNr7xTU53DmnavQHMFOcXwfM=
github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E=
github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
@ -108,15 +106,15 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k=
github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.9.0 h1:CVwQbE+ZziwlPqrJ7LRyUF6GvCA+6gj7MTCsayaK9t0=
github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3Tr1ipoypjAUtc=
github.com/docker/cli v28.3.0+incompatible h1:s+ttruVLhB5ayeuf2BciwDVxYdKi+RoUlxmwNHV3Vfo=
github.com/docker/cli v28.3.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.10.0 h1:bOD6mKynPQgojQi3s2jgcUWGp/Ebqy1SeCr9VfKQLLU=
github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09fzRHP4aX1qwp1U=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.3.0+incompatible h1:ffS62aKWupCWdvcee7nBU9fhnmknOqDPaJAMtfK0ImQ=
github.com/docker/docker v28.3.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
@ -146,13 +144,14 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
@ -181,8 +180,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -191,8 +190,8 @@ github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
@ -206,10 +205,8 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/in-toto/in-toto-golang v0.5.0 h1:hb8bgwr0M2hGdDsLjkJ3ZqJ8JFLL/tgYdAxF/XEFBbY=
github.com/in-toto/in-toto-golang v0.5.0/go.mod h1:/Rq0IZHLV7Ku5gielPT4wPHJfH1GdHMCq8+WPxw8/BE=
github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU=
github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
@ -253,8 +250,8 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZX
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/buildkit v0.22.0-rc1 h1:Q47jZZws7+0WhucTcm35NRV8NcO6n1SwIikzfqcGKLo=
github.com/moby/buildkit v0.22.0-rc1/go.mod h1:j4pP5hxiTWcz7xuTK2cyxQislHl/N2WWHzOy43DlLJw=
github.com/moby/buildkit v0.23.0-rc1.0.20250618182037-9b91d20367db h1:ZzrDuG9G1A/RwJvuogNplxCEKsIUQh1CqEnqbOGFgKE=
github.com/moby/buildkit v0.23.0-rc1.0.20250618182037-9b91d20367db/go.mod h1:v5jMDvQgUyidk3wu3NvVAAd5JJo83nfet9Gf/o0+EAQ=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
@ -294,22 +291,22 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8=
github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
@ -323,14 +320,14 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
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/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
@ -340,8 +337,8 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE=
github.com/secure-systems-lab/go-securesystemslib v0.4.0/go.mod h1:FGBZgq2tXWICsxWQW1msNf49F0Pf2Op5Htayx335Qbs=
github.com/secure-systems-lab/go-securesystemslib v0.6.0 h1:T65atpAVCJQK14UA57LMdZGpHi4QYSH/9FZyNGqMYIA=
github.com/secure-systems-lab/go-securesystemslib v0.6.0/go.mod h1:8Mtpo9JKks/qhPG4HGZ2LGMvrPbzuxwfz/f/zLfEWkk=
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU=
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
@ -350,8 +347,8 @@ github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjM
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY=
github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI=
github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk=
github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE=
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
@ -380,10 +377,10 @@ github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4D
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4=
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144 h1:k9tdF32oJYwtjzMx+D26M6eYiCaAPdJ7tyN7tF1oU5Q=
github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f h1:MoxeMfHAe5Qj/ySSBfL8A7l1V+hxuluj8owsIEEZipI=
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE=
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117 h1:XFwyh2JZwR5aiKLXHX2C1n0v5F11dCJpyGL1W/Cpl3U=
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117/go.mod h1:3Ez1Paeg+0Ghu3KwpEGC1HgZ4CHDlg+Ez/5Baeomk54=
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
@ -411,36 +408,38 @@ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0/go.mod h1:n8MR6/liuGB5EmTETUBeU5ZgqMOlqKRxUaqPQBOANZ8=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 h1:4BZHA+B1wXEQoGNHxW8mURaLhcdGwvRnmhGbm+odRbc=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0/go.mod h1:3qi2EEwMgB4xnKgPLqsDP3j9qxnHDZeHsnAxfjQqTko=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 h1:ZsXq73BERAiNuuFXYqP4MR5hBrjXfMGSO+Cx7qoOZiM=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0/go.mod h1:hg1zaDMpyZJuUzjFxFsRYBoccE86tM9Uf4IqNMUxvrY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -468,8 +467,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -480,8 +479,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
@ -501,23 +500,25 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw=
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
@ -527,28 +528,26 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0=
k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk=
k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw=
k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc=
k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs=
k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@ -1,98 +0,0 @@
# syntax=docker/dockerfile:1
# Forked from https://github.com/moby/buildkit/blob/e1b3b6c4abf7684f13e6391e5f7bc9210752687a/hack/dockerfiles/generated-files.Dockerfile
# Copyright The BuildKit Authors.
# Copyright The Buildx Authors.
# Licensed under the Apache License, Version 2.0
ARG GO_VERSION=1.24
ARG PROTOC_VERSION=3.11.4
ARG PROTOC_GOOGLEAPIS_VERSION=2af421884dd468d565137215c946ebe4e245ae26
# protoc is dynamically linked to glibc so can't use alpine base
FROM golang:${GO_VERSION}-bookworm AS base
RUN apt-get update && apt-get --no-install-recommends install -y git unzip
FROM base AS protoc
ARG PROTOC_VERSION
ARG TARGETOS
ARG TARGETARCH
RUN <<EOT
set -e
arch=$(echo $TARGETARCH | sed -e s/amd64/x86_64/ -e s/arm64/aarch_64/)
wget -q https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-${TARGETOS}-${arch}.zip
unzip protoc-${PROTOC_VERSION}-${TARGETOS}-${arch}.zip -d /opt/protoc
EOT
FROM base AS googleapis
ARG PROTOC_GOOGLEAPIS_VERSION
RUN <<EOT
set -e
wget -q https://github.com/googleapis/googleapis/archive/${PROTOC_GOOGLEAPIS_VERSION}.zip -O googleapis.zip
unzip googleapis.zip '*.proto' -d /opt
mkdir -p /opt/googleapis
mv /opt/googleapis-${PROTOC_GOOGLEAPIS_VERSION} /opt/googleapis/include
EOT
FROM base AS gobuild-base
WORKDIR /app
FROM gobuild-base AS vtprotobuf
RUN --mount=type=bind,source=go.mod,target=/app/go.mod \
--mount=type=bind,source=go.sum,target=/app/go.sum \
--mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/go/pkg/mod <<EOT
set -e
mkdir -p /opt/vtprotobuf
go mod download github.com/planetscale/vtprotobuf
cp -r $(go list -m -f='{{.Dir}}' github.com/planetscale/vtprotobuf)/include /opt/vtprotobuf
EOT
FROM gobuild-base AS vendored
RUN --mount=type=bind,source=vendor,target=/app <<EOT
set -e
mkdir -p /opt/vendored/include
find . -name '*.proto' | tar -cf - --files-from - | tar -C /opt/vendored/include -xf -
EOT
FROM gobuild-base AS tools
RUN --mount=type=bind,source=go.mod,target=/app/go.mod,ro \
--mount=type=bind,source=go.sum,target=/app/go.sum,ro \
--mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/go/pkg/mod \
go install \
github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto \
google.golang.org/protobuf/cmd/protoc-gen-go \
google.golang.org/grpc/cmd/protoc-gen-go-grpc
COPY --link --from=protoc /opt/protoc /usr/local
COPY --link --from=googleapis /opt/googleapis /usr/local
COPY --link --from=vtprotobuf /opt/vtprotobuf /usr/local
COPY --link --from=vendored /opt/vendored /usr/local
FROM tools AS generated
RUN --mount=type=bind,target=github.com/docker/buildx,ro <<EOT
set -ex
mkdir /out
find github.com/docker/buildx -name '*.proto' -o -name vendor -prune -false | xargs \
protoc --go_out=/out --go-grpc_out=require_unimplemented_servers=false:/out \
--go-vtproto_out=features=marshal+unmarshal+size+equal+pool+clone:/out
EOT
FROM scratch AS update
COPY --from=generated /out/github.com/docker/buildx /
FROM gobuild-base AS validate
RUN --mount=type=bind,target=.,rw \
--mount=type=bind,from=update,target=/generated-files <<EOT
set -e
git add -A
if [ "$(ls -A /generated-files)" ]; then
cp -rf /generated-files/* .
fi
diff=$(git status --porcelain -- ':!vendor' '**/*.pb.go')
if [ -n "$diff" ]; then
echo >&2 'ERROR: The result of "go generate" differs. Please update with "make generated-files"'
echo "$diff"
exit 1
fi
EOT

View File

@ -3,7 +3,7 @@
ARG GO_VERSION=1.24
ARG ALPINE_VERSION=3.21
ARG GOVULNCHECK_VERSION=v1.1.3
ARG GOVULNCHECK_VERSION=v1.1.4
ARG FORMAT="text"
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
@ -20,12 +20,6 @@ RUN --mount=type=bind,target=. <<EOT
set -ex
mkdir /out
govulncheck -format ${FORMAT} ./... | tee /out/govulncheck.out
if [ "${FORMAT}" = "sarif" ]; then
# Make sure "results" field is defined in SARIF output otherwise GitHub Code Scanning
# will fail when uploading report with "Invalid SARIF. Missing 'results' array in run."
# Relates to https://github.com/golang/vuln/blob/ffdef74cc44d7eb71931d8d414c478b966812488/internal/sarif/sarif.go#L69
jq '(.runs[] | select(.results == null) | .results) |= []' /out/govulncheck.out | tee >(sponge /out/govulncheck.out)
fi
EOT
FROM scratch AS output

View File

@ -13,7 +13,7 @@ ARG GOPLS_ANALYZERS="embeddirective fillreturns infertypeargs maprange modernize
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
RUN apk add --no-cache git gcc musl-dev
RUN apk add --no-cache git gcc musl-dev binutils-gold
FROM base AS golangci-build
WORKDIR /src
@ -91,4 +91,25 @@ RUN --mount=target=. \
done
EOF
FROM base AS modernize-fix-run
COPY --link --from=xx / /
ARG TARGETNAME
ARG TARGETPLATFORM
WORKDIR /go/src/github.com/docker/buildx
RUN --mount=target=.,rw \
--mount=target=/root/.cache,type=cache,id=lint-cache-${TARGETNAME}-${TARGETPLATFORM} \
--mount=target=/gopls-analyzers,from=gopls,source=/out <<EOF
set -ex
xx-go --wrap
mkdir /out
/gopls-analyzers/modernize -fix ./...
for file in $(git status --porcelain | awk '/^ M/ {print $2}'); do
mkdir -p /out/$(dirname $file)
cp $file /out/$file
done
EOF
FROM scratch AS modernize-fix
COPY --link --from=modernize-fix-run /out /
FROM lint

View File

@ -5,7 +5,7 @@ import (
"fmt"
"io"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/build"
"github.com/docker/buildx/monitor/types"
"github.com/pkg/errors"
)
@ -13,11 +13,11 @@ import (
type ExecCmd struct {
m types.Monitor
invokeConfig *controllerapi.InvokeConfig
invokeConfig *build.InvokeConfig
stdout io.WriteCloser
}
func NewExecCmd(m types.Monitor, invokeConfig *controllerapi.InvokeConfig, stdout io.WriteCloser) types.Command {
func NewExecCmd(m types.Monitor, invokeConfig *build.InvokeConfig, stdout io.WriteCloser) types.Command {
return &ExecCmd{m, invokeConfig, stdout}
}
@ -38,7 +38,7 @@ func (cm *ExecCmd) Exec(ctx context.Context, args []string) error {
if len(args) < 2 {
return errors.Errorf("command must be passed")
}
cfg := &controllerapi.InvokeConfig{
cfg := &build.InvokeConfig{
Entrypoint: []string{args[1]},
Cmd: args[2:],
NoCmd: false,

View File

@ -2,30 +2,16 @@ package commands
import (
"context"
"fmt"
"io"
cbuild "github.com/docker/buildx/controller/build"
controllererrors "github.com/docker/buildx/controller/errdefs"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/monitor/types"
"github.com/docker/buildx/util/progress"
"github.com/moby/buildkit/solver/errdefs"
"github.com/pkg/errors"
)
type ReloadCmd struct {
m types.Monitor
stdout io.WriteCloser
progress *progress.Printer
options *cbuild.Options
invokeConfig *controllerapi.InvokeConfig
}
func NewReloadCmd(m types.Monitor, stdout io.WriteCloser, progress *progress.Printer, options *cbuild.Options, invokeConfig *controllerapi.InvokeConfig) types.Command {
return &ReloadCmd{m, stdout, progress, options, invokeConfig}
func NewReloadCmd(m types.Monitor) types.Command {
return &ReloadCmd{m: m}
}
func (cm *ReloadCmd) Info() types.CommandInfo {
@ -40,31 +26,6 @@ Usage:
}
func (cm *ReloadCmd) Exec(ctx context.Context, args []string) error {
bo := cm.m.Inspect(ctx)
var resultUpdated bool
cm.progress.Unpause()
_, _, err := cm.m.Build(ctx, bo, nil, cm.progress) // TODO: support stdin, hold build ref
cm.progress.Pause()
if err != nil {
var be *controllererrors.BuildError
if errors.As(err, &be) {
resultUpdated = true
} else {
fmt.Printf("failed to reload: %v\n", err)
}
// report error
for _, s := range errdefs.Sources(err) {
s.Print(cm.stdout)
}
fmt.Fprintf(cm.stdout, "ERROR: %v\n", err)
} else {
resultUpdated = true
}
if resultUpdated {
// rollback the running container with the new result
id := cm.m.Rollback(ctx, cm.invokeConfig)
fmt.Fprintf(cm.stdout, "Interactive container was restarted with process %q. Press Ctrl-a-c to switch to the new container\n", id)
}
cm.m.Reload()
return nil
}

View File

@ -5,18 +5,18 @@ import (
"fmt"
"io"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/build"
"github.com/docker/buildx/monitor/types"
)
type RollbackCmd struct {
m types.Monitor
invokeConfig *controllerapi.InvokeConfig
invokeConfig *build.InvokeConfig
stdout io.WriteCloser
}
func NewRollbackCmd(m types.Monitor, invokeConfig *controllerapi.InvokeConfig, stdout io.WriteCloser) types.Command {
func NewRollbackCmd(m types.Monitor, invokeConfig *build.InvokeConfig, stdout io.WriteCloser) types.Command {
return &RollbackCmd{m, invokeConfig, stdout}
}

View File

@ -4,45 +4,117 @@ import (
"context"
"fmt"
"io"
"os"
"sort"
"sync"
"sync/atomic"
"text/tabwriter"
"time"
"github.com/containerd/console"
"github.com/docker/buildx/build"
cbuild "github.com/docker/buildx/controller/build"
"github.com/docker/buildx/controller/control"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/monitor/commands"
"github.com/docker/buildx/monitor/processes"
"github.com/docker/buildx/monitor/types"
"github.com/docker/buildx/util/ioset"
"github.com/docker/buildx/util/progress"
"github.com/google/shlex"
"github.com/moby/buildkit/client"
gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/solver/errdefs"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/term"
)
type MonitorBuildResult struct {
Resp *client.SolveResponse
Err error
var ErrReload = errors.New("monitor: reload")
type Monitor struct {
invokeConfig *build.InvokeConfig
printer *progress.Printer
stdin *ioset.SingleForwarder
stdout io.WriteCloser
stderr io.WriteCloser
}
func New(cfg *build.InvokeConfig, stdin io.ReadCloser, stdout, stderr io.WriteCloser, printer *progress.Printer) *Monitor {
m := &Monitor{
invokeConfig: cfg,
printer: printer,
stdin: ioset.NewSingleForwarder(),
stdout: stdout,
stderr: stderr,
}
m.stdin.SetReader(stdin)
return m
}
func (m *Monitor) Handler() build.Handler {
return build.Handler{
Evaluate: m.Evaluate,
}
}
func (m *Monitor) Evaluate(ctx context.Context, c gateway.Client, res *gateway.Result) error {
buildErr := res.EachRef(func(ref gateway.Reference) error {
return ref.Evaluate(ctx)
})
if m.invokeConfig.NeedsDebug(buildErr) {
// Allow some time to ensure status updates are sent.
time.Sleep(200 * time.Millisecond)
// Print errors before launching monitor
if err := printError(buildErr, m.printer); err != nil {
logrus.Warnf("failed to print error information: %v", err)
}
rCtx := build.NewResultHandle(ctx, c, res, buildErr)
if monitorErr := m.Run(ctx, rCtx); monitorErr != nil {
if errors.Is(monitorErr, build.ErrRestart) {
return build.ErrRestart
}
logrus.Warnf("failed to run monitor: %v", monitorErr)
}
}
return buildErr
}
func (m *Monitor) Run(ctx context.Context, rCtx *build.ResultHandle) error {
if rCtx != nil {
defer rCtx.Done()
}
pr, pw := io.Pipe()
m.stdin.SetWriter(pw, func() io.WriteCloser {
pw.Close() // propagate EOF
return nil
})
con := console.Current()
if err := con.SetRaw(); err != nil {
return errors.Errorf("failed to configure terminal: %v", err)
}
defer con.Reset()
monitorErr := RunMonitor(ctx, m.invokeConfig, rCtx, pr, m.stdout, m.stderr, m.printer)
if err := pw.Close(); err != nil {
logrus.Debug("failed to close monitor stdin pipe reader")
}
return monitorErr
}
func (m *Monitor) Close() error {
return m.stdin.Close()
}
// RunMonitor provides an interactive session for running and managing containers via specified IO.
func RunMonitor(ctx context.Context, curRef string, options *cbuild.Options, invokeConfig *controllerapi.InvokeConfig, c control.BuildxController, stdin io.ReadCloser, stdout io.WriteCloser, stderr console.File, progress *progress.Printer) (*MonitorBuildResult, error) {
defer func() {
if err := c.Close(); err != nil {
logrus.Warnf("close error: %v", err)
}
}()
func RunMonitor(ctx context.Context, invokeConfig *build.InvokeConfig, rCtx *build.ResultHandle, stdin io.ReadCloser, stdout, stderr io.WriteCloser, progress *progress.Printer) error {
progress.Pause()
defer progress.Resume()
if err := progress.Pause(); err != nil {
return nil, err
}
defer progress.Unpause()
defer stdin.Close()
monitorIn, monitorOut := ioset.Pipe()
defer func() {
@ -70,8 +142,9 @@ func RunMonitor(ctx context.Context, curRef string, options *cbuild.Options, inv
invokeForwarder := ioset.NewForwarder()
invokeForwarder.SetIn(&containerIn)
m := &monitor{
BuildxController: c,
invokeIO: invokeForwarder,
rCtx: rCtx,
processes: processes.NewManager(),
invokeIO: invokeForwarder,
muxIO: ioset.NewMuxIO(ioset.In{
Stdin: io.NopCloser(stdin),
Stdout: nopCloser{stdout},
@ -84,7 +157,13 @@ func RunMonitor(ctx context.Context, curRef string, options *cbuild.Options, inv
return "Switched IO\n"
}),
}
m.ref.Store(curRef)
m.ctx, m.cancel = context.WithCancelCause(context.Background())
defer func() {
if err := m.Close(); err != nil {
logrus.Warnf("close error: %v", err)
}
}()
// Start container automatically
fmt.Fprintf(stdout, "Launching interactive container. Press Ctrl-a-c to switch to monitor console\n")
@ -94,7 +173,7 @@ func RunMonitor(ctx context.Context, curRef string, options *cbuild.Options, inv
fmt.Fprintf(stdout, "Interactive container was restarted with process %q. Press Ctrl-a-c to switch to the new container\n", id)
availableCommands := []types.Command{
commands.NewReloadCmd(m, stdout, progress, options, invokeConfig),
commands.NewReloadCmd(m),
commands.NewRollbackCmd(m, invokeConfig, stdout),
commands.NewAttachCmd(m, stdout),
commands.NewExecCmd(m, invokeConfig, stdout),
@ -126,6 +205,11 @@ func RunMonitor(ctx context.Context, curRef string, options *cbuild.Options, inv
}()
t := term.NewTerminal(readWriter{in.Stdin, in.Stdout}, "(buildx) ")
for {
if err := m.ctx.Err(); err != nil {
errCh <- context.Cause(m.ctx)
return
}
l, err := t.ReadLine()
if err != nil {
if err != io.EOF {
@ -174,10 +258,10 @@ func RunMonitor(ctx context.Context, curRef string, options *cbuild.Options, inv
select {
case <-doneCh:
m.close()
return m.lastBuildResult, nil
return nil
case err := <-errCh:
m.close()
return m.lastBuildResult, err
return err
case <-monitorDisableCh:
}
monitorForwarder.SetOut(nil)
@ -231,36 +315,60 @@ type readWriter struct {
}
type monitor struct {
control.BuildxController
ref atomic.Value
ctx context.Context
cancel context.CancelCauseFunc
rCtx *build.ResultHandle
muxIO *ioset.MuxIO
invokeIO *ioset.Forwarder
invokeCancel func()
attachedPid atomic.Value
lastBuildResult *MonitorBuildResult
processes *processes.Manager
}
func (m *monitor) Build(ctx context.Context, options *cbuild.Options, in io.ReadCloser, progress progress.Writer) (resp *client.SolveResponse, input *build.Inputs, err error) {
resp, _, err = m.BuildxController.Build(ctx, options, in, progress)
m.lastBuildResult = &MonitorBuildResult{Resp: resp, Err: err} // Record build result
return
func (m *monitor) Invoke(ctx context.Context, pid string, cfg *build.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error {
proc, ok := m.processes.Get(pid)
if !ok {
// Start a new process.
if m.rCtx == nil {
return errors.New("no build result is registered")
}
var err error
proc, err = m.processes.StartProcess(pid, m.rCtx, cfg)
if err != nil {
return err
}
}
// Attach containerIn to this process
ioCancelledCh := make(chan struct{})
proc.ForwardIO(&ioset.In{Stdin: ioIn, Stdout: ioOut, Stderr: ioErr}, func(error) { close(ioCancelledCh) })
select {
case <-ioCancelledCh:
return errors.Errorf("io cancelled")
case err := <-proc.Done():
return err
case <-ctx.Done():
return context.Cause(ctx)
}
}
func (m *monitor) Rollback(ctx context.Context, cfg *controllerapi.InvokeConfig) string {
func (m *monitor) Rollback(ctx context.Context, cfg *build.InvokeConfig) string {
pid := identity.NewID()
cfg1 := cfg
cfg1.Rollback = true
return m.startInvoke(ctx, pid, cfg1)
}
func (m *monitor) Exec(ctx context.Context, cfg *controllerapi.InvokeConfig) string {
func (m *monitor) Exec(ctx context.Context, cfg *build.InvokeConfig) string {
return m.startInvoke(ctx, identity.NewID(), cfg)
}
func (m *monitor) Attach(ctx context.Context, pid string) {
m.startInvoke(ctx, pid, &controllerapi.InvokeConfig{})
m.startInvoke(ctx, pid, &build.InvokeConfig{})
}
func (m *monitor) Detach() {
@ -269,6 +377,10 @@ func (m *monitor) Detach() {
}
}
func (m *monitor) Reload() {
m.cancel(build.ErrRestart)
}
func (m *monitor) AttachedPID() string {
return m.attachedPid.Load().(string)
}
@ -277,7 +389,7 @@ func (m *monitor) close() {
m.Detach()
}
func (m *monitor) startInvoke(ctx context.Context, pid string, cfg *controllerapi.InvokeConfig) string {
func (m *monitor) startInvoke(ctx context.Context, pid string, cfg *build.InvokeConfig) string {
if m.invokeCancel != nil {
m.invokeCancel() // Finish existing attach
}
@ -303,7 +415,7 @@ func (m *monitor) startInvoke(ctx context.Context, pid string, cfg *controllerap
return pid
}
func (m *monitor) invoke(ctx context.Context, pid string, cfg *controllerapi.InvokeConfig) error {
func (m *monitor) invoke(ctx context.Context, pid string, cfg *build.InvokeConfig) error {
m.muxIO.Enable(1)
defer m.muxIO.Disable(1)
if err := m.muxIO.SwitchTo(1); err != nil {
@ -332,8 +444,40 @@ func (m *monitor) invoke(ctx context.Context, pid string, cfg *controllerapi.Inv
return err
}
func (m *monitor) Close() error {
m.cancelRunningProcesses()
return nil
}
func (m *monitor) ListProcesses(ctx context.Context) (infos []*processes.ProcessInfo, retErr error) {
return m.processes.ListProcesses(), nil
}
func (m *monitor) DisconnectProcess(ctx context.Context, pid string) error {
return m.processes.DeleteProcess(pid)
}
func (m *monitor) cancelRunningProcesses() {
m.processes.CancelRunningProcesses()
}
type nopCloser struct {
io.Writer
}
func (c nopCloser) Close() error { return nil }
func printError(err error, printer *progress.Printer) error {
if err == nil {
return nil
}
printer.Pause()
defer printer.Resume()
for _, s := range errdefs.Sources(err) {
s.Print(os.Stderr)
}
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
return nil
}

View File

@ -6,7 +6,6 @@ import (
"sync/atomic"
"github.com/docker/buildx/build"
"github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/util/ioset"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -15,7 +14,7 @@ import (
// Process provides methods to control a process.
type Process struct {
inEnd *ioset.Forwarder
invokeConfig *pb.InvokeConfig
invokeConfig *build.InvokeConfig
errCh chan error
processCancel func()
serveIOCancel func(error)
@ -98,7 +97,7 @@ func (m *Manager) DeleteProcess(id string) error {
// When a container isn't available (i.e. first time invoking or the container has exited) or cfg.Rollback is set,
// this method will start a new container and run the process in it. Otherwise, this method starts a new process in the
// existing container.
func (m *Manager) StartProcess(pid string, resultCtx *build.ResultHandle, cfg *pb.InvokeConfig) (*Process, error) {
func (m *Manager) StartProcess(pid string, resultCtx *build.ResultHandle, cfg *build.InvokeConfig) (*Process, error) {
// Get the target result to invoke a container from
var ctr *build.Container
if a := m.container.Load(); a != nil {
@ -157,5 +156,5 @@ func (m *Manager) StartProcess(pid string, resultCtx *build.ResultHandle, cfg *p
type ProcessInfo struct {
ProcessID string
InvokeConfig *pb.InvokeConfig
InvokeConfig *build.InvokeConfig
}

View File

@ -2,20 +2,29 @@ package types
import (
"context"
"io"
"github.com/docker/buildx/controller/control"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/build"
"github.com/docker/buildx/monitor/processes"
)
// Monitor provides APIs for attaching and controlling the buildx server.
type Monitor interface {
control.BuildxController
// Invoke starts an IO session into the specified process.
// If pid doesn't match to any running processes, it starts a new process with the specified config.
// If there is no container running or InvokeConfig.Rollback is specified, the process will start in a newly created container.
// NOTE: If needed, in the future, we can split this API into three APIs (NewContainer, NewProcess and Attach).
Invoke(ctx context.Context, pid string, options *build.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error
ListProcesses(ctx context.Context) (infos []*processes.ProcessInfo, retErr error)
DisconnectProcess(ctx context.Context, pid string) error
// Rollback re-runs the interactive container with initial rootfs contents.
Rollback(ctx context.Context, cfg *controllerapi.InvokeConfig) string
Rollback(ctx context.Context, cfg *build.InvokeConfig) string
// Rollback executes a process in the interactive container.
Exec(ctx context.Context, cfg *controllerapi.InvokeConfig) string
Exec(ctx context.Context, cfg *build.InvokeConfig) string
// Attach attaches IO to a process in the container.
Attach(ctx context.Context, pid string)
@ -25,6 +34,11 @@ type Monitor interface {
// Detach detaches IO from the container.
Detach()
// Reload will signal the monitor to be reloaded.
Reload()
io.Closer
}
// CommandInfo is information about a command.

View File

@ -74,11 +74,14 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
testBakeLoadPush,
testListTargets,
testListVariables,
testListTypedVariables,
testBakeCallCheck,
testBakeCallCheckFlag,
testBakeCallMetadata,
testBakeMultiPlatform,
testBakeCheckCallOutput,
testBakeExtraHosts,
testBakeFileFromEnvironment,
}
func testBakePrint(t *testing.T, sb integration.Sandbox) {
@ -1394,9 +1397,9 @@ target "default" {
dtprv, err := json.Marshal(md.Default.BuildProvenance)
require.NoError(t, err)
var prv provenancetypes.ProvenancePredicate
var prv provenancetypes.ProvenancePredicateSLSA02
require.NoError(t, json.Unmarshal(dtprv, &prv))
require.Equal(t, provenancetypes.BuildKitBuildType, prv.BuildType)
require.Equal(t, provenancetypes.BuildKitBuildType02, prv.BuildType)
}
func testBakeMetadataWarnings(t *testing.T, sb integration.Sandbox) {
@ -1737,7 +1740,93 @@ target "default" {
)
require.NoError(t, err, out)
require.Equal(t, "VARIABLE\tVALUE\tDESCRIPTION\nabc\t\t<null>\t\ndef\t\t\t\nfoo\t\tbar\tThis is foo", strings.TrimSpace(out))
require.Equal(t, "VARIABLE\tTYPE\tVALUE\tDESCRIPTION\nabc\t\t\t<null>\t\ndef\t\t\t\t\nfoo\t\t\tbar\tThis is foo", strings.TrimSpace(out))
}
func testListTypedVariables(t *testing.T, sb integration.Sandbox) {
bakefile := []byte(`
variable "abc" {
type = string
default = "bar"
description = "This is abc"
}
variable "def" {
type = string
description = "simple type, no default"
}
variable "ghi" {
type = number
default = 99
description = "simple type w/ default"
}
variable "jkl" {
type = list(string)
default = ["hello"]
description = "collection with quoted strings"
}
variable "mno" {
type = list(number)
description = "collection, no default"
}
variable "pqr" {
type = tuple([number, string, bool])
default = [99, "99", true]
}
variable "stu" {
type = map(string)
default = {"foo": "bar"}
}
variable "vwx" {
type = set(bool)
default = null
description = "collection, null default"
}
// untyped, but previously didn't have its value output
variable "wxy" {
default = ["foo"]
description = "inferred tuple"
}
// untyped, but previously didn't have its value output
variable "xyz" {
default = {"foo": "bar"}
description = "inferred object"
}
// untyped, but previously didn't have its value output
variable "yza" {
default = true
description = "inferred bool"
}
target "default" {
}
`)
dir := tmpdir(
t,
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
)
out, err := bakeCmd(
sb,
withDir(dir),
withArgs("--list=variables"),
)
require.NoError(t, err, out)
require.Equal(t,
"VARIABLE\tTYPE\t\tVALUE\t\tDESCRIPTION\n"+
"abc\t\tstring\t\tbar\t\tThis is abc\n"+
"def\t\tstring\t\t<null>\t\tsimple type, no default\n"+
"ghi\t\tnumber\t\t99\t\tsimple type w/ default\n"+
"jkl\t\tlist of string\t[\"hello\"]\tcollection with quoted strings\n"+
"mno\t\tlist of number\t<null>\t\tcollection, no default\n"+
// the implementation for tuple's 'friendly name' is very basic
// and marked as TODO, so this may change/break at some point
"pqr\t\ttuple\t\t[99,\"99\",true]\t\n"+
"stu\t\tmap of string\t{\"foo\":\"bar\"}\t\n"+
"vwx\t\tset of bool\t<null>\t\tcollection, null default\n"+
"wxy\t\t\t\t[\"foo\"]\t\tinferred tuple\n"+
"xyz\t\t\t\t{\"foo\":\"bar\"}\tinferred object\n"+
"yza\t\t\t\ttrue\t\tinferred bool",
strings.TrimSpace(out))
}
func testBakeCallCheck(t *testing.T, sb integration.Sandbox) {
@ -2075,6 +2164,193 @@ target "third" {
})
}
func testBakeExtraHosts(t *testing.T, sb integration.Sandbox) {
dockerfile := []byte(`
FROM busybox
RUN cat /etc/hosts | grep myhost | grep 1.2.3.4
RUN cat /etc/hosts | grep myhostmulti | grep 162.242.195.81
RUN cat /etc/hosts | grep myhostmulti | grep 162.242.195.82
`)
bakefile := []byte(`
target "default" {
extra-hosts = {
myhost = "1.2.3.4"
myhostmulti = "162.242.195.81,162.242.195.82"
}
}
`)
dir := tmpdir(
t,
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
fstest.CreateFile("Dockerfile", dockerfile, 0600),
)
out, err := bakeCmd(
sb,
withDir(dir),
)
require.NoError(t, err, out)
}
func testBakeFileFromEnvironment(t *testing.T, sb integration.Sandbox) {
bakeFileFirst := []byte(`
target "first" {
dockerfile-inline = "FROM scratch\nCOPY first /"
}
`)
bakeFileSecond := []byte(`
target "second" {
dockerfile-inline = "FROM scratch\nCOPY second /"
}
`)
t.Run("single file", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "first"),
withEnv("BUILDX_BAKE_FILE=first.hcl"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
require.Contains(t, string(dt), `#1 [internal] load local bake definitions from BUILDX_BAKE_FILE env`)
require.Contains(t, string(dt), `#1 reading first.hcl`)
})
t.Run("single file, default ignored if present", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
fstest.CreateFile("docker-bake.hcl", []byte("invalid bake file"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "first"),
withEnv("BUILDX_BAKE_FILE=first.hcl"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
require.Contains(t, string(dt), `#1 [internal] load local bake definitions from BUILDX_BAKE_FILE env`)
require.Contains(t, string(dt), `#1 reading first.hcl`)
require.NotContains(t, string(dt), "docker-bake.hcl")
})
t.Run("multiple files", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
fstest.CreateFile("second.hcl", bakeFileSecond, 0600),
fstest.CreateFile("second", []byte("second"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "second", "first"),
withEnv("BUILDX_BAKE_FILE=first.hcl"+string(os.PathListSeparator)+"second.hcl"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
require.Contains(t, string(dt), `#1 [internal] load local bake definitions from BUILDX_BAKE_FILE env`)
require.Contains(t, string(dt), `#1 reading first.hcl`)
require.Contains(t, string(dt), `#1 reading second.hcl`)
})
t.Run("multiple files, custom separator", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
fstest.CreateFile("second.hcl", bakeFileSecond, 0600),
fstest.CreateFile("second", []byte("second"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "second", "first"),
withEnv("BUILDX_BAKE_PATH_SEPARATOR=@", "BUILDX_BAKE_FILE=first.hcl@second.hcl"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
require.Contains(t, string(dt), `#1 [internal] load local bake definitions from BUILDX_BAKE_FILE env`)
require.Contains(t, string(dt), `#1 reading first.hcl`)
require.Contains(t, string(dt), `#1 reading second.hcl`)
})
t.Run("multiple files, one STDIN", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
fstest.CreateFile("second", []byte("second"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "second", "first"),
withEnv("BUILDX_BAKE_FILE=first.hcl"+string(os.PathListSeparator)+"-"))
w, err := cmd.StdinPipe()
require.NoError(t, err)
go func() {
defer w.Close()
w.Write(bakeFileSecond)
}()
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
require.Contains(t, string(dt), `#1 [internal] load local bake definitions from BUILDX_BAKE_FILE env`)
require.Contains(t, string(dt), `#1 reading first.hcl`)
require.Contains(t, string(dt), `#1 reading from stdin`)
})
t.Run("env ignored if file arg passed", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
fstest.CreateFile("second.hcl", bakeFileSecond, 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "-f", "first.hcl", "first", "second"),
withEnv("BUILDX_BAKE_FILE=second.hcl"))
dt, err := cmd.CombinedOutput()
require.Error(t, err, string(dt))
require.Contains(t, string(dt), "failed to find target second")
})
t.Run("file does not exist", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "first"),
withEnv("BUILDX_BAKE_FILE=wrong.hcl"))
dt, err := cmd.CombinedOutput()
require.Error(t, err, string(dt))
require.Contains(t, string(dt), "wrong.hcl: no such file or directory")
})
for kind, val := range map[string]string{"missing": "", "whitespace": " "} {
t.Run(kind+" value ignored", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "first"),
withEnv(fmt.Sprintf("BUILDX_BAKE_FILE=%s", val)))
dt, err := cmd.CombinedOutput()
require.Error(t, err, string(dt))
require.Contains(t, string(dt), "couldn't find a bake definition")
})
}
}
func writeTempPrivateKey(fp string) error {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {

View File

@ -77,6 +77,7 @@ var buildTests = []func(t *testing.T, sb integration.Sandbox){
testBuildDefaultLoad,
testBuildCall,
testCheckCallOutput,
testBuildExtraHosts,
}
func testBuild(t *testing.T, sb integration.Sandbox) {
@ -832,9 +833,9 @@ func buildMetadataProvenance(t *testing.T, sb integration.Sandbox, metadataMode
dtprv, err := json.Marshal(md.BuildProvenance)
require.NoError(t, err)
var prv provenancetypes.ProvenancePredicate
var prv provenancetypes.ProvenancePredicateSLSA02
require.NoError(t, json.Unmarshal(dtprv, &prv))
require.Equal(t, provenancetypes.BuildKitBuildType, prv.BuildType)
require.Equal(t, provenancetypes.BuildKitBuildType02, prv.BuildType)
}
func testBuildMetadataWarnings(t *testing.T, sb integration.Sandbox) {
@ -1322,6 +1323,24 @@ cOpy Dockerfile .
})
}
func testBuildExtraHosts(t *testing.T, sb integration.Sandbox) {
dockerfile := []byte(`
FROM busybox
RUN cat /etc/hosts | grep myhost | grep 1.2.3.4
RUN cat /etc/hosts | grep myhostmulti | grep 162.242.195.81
RUN cat /etc/hosts | grep myhostmulti | grep 162.242.195.82
`)
dir := tmpdir(t, fstest.CreateFile("Dockerfile", dockerfile, 0600))
cmd := buildxCmd(sb, withArgs("build",
"--add-host=myhost=1.2.3.4",
"--add-host=myhostmulti=162.242.195.81",
"--add-host=myhostmulti=162.242.195.82",
"--output=type=cacheonly", dir),
)
out, err := cmd.CombinedOutput()
require.NoError(t, err, string(out))
}
func createTestProject(t *testing.T) string {
dockerfile := []byte(`
FROM busybox:latest AS base

View File

@ -7,7 +7,6 @@ import (
"strconv"
"strings"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/pkg/errors"
"github.com/tonistiigi/go-csvvalue"
)
@ -33,16 +32,32 @@ func (a Attests) Normalize() Attests {
return removeAttestDupes(a)
}
func (a Attests) ToPB() []*controllerapi.Attest {
if len(a) == 0 {
return nil
}
func (a Attests) ToMap() map[string]*string {
result := map[string]*string{}
for _, attest := range a {
// ignore duplicates
if _, ok := result[attest.Type]; ok {
continue
}
entries := make([]*controllerapi.Attest, len(a))
for i, entry := range a {
entries[i] = entry.ToPB()
if attest.Disabled {
result[attest.Type] = nil
continue
}
var b csvBuilder
if attest.Type != "" {
b.Write("type", attest.Type)
}
if attest.Disabled {
b.Write("disabled", "true")
}
b.WriteAttributes(attest.Attrs)
s := b.String()
result[attest.Type] = &s
}
return entries
return result
}
type Attest struct {
@ -72,23 +87,6 @@ func (a *Attest) String() string {
return b.String()
}
func (a *Attest) ToPB() *controllerapi.Attest {
var b csvBuilder
if a.Type != "" {
b.Write("type", a.Type)
}
if a.Disabled {
b.Write("disabled", "true")
}
b.WriteAttributes(a.Attrs)
return &controllerapi.Attest{
Type: a.Type,
Disabled: a.Disabled,
Attrs: b.String(),
}
}
func (a *Attest) MarshalJSON() ([]byte, error) {
m := make(map[string]any, len(a.Attrs)+2)
for k, v := range a.Attrs {
@ -182,7 +180,7 @@ func CanonicalizeAttest(attestType string, in string) string {
return fmt.Sprintf("type=%s,%s", attestType, in)
}
func ParseAttests(in []string) ([]*controllerapi.Attest, error) {
func ParseAttests(in []string) (Attests, error) {
var outs []*Attest
for _, s := range in {
var out Attest
@ -191,66 +189,7 @@ func ParseAttests(in []string) ([]*controllerapi.Attest, error) {
}
outs = append(outs, &out)
}
return ConvertAttests(outs)
}
// ConvertAttests converts Attestations for the controller API from
// the ones in this package.
//
// Attestations of the same type will cause an error. Some tools,
// like bake, remove the duplicates before calling this function.
func ConvertAttests(in []*Attest) ([]*controllerapi.Attest, error) {
out := make([]*controllerapi.Attest, 0, len(in))
// Check for duplicate attestations while we convert them
// to the controller API.
found := map[string]struct{}{}
for _, attest := range in {
if _, ok := found[attest.Type]; ok {
return nil, errors.Errorf("duplicate attestation field %s", attest.Type)
}
found[attest.Type] = struct{}{}
out = append(out, attest.ToPB())
}
return out, nil
}
func ParseAttest(in string) (*controllerapi.Attest, error) {
if in == "" {
return nil, nil
}
fields, err := csvvalue.Fields(in, nil)
if err != nil {
return nil, err
}
attest := controllerapi.Attest{
Attrs: in,
}
for _, field := range fields {
key, value, ok := strings.Cut(field, "=")
if !ok {
return nil, errors.Errorf("invalid value %s", field)
}
key = strings.TrimSpace(strings.ToLower(key))
switch key {
case "type":
attest.Type = value
case "disabled":
disabled, err := strconv.ParseBool(value)
if err != nil {
return nil, errors.Wrapf(err, "invalid value %s", field)
}
attest.Disabled = disabled
}
}
if attest.Type == "" {
return nil, errors.Errorf("attestation type not specified")
}
return &attest, nil
return outs, nil
}
func removeAttestDupes(s []*Attest) []*Attest {

View File

@ -1,15 +1,10 @@
package buildflags
import (
"context"
"encoding/json"
"maps"
"os"
"strconv"
"strings"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/pkg/errors"
"github.com/tonistiigi/go-csvvalue"
"github.com/zclconf/go-cty/cty"
@ -35,22 +30,6 @@ func (o CacheOptions) Normalize() CacheOptions {
return removeDupes(o)
}
func (o CacheOptions) ToPB() []*controllerapi.CacheOptionsEntry {
if len(o) == 0 {
return nil
}
var outs []*controllerapi.CacheOptionsEntry
for _, entry := range o {
pb := entry.ToPB()
if !isActive(pb) {
continue
}
outs = append(outs, pb)
}
return outs
}
type CacheOptionsEntry struct {
Type string `json:"type"`
Attrs map[string]string `json:"attrs,omitempty"`
@ -81,16 +60,6 @@ func (e *CacheOptionsEntry) String() string {
return b.String()
}
func (e *CacheOptionsEntry) ToPB() *controllerapi.CacheOptionsEntry {
ci := &controllerapi.CacheOptionsEntry{
Type: e.Type,
Attrs: maps.Clone(e.Attrs),
}
addGithubToken(ci)
addAwsCredentials(ci)
return ci
}
func (e *CacheOptionsEntry) MarshalJSON() ([]byte, error) {
m := maps.Clone(e.Attrs)
if m == nil {
@ -204,75 +173,3 @@ func ParseCacheEntry(in []string) (CacheOptions, error) {
}
return opts, nil
}
func addGithubToken(ci *controllerapi.CacheOptionsEntry) {
if ci.Type != "gha" {
return
}
version, ok := ci.Attrs["version"]
if !ok {
// https://github.com/actions/toolkit/blob/2b08dc18f261b9fdd978b70279b85cbef81af8bc/packages/cache/src/internal/config.ts#L19
if v, ok := os.LookupEnv("ACTIONS_CACHE_SERVICE_V2"); ok {
if b, err := strconv.ParseBool(v); err == nil && b {
version = "2"
}
}
}
if _, ok := ci.Attrs["token"]; !ok {
if v, ok := os.LookupEnv("ACTIONS_RUNTIME_TOKEN"); ok {
ci.Attrs["token"] = v
}
}
if _, ok := ci.Attrs["url_v2"]; !ok && version == "2" {
// https://github.com/actions/toolkit/blob/2b08dc18f261b9fdd978b70279b85cbef81af8bc/packages/cache/src/internal/config.ts#L34-L35
if v, ok := os.LookupEnv("ACTIONS_RESULTS_URL"); ok {
ci.Attrs["url_v2"] = v
}
}
if _, ok := ci.Attrs["url"]; !ok {
// https://github.com/actions/toolkit/blob/2b08dc18f261b9fdd978b70279b85cbef81af8bc/packages/cache/src/internal/config.ts#L28-L33
if v, ok := os.LookupEnv("ACTIONS_CACHE_URL"); ok {
ci.Attrs["url"] = v
} else if v, ok := os.LookupEnv("ACTIONS_RESULTS_URL"); ok {
ci.Attrs["url"] = v
}
}
}
func addAwsCredentials(ci *controllerapi.CacheOptionsEntry) {
if ci.Type != "s3" {
return
}
_, okAccessKeyID := ci.Attrs["access_key_id"]
_, okSecretAccessKey := ci.Attrs["secret_access_key"]
// If the user provides access_key_id, secret_access_key, do not override the session token.
if okAccessKeyID && okSecretAccessKey {
return
}
ctx := context.TODO()
awsConfig, err := awsconfig.LoadDefaultConfig(ctx)
if err != nil {
return
}
credentials, err := awsConfig.Credentials.Retrieve(ctx)
if err != nil {
return
}
if !okAccessKeyID && credentials.AccessKeyID != "" {
ci.Attrs["access_key_id"] = credentials.AccessKeyID
}
if !okSecretAccessKey && credentials.SecretAccessKey != "" {
ci.Attrs["secret_access_key"] = credentials.SecretAccessKey
}
if _, ok := ci.Attrs["session_token"]; !ok && credentials.SessionToken != "" {
ci.Attrs["session_token"] = credentials.SessionToken
}
}
func isActive(pb *controllerapi.CacheOptionsEntry) bool {
// Always active if not gha.
if pb.Type != "gha" {
return true
}
return pb.Attrs["token"] != "" && (pb.Attrs["url"] != "" || pb.Attrs["url_v2"] != "")
}

View File

@ -4,42 +4,10 @@ import (
"encoding/json"
"testing"
"github.com/docker/buildx/controller/pb"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"
)
func TestCacheOptions_DerivedVars(t *testing.T) {
t.Setenv("ACTIONS_RUNTIME_TOKEN", "sensitive_token")
t.Setenv("ACTIONS_CACHE_URL", "https://cache.github.com")
t.Setenv("AWS_ACCESS_KEY_ID", "definitely_dont_look_here")
t.Setenv("AWS_SECRET_ACCESS_KEY", "hackers_please_dont_steal")
t.Setenv("AWS_SESSION_TOKEN", "not_a_mitm_attack")
cacheFrom, err := ParseCacheEntry([]string{"type=gha", "type=s3,region=us-west-2,bucket=my_bucket,name=my_image"})
require.NoError(t, err)
require.Equal(t, []*pb.CacheOptionsEntry{
{
Type: "gha",
Attrs: map[string]string{
"token": "sensitive_token",
"url": "https://cache.github.com",
},
},
{
Type: "s3",
Attrs: map[string]string{
"region": "us-west-2",
"bucket": "my_bucket",
"name": "my_image",
"access_key_id": "definitely_dont_look_here",
"secret_access_key": "hackers_please_dont_steal",
"session_token": "not_a_mitm_attack",
},
},
}, cacheFrom.ToPB())
}
func TestCacheOptions(t *testing.T) {
t.Run("MarshalJSON", func(t *testing.T) {
cache := CacheOptions{

View File

@ -1,17 +1,37 @@
package buildflags
import (
"fmt"
"strconv"
"strings"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/pkg/errors"
"github.com/tonistiigi/go-csvvalue"
)
const defaultCallFunc = "build"
func ParseCallFunc(str string) (*controllerapi.CallFunc, error) {
type CallFunc struct {
Name string
Format string
IgnoreStatus bool
}
func (x *CallFunc) String() string {
var elems []string
if x.Name != "" {
elems = append(elems, fmt.Sprintf("Name:%q", x.Name))
}
if x.Format != "" {
elems = append(elems, fmt.Sprintf("Format:%q", x.Format))
}
if x.IgnoreStatus {
elems = append(elems, fmt.Sprintf("IgnoreStatus:%v", x.IgnoreStatus))
}
return strings.Join(elems, " ")
}
func ParseCallFunc(str string) (*CallFunc, error) {
if str == "" {
return nil, nil
}
@ -20,7 +40,7 @@ func ParseCallFunc(str string) (*controllerapi.CallFunc, error) {
if err != nil {
return nil, err
}
f := &controllerapi.CallFunc{}
f := &CallFunc{}
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
if len(parts) == 2 {

View File

@ -9,7 +9,6 @@ import (
"strings"
"github.com/containerd/platforms"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
@ -38,18 +37,6 @@ func (e Exports) Normalize() Exports {
return removeDupes(e)
}
func (e Exports) ToPB() []*controllerapi.ExportEntry {
if len(e) == 0 {
return nil
}
entries := make([]*controllerapi.ExportEntry, len(e))
for i, entry := range e {
entries[i] = entry.ToPB()
}
return entries
}
type ExportEntry struct {
Type string `json:"type"`
Attrs map[string]string `json:"attrs,omitempty"`
@ -77,14 +64,6 @@ func (e *ExportEntry) String() string {
return b.String()
}
func (e *ExportEntry) ToPB() *controllerapi.ExportEntry {
return &controllerapi.ExportEntry{
Type: e.Type,
Attrs: maps.Clone(e.Attrs),
Destination: e.Destination,
}
}
func (e *ExportEntry) MarshalJSON() ([]byte, error) {
m := maps.Clone(e.Attrs)
if m == nil {
@ -164,12 +143,12 @@ func (e *ExportEntry) validate() error {
return nil
}
func ParseExports(inp []string) ([]*controllerapi.ExportEntry, error) {
func ParseExports(inp []string) ([]*ExportEntry, error) {
if len(inp) == 0 {
return nil, nil
}
export := make(Exports, 0, len(inp))
exports := make(Exports, 0, len(inp))
for _, s := range inp {
if s == "" {
continue
@ -179,9 +158,9 @@ func ParseExports(inp []string) ([]*controllerapi.ExportEntry, error) {
if err := out.UnmarshalText([]byte(s)); err != nil {
return nil, err
}
export = append(export, &out)
exports = append(exports, &out)
}
return export.ToPB(), nil
return exports, nil
}
func ParseAnnotations(inp []string) (map[exptypes.AnnotationKey]string, error) {

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"strings"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/pkg/errors"
"github.com/tonistiigi/go-csvvalue"
)
@ -30,18 +29,6 @@ func (s Secrets) Normalize() Secrets {
return removeSecretDupes(s)
}
func (s Secrets) ToPB() []*controllerapi.Secret {
if len(s) == 0 {
return nil
}
entries := make([]*controllerapi.Secret, len(s))
for i, entry := range s {
entries[i] = entry.ToPB()
}
return entries
}
type Secret struct {
ID string `json:"id,omitempty"`
FilePath string `json:"src,omitempty"`
@ -66,14 +53,6 @@ func (s *Secret) String() string {
return b.String()
}
func (s *Secret) ToPB() *controllerapi.Secret {
return &controllerapi.Secret{
ID: s.ID,
FilePath: s.FilePath,
Env: s.Env,
}
}
func (s *Secret) UnmarshalJSON(data []byte) error {
var v struct {
ID string `json:"id,omitempty"`
@ -132,8 +111,8 @@ func (s *Secret) UnmarshalText(text []byte) error {
return nil
}
func ParseSecretSpecs(sl []string) ([]*controllerapi.Secret, error) {
fs := make([]*controllerapi.Secret, 0, len(sl))
func ParseSecretSpecs(sl []string) (Secrets, error) {
fs := make([]*Secret, 0, len(sl))
for _, v := range sl {
if v == "" {
continue
@ -148,12 +127,12 @@ func ParseSecretSpecs(sl []string) ([]*controllerapi.Secret, error) {
return fs, nil
}
func parseSecret(value string) (*controllerapi.Secret, error) {
func parseSecret(value string) (*Secret, error) {
var s Secret
if err := s.UnmarshalText([]byte(value)); err != nil {
return nil, err
}
return s.ToPB(), nil
return &s, nil
}
func removeSecretDupes(s []*Secret) []*Secret {

View File

@ -6,7 +6,6 @@ import (
"slices"
"strings"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/moby/buildkit/util/gitutil"
)
@ -31,18 +30,6 @@ func (s SSHKeys) Normalize() SSHKeys {
return removeSSHDupes(s)
}
func (s SSHKeys) ToPB() []*controllerapi.SSH {
if len(s) == 0 {
return nil
}
entries := make([]*controllerapi.SSH, len(s))
for i, entry := range s {
entries[i] = entry.ToPB()
}
return entries
}
type SSH struct {
ID string `json:"id,omitempty" cty:"id"`
Paths []string `json:"paths,omitempty" cty:"paths"`
@ -70,13 +57,6 @@ func (s *SSH) String() string {
return b.String()
}
func (s *SSH) ToPB() *controllerapi.SSH {
return &controllerapi.SSH{
ID: s.ID,
Paths: s.Paths,
}
}
func (s *SSH) UnmarshalJSON(data []byte) error {
var v struct {
ID string `json:"id,omitempty"`
@ -103,8 +83,8 @@ func (s *SSH) UnmarshalText(text []byte) error {
return nil
}
func ParseSSHSpecs(sl []string) ([]*controllerapi.SSH, error) {
var outs []*controllerapi.SSH
func ParseSSHSpecs(sl []string) ([]*SSH, error) {
var outs []*SSH
if len(sl) == 0 {
return nil, nil
}
@ -118,7 +98,7 @@ func ParseSSHSpecs(sl []string) ([]*controllerapi.SSH, error) {
if err := out.UnmarshalText([]byte(s)); err != nil {
return nil, err
}
outs = append(outs, out.ToPB())
outs = append(outs, &out)
}
return outs, nil
}

103
util/imagetools/auth.go Normal file
View File

@ -0,0 +1,103 @@
package imagetools
import (
"context"
"encoding/base64"
"encoding/json"
"net/http"
"sync"
"time"
"github.com/containerd/containerd/v2/core/remotes/docker"
"github.com/distribution/reference"
"github.com/docker/cli/cli/config/types"
)
type authConfig struct {
mu sync.Mutex
authConfigCache map[string]authConfigCacheEntry
cfg Auth
}
type authConfigCacheEntry struct {
Created time.Time
Auth types.AuthConfig
}
func newAuthConfig(a Auth) *authConfig {
return &authConfig{
authConfigCache: map[string]authConfigCacheEntry{},
cfg: a,
}
}
func (a *authConfig) credentials(host string) (string, string, error) {
ac, err := a.authConfig(host)
if err != nil {
return "", "", err
}
if ac.IdentityToken != "" {
return "", ac.IdentityToken, nil
}
return ac.Username, ac.Password, nil
}
func (a *authConfig) authConfig(host string) (types.AuthConfig, error) {
const defaultExpiration = 2 * time.Minute
if host == "registry-1.docker.io" {
host = "https://index.docker.io/v1/"
}
a.mu.Lock()
defer a.mu.Unlock()
if c, ok := a.authConfigCache[host]; ok && time.Since(c.Created) <= defaultExpiration {
return c.Auth, nil
}
ac, err := a.cfg.GetAuthConfig(host)
if err != nil {
return types.AuthConfig{}, err
}
a.authConfigCache[host] = authConfigCacheEntry{
Created: time.Now(),
Auth: ac,
}
return ac, 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
}
type withBearerAuthorizer struct {
docker.Authorizer
AuthConfig *authConfig
}
func (a *withBearerAuthorizer) Authorize(ctx context.Context, req *http.Request) error {
ac, err := a.AuthConfig.authConfig(req.Host)
if err == nil && ac.RegistryToken != "" {
req.Header.Set("Authorization", "Bearer "+ac.RegistryToken)
return nil
}
return a.Authorizer.Authorize(ctx, req)
}

View File

@ -3,8 +3,6 @@ package imagetools
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"io"
"net/http"
@ -36,8 +34,14 @@ type Resolver struct {
}
func New(opt Opt) *Resolver {
ac := newAuthConfig(opt.Auth)
dockerAuth := docker.NewDockerAuthorizer(docker.WithAuthCreds(ac.credentials), docker.WithAuthClient(http.DefaultClient))
auth := &withBearerAuthorizer{
Authorizer: dockerAuth,
AuthConfig: ac,
}
return &Resolver{
auth: docker.NewDockerAuthorizer(docker.WithAuthCreds(toCredentialsFunc(opt.Auth)), docker.WithAuthClient(http.DefaultClient)),
auth: auth,
hosts: resolver.NewRegistryConfig(opt.RegistryConfig),
buffer: contentutil.NewBuffer(),
}
@ -121,42 +125,3 @@ func parseRef(s string) (reference.Named, error) {
ref = reference.TagNameOnly(ref)
return ref, nil
}
func toCredentialsFunc(a Auth) func(string) (string, string, error) {
return func(host string) (string, string, error) {
if host == "registry-1.docker.io" {
host = "https://index.docker.io/v1/"
}
ac, err := a.GetAuthConfig(host)
if err != nil {
return "", "", err
}
if ac.IdentityToken != "" {
return "", ac.IdentityToken, nil
}
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

@ -229,20 +229,24 @@ func copyToFunc(r io.Reader, wFunc func() (io.Writer, error)) error {
buf := make([]byte, 4096)
for {
n, readErr := r.Read(buf)
if readErr != nil && readErr != io.EOF {
return readErr
}
w, err := wFunc()
if err != nil {
return err
}
if w != nil {
if _, err := w.Write(buf[:n]); err != nil {
logrus.WithError(err).Debugf("failed to copy")
if n > 0 {
w, err := wFunc()
if err != nil {
return err
}
if w != nil {
if _, err := w.Write(buf[:n]); err != nil {
logrus.WithError(err).Debugf("failed to copy")
}
}
}
if readErr == io.EOF {
return nil
if readErr != nil {
if isReaderClosed(readErr) {
return nil
}
return readErr
}
}
}
@ -255,3 +259,7 @@ type readerWithClose struct {
func (r *readerWithClose) Close() error {
return r.closeFunc()
}
func isReaderClosed(err error) bool {
return errors.Is(err, io.EOF) || errors.Is(err, io.ErrClosedPipe)
}

View File

@ -133,7 +133,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -271,7 +272,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -409,7 +411,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -547,7 +550,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -664,7 +668,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -802,7 +807,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -940,7 +946,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -1078,7 +1085,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -1321,7 +1329,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -1459,7 +1468,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -1597,7 +1607,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -1756,7 +1767,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -1894,7 +1906,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -2032,7 +2045,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -2170,7 +2184,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -2308,7 +2323,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -2446,7 +2462,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -2584,7 +2601,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -3289,7 +3307,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -3355,7 +3374,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -3450,7 +3470,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -3545,7 +3566,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -3640,7 +3662,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -3735,7 +3758,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -3830,7 +3854,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -3925,7 +3950,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -4020,7 +4046,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -4115,7 +4142,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -4189,7 +4217,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -4284,7 +4313,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -4358,7 +4388,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -4453,7 +4484,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -4548,7 +4580,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -4664,7 +4697,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -4971,7 +5005,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -5045,7 +5080,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -5119,7 +5155,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -5193,7 +5230,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -5288,7 +5326,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -5362,7 +5401,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -5457,7 +5497,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -5552,7 +5593,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -5647,7 +5689,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -5742,7 +5785,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -5858,7 +5902,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -6165,7 +6210,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -6281,7 +6327,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -6588,7 +6635,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -6710,7 +6758,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -6805,7 +6854,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -6879,7 +6929,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -6953,7 +7004,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -7027,7 +7079,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -7122,7 +7175,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -7196,7 +7250,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -7270,7 +7325,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -7344,7 +7400,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -7652,7 +7709,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -7726,7 +7784,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -7833,7 +7892,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -7907,7 +7967,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -7981,7 +8042,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -8055,7 +8117,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -8129,7 +8192,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -8203,7 +8267,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -10399,7 +10464,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -10537,7 +10603,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -10654,7 +10721,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -10835,7 +10903,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -10930,7 +10999,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -11025,7 +11095,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
{
@ -11120,6 +11191,7 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}

View File

@ -134,7 +134,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -272,7 +273,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -410,7 +412,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -548,7 +551,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -665,7 +669,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -803,7 +808,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -941,7 +947,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -1079,7 +1086,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -1322,7 +1330,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -1460,7 +1469,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -1598,7 +1608,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -1757,7 +1768,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -1895,7 +1907,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -2033,7 +2046,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -2171,7 +2185,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -2309,7 +2324,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -2447,7 +2463,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -2585,7 +2602,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -3290,7 +3308,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -3356,7 +3375,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -3451,7 +3471,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -3546,7 +3567,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -3641,7 +3663,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -3736,7 +3759,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -3831,7 +3855,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -3926,7 +3951,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -4021,7 +4047,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -4116,7 +4143,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -4190,7 +4218,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -4285,7 +4314,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -4359,7 +4389,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -4454,7 +4485,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -4549,7 +4581,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -4665,7 +4698,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -4972,7 +5006,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -5046,7 +5081,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -5120,7 +5156,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -5194,7 +5231,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -5289,7 +5327,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -5363,7 +5402,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -5458,7 +5498,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -5553,7 +5594,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -5648,7 +5690,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -5743,7 +5786,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -5859,7 +5903,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -6166,7 +6211,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -6282,7 +6328,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -6589,7 +6636,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -6711,7 +6759,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -6806,7 +6855,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -6880,7 +6930,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -6954,7 +7005,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -7028,7 +7080,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -7123,7 +7176,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -7197,7 +7251,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -7271,7 +7326,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -7345,7 +7401,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -7653,7 +7710,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -7727,7 +7785,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -7834,7 +7893,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -7908,7 +7968,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -7982,7 +8043,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -8056,7 +8118,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -8130,7 +8193,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -8204,7 +8268,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -10400,7 +10465,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -10538,7 +10604,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -10655,7 +10722,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "semver:0.40.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -10729,7 +10797,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -10836,7 +10905,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/otel/sdk/tracer",
"Version": "",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -10931,7 +11001,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -11026,7 +11097,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
},
{
@ -11121,7 +11193,8 @@
"InstrumentationLibrary": {
"Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
"Version": "0.45.0",
"SchemaURL": ""
"SchemaURL": "",
"Attributes": null
}
}
]
]

View File

@ -30,7 +30,7 @@ func TestParseSpanStubs(t *testing.T) {
require.NoError(t, err)
dtotel, err := os.ReadFile(otlpFixture)
require.NoError(t, err)
require.Equal(t, string(dtotel), string(dtSpanStubs))
require.Equal(t, string(bytes.TrimSpace(dtotel)), string(dtSpanStubs))
exp, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
require.NoError(t, err)

View File

@ -16,12 +16,24 @@ import (
"go.opentelemetry.io/otel/metric"
)
type Printer struct {
status chan *client.SolveStatus
type printerState int
const (
printerStateDone printerState = iota
printerStateRunning
printerStatePaused
)
type Printer struct {
out console.File
mode progressui.DisplayMode
opt *printerOpts
status chan *client.SolveStatus
interrupt chan interruptRequest
state printerState
ready chan struct{}
done chan struct{}
paused chan struct{}
closeOnce sync.Once
err error
@ -39,8 +51,8 @@ type Printer struct {
func (p *Printer) Wait() error {
p.closeOnce.Do(func() {
close(p.status)
<-p.done
})
<-p.done
return p.err
}
@ -54,13 +66,23 @@ func (p *Printer) IsDone() bool {
}
func (p *Printer) Pause() error {
p.paused = make(chan struct{})
return p.Wait()
done := make(chan struct{})
p.interrupt <- interruptRequest{
desiredState: printerStatePaused,
done: done,
}
// Need to wait for a response to confirm we have control
// of the console output.
<-done
return nil
}
func (p *Printer) Unpause() {
close(p.paused)
<-p.ready
func (p *Printer) Resume() {
p.interrupt <- interruptRequest{
desiredState: printerStateRunning,
}
// Do not care about waiting for a response.
}
func (p *Printer) Write(s *client.SolveStatus) {
@ -115,42 +137,115 @@ func NewPrinter(ctx context.Context, out console.File, mode progressui.DisplayMo
}
pw := &Printer{
ready: make(chan struct{}),
metrics: opt.mw,
out: out,
mode: mode,
opt: opt,
status: make(chan *client.SolveStatus),
interrupt: make(chan interruptRequest),
state: printerStateRunning,
done: make(chan struct{}),
metrics: opt.mw,
}
go pw.run(ctx, d)
return pw, nil
}
func (p *Printer) run(ctx context.Context, d progressui.Display) {
defer close(p.done)
defer close(p.interrupt)
var ss []*client.SolveStatus
for {
switch p.state {
case printerStatePaused:
ss, p.err = p.bufferDisplay(ctx, ss)
case printerStateRunning:
p.warnings, ss, p.err = p.updateDisplay(ctx, d, ss)
if p.opt.onclose != nil {
p.opt.onclose()
}
}
if p.state == printerStateDone {
break
}
d, _ = p.newDisplay()
}
}
func (p *Printer) newDisplay() (progressui.Display, error) {
return progressui.NewDisplay(p.out, p.mode, p.opt.displayOpts...)
}
func (p *Printer) updateDisplay(ctx context.Context, d progressui.Display, ss []*client.SolveStatus) ([]client.VertexWarning, []*client.SolveStatus, error) {
p.logMu.Lock()
p.logSourceMap = map[digest.Digest]any{}
p.logMu.Unlock()
resumeLogs := logutil.Pause(logrus.StandardLogger())
defer resumeLogs()
interruptCh := make(chan interruptRequest, 1)
ingress := make(chan *client.SolveStatus)
go func() {
defer close(ingress)
defer close(interruptCh)
for _, s := range ss {
ingress <- s
}
for {
pw.status = make(chan *client.SolveStatus)
pw.done = make(chan struct{})
pw.closeOnce = sync.Once{}
pw.logMu.Lock()
pw.logSourceMap = map[digest.Digest]any{}
pw.logMu.Unlock()
resumeLogs := logutil.Pause(logrus.StandardLogger())
close(pw.ready)
// not using shared context to not disrupt display but let is finish reporting errors
pw.warnings, pw.err = d.UpdateFrom(ctx, pw.status)
resumeLogs()
close(pw.done)
if opt.onclose != nil {
opt.onclose()
select {
case s, ok := <-p.status:
if !ok {
return
}
ingress <- s
case req := <-p.interrupt:
interruptCh <- req
return
case <-ctx.Done():
return
}
if pw.paused == nil {
break
}
pw.ready = make(chan struct{})
<-pw.paused
pw.paused = nil
d, _ = progressui.NewDisplay(out, mode, opt.displayOpts...)
}
}()
<-pw.ready
return pw, nil
warnings, err := d.UpdateFrom(context.Background(), ingress)
if err == nil {
err = context.Cause(ctx)
}
interrupt := <-interruptCh
p.state = interrupt.desiredState
interrupt.close()
return warnings, nil, err
}
// bufferDisplay will buffer display updates from the status channel into a
// slice.
//
// This method returns if either status gets closed or if an interrupt is received.
func (p *Printer) bufferDisplay(ctx context.Context, ss []*client.SolveStatus) ([]*client.SolveStatus, error) {
for {
select {
case s, ok := <-p.status:
if !ok {
p.state = printerStateDone
return ss, nil
}
ss = append(ss, s)
case req := <-p.interrupt:
p.state = req.desiredState
req.close()
return ss, nil
case <-ctx.Done():
p.state = printerStateDone
return nil, context.Cause(ctx)
}
}
}
func (p *Printer) WriteBuildRef(target string, ref string) {
@ -221,3 +316,14 @@ func dedupWarnings(inp []client.VertexWarning) []client.VertexWarning {
}
return res
}
type interruptRequest struct {
desiredState printerState
done chan<- struct{}
}
func (req *interruptRequest) close() {
if req.done != nil {
close(req.done)
}
}

View File

@ -1,93 +0,0 @@
# go-fuzz-headers
This repository contains various helper functions for go fuzzing. It is mostly used in combination with [go-fuzz](https://github.com/dvyukov/go-fuzz), but compatibility with fuzzing in the standard library will also be supported. Any coverage guided fuzzing engine that provides an array or slice of bytes can be used with go-fuzz-headers.
## Usage
Using go-fuzz-headers is easy. First create a new consumer with the bytes provided by the fuzzing engine:
```go
import (
fuzz "github.com/AdaLogics/go-fuzz-headers"
)
data := []byte{'R', 'a', 'n', 'd', 'o', 'm'}
f := fuzz.NewConsumer(data)
```
This creates a `Consumer` that consumes the bytes of the input as it uses them to fuzz different types.
After that, `f` can be used to easily create fuzzed instances of different types. Below are some examples:
### Structs
One of the most useful features of go-fuzz-headers is its ability to fill structs with the data provided by the fuzzing engine. This is done with a single line:
```go
type Person struct {
Name string
Age int
}
p := Person{}
// Fill p with values based on the data provided by the fuzzing engine:
err := f.GenerateStruct(&p)
```
This includes nested structs too. In this example, the fuzz Consumer will also insert values in `p.BestFriend`:
```go
type PersonI struct {
Name string
Age int
BestFriend PersonII
}
type PersonII struct {
Name string
Age int
}
p := PersonI{}
err := f.GenerateStruct(&p)
```
If the consumer should insert values for unexported fields as well as exported, this can be enabled with:
```go
f.AllowUnexportedFields()
```
...and disabled with:
```go
f.DisallowUnexportedFields()
```
### Other types:
Other useful APIs:
```go
createdString, err := f.GetString() // Gets a string
createdInt, err := f.GetInt() // Gets an integer
createdByte, err := f.GetByte() // Gets a byte
createdBytes, err := f.GetBytes() // Gets a byte slice
createdBool, err := f.GetBool() // Gets a boolean
err := f.FuzzMap(target_map) // Fills a map
createdTarBytes, err := f.TarBytes() // Gets bytes of a valid tar archive
err := f.CreateFiles(inThisDir) // Fills inThisDir with files
createdString, err := f.GetStringFrom("anyCharInThisString", ofThisLength) // Gets a string that consists of chars from "anyCharInThisString" and has the exact length "ofThisLength"
```
Most APIs are added as they are needed.
## Projects that use go-fuzz-headers
- [runC](https://github.com/opencontainers/runc)
- [Istio](https://github.com/istio/istio)
- [Vitess](https://github.com/vitessio/vitess)
- [Containerd](https://github.com/containerd/containerd)
Feel free to add your own project to the list, if you use go-fuzz-headers to fuzz it.
## Status
The project is under development and will be updated regularly.
## References
go-fuzz-headers' approach to fuzzing structs is strongly inspired by [gofuzz](https://github.com/google/gofuzz).

View File

@ -1,960 +0,0 @@
// Copyright 2023 The go-fuzz-headers Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gofuzzheaders
import (
"archive/tar"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"time"
"unsafe"
)
var (
MaxTotalLen uint32 = 2000000
maxDepth = 100
)
func SetMaxTotalLen(newLen uint32) {
MaxTotalLen = newLen
}
type ConsumeFuzzer struct {
data []byte
dataTotal uint32
CommandPart []byte
RestOfArray []byte
NumberOfCalls int
position uint32
fuzzUnexportedFields bool
forceUTF8Strings bool
curDepth int
Funcs map[reflect.Type]reflect.Value
}
func IsDivisibleBy(n int, divisibleby int) bool {
return (n % divisibleby) == 0
}
func NewConsumer(fuzzData []byte) *ConsumeFuzzer {
return &ConsumeFuzzer{
data: fuzzData,
dataTotal: uint32(len(fuzzData)),
Funcs: make(map[reflect.Type]reflect.Value),
curDepth: 0,
}
}
func (f *ConsumeFuzzer) Split(minCalls, maxCalls int) error {
if f.dataTotal == 0 {
return errors.New("could not split")
}
numberOfCalls := int(f.data[0])
if numberOfCalls < minCalls || numberOfCalls > maxCalls {
return errors.New("bad number of calls")
}
if int(f.dataTotal) < numberOfCalls+numberOfCalls+1 {
return errors.New("length of data does not match required parameters")
}
// Define part 2 and 3 of the data array
commandPart := f.data[1 : numberOfCalls+1]
restOfArray := f.data[numberOfCalls+1:]
// Just a small check. It is necessary
if len(commandPart) != numberOfCalls {
return errors.New("length of commandPart does not match number of calls")
}
// Check if restOfArray is divisible by numberOfCalls
if !IsDivisibleBy(len(restOfArray), numberOfCalls) {
return errors.New("length of commandPart does not match number of calls")
}
f.CommandPart = commandPart
f.RestOfArray = restOfArray
f.NumberOfCalls = numberOfCalls
return nil
}
func (f *ConsumeFuzzer) AllowUnexportedFields() {
f.fuzzUnexportedFields = true
}
func (f *ConsumeFuzzer) DisallowUnexportedFields() {
f.fuzzUnexportedFields = false
}
func (f *ConsumeFuzzer) AllowNonUTF8Strings() {
f.forceUTF8Strings = false
}
func (f *ConsumeFuzzer) DisallowNonUTF8Strings() {
f.forceUTF8Strings = true
}
func (f *ConsumeFuzzer) GenerateStruct(targetStruct interface{}) error {
e := reflect.ValueOf(targetStruct).Elem()
return f.fuzzStruct(e, false)
}
func (f *ConsumeFuzzer) setCustom(v reflect.Value) error {
// First: see if we have a fuzz function for it.
doCustom, ok := f.Funcs[v.Type()]
if !ok {
return fmt.Errorf("could not find a custom function")
}
switch v.Kind() {
case reflect.Ptr:
if v.IsNil() {
if !v.CanSet() {
return fmt.Errorf("could not use a custom function")
}
v.Set(reflect.New(v.Type().Elem()))
}
case reflect.Map:
if v.IsNil() {
if !v.CanSet() {
return fmt.Errorf("could not use a custom function")
}
v.Set(reflect.MakeMap(v.Type()))
}
default:
return fmt.Errorf("could not use a custom function")
}
verr := doCustom.Call([]reflect.Value{v, reflect.ValueOf(Continue{
F: f,
})})
// check if we return an error
if verr[0].IsNil() {
return nil
}
return fmt.Errorf("could not use a custom function")
}
func (f *ConsumeFuzzer) fuzzStruct(e reflect.Value, customFunctions bool) error {
if f.curDepth >= maxDepth {
// return err or nil here?
return nil
}
f.curDepth++
defer func() { f.curDepth-- }()
// We check if we should check for custom functions
if customFunctions && e.IsValid() && e.CanAddr() {
err := f.setCustom(e.Addr())
if err != nil {
return err
}
}
switch e.Kind() {
case reflect.Struct:
for i := 0; i < e.NumField(); i++ {
var v reflect.Value
if !e.Field(i).CanSet() {
if f.fuzzUnexportedFields {
v = reflect.NewAt(e.Field(i).Type(), unsafe.Pointer(e.Field(i).UnsafeAddr())).Elem()
}
if err := f.fuzzStruct(v, customFunctions); err != nil {
return err
}
} else {
v = e.Field(i)
if err := f.fuzzStruct(v, customFunctions); err != nil {
return err
}
}
}
case reflect.String:
str, err := f.GetString()
if err != nil {
return err
}
if e.CanSet() {
e.SetString(str)
}
case reflect.Slice:
var maxElements uint32
// Byte slices should not be restricted
if e.Type().String() == "[]uint8" {
maxElements = 10000000
} else {
maxElements = 50
}
randQty, err := f.GetUint32()
if err != nil {
return err
}
numOfElements := randQty % maxElements
if (f.dataTotal - f.position) < numOfElements {
numOfElements = f.dataTotal - f.position
}
uu := reflect.MakeSlice(e.Type(), int(numOfElements), int(numOfElements))
for i := 0; i < int(numOfElements); i++ {
// If we have more than 10, then we can proceed with that.
if err := f.fuzzStruct(uu.Index(i), customFunctions); err != nil {
if i >= 10 {
if e.CanSet() {
e.Set(uu)
}
return nil
} else {
return err
}
}
}
if e.CanSet() {
e.Set(uu)
}
case reflect.Uint:
newInt, err := f.GetUint()
if err != nil {
return err
}
if e.CanSet() {
e.SetUint(uint64(newInt))
}
case reflect.Uint16:
newInt, err := f.GetUint16()
if err != nil {
return err
}
if e.CanSet() {
e.SetUint(uint64(newInt))
}
case reflect.Uint32:
newInt, err := f.GetUint32()
if err != nil {
return err
}
if e.CanSet() {
e.SetUint(uint64(newInt))
}
case reflect.Uint64:
newInt, err := f.GetInt()
if err != nil {
return err
}
if e.CanSet() {
e.SetUint(uint64(newInt))
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
newInt, err := f.GetInt()
if err != nil {
return err
}
if e.CanSet() {
e.SetInt(int64(newInt))
}
case reflect.Float32:
newFloat, err := f.GetFloat32()
if err != nil {
return err
}
if e.CanSet() {
e.SetFloat(float64(newFloat))
}
case reflect.Float64:
newFloat, err := f.GetFloat64()
if err != nil {
return err
}
if e.CanSet() {
e.SetFloat(float64(newFloat))
}
case reflect.Map:
if e.CanSet() {
e.Set(reflect.MakeMap(e.Type()))
const maxElements = 50
randQty, err := f.GetInt()
if err != nil {
return err
}
numOfElements := randQty % maxElements
for i := 0; i < numOfElements; i++ {
key := reflect.New(e.Type().Key()).Elem()
if err := f.fuzzStruct(key, customFunctions); err != nil {
return err
}
val := reflect.New(e.Type().Elem()).Elem()
if err = f.fuzzStruct(val, customFunctions); err != nil {
return err
}
e.SetMapIndex(key, val)
}
}
case reflect.Ptr:
if e.CanSet() {
e.Set(reflect.New(e.Type().Elem()))
if err := f.fuzzStruct(e.Elem(), customFunctions); err != nil {
return err
}
return nil
}
case reflect.Uint8:
b, err := f.GetByte()
if err != nil {
return err
}
if e.CanSet() {
e.SetUint(uint64(b))
}
case reflect.Bool:
b, err := f.GetBool()
if err != nil {
return err
}
if e.CanSet() {
e.SetBool(b)
}
}
return nil
}
func (f *ConsumeFuzzer) GetStringArray() (reflect.Value, error) {
// The max size of the array:
const max uint32 = 20
arraySize := f.position
if arraySize > max {
arraySize = max
}
stringArray := reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf("string")), int(arraySize), int(arraySize))
if f.position+arraySize >= f.dataTotal {
return stringArray, errors.New("could not make string array")
}
for i := 0; i < int(arraySize); i++ {
stringSize := uint32(f.data[f.position])
if f.position+stringSize >= f.dataTotal {
return stringArray, nil
}
stringToAppend := string(f.data[f.position : f.position+stringSize])
strVal := reflect.ValueOf(stringToAppend)
stringArray = reflect.Append(stringArray, strVal)
f.position += stringSize
}
return stringArray, nil
}
func (f *ConsumeFuzzer) GetInt() (int, error) {
if f.position >= f.dataTotal {
return 0, errors.New("not enough bytes to create int")
}
returnInt := int(f.data[f.position])
f.position++
return returnInt, nil
}
func (f *ConsumeFuzzer) GetByte() (byte, error) {
if f.position >= f.dataTotal {
return 0x00, errors.New("not enough bytes to get byte")
}
returnByte := f.data[f.position]
f.position++
return returnByte, nil
}
func (f *ConsumeFuzzer) GetNBytes(numberOfBytes int) ([]byte, error) {
if f.position >= f.dataTotal {
return nil, errors.New("not enough bytes to get byte")
}
returnBytes := make([]byte, 0, numberOfBytes)
for i := 0; i < numberOfBytes; i++ {
newByte, err := f.GetByte()
if err != nil {
return nil, err
}
returnBytes = append(returnBytes, newByte)
}
return returnBytes, nil
}
func (f *ConsumeFuzzer) GetUint16() (uint16, error) {
u16, err := f.GetNBytes(2)
if err != nil {
return 0, err
}
littleEndian, err := f.GetBool()
if err != nil {
return 0, err
}
if littleEndian {
return binary.LittleEndian.Uint16(u16), nil
}
return binary.BigEndian.Uint16(u16), nil
}
func (f *ConsumeFuzzer) GetUint32() (uint32, error) {
u32, err := f.GetNBytes(4)
if err != nil {
return 0, err
}
return binary.BigEndian.Uint32(u32), nil
}
func (f *ConsumeFuzzer) GetUint64() (uint64, error) {
u64, err := f.GetNBytes(8)
if err != nil {
return 0, err
}
littleEndian, err := f.GetBool()
if err != nil {
return 0, err
}
if littleEndian {
return binary.LittleEndian.Uint64(u64), nil
}
return binary.BigEndian.Uint64(u64), nil
}
func (f *ConsumeFuzzer) GetUint() (uint, error) {
var zero uint
size := int(unsafe.Sizeof(zero))
if size == 8 {
u64, err := f.GetUint64()
if err != nil {
return 0, err
}
return uint(u64), nil
}
u32, err := f.GetUint32()
if err != nil {
return 0, err
}
return uint(u32), nil
}
func (f *ConsumeFuzzer) GetBytes() ([]byte, error) {
var length uint32
var err error
length, err = f.GetUint32()
if err != nil {
return nil, errors.New("not enough bytes to create byte array")
}
if length == 0 {
length = 30
}
bytesLeft := f.dataTotal - f.position
if bytesLeft <= 0 {
return nil, errors.New("not enough bytes to create byte array")
}
// If the length is the same as bytes left, we will not overflow
// the remaining bytes.
if length != bytesLeft {
length = length % bytesLeft
}
byteBegin := f.position
if byteBegin+length < byteBegin {
return nil, errors.New("numbers overflow")
}
f.position = byteBegin + length
return f.data[byteBegin:f.position], nil
}
func (f *ConsumeFuzzer) GetString() (string, error) {
if f.position >= f.dataTotal {
return "nil", errors.New("not enough bytes to create string")
}
length, err := f.GetUint32()
if err != nil {
return "nil", errors.New("not enough bytes to create string")
}
if f.position > MaxTotalLen {
return "nil", errors.New("created too large a string")
}
byteBegin := f.position
if byteBegin >= f.dataTotal {
return "nil", errors.New("not enough bytes to create string")
}
if byteBegin+length > f.dataTotal {
return "nil", errors.New("not enough bytes to create string")
}
if byteBegin > byteBegin+length {
return "nil", errors.New("numbers overflow")
}
f.position = byteBegin + length
s := string(f.data[byteBegin:f.position])
if f.forceUTF8Strings {
s = strings.ToValidUTF8(s, "")
}
return s, nil
}
func (f *ConsumeFuzzer) GetBool() (bool, error) {
if f.position >= f.dataTotal {
return false, errors.New("not enough bytes to create bool")
}
if IsDivisibleBy(int(f.data[f.position]), 2) {
f.position++
return true, nil
} else {
f.position++
return false, nil
}
}
func (f *ConsumeFuzzer) FuzzMap(m interface{}) error {
return f.GenerateStruct(m)
}
func returnTarBytes(buf []byte) ([]byte, error) {
return buf, nil
// Count files
var fileCounter int
tr := tar.NewReader(bytes.NewReader(buf))
for {
_, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
fileCounter++
}
if fileCounter >= 1 {
return buf, nil
}
return nil, fmt.Errorf("not enough files were created\n")
}
func setTarHeaderFormat(hdr *tar.Header, f *ConsumeFuzzer) error {
ind, err := f.GetInt()
if err != nil {
hdr.Format = tar.FormatGNU
//return nil
}
switch ind % 4 {
case 0:
hdr.Format = tar.FormatUnknown
case 1:
hdr.Format = tar.FormatUSTAR
case 2:
hdr.Format = tar.FormatPAX
case 3:
hdr.Format = tar.FormatGNU
}
return nil
}
func setTarHeaderTypeflag(hdr *tar.Header, f *ConsumeFuzzer) error {
ind, err := f.GetInt()
if err != nil {
return err
}
switch ind % 13 {
case 0:
hdr.Typeflag = tar.TypeReg
case 1:
hdr.Typeflag = tar.TypeLink
linkname, err := f.GetString()
if err != nil {
return err
}
hdr.Linkname = linkname
case 2:
hdr.Typeflag = tar.TypeSymlink
linkname, err := f.GetString()
if err != nil {
return err
}
hdr.Linkname = linkname
case 3:
hdr.Typeflag = tar.TypeChar
case 4:
hdr.Typeflag = tar.TypeBlock
case 5:
hdr.Typeflag = tar.TypeDir
case 6:
hdr.Typeflag = tar.TypeFifo
case 7:
hdr.Typeflag = tar.TypeCont
case 8:
hdr.Typeflag = tar.TypeXHeader
case 9:
hdr.Typeflag = tar.TypeXGlobalHeader
case 10:
hdr.Typeflag = tar.TypeGNUSparse
case 11:
hdr.Typeflag = tar.TypeGNULongName
case 12:
hdr.Typeflag = tar.TypeGNULongLink
}
return nil
}
func (f *ConsumeFuzzer) createTarFileBody() ([]byte, error) {
return f.GetBytes()
/*length, err := f.GetUint32()
if err != nil {
return nil, errors.New("not enough bytes to create byte array")
}
// A bit of optimization to attempt to create a file body
// when we don't have as many bytes left as "length"
remainingBytes := f.dataTotal - f.position
if remainingBytes <= 0 {
return nil, errors.New("created too large a string")
}
if f.position+length > MaxTotalLen {
return nil, errors.New("created too large a string")
}
byteBegin := f.position
if byteBegin >= f.dataTotal {
return nil, errors.New("not enough bytes to create byte array")
}
if length == 0 {
return nil, errors.New("zero-length is not supported")
}
if byteBegin+length >= f.dataTotal {
return nil, errors.New("not enough bytes to create byte array")
}
if byteBegin+length < byteBegin {
return nil, errors.New("numbers overflow")
}
f.position = byteBegin + length
return f.data[byteBegin:f.position], nil*/
}
// getTarFileName is similar to GetString(), but creates string based
// on the length of f.data to reduce the likelihood of overflowing
// f.data.
func (f *ConsumeFuzzer) getTarFilename() (string, error) {
return f.GetString()
/*length, err := f.GetUint32()
if err != nil {
return "nil", errors.New("not enough bytes to create string")
}
// A bit of optimization to attempt to create a file name
// when we don't have as many bytes left as "length"
remainingBytes := f.dataTotal - f.position
if remainingBytes <= 0 {
return "nil", errors.New("created too large a string")
}
if f.position > MaxTotalLen {
return "nil", errors.New("created too large a string")
}
byteBegin := f.position
if byteBegin >= f.dataTotal {
return "nil", errors.New("not enough bytes to create string")
}
if byteBegin+length > f.dataTotal {
return "nil", errors.New("not enough bytes to create string")
}
if byteBegin > byteBegin+length {
return "nil", errors.New("numbers overflow")
}
f.position = byteBegin + length
return string(f.data[byteBegin:f.position]), nil*/
}
type TarFile struct {
Hdr *tar.Header
Body []byte
}
// TarBytes returns valid bytes for a tar archive
func (f *ConsumeFuzzer) TarBytes() ([]byte, error) {
numberOfFiles, err := f.GetInt()
if err != nil {
return nil, err
}
var tarFiles []*TarFile
tarFiles = make([]*TarFile, 0)
const maxNoOfFiles = 100
for i := 0; i < numberOfFiles%maxNoOfFiles; i++ {
var filename string
var filebody []byte
var sec, nsec int
var err error
filename, err = f.getTarFilename()
if err != nil {
var sb strings.Builder
sb.WriteString("file-")
sb.WriteString(strconv.Itoa(i))
filename = sb.String()
}
filebody, err = f.createTarFileBody()
if err != nil {
var sb strings.Builder
sb.WriteString("filebody-")
sb.WriteString(strconv.Itoa(i))
filebody = []byte(sb.String())
}
sec, err = f.GetInt()
if err != nil {
sec = 1672531200 // beginning of 2023
}
nsec, err = f.GetInt()
if err != nil {
nsec = 1703980800 // end of 2023
}
hdr := &tar.Header{
Name: filename,
Size: int64(len(filebody)),
Mode: 0o600,
ModTime: time.Unix(int64(sec), int64(nsec)),
}
if err := setTarHeaderTypeflag(hdr, f); err != nil {
return []byte(""), err
}
if err := setTarHeaderFormat(hdr, f); err != nil {
return []byte(""), err
}
tf := &TarFile{
Hdr: hdr,
Body: filebody,
}
tarFiles = append(tarFiles, tf)
}
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
defer tw.Close()
for _, tf := range tarFiles {
tw.WriteHeader(tf.Hdr)
tw.Write(tf.Body)
}
return buf.Bytes(), nil
}
// This is similar to TarBytes, but it returns a series of
// files instead of raw tar bytes. The advantage of this
// api is that it is cheaper in terms of cpu power to
// modify or check the files in the fuzzer with TarFiles()
// because it avoids creating a tar reader.
func (f *ConsumeFuzzer) TarFiles() ([]*TarFile, error) {
numberOfFiles, err := f.GetInt()
if err != nil {
return nil, err
}
var tarFiles []*TarFile
tarFiles = make([]*TarFile, 0)
const maxNoOfFiles = 100
for i := 0; i < numberOfFiles%maxNoOfFiles; i++ {
filename, err := f.getTarFilename()
if err != nil {
return tarFiles, err
}
filebody, err := f.createTarFileBody()
if err != nil {
return tarFiles, err
}
sec, err := f.GetInt()
if err != nil {
return tarFiles, err
}
nsec, err := f.GetInt()
if err != nil {
return tarFiles, err
}
hdr := &tar.Header{
Name: filename,
Size: int64(len(filebody)),
Mode: 0o600,
ModTime: time.Unix(int64(sec), int64(nsec)),
}
if err := setTarHeaderTypeflag(hdr, f); err != nil {
hdr.Typeflag = tar.TypeReg
}
if err := setTarHeaderFormat(hdr, f); err != nil {
return tarFiles, err // should not happend
}
tf := &TarFile{
Hdr: hdr,
Body: filebody,
}
tarFiles = append(tarFiles, tf)
}
return tarFiles, nil
}
// CreateFiles creates pseudo-random files in rootDir.
// It creates subdirs and places the files there.
// It is the callers responsibility to ensure that
// rootDir exists.
func (f *ConsumeFuzzer) CreateFiles(rootDir string) error {
numberOfFiles, err := f.GetInt()
if err != nil {
return err
}
maxNumberOfFiles := numberOfFiles % 4000 // This is completely arbitrary
if maxNumberOfFiles == 0 {
return errors.New("maxNumberOfFiles is nil")
}
var noOfCreatedFiles int
for i := 0; i < maxNumberOfFiles; i++ {
// The file to create:
fileName, err := f.GetString()
if err != nil {
if noOfCreatedFiles > 0 {
// If files have been created, we don't return an error.
break
} else {
return errors.New("could not get fileName")
}
}
if strings.Contains(fileName, "..") || (len(fileName) > 0 && fileName[0] == 47) || strings.Contains(fileName, "\\") {
continue
}
fullFilePath := filepath.Join(rootDir, fileName)
// Find the subdirectory of the file
if subDir := filepath.Dir(fileName); subDir != "" && subDir != "." {
// create the dir first; avoid going outside the root dir
if strings.Contains(subDir, "../") || (len(subDir) > 0 && subDir[0] == 47) || strings.Contains(subDir, "\\") {
continue
}
dirPath := filepath.Join(rootDir, subDir)
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
err2 := os.MkdirAll(dirPath, 0o777)
if err2 != nil {
continue
}
}
fullFilePath = filepath.Join(dirPath, fileName)
} else {
// Create symlink
createSymlink, err := f.GetBool()
if err != nil {
if noOfCreatedFiles > 0 {
break
} else {
return errors.New("could not create the symlink")
}
}
if createSymlink {
symlinkTarget, err := f.GetString()
if err != nil {
return err
}
err = os.Symlink(symlinkTarget, fullFilePath)
if err != nil {
return err
}
// stop loop here, since a symlink needs no further action
noOfCreatedFiles++
continue
}
// We create a normal file
fileContents, err := f.GetBytes()
if err != nil {
if noOfCreatedFiles > 0 {
break
} else {
return errors.New("could not create the file")
}
}
err = os.WriteFile(fullFilePath, fileContents, 0o666)
if err != nil {
continue
}
noOfCreatedFiles++
}
}
return nil
}
// GetStringFrom returns a string that can only consist of characters
// included in possibleChars. It returns an error if the created string
// does not have the specified length.
func (f *ConsumeFuzzer) GetStringFrom(possibleChars string, length int) (string, error) {
if (f.dataTotal - f.position) < uint32(length) {
return "", errors.New("not enough bytes to create a string")
}
output := make([]byte, 0, length)
for i := 0; i < length; i++ {
charIndex, err := f.GetInt()
if err != nil {
return string(output), err
}
output = append(output, possibleChars[charIndex%len(possibleChars)])
}
return string(output), nil
}
func (f *ConsumeFuzzer) GetRune() ([]rune, error) {
stringToConvert, err := f.GetString()
if err != nil {
return []rune("nil"), err
}
return []rune(stringToConvert), nil
}
func (f *ConsumeFuzzer) GetFloat32() (float32, error) {
u32, err := f.GetNBytes(4)
if err != nil {
return 0, err
}
littleEndian, err := f.GetBool()
if err != nil {
return 0, err
}
if littleEndian {
u32LE := binary.LittleEndian.Uint32(u32)
return math.Float32frombits(u32LE), nil
}
u32BE := binary.BigEndian.Uint32(u32)
return math.Float32frombits(u32BE), nil
}
func (f *ConsumeFuzzer) GetFloat64() (float64, error) {
u64, err := f.GetNBytes(8)
if err != nil {
return 0, err
}
littleEndian, err := f.GetBool()
if err != nil {
return 0, err
}
if littleEndian {
u64LE := binary.LittleEndian.Uint64(u64)
return math.Float64frombits(u64LE), nil
}
u64BE := binary.BigEndian.Uint64(u64)
return math.Float64frombits(u64BE), nil
}
func (f *ConsumeFuzzer) CreateSlice(targetSlice interface{}) error {
return f.GenerateStruct(targetSlice)
}

View File

@ -1,62 +0,0 @@
// Copyright 2023 The go-fuzz-headers Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gofuzzheaders
import (
"fmt"
"reflect"
)
type Continue struct {
F *ConsumeFuzzer
}
func (f *ConsumeFuzzer) AddFuncs(fuzzFuncs []interface{}) {
for i := range fuzzFuncs {
v := reflect.ValueOf(fuzzFuncs[i])
if v.Kind() != reflect.Func {
panic("Need only funcs!")
}
t := v.Type()
if t.NumIn() != 2 || t.NumOut() != 1 {
fmt.Println(t.NumIn(), t.NumOut())
panic("Need 2 in and 1 out params. In must be the type. Out must be an error")
}
argT := t.In(0)
switch argT.Kind() {
case reflect.Ptr, reflect.Map:
default:
panic("fuzzFunc must take pointer or map type")
}
if t.In(1) != reflect.TypeOf(Continue{}) {
panic("fuzzFunc's second parameter must be type Continue")
}
f.Funcs[argT] = v
}
}
func (f *ConsumeFuzzer) GenerateWithCustom(targetStruct interface{}) error {
e := reflect.ValueOf(targetStruct).Elem()
return f.fuzzStruct(e, true)
}
func (c Continue) GenerateStruct(targetStruct interface{}) error {
return c.F.GenerateStruct(targetStruct)
}
func (c Continue) GenerateStructWithCustom(targetStruct interface{}) error {
return c.F.GenerateWithCustom(targetStruct)
}

View File

@ -1,556 +0,0 @@
// Copyright 2023 The go-fuzz-headers Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gofuzzheaders
import (
"fmt"
"strings"
)
// returns a keyword by index
func getKeyword(f *ConsumeFuzzer) (string, error) {
index, err := f.GetInt()
if err != nil {
return keywords[0], err
}
for i, k := range keywords {
if i == index {
return k, nil
}
}
return keywords[0], fmt.Errorf("could not get a kw")
}
// Simple utility function to check if a string
// slice contains a string.
func containsString(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
// These keywords are used specifically for fuzzing Vitess
var keywords = []string{
"accessible", "action", "add", "after", "against", "algorithm",
"all", "alter", "always", "analyze", "and", "as", "asc", "asensitive",
"auto_increment", "avg_row_length", "before", "begin", "between",
"bigint", "binary", "_binary", "_utf8mb4", "_utf8", "_latin1", "bit",
"blob", "bool", "boolean", "both", "by", "call", "cancel", "cascade",
"cascaded", "case", "cast", "channel", "change", "char", "character",
"charset", "check", "checksum", "coalesce", "code", "collate", "collation",
"column", "columns", "comment", "committed", "commit", "compact", "complete",
"compressed", "compression", "condition", "connection", "constraint", "continue",
"convert", "copy", "cume_dist", "substr", "substring", "create", "cross",
"csv", "current_date", "current_time", "current_timestamp", "current_user",
"cursor", "data", "database", "databases", "day", "day_hour", "day_microsecond",
"day_minute", "day_second", "date", "datetime", "dec", "decimal", "declare",
"default", "definer", "delay_key_write", "delayed", "delete", "dense_rank",
"desc", "describe", "deterministic", "directory", "disable", "discard",
"disk", "distinct", "distinctrow", "div", "double", "do", "drop", "dumpfile",
"duplicate", "dynamic", "each", "else", "elseif", "empty", "enable",
"enclosed", "encryption", "end", "enforced", "engine", "engines", "enum",
"error", "escape", "escaped", "event", "exchange", "exclusive", "exists",
"exit", "explain", "expansion", "export", "extended", "extract", "false",
"fetch", "fields", "first", "first_value", "fixed", "float", "float4",
"float8", "flush", "for", "force", "foreign", "format", "from", "full",
"fulltext", "function", "general", "generated", "geometry", "geometrycollection",
"get", "global", "gtid_executed", "grant", "group", "grouping", "groups",
"group_concat", "having", "header", "high_priority", "hosts", "hour", "hour_microsecond",
"hour_minute", "hour_second", "if", "ignore", "import", "in", "index", "indexes",
"infile", "inout", "inner", "inplace", "insensitive", "insert", "insert_method",
"int", "int1", "int2", "int3", "int4", "int8", "integer", "interval",
"into", "io_after_gtids", "is", "isolation", "iterate", "invoker", "join",
"json", "json_table", "key", "keys", "keyspaces", "key_block_size", "kill", "lag",
"language", "last", "last_value", "last_insert_id", "lateral", "lead", "leading",
"leave", "left", "less", "level", "like", "limit", "linear", "lines",
"linestring", "load", "local", "localtime", "localtimestamp", "lock", "logs",
"long", "longblob", "longtext", "loop", "low_priority", "manifest",
"master_bind", "match", "max_rows", "maxvalue", "mediumblob", "mediumint",
"mediumtext", "memory", "merge", "microsecond", "middleint", "min_rows", "minute",
"minute_microsecond", "minute_second", "mod", "mode", "modify", "modifies",
"multilinestring", "multipoint", "multipolygon", "month", "name",
"names", "natural", "nchar", "next", "no", "none", "not", "no_write_to_binlog",
"nth_value", "ntile", "null", "numeric", "of", "off", "offset", "on",
"only", "open", "optimize", "optimizer_costs", "option", "optionally",
"or", "order", "out", "outer", "outfile", "over", "overwrite", "pack_keys",
"parser", "partition", "partitioning", "password", "percent_rank", "plugins",
"point", "polygon", "precision", "primary", "privileges", "processlist",
"procedure", "query", "quarter", "range", "rank", "read", "reads", "read_write",
"real", "rebuild", "recursive", "redundant", "references", "regexp", "relay",
"release", "remove", "rename", "reorganize", "repair", "repeat", "repeatable",
"replace", "require", "resignal", "restrict", "return", "retry", "revert",
"revoke", "right", "rlike", "rollback", "row", "row_format", "row_number",
"rows", "s3", "savepoint", "schema", "schemas", "second", "second_microsecond",
"security", "select", "sensitive", "separator", "sequence", "serializable",
"session", "set", "share", "shared", "show", "signal", "signed", "slow",
"smallint", "spatial", "specific", "sql", "sqlexception", "sqlstate",
"sqlwarning", "sql_big_result", "sql_cache", "sql_calc_found_rows",
"sql_no_cache", "sql_small_result", "ssl", "start", "starting",
"stats_auto_recalc", "stats_persistent", "stats_sample_pages", "status",
"storage", "stored", "straight_join", "stream", "system", "vstream",
"table", "tables", "tablespace", "temporary", "temptable", "terminated",
"text", "than", "then", "time", "timestamp", "timestampadd", "timestampdiff",
"tinyblob", "tinyint", "tinytext", "to", "trailing", "transaction", "tree",
"traditional", "trigger", "triggers", "true", "truncate", "uncommitted",
"undefined", "undo", "union", "unique", "unlock", "unsigned", "update",
"upgrade", "usage", "use", "user", "user_resources", "using", "utc_date",
"utc_time", "utc_timestamp", "validation", "values", "variables", "varbinary",
"varchar", "varcharacter", "varying", "vgtid_executed", "virtual", "vindex",
"vindexes", "view", "vitess", "vitess_keyspaces", "vitess_metadata",
"vitess_migration", "vitess_migrations", "vitess_replication_status",
"vitess_shards", "vitess_tablets", "vschema", "warnings", "when",
"where", "while", "window", "with", "without", "work", "write", "xor",
"year", "year_month", "zerofill",
}
// Keywords that could get an additional keyword
var needCustomString = []string{
"DISTINCTROW", "FROM", // Select keywords:
"GROUP BY", "HAVING", "WINDOW",
"FOR",
"ORDER BY", "LIMIT",
"INTO", "PARTITION", "AS", // Insert Keywords:
"ON DUPLICATE KEY UPDATE",
"WHERE", "LIMIT", // Delete keywords
"INFILE", "INTO TABLE", "CHARACTER SET", // Load keywords
"TERMINATED BY", "ENCLOSED BY",
"ESCAPED BY", "STARTING BY",
"TERMINATED BY", "STARTING BY",
"IGNORE",
"VALUE", "VALUES", // Replace tokens
"SET", // Update tokens
"ENGINE =", // Drop tokens
"DEFINER =", "ON SCHEDULE", "RENAME TO", // Alter tokens
"COMMENT", "DO", "INITIAL_SIZE = ", "OPTIONS",
}
var alterTableTokens = [][]string{
{"CUSTOM_FUZZ_STRING"},
{"CUSTOM_ALTTER_TABLE_OPTIONS"},
{"PARTITION_OPTIONS_FOR_ALTER_TABLE"},
}
var alterTokens = [][]string{
{
"DATABASE", "SCHEMA", "DEFINER = ", "EVENT", "FUNCTION", "INSTANCE",
"LOGFILE GROUP", "PROCEDURE", "SERVER",
},
{"CUSTOM_FUZZ_STRING"},
{
"ON SCHEDULE", "ON COMPLETION PRESERVE", "ON COMPLETION NOT PRESERVE",
"ADD UNDOFILE", "OPTIONS",
},
{"RENAME TO", "INITIAL_SIZE = "},
{"ENABLE", "DISABLE", "DISABLE ON SLAVE", "ENGINE"},
{"COMMENT"},
{"DO"},
}
var setTokens = [][]string{
{"CHARACTER SET", "CHARSET", "CUSTOM_FUZZ_STRING", "NAMES"},
{"CUSTOM_FUZZ_STRING", "DEFAULT", "="},
{"CUSTOM_FUZZ_STRING"},
}
var dropTokens = [][]string{
{"TEMPORARY", "UNDO"},
{
"DATABASE", "SCHEMA", "EVENT", "INDEX", "LOGFILE GROUP",
"PROCEDURE", "FUNCTION", "SERVER", "SPATIAL REFERENCE SYSTEM",
"TABLE", "TABLESPACE", "TRIGGER", "VIEW",
},
{"IF EXISTS"},
{"CUSTOM_FUZZ_STRING"},
{"ON", "ENGINE = ", "RESTRICT", "CASCADE"},
}
var renameTokens = [][]string{
{"TABLE"},
{"CUSTOM_FUZZ_STRING"},
{"TO"},
{"CUSTOM_FUZZ_STRING"},
}
var truncateTokens = [][]string{
{"TABLE"},
{"CUSTOM_FUZZ_STRING"},
}
var createTokens = [][]string{
{"OR REPLACE", "TEMPORARY", "UNDO"}, // For create spatial reference system
{
"UNIQUE", "FULLTEXT", "SPATIAL", "ALGORITHM = UNDEFINED", "ALGORITHM = MERGE",
"ALGORITHM = TEMPTABLE",
},
{
"DATABASE", "SCHEMA", "EVENT", "FUNCTION", "INDEX", "LOGFILE GROUP",
"PROCEDURE", "SERVER", "SPATIAL REFERENCE SYSTEM", "TABLE", "TABLESPACE",
"TRIGGER", "VIEW",
},
{"IF NOT EXISTS"},
{"CUSTOM_FUZZ_STRING"},
}
/*
// For future use.
var updateTokens = [][]string{
{"LOW_PRIORITY"},
{"IGNORE"},
{"SET"},
{"WHERE"},
{"ORDER BY"},
{"LIMIT"},
}
*/
var replaceTokens = [][]string{
{"LOW_PRIORITY", "DELAYED"},
{"INTO"},
{"PARTITION"},
{"CUSTOM_FUZZ_STRING"},
{"VALUES", "VALUE"},
}
var loadTokens = [][]string{
{"DATA"},
{"LOW_PRIORITY", "CONCURRENT", "LOCAL"},
{"INFILE"},
{"REPLACE", "IGNORE"},
{"INTO TABLE"},
{"PARTITION"},
{"CHARACTER SET"},
{"FIELDS", "COLUMNS"},
{"TERMINATED BY"},
{"OPTIONALLY"},
{"ENCLOSED BY"},
{"ESCAPED BY"},
{"LINES"},
{"STARTING BY"},
{"TERMINATED BY"},
{"IGNORE"},
{"LINES", "ROWS"},
{"CUSTOM_FUZZ_STRING"},
}
// These Are everything that comes after "INSERT"
var insertTokens = [][]string{
{"LOW_PRIORITY", "DELAYED", "HIGH_PRIORITY", "IGNORE"},
{"INTO"},
{"PARTITION"},
{"CUSTOM_FUZZ_STRING"},
{"AS"},
{"ON DUPLICATE KEY UPDATE"},
}
// These are everything that comes after "SELECT"
var selectTokens = [][]string{
{"*", "CUSTOM_FUZZ_STRING", "DISTINCTROW"},
{"HIGH_PRIORITY"},
{"STRAIGHT_JOIN"},
{"SQL_SMALL_RESULT", "SQL_BIG_RESULT", "SQL_BUFFER_RESULT"},
{"SQL_NO_CACHE", "SQL_CALC_FOUND_ROWS"},
{"CUSTOM_FUZZ_STRING"},
{"FROM"},
{"WHERE"},
{"GROUP BY"},
{"HAVING"},
{"WINDOW"},
{"ORDER BY"},
{"LIMIT"},
{"CUSTOM_FUZZ_STRING"},
{"FOR"},
}
// These are everything that comes after "DELETE"
var deleteTokens = [][]string{
{"LOW_PRIORITY", "QUICK", "IGNORE", "FROM", "AS"},
{"PARTITION"},
{"WHERE"},
{"ORDER BY"},
{"LIMIT"},
}
var alter_table_options = []string{
"ADD", "COLUMN", "FIRST", "AFTER", "INDEX", "KEY", "FULLTEXT", "SPATIAL",
"CONSTRAINT", "UNIQUE", "FOREIGN KEY", "CHECK", "ENFORCED", "DROP", "ALTER",
"NOT", "INPLACE", "COPY", "SET", "VISIBLE", "INVISIBLE", "DEFAULT", "CHANGE",
"CHARACTER SET", "COLLATE", "DISABLE", "ENABLE", "KEYS", "TABLESPACE", "LOCK",
"FORCE", "MODIFY", "SHARED", "EXCLUSIVE", "NONE", "ORDER BY", "RENAME COLUMN",
"AS", "=", "ASC", "DESC", "WITH", "WITHOUT", "VALIDATION", "ADD PARTITION",
"DROP PARTITION", "DISCARD PARTITION", "IMPORT PARTITION", "TRUNCATE PARTITION",
"COALESCE PARTITION", "REORGANIZE PARTITION", "EXCHANGE PARTITION",
"ANALYZE PARTITION", "CHECK PARTITION", "OPTIMIZE PARTITION", "REBUILD PARTITION",
"REPAIR PARTITION", "REMOVE PARTITIONING", "USING", "BTREE", "HASH", "COMMENT",
"KEY_BLOCK_SIZE", "WITH PARSER", "AUTOEXTEND_SIZE", "AUTO_INCREMENT", "AVG_ROW_LENGTH",
"CHECKSUM", "INSERT_METHOD", "ROW_FORMAT", "DYNAMIC", "FIXED", "COMPRESSED", "REDUNDANT",
"COMPACT", "SECONDARY_ENGINE_ATTRIBUTE", "STATS_AUTO_RECALC", "STATS_PERSISTENT",
"STATS_SAMPLE_PAGES", "ZLIB", "LZ4", "ENGINE_ATTRIBUTE", "KEY_BLOCK_SIZE", "MAX_ROWS",
"MIN_ROWS", "PACK_KEYS", "PASSWORD", "COMPRESSION", "CONNECTION", "DIRECTORY",
"DELAY_KEY_WRITE", "ENCRYPTION", "STORAGE", "DISK", "MEMORY", "UNION",
}
// Creates an 'alter table' statement. 'alter table' is an exception
// in that it has its own function. The majority of statements
// are created by 'createStmt()'.
func createAlterTableStmt(f *ConsumeFuzzer) (string, error) {
maxArgs, err := f.GetInt()
if err != nil {
return "", err
}
maxArgs = maxArgs % 30
if maxArgs == 0 {
return "", fmt.Errorf("could not create alter table stmt")
}
var stmt strings.Builder
stmt.WriteString("ALTER TABLE ")
for i := 0; i < maxArgs; i++ {
// Calculate if we get existing token or custom string
tokenType, err := f.GetInt()
if err != nil {
return "", err
}
if tokenType%4 == 1 {
customString, err := f.GetString()
if err != nil {
return "", err
}
stmt.WriteString(" " + customString)
} else {
tokenIndex, err := f.GetInt()
if err != nil {
return "", err
}
stmt.WriteString(" " + alter_table_options[tokenIndex%len(alter_table_options)])
}
}
return stmt.String(), nil
}
func chooseToken(tokens []string, f *ConsumeFuzzer) (string, error) {
index, err := f.GetInt()
if err != nil {
return "", err
}
var token strings.Builder
token.WriteString(tokens[index%len(tokens)])
if token.String() == "CUSTOM_FUZZ_STRING" {
customFuzzString, err := f.GetString()
if err != nil {
return "", err
}
return customFuzzString, nil
}
// Check if token requires an argument
if containsString(needCustomString, token.String()) {
customFuzzString, err := f.GetString()
if err != nil {
return "", err
}
token.WriteString(" " + customFuzzString)
}
return token.String(), nil
}
var stmtTypes = map[string][][]string{
"DELETE": deleteTokens,
"INSERT": insertTokens,
"SELECT": selectTokens,
"LOAD": loadTokens,
"REPLACE": replaceTokens,
"CREATE": createTokens,
"DROP": dropTokens,
"RENAME": renameTokens,
"TRUNCATE": truncateTokens,
"SET": setTokens,
"ALTER": alterTokens,
"ALTER TABLE": alterTableTokens, // ALTER TABLE has its own set of tokens
}
var stmtTypeEnum = map[int]string{
0: "DELETE",
1: "INSERT",
2: "SELECT",
3: "LOAD",
4: "REPLACE",
5: "CREATE",
6: "DROP",
7: "RENAME",
8: "TRUNCATE",
9: "SET",
10: "ALTER",
11: "ALTER TABLE",
}
func createStmt(f *ConsumeFuzzer) (string, error) {
stmtIndex, err := f.GetInt()
if err != nil {
return "", err
}
stmtIndex = stmtIndex % len(stmtTypes)
queryType := stmtTypeEnum[stmtIndex]
tokens := stmtTypes[queryType]
// We have custom creator for ALTER TABLE
if queryType == "ALTER TABLE" {
query, err := createAlterTableStmt(f)
if err != nil {
return "", err
}
return query, nil
}
// Here we are creating a query that is not
// an 'alter table' query. For available
// queries, see "stmtTypes"
// First specify the first query keyword:
var query strings.Builder
query.WriteString(queryType)
// Next create the args for the
queryArgs, err := createStmtArgs(tokens, f)
if err != nil {
return "", err
}
query.WriteString(" " + queryArgs)
return query.String(), nil
}
// Creates the arguments of a statements. In a select statement
// that would be everything after "select".
func createStmtArgs(tokenslice [][]string, f *ConsumeFuzzer) (string, error) {
var query, token strings.Builder
// We go through the tokens in the tokenslice,
// create the respective token and add it to
// "query"
for _, tokens := range tokenslice {
// For extra randomization, the fuzzer can
// choose to not include this token.
includeThisToken, err := f.GetBool()
if err != nil {
return "", err
}
if !includeThisToken {
continue
}
// There may be several tokens to choose from:
if len(tokens) > 1 {
chosenToken, err := chooseToken(tokens, f)
if err != nil {
return "", err
}
query.WriteString(" " + chosenToken)
} else {
token.WriteString(tokens[0])
// In case the token is "CUSTOM_FUZZ_STRING"
// we will then create a non-structured string
if token.String() == "CUSTOM_FUZZ_STRING" {
customFuzzString, err := f.GetString()
if err != nil {
return "", err
}
query.WriteString(" " + customFuzzString)
continue
}
// Check if token requires an argument.
// Tokens that take an argument can be found
// in 'needCustomString'. If so, we add a
// non-structured string to the token.
if containsString(needCustomString, token.String()) {
customFuzzString, err := f.GetString()
if err != nil {
return "", err
}
token.WriteString(fmt.Sprintf(" %s", customFuzzString))
}
query.WriteString(fmt.Sprintf(" %s", token.String()))
}
}
return query.String(), nil
}
// Creates a semi-structured query. It creates a string
// that is a combination of the keywords and random strings.
func createQuery(f *ConsumeFuzzer) (string, error) {
queryLen, err := f.GetInt()
if err != nil {
return "", err
}
maxLen := queryLen % 60
if maxLen == 0 {
return "", fmt.Errorf("could not create a query")
}
var query strings.Builder
for i := 0; i < maxLen; i++ {
// Get a new token:
useKeyword, err := f.GetBool()
if err != nil {
return "", err
}
if useKeyword {
keyword, err := getKeyword(f)
if err != nil {
return "", err
}
query.WriteString(" " + keyword)
} else {
customString, err := f.GetString()
if err != nil {
return "", err
}
query.WriteString(" " + customString)
}
}
if query.String() == "" {
return "", fmt.Errorf("could not create a query")
}
return query.String(), nil
}
// GetSQLString is the API that users interact with.
//
// Usage:
//
// f := NewConsumer(data)
// sqlString, err := f.GetSQLString()
func (f *ConsumeFuzzer) GetSQLString() (string, error) {
var query string
veryStructured, err := f.GetBool()
if err != nil {
return "", err
}
if veryStructured {
query, err = createStmt(f)
if err != nil {
return "", err
}
} else {
query, err = createQuery(f)
if err != nil {
return "", err
}
}
return query, nil
}

View File

@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS
Copyright 2013-2017 Docker, Inc.
Copyright 2020 The Compose Specification Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -401,7 +401,17 @@
"type": "object",
"description": "Provider-specific options.",
"patternProperties": {
"^.+$": {"type": ["string", "number", "null"]}
"^.+$": {"oneOf": [
{ "type": ["string", "number", "boolean"] },
{ "type": "array", "items": {"type": ["string", "number", "boolean"]}}
]}
}
},
"configs": {
"type": "object",
"description": "Config files to pass to the provider.",
"patternProperties": {
"^.+$": {"type": ["string"]}
}
}
},

View File

@ -1229,8 +1229,8 @@ func deriveDeepCopy_12(dst, src []DeviceMapping) {
func deriveDeepCopy_13(dst, src *ServiceProviderConfig) {
dst.Type = src.Type
if src.Options != nil {
dst.Options = make(map[string]string, len(src.Options))
deriveDeepCopy_4(dst.Options, src.Options)
dst.Options = make(map[string][]string, len(src.Options))
deriveDeepCopy_15(dst.Options, src.Options)
} else {
dst.Options = nil
}

View File

@ -40,3 +40,27 @@ func (d *Options) DecodeMapstructure(value interface{}) error {
}
return nil
}
// MultiOptions allow option to be repeated
type MultiOptions map[string][]string
func (d *MultiOptions) DecodeMapstructure(value interface{}) error {
switch v := value.(type) {
case map[string]interface{}:
m := make(map[string][]string)
for key, e := range v {
switch e := e.(type) {
case []interface{}:
for _, v := range e {
m[key] = append(m[key], fmt.Sprint(v))
}
default:
m[key] = append(m[key], fmt.Sprint(e))
}
}
*d = m
default:
return fmt.Errorf("invalid type %T for options", value)
}
return nil
}

View File

@ -143,9 +143,9 @@ type ServiceConfig struct {
}
type ServiceProviderConfig struct {
Type string `yaml:"type,omitempty" json:"driver,omitempty"`
Options Options `yaml:"options,omitempty" json:"options,omitempty"`
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
Type string `yaml:"type,omitempty" json:"driver,omitempty"`
Options MultiOptions `yaml:"options,omitempty" json:"options,omitempty"`
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
}
// MarshalYAML makes ServiceConfig implement yaml.Marshaller

View File

@ -18,6 +18,7 @@ package validation
import (
"fmt"
"net"
"strings"
"github.com/compose-spec/compose-go/v2/tree"
@ -29,6 +30,7 @@ var checks = map[tree.Path]checkerFunc{
"volumes.*": checkVolume,
"configs.*": checkFileObject("file", "environment", "content"),
"secrets.*": checkFileObject("file", "environment"),
"services.*.ports.*": checkIPAddress,
"services.*.develop.watch.*.path": checkPath,
"services.*.deploy.resources.reservations.devices.*": checkDeviceRequest,
"services.*.gpus.*": checkDeviceRequest,
@ -105,3 +107,13 @@ func checkDeviceRequest(value any, p tree.Path) error {
}
return nil
}
func checkIPAddress(value any, p tree.Path) error {
if v, ok := value.(map[string]any); ok {
ip, ok := v["host_ip"]
if ok && net.ParseIP(ip.(string)) == nil {
return fmt.Errorf("%s: invalid ip address: %s", p, ip)
}
}
return nil
}

View File

@ -1,5 +1,5 @@
//go:build !darwin && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows && !zos
// +build !darwin,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows,!zos
//go:build !darwin && !freebsd && !linux && !netbsd && !openbsd && !windows && !zos
// +build !darwin,!freebsd,!linux,!netbsd,!openbsd,!windows,!zos
/*
Copyright The containerd Authors.

View File

@ -31,6 +31,15 @@ func NewPty() (Console, string, error) {
if err != nil {
return nil, "", err
}
return NewPtyFromFile(f)
}
// NewPtyFromFile creates a new pty pair, just like [NewPty] except that the
// provided [os.File] is used as the master rather than automatically creating
// a new master from /dev/ptmx. The ownership of [os.File] is passed to the
// returned [Console], so the caller must be careful to not call Close on the
// underlying file.
func NewPtyFromFile(f File) (Console, string, error) {
slave, err := ptsname(f)
if err != nil {
return nil, "", err

View File

@ -18,7 +18,6 @@ package console
import (
"fmt"
"os"
"golang.org/x/sys/unix"
)
@ -30,12 +29,12 @@ const (
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
func unlockpt(f *os.File) error {
func unlockpt(f File) error {
return unix.IoctlSetPointerInt(int(f.Fd()), unix.TIOCPTYUNLK, 0)
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
func ptsname(f File) (string, error) {
n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCPTYGNAME)
if err != nil {
return "", err

View File

@ -21,7 +21,6 @@ package console
import (
"fmt"
"os"
"golang.org/x/sys/unix"
)
@ -39,7 +38,7 @@ const (
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
func unlockpt(f *os.File) error {
func unlockpt(f File) error {
fd := C.int(f.Fd())
if _, err := C.unlockpt(fd); err != nil {
C.close(fd)
@ -49,7 +48,7 @@ func unlockpt(f *os.File) error {
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
func ptsname(f File) (string, error) {
n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCGPTN)
if err != nil {
return "", err

View File

@ -21,7 +21,6 @@ package console
import (
"fmt"
"os"
"golang.org/x/sys/unix"
)
@ -42,12 +41,12 @@ const (
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
func unlockpt(f *os.File) error {
func unlockpt(f File) error {
panic("unlockpt() support requires cgo.")
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
func ptsname(f File) (string, error) {
n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCGPTN)
if err != nil {
return "", err

View File

@ -18,7 +18,6 @@ package console
import (
"fmt"
"os"
"unsafe"
"golang.org/x/sys/unix"
@ -31,7 +30,7 @@ const (
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
func unlockpt(f *os.File) error {
func unlockpt(f File) error {
var u int32
// XXX do not use unix.IoctlSetPointerInt here, see commit dbd69c59b81.
if _, _, err := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))); err != 0 {
@ -41,7 +40,7 @@ func unlockpt(f *os.File) error {
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
func ptsname(f File) (string, error) {
var u uint32
// XXX do not use unix.IoctlGetInt here, see commit dbd69c59b81.
if _, _, err := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&u))); err != 0 {

View File

@ -18,7 +18,6 @@ package console
import (
"bytes"
"os"
"golang.org/x/sys/unix"
)
@ -31,12 +30,12 @@ const (
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
// This does not exist on NetBSD, it does not allocate controlling terminals on open
func unlockpt(f *os.File) error {
func unlockpt(f File) error {
return nil
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
func ptsname(f File) (string, error) {
ptm, err := unix.IoctlGetPtmget(int(f.Fd()), unix.TIOCPTSNAME)
if err != nil {
return "", err

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