Skip to content

New gateway exporter#6393

Open
jedevc wants to merge 8 commits intomoby:masterfrom
jedevc:gateway-exporter
Open

New gateway exporter#6393
jedevc wants to merge 8 commits intomoby:masterfrom
jedevc:gateway-exporter

Conversation

@jedevc
Copy link
Member

@jedevc jedevc commented Dec 2, 2025

Fixes #3037.

Follow-ups:

  • Secret passing. This PR is already going to be large, and adding support for secrets also intersects with secrets for cache exporter backends, so it's not super trivial to just add on.

Comment on lines 79 to 106
var foundFiles []string
var foundDescs []ocispecs.Descriptor
export := func(ctx context.Context, c gateway.Client, conn *grpc.ClientConn, _ exptypes.ExporterTarget, result *gateway.Result) error {
entries, err := result.Ref.ReadDir(ctx, gateway.ReadDirRequest{Path: "/"})
if err != nil {
return err
}
for _, entry := range entries {
foundFiles = append(foundFiles, entry.Path)
}

store := contentproxy.NewContentStore(conn)
descs, err := result.Ref.GetRemote(ctx)
if err != nil {
return err
}
foundDescs = filterAvailableDescriptors(ctx, store, descs)

return nil
}

_, err = c.BuildExport(sb.Context(), client.SolveOpt{}, "", frontend, export, nil)
require.NoError(t, err)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a fun interface to me - allowing the client to easily do its own local export. This actually works entirely without any server-side changes - it's just calling the exporter function right after a build, using the same gateway.

