-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
access gateway API from client #533
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just an idea but what if we do not add NewBuild/DeleteBuild
(yet) and just change the current Solve
request to have a behavior that when both frontend and llb definition are unspecified in the request it opens the interactive gateway instead and blocks on waiting for it to return.
|
||
func RunX(ctx context.Context, f client.BuildFunc, client *grpcClient) (retError error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this method of GRPCClient
@@ -66,16 +92,21 @@ func convertRef(ref client.Reference) (string, error) { | |||
return r.id, nil | |||
} | |||
|
|||
func Run(ctx context.Context, f client.BuildFunc) (retError error) { | |||
func Run(ctx context.Context, f client.BuildFunc) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Run/RunFromEnvironment/
"google.golang.org/grpc" | ||
"google.golang.org/grpc/status" | ||
) | ||
|
||
const frontendPrefix = "BUILDKIT_FRONTEND_OPT_" | ||
|
||
func New(ctx context.Context, session, build, product string, c pb.LLBBridgeClient, w []client.WorkerInfo) (*grpcClient, error) { | ||
ctx = buildid.AppendToOutgoingContext(ctx, build) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is better to create a wrapper around pb.LLBBridgeClient
methods for injecting the buildid than to do it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That wrapper would then be in the client
pkg.
Thanks, I'll address the individual comments you made. The proposal you make for a new |
I've made the suggested changes. RE the |
Oh, I forgot the third thing I was going to mention: I had been planning to make a demonstrator for this by doing a clone and hack job on one of the
so maybe I should just fix that TODO (in-place rather than clone and hack style)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT, worth trying that first or shall I go straight to the new "mode" for Solve?
I think reusing the Solve
would remove the duplication from the server side.
There could be something like:
if req.Definition == nil && req.Frontend == "" {
res, err = s.registerGatewayForwarder(ctx, s.Bridge(j)).waitResult(ctx)
if err != nil {
return nil, err
}
}
In the current solver and existing bridge/exporter logic should still work.
Refactoring is still needed to remove the duplication from the client though.
so maybe I should just fix that TODO (in-place rather than clone and hack style)?
I think this TODO is just to switch to tonistiigi/dockerfile
. Its ok to have some duplication in examples initially. We can decide later if any of them becomes obsolete.
frontend/gateway/gateway.go
Outdated
@@ -492,6 +497,10 @@ func (lbf *llbBridgeForwarder) Return(ctx context.Context, in *pb.ReturnRequest) | |||
return &pb.ReturnResponse{}, nil | |||
} | |||
|
|||
func (lbf *llbBridgeForwarder) Result() *frontend.Result { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure error is handled as well. This should probably return a channel that Solve()
can wait on (in the case where there is no DeleteBuild()
), or be a blocking function.
sessionID: session, | ||
workers: w, | ||
product: product, | ||
caps: pb.Caps.CapSet(resp.FrontendAPICaps), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rebase as llbCaps
was added to here last week.
Ack. Will try it. There's a small race since the client will have to call
OK, will clone and hack. |
61a9018
to
75805e1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tonistiigi I've switched over to the new mode for Solve
and it seems to be working well.
I've left some self-review on a few things that I'm not sure about.
client/build.go
Outdated
} | ||
|
||
feOpts := opt.FrontendAttrs | ||
opt.FrontendAttrs = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided it was better to reuse SolveOpt
with the switch to using Solve
instead of Create
/Delete
, it results in this little wrinkle but I think that's tollerable compared with copying every field of a BuildOpt
into a SolveOpt
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is fine atm. We may need to look this over later. Maybe remove separate Solve()
completely and figure out what is the best way to pass args.
client/build.go
Outdated
} | ||
|
||
cb := func(ref string, s *session.Session) error { | ||
g, err := grpcclient.New(ctx, feOpts, s.ID(), "", c.gatewayClient(ref), gworkers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pass product as ""
on the logic that the build
callback is in the process so it surely already knows whatever it needs to know, but maybe this should be an argument to this Build
method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user should pass this in. The default can be "buildkit"
client/build.go
Outdated
if err != nil { | ||
return err | ||
} | ||
// TBD defer teardown |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there isn't anything to do here, will drop this comment.
frontend/gateway/gateway.go
Outdated
@@ -224,15 +224,27 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten | |||
return lbf.result, nil | |||
} | |||
|
|||
func newLLBBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge, workers frontend.WorkerInfos) (*llbBridgeForwarder, error) { | |||
func (lbf *llbBridgeForwarder) Result() (*frontend.Result, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously (comment is now collapsed) you said:
This should probably return a channel that Solve() can wait on (in the case where there is no DeleteBuild()), or be a blocking function.
The channel ended up being in the forwarder (solver/llbsolver/gateway.gatewayForwarder
) (I confess I'd forgotten about your comment). Should I move it or is it ok as I have it now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the channel would be slightly better in the forwarder. If not then this needs some locking and detection if Return()
was called. When this was private it made lots of assumptions when it would be called. Now that it is public we need to protect the cases when it gets called out of place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the channel would be slightly better in the forwarder.
I'm confused because it is in the forwarder right now, but perhaps we are talking about different things/places with that name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, yeah everything is called a forwarder :) . I meant it might be better in here as a method of llbBridgeForwarder
. Maybe we can find a different name for gatewayForwarder
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Proxy" is generally pretty overloaded but not (yet? 😉) in buildkit, so gatewayForwarder
→gatewayServerProxy
perhaps? Or since it wraps an LLBBridgeServer
perhaps LLBBridgeServerProxy
?
For moving the channel I'll change it to be similar to a Context
e.g. a Done() <-chan struct()
and a Result()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it permissible for frontends to call Return
more than once? If so are the semantics to keep the first or last answer?
Currently the code independently keeps the most recent err and result, so you could end up setting both which is probably undesirable...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went with latching the first Return
but easy enough to change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it permissible for frontends to call Return more than once?
Not atm but happy to hear use cases. If we keep the restriction we should probably validate it and produce an error if frontend misbehaves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, I'll modify what I did here to have setResult
error if there is already a result (right now it just logs).
|
||
export := client.caps.Supports(pb.CapReturnResult) == nil | ||
func (c *grpcClient) Run(ctx context.Context, f client.BuildFunc) (retError error) { | ||
// TBD: in a client-side gateway situation if !export and f() returns an error, how to kick the parent `Solve` into exiting? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit unclear on this, although I've moved the call to f
later such that the if export defer
stuff runs even if it fails I'm not convinced that is necessary -- the return from f
would cause the surrounding errgroup
to cancel which ought to cancel the other goroutine doing the Solve
-- but I failed to trace the code far enough to confirm that cancelling the context would really cancel the gRPC.
Also the lack of similar handling in the !export
case concerns me. Not sure what I should do there (depending onthe above, perhaps the answer is "nothing").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The export
is never false in client gateway situation as client-gateway
is (will be) implemented later than CapReturnResult
that is already merged.
On the client side though, when this function fails. I think we should wait(maybe with timeout) for the main solve to error as well instead of returning early.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, so if we can rely on c.client.Return
always being called (which it is now I've moved the call after the defer
) then I think we can squash retError
to nil
in the defer, (unless c.client.Return
itself fails, we should propagate that error still).
The call to c.client.Return
will cause the main Solve
return with that error as the overall result, instead of the errgroup
propagating the error from f
.
I'm not 100% sure on the impact in the regular frontend/gateway case though, seems like the same overall logic should apply?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can squash retError to nil in the defer, (unless c.client.Return itself fails, we should propagate that error still).
No, we should still return it here. For example, a frontend should use it to return an error code as well. We can skip it in the client if we have the main error instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have cb
in Client.Build
capture any err
but still return nil
? Then we take the result of c.solve
and if it is an err use that, but if it is not an error use the err
capturerd from the cb
instead? Something like:
var cbErr
cb := func(ref string, s *session.Session) error {
g, cbErr := grpcclient.New(ctx, feOpts, s.ID(), product, c.gatewayClientForBuild(ref), gworkers)
if cbErr != nil {
return nil
}
cbErr = g.Run(ctx, buildFunc)
return nil
}
if err := c.solve(ctx, nil, cb, opt, statusChan); err != nil {
return err
}
return cbErr
???
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wasn't quite right, I've pushed a commit along these lines though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make this slightly safer we could use a timeout. So if cb errors, and 5 seconds passes it will cancel the main context. This code can also be in to solve
itself, not handled by the closure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I left it in a separate commit for now.
frontend/gateway/pb/caps.go
Outdated
@@ -15,6 +15,7 @@ const ( | |||
CapReadFile apicaps.CapID = "readfile" | |||
CapReturnResult apicaps.CapID = "return" | |||
CapReturnMap apicaps.CapID = "returnmap" | |||
//TBD CapClientGateway? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is probably needed, an older buildkitd
would successfully return an empty Result
if Solve
was called by a new client with def
and frontend
both empty, I think the client needs return an error if this is attempted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As client gateway can only be used from the controlAPI
can we make it so that gateway calls to solve continue to return an empty result. I think this is already the case, but verify. Then no cap is needed as control API is not covered by caps at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make it so that gateway calls to solve continue to return an empty result
Under which circumstances?
Also not sure if by "gateway calls to solve" you mean calls to controlapi.Solve
with def == nil && frontend == nil
or when the callback given to controlapi.Build
calls the gateway/client.Client.Solve
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The latter. When frontend (or this new logic) calls Solve()
through the gateway grpc api.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add a test case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It panics
on a nil deref at the range creq.Definition.Metadata
at the top of grpcClient.Solve
. Which I suspect a frontend
could hit today. I'll add the surrounding check along with the test of that code path via this new logic.
Is there scaffolding for integration testing the APIs available to a proper frontend somewhere I'm failing to find? I think that might be a bit hard to arrange (perhaps it would involve gateway-devel
somehow) but testing via this new mode should suffice for lots of the interesting cases I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the integration tests are lacking that atm. First thing we could do is add a gateway switch to dockerfile tests similarly to the current worker ones. So these tests run using builtin frontend, using the dockerfile pkg directly through client gw and using the master build gateway image. After that we can add something that builds the gateway image from source and tests it directly.
For direct testing of the gateway API though client we can add regular new integration tests under client like you have already started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So these tests run using builtin frontend, using the dockerfile pkg directly through client gw and using the master build gateway image
So we'd be looking at adding another dimension similar to the code in util/testutil/integration/run.go
(which runs a set of tests on a range of workers) to frontend/dockerfile/dockerfile_test.go
, except this time iterating over "modes". I'll have a dig.
I'm going to nuke the
//TBD CapClientGateway?
comment this thread is derived from (the topic has skewed a bit), which sadly means this will all get collapsed...
solver/llbsolver/gateway.go
Outdated
gwf.mu.Lock() | ||
defer gwf.mu.Unlock() | ||
|
||
// TBD cancel things, cleanup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure that there is anything required here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sgtm
client/build.go
Outdated
opt.FrontendAttrs = nil | ||
|
||
cb := func(ref string, s *session.Session) error { | ||
g, err := grpcclient.New(ctx, feOpts, s.ID(), "productTBD", c.gatewayClient(ref), nil /*TBD: worker info*/) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
product should be passed in with opt and default to buildkit
client/build.go
Outdated
|
||
type gatewayClient struct { | ||
gateway gatewayapi.LLBBridgeClient | ||
buildid string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
buildID
client/build.go
Outdated
return &gatewayClient{g, buildid} | ||
} | ||
|
||
type gatewayClient struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gatewayClientForBuild
client/build.go
Outdated
} | ||
// TBD defer teardown | ||
|
||
if err := g.Run(ctx, build); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment for later cleanup. Lets try to avoid generic build
where possible as it can mean anything. BuildFunc
is fine
client/build.go
Outdated
} | ||
|
||
cb := func(ref string, s *session.Session) error { | ||
g, err := grpcclient.New(ctx, feOpts, s.ID(), "", c.gatewayClient(ref), gworkers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user should pass this in. The default can be "buildkit"
frontend/gateway/gateway.go
Outdated
@@ -224,15 +224,27 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten | |||
return lbf.result, nil | |||
} | |||
|
|||
func newLLBBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge, workers frontend.WorkerInfos) (*llbBridgeForwarder, error) { | |||
func (lbf *llbBridgeForwarder) Result() (*frontend.Result, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the channel would be slightly better in the forwarder. If not then this needs some locking and detection if Return()
was called. When this was private it made lots of assumptions when it would be called. Now that it is public we need to protect the cases when it gets called out of place.
solver/llbsolver/gateway.go
Outdated
gwf.mu.Lock() | ||
defer gwf.mu.Unlock() | ||
|
||
// TBD cancel things, cleanup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sgtm
solver/llbsolver/solver.go
Outdated
s.solver = solver.NewSolver(solver.SolverOpt{ | ||
ResolveOpFunc: s.resolver(), | ||
DefaultCache: cache, | ||
}) | ||
return s, nil | ||
} | ||
|
||
func (s *Solver) RegisterGatewayForwarder(server *grpc.Server) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks bit weird construction. I'd suggest controller makes the gateway forwarder(no parameters) and registers the server. Then on Register
, solver creates gateway.NewBridgeForwarder
and registers the ID with the bridgeforwarder. Now the lookup is purely a map lookup (with the race handling) based on ID and never calls to the solver or no need to Finalize()
the job.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is the one time start of day registration of the gRPC endpoint. Sounds like you are mostly talking about the s.gatewayForwarder.RegisterJob
stuff in Solver.Solve
though which dynamically adds the Job
(and hence the NewBridgeForwarder
) to the map though?
The issue is that on the client side the calls to Solve
and the use of the gateway
happen in parallel with no interlock. We cannot guarantee we have got as far as registering the forwarder before the gateway client makes its first RPC. So I was reusing the existing code in the job code which already knows how to wait for a job to be registered. Sound like you would instead prefer to duplicate the mechanism from Solver.Get()
into gatewayForwarder.lookupJob
(suitably renamed)?
I'll do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like you are mostly talking about the s.gatewayForwarder.RegisterJob
Yes, it was confusing. When "registers the server" I meant this function and with Register
I meant gatewayForwarder.RegisterJob
Sound like you would instead prefer to duplicate the mechanism from Solver.Get() into gatewayForwarder.lookupJob (suitably renamed)?
Yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case it wasn't clear I also expected this RegisterGatewayForwarder
to be replaced by passing in the gatewayforwarder to the solver constructor. Looking at gatewayforwarder, it doesn't seem to use any private stuff from solver anymore so doesn't need to be in same pkg as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've pulled the setup into NewController
and moved the code to control/gateway
. Let me know if you would rather I pulled this all the way back to cmd/buildkitd/main.go
(e.g. the caller of NewController
) and/or if you have another preference for where the code live (I'm not super keen on my choice but couldn't really decide on anything better).
frontend/gateway/pb/caps.go
Outdated
@@ -15,6 +15,7 @@ const ( | |||
CapReadFile apicaps.CapID = "readfile" | |||
CapReturnResult apicaps.CapID = "return" | |||
CapReturnMap apicaps.CapID = "returnmap" | |||
//TBD CapClientGateway? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As client gateway can only be used from the controlAPI
can we make it so that gateway calls to solve continue to return an empty result. I think this is already the case, but verify. Then no cap is needed as control API is not covered by caps at the moment.
|
||
export := client.caps.Supports(pb.CapReturnResult) == nil | ||
func (c *grpcClient) Run(ctx context.Context, f client.BuildFunc) (retError error) { | ||
// TBD: in a client-side gateway situation if !export and f() returns an error, how to kick the parent `Solve` into exiting? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The export
is never false in client gateway situation as client-gateway
is (will be) implemented later than CapReturnResult
that is already merged.
On the client side though, when this function fails. I think we should wait(maybe with timeout) for the main solve to error as well instead of returning early.
1987f2e
to
7fc8ee2
Compare
8dcd6b5
to
50140d0
Compare
@tonistiigi what is your opinion of next steps here? Do we want to wait for #552 to get into shape first? I think the review so far has been addressed (hopefully I didn't miss anything). I guess I should rebase/squash the final 3 commits back into the first one. |
@ijc If the gateway itself has integration tests then I don't think Dockerfile is a blocker. Just remove the wip after it is ready for review for your. |
Provide a `New` method and a new `Run` method on the `grpcClient` to allow manual creation of a client in addition to the current ability to initialise from the environment. Accordingly the existing `Run` method becomes `RunFromEnvironment`. Signed-off-by: Ian Campbell <ijc@docker.com>
f1e28db
to
60ce91c
Compare
@tonistiigi Thanks. I did a pass through the PR and tidied up a bunch of things here and there, rebased, squashed (while also pulling out some standalone fixes) and updated commit messages etc. Will drop the WIP. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looking good
cc @AkihiroSuda
client/build.go
Outdated
} | ||
|
||
if product == "" { | ||
product = "buildkit" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
apicaps.ExportedProduct
client/solve.go
Outdated
return nil | ||
} | ||
|
||
// If the callback failed then we wait up to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment that normally error in here should automatically cause the Solve()
to error as well. This is a fallback.
client/solve.go
Outdated
|
||
type buildCB func(ref string, s *session.Session) error | ||
|
||
func (c *Client) solve(ctx context.Context, def *llb.Definition, build buildCB, opt SolveOpt, statusChan chan *SolveStatus) (*SolveResponse, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe s/build/runGateway/
ch := make(chan *client.SolveStatus) | ||
eg, ctx := errgroup.WithContext(ctx) | ||
eg.Go(func() error { | ||
_, err := c.Build(ctx, *buildOpt, "", dockerfile.Build, ch) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know I said before that duplication doesn't matter in here but as it turned out that the changes from the other file are really minimal we can probably just switch this behavior with an environment variable. This is low priority and can be follow-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was easy, so I went ahead and recombined them.
|
||
c := pb.NewLLBBridgeClient(conn) | ||
|
||
func New(ctx context.Context, opts map[string]string, session, product string, c pb.LLBBridgeClient, w []client.WorkerInfo) (*grpcClient, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return client.Client
from a public func
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
client.Client
lacks the Run
method which both callers want to use. Run
needs to dig inside the grpcClient
so I can't just make this a global method which takes a client.Client
.
In fact the callers only care about the Run
method and none of the client.Client
methods, the latter are only needed when Run
calls the provided callbacks.
I'm going to try adding a GrpcClient
interface with just the Run
method, see how that looks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't look too bad to me, but I'll leave it as a separate commit for now so you can cast judgement on it.
solver/llbsolver/solver.go
Outdated
s.solver = solver.NewSolver(solver.SolverOpt{ | ||
ResolveOpFunc: s.resolver(), | ||
DefaultCache: cache, | ||
}) | ||
return s, nil | ||
} | ||
|
||
func (s *Solver) RegisterGatewayForwarder(server *grpc.Server) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case it wasn't clear I also expected this RegisterGatewayForwarder
to be replaced by passing in the gatewayforwarder to the solver constructor. Looking at gatewayforwarder, it doesn't seem to use any private stuff from solver anymore so doesn't need to be in same pkg as well.
Returning an interface rather than a private-struct from a public interface is good practice. Signed-off-by: Ian Campbell <ijc@docker.com>
…e cases. Refactor the setting/getting of the result for a `llbBridgeForwarder` in order to check that `Return` is only called once and only with the correct options. Signed-off-by: Ian Campbell <ijc@docker.com>
Avoids panicing when accessing `creq.Definition.Metadata`. Signed-off-by: Ian Campbell <ijc@docker.com>
Reorder the code slightly so that an error returned by the call to the `BuildFunc` is picked up by the `defer`d error handling. Signed-off-by: Ian Campbell <ijc@docker.com>
2b47a8e
to
ccdc030
Compare
cli.BoolFlag{ | ||
Name: "clientside-frontend", | ||
Usage: "run dockerfile frontend client side, rather than builtin to buildkitd", | ||
EnvVar: "CLIENTSIDE_FRONTEND", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BUILDKIT_CLIENTSIDE_FRONTEND?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (can't approve in github UI as I created the PR)
control/control.go
Outdated
} | ||
|
||
func NewController(opt Opt) (*Controller, error) { | ||
cache := solver.NewCacheManager("local", opt.CacheKeyStorage, worker.NewCacheResultStorage(opt.WorkerController)) | ||
|
||
solver, err := llbsolver.New(opt.WorkerController, opt.Frontends, cache, opt.ResolveCacheImporterFunc) | ||
gatewayForwarder := controlGateway.NewGatewayForwarder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
capitalization in a pkg name is weird
This allows builder code to be written which can be built as either a gateway container or in a purely client side configuration, giving implementors more flexibility. Now when `Solve` sees a request with neither a definition nor a frontend specified it will make the job available via new LLBBridge endpoints on the control socket which the client can then use. These end points require the job id to be present in the gRPC metadata and a client side object is added to facilitate this. The `llbBridgeForwarder` type is now exposed as a public `interface LLBBridgeForwarder` which satisfies the underlying gRPC server interface (`pb.LLBBridgeServer`) as well as a new `Done()` & `Result()` pair which can be used to wait for the client to call `Return()` (using a model similar to `context.Context`). Signed-off-by: Ian Campbell <ijc@docker.com>
Adds an option/envvar to `examples/build-using-dockerfile` which uses a client side instance of the dockerfile frontend. Signed-off-by: Ian Campbell <ijc@docker.com>
Addressed the example envvar name and the capitalisation of the controlGateway import. I also folded the |
opening to leave some comments to @ijc
ref #472