diff --git a/dagger/client.go b/dagger/client.go index 3635301a..5b3674d3 100644 --- a/dagger/client.go +++ b/dagger/client.go @@ -113,7 +113,9 @@ func (c *Client) buildfn(ctx context.Context, deployment *Deployment, fn ClientD Msg("spawning buildkit job") resp, err := c.c.Build(ctx, opts, "", func(ctx context.Context, gw bkgw.Client) (*bkgw.Result, error) { - s := NewSolver(c.c, gw, ch, c.noCache) + // buildkit auth provider (registry) + auth := newRegistryAuthProvider() + s := NewSolver(c.c, gw, ch, auth, c.noCache) lg.Debug().Msg("loading configuration") if err := deployment.LoadPlan(ctx, s); err != nil { diff --git a/dagger/pipeline.go b/dagger/pipeline.go index f0ca2999..12e178ef 100644 --- a/dagger/pipeline.go +++ b/dagger/pipeline.go @@ -197,6 +197,8 @@ func (p *Pipeline) doOp(ctx context.Context, op *compiler.Value, st llb.State) ( return p.Exec(ctx, op, st) case "export": return p.Export(ctx, op, st) + case "docker-login": + return p.DockerLogin(ctx, op, st) case "fetch-container": return p.FetchContainer(ctx, op, st) case "push-container": @@ -559,6 +561,32 @@ func (p *Pipeline) Load(ctx context.Context, op *compiler.Value, st llb.State) ( return from.State(), nil } +func (p *Pipeline) DockerLogin(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) { + username, err := op.Lookup("username").String() + if err != nil { + return st, err + } + + secret, err := op.Lookup("secret").String() + if err != nil { + return st, err + } + + target, err := op.Lookup("target").String() + if err != nil { + return st, err + } + + p.s.auth.AddCredentials(target, username, secret) + log. + Ctx(ctx). + Debug(). + Str("target", target). + Msg("docker login to registry") + + return st, nil +} + func (p *Pipeline) FetchContainer(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) { rawRef, err := op.Lookup("ref").String() if err != nil { diff --git a/dagger/registryauth.go b/dagger/registryauth.go new file mode 100644 index 00000000..5acd618a --- /dev/null +++ b/dagger/registryauth.go @@ -0,0 +1,86 @@ +package dagger + +import ( + "context" + "net/url" + "strings" + "sync" + + bkauth "github.com/moby/buildkit/session/auth" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// registryAuthProvider is a buildkit provider for registry authentication +// Adapted from: https://github.com/moby/buildkit/blob/master/session/auth/authprovider/authprovider.go +type registryAuthProvider struct { + credentials map[string]*bkauth.CredentialsResponse + m sync.RWMutex +} + +func newRegistryAuthProvider() *registryAuthProvider { + return ®istryAuthProvider{ + credentials: map[string]*bkauth.CredentialsResponse{}, + } +} + +func (a *registryAuthProvider) AddCredentials(target, username, secret string) { + a.m.Lock() + defer a.m.Unlock() + + a.credentials[target] = &bkauth.CredentialsResponse{ + Username: username, + Secret: secret, + } +} + +func (a *registryAuthProvider) Register(server *grpc.Server) { + bkauth.RegisterAuthServer(server, a) +} + +func (a *registryAuthProvider) Credentials(ctx context.Context, req *bkauth.CredentialsRequest) (*bkauth.CredentialsResponse, error) { + reqURL, err := parseAuthHost(req.Host) + if err != nil { + return nil, err + } + + a.m.RLock() + defer a.m.RUnlock() + + for authHost, auth := range a.credentials { + u, err := parseAuthHost(authHost) + if err != nil { + return nil, err + } + + if u.Host == reqURL.Host { + return auth, nil + } + } + + return &bkauth.CredentialsResponse{}, nil +} + +func parseAuthHost(host string) (*url.URL, error) { + if host == "registry-1.docker.io" { + host = "https://index.docker.io/v1/" + } + + if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") { + host = "https://" + host + } + return url.Parse(host) +} + +func (a *registryAuthProvider) FetchToken(ctx context.Context, req *bkauth.FetchTokenRequest) (rr *bkauth.FetchTokenResponse, err error) { + return nil, status.Errorf(codes.Unavailable, "client side tokens not implemented") +} + +func (a *registryAuthProvider) GetTokenAuthority(ctx context.Context, req *bkauth.GetTokenAuthorityRequest) (*bkauth.GetTokenAuthorityResponse, error) { + return nil, status.Errorf(codes.Unavailable, "client side tokens not implemented") +} + +func (a *registryAuthProvider) VerifyTokenAuthority(ctx context.Context, req *bkauth.VerifyTokenAuthorityRequest) (*bkauth.VerifyTokenAuthorityResponse, error) { + return nil, status.Errorf(codes.Unavailable, "client side tokens not implemented") +} diff --git a/dagger/solver.go b/dagger/solver.go index 3a78c0c1..e057155b 100644 --- a/dagger/solver.go +++ b/dagger/solver.go @@ -13,7 +13,6 @@ import ( "github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb" bkgw "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/session" - "github.com/moby/buildkit/session/auth/authprovider" bkpb "github.com/moby/buildkit/solver/pb" "github.com/opencontainers/go-digest" "github.com/rs/zerolog/log" @@ -23,14 +22,16 @@ type Solver struct { events chan *bk.SolveStatus control *bk.Client gw bkgw.Client + auth *registryAuthProvider noCache bool } -func NewSolver(control *bk.Client, gw bkgw.Client, events chan *bk.SolveStatus, noCache bool) Solver { +func NewSolver(control *bk.Client, gw bkgw.Client, events chan *bk.SolveStatus, auth *registryAuthProvider, noCache bool) Solver { return Solver{ events: events, control: control, gw: gw, + auth: auth, noCache: noCache, } } @@ -148,9 +149,7 @@ func (s Solver) Export(ctx context.Context, st llb.State, img *dockerfile2llb.Im opts := bk.SolveOpt{ Exports: []bk.ExportEntry{output}, - Session: []session.Attachable{ - authprovider.NewDockerAuthProvider(log.Ctx(ctx)), - }, + Session: []session.Attachable{s.auth}, } ch := make(chan *bk.SolveStatus)