We have access to the entire buildkit content store (so there's no isolation here, but this is client-trusted code).

Comment on lines 441 to 557
func buildSampleExporter(ctx context.Context, c *client.Client, dest string) error {
gatewayDir, err := fsutil.NewFS(integration.BuildkitSourcePath)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't work out a good test strategy that doesn't involve doing this 😢

To actually test the gateway exporter codepath + the isolation it provides, we actually need to build one so we can test it. It feels weird to do this in a test (as @tonistiigi points out in #3633 (comment)). But not sure what the alternative is 🤷 Some tests in exporter_test.go might not need it, but could otherwise just build it in bake, and connect it in?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I seem to remember writing this code before - but can't remember when.

@tonistiigi is there another implementation of this that you're aware of?

return exporter.NewConfig()
}

func (e *gatewayExporterInstance) Export(ctx context.Context, llbBridge frontend.FrontendLLBBridge, exec executor.Executor, src *exporter.Source, inlineCache exptypes.InlineCache, sessionID string) (_ map[string]string, descref exporter.DescriptorReference, err error) {
Copy link
Member Author

@jedevc jedevc Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the gateway exporter allow returning an arbitrary exporter response map? I haven't added that yet.

Seems like it would be nice to allow it:

  • Could either reuse the existing Return gateway API
    • New exporter response field
    • Use the result metadata somehow
  • Or create a new API (hoping to avoid this)

Comment on lines +261 to +262
// Target indicates the target type of the exporter
ExporterTarget Target = 3;
Copy link
Member Author

@jedevc jedevc Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is required to support the new file/dir keys (pointing out, since I didn't realize this in #3037 (comment)).

Without something like this, the exporter can't actually know what type was attached (since we strip out the attribute itself to avoid leaking it to the server).

@github-actions github-actions bot added area/dependencies Pull requests that update a dependency file area/ci area/docs labels Dec 5, 2025
@jedevc jedevc force-pushed the gateway-exporter branch 2 times, most recently from 45e4d38 to a930686 Compare December 5, 2025 10:50
@github-actions github-actions bot removed area/hack building buildkit itself area/dependencies Pull requests that update a dependency file area/ci area/docs labels Dec 5, 2025
@github-actions github-actions bot added area/hack building buildkit itself area/ci labels Dec 5, 2025
@jedevc
Copy link
Member Author

jedevc commented Dec 5, 2025

Getting really close now, the only things I need to get done are:

  • Tests 🎉
  • Some refactorings of code that got that little bit more complicated
  • Authoring a real commit history 👀

Edit: done! This PR is now ready for review.

Signed-off-by: Justin Chadwell <me@jedevc.com>
This allows the client to be explicit as to what client-side inputs the
client has provided (instead of implying this through the use of
exporter attributes).

Signed-off-by: Justin Chadwell <me@jedevc.com>
Additionally, simplify some of the result loading/stashing logic into
helpers.

Signed-off-by: Justin Chadwell <me@jedevc.com>
We'll need this in the exporter package, so break it up a little bit so
it can be used there later.

Signed-off-by: Justin Chadwell <me@jedevc.com>
This wasn't previously used *at all*. However, now, we need to be able
to do this, since exporters will speak the gateway api, and we want to
be able to access provenance attestations.

Signed-off-by: Justin Chadwell <me@jedevc.com>
Signed-off-by: Justin Chadwell <me@jedevc.com>
Signed-off-by: Justin Chadwell <me@jedevc.com>
Signed-off-by: Justin Chadwell <me@jedevc.com>
@jedevc jedevc marked this pull request as ready for review December 8, 2025 17:01
@jedevc jedevc requested a review from tonistiigi December 8, 2025 17:02
@WYGIN
Copy link

WYGIN commented Dec 9, 2025

Will it be a breaking change? How can I figure out if a buildkit demon support BuildFromEnvironment or ExportFromEnvironment supported As a gateway frontend?

@jedevc
Copy link
Member Author

jedevc commented Dec 9, 2025

This will not be a breaking change.

ExportFromEnvironment will only be callable when invoked from --output type=gateway,source=<image>. We should probably add a sanity check that it's running in the right environment though - so that if you try and put a frontend image instead of a exporter image, you get a sane error.

@thompson-shaun thompson-shaun added this to the v0.27.0 milestone Jan 8, 2026
@crazy-max crazy-max modified the milestones: v0.27.0, v0.28.0 Jan 9, 2026
@tonistiigi
Copy link
Member

tonistiigi commented Jan 14, 2026

We discussed this quite a lot with @jedevc before the holidays. Putting some summary in here.

To fit this better with the BuildKit provenance and tracking/security guarantees, it would be better to model this more like a post-process step, rather than at the same level as built-in/trusted exporters. In something like buildx it could still be exposed in --output array if that makes sense.

The difference/logic is that:

  • Build runs and returns a result
  • This result is then passed to the postprocessor chain. Postprocessor is a similar component to the frontend based on the gateway API. It can take inputs in the same way, and it returns one result if complete. Postprocessor runs in frontend-like sandbox environment.
  • If the postprocessor is for generating new file formats (eg. ISO) then it converts input via LLB commands and returns the result of that conversion. BuildKit would solve the additional LLB as a regular build (this can all be lazy per BuildKit solver semantics). The final result, including the conversion steps, is captured by BuildKit provenance. If the build specifies any built-in exports, then these now get the converted result. So -o out/, the out dir would now contain the iso instead of the original build result. The postprocessor itself does not contain a "mini version" of the capabilities of the local exporter and does not do file transfers on the client session. Any builtin exporter can be used after postprocessor. Technically, this is almost identical to chaining targets in buildx bake, just put into a form that promotes reusable exporter components instead of needing to redefine the exact conversion commands for each project.
  • If the postprocessor is for doing some kind of deployment action, then it can do its thing and just return the input ref back as a result. Then the build output does not change, and provenance would just report that the build also called a postprocessor command on a specific image.

This isn't too different from the current solution, and somewhat simpler actually. The postprocessor is very similar to the frontend.

Things that are still possible in this model, but I think could be left out of the first PR and updated later:

  • Blob access after evaluating ref (current gateway API only has file access)
  • Postprocessor steps should have access to Build secrets via their own Secrets namespace so that they don't access build secrets (outside of LLB) and build steps can't see deployment secrets.
  • After this is all complete, we can see if this could be reused for some of the current rewrite-timestamps and SBOM logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exporter plugin (like frontend but for exporters)

5 participants