Merge pull request #373 from samalba/docker-login

Docker login
This commit is contained in:
Andrea Luzzardi 2021-04-27 10:35:44 -07:00 committed by GitHub
commit 46aa2877ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 165 additions and 30 deletions

View File

@ -19,6 +19,7 @@ import (
_ "github.com/moby/buildkit/client/connhelper/dockercontainer" // import the container connection driver _ "github.com/moby/buildkit/client/connhelper/dockercontainer" // import the container connection driver
"github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/client/llb"
bkgw "github.com/moby/buildkit/frontend/gateway/client" bkgw "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/session"
// docker output // docker output
"dagger.io/go/pkg/buildkitd" "dagger.io/go/pkg/buildkitd"
@ -101,9 +102,13 @@ func (c *Client) buildfn(ctx context.Context, deployment *Deployment, fn ClientD
localdirs[label] = abs localdirs[label] = abs
} }
// buildkit auth provider (registry)
auth := newRegistryAuthProvider()
// Setup solve options // Setup solve options
opts := bk.SolveOpt{ opts := bk.SolveOpt{
LocalDirs: localdirs, LocalDirs: localdirs,
Session: []session.Attachable{auth},
} }
// Call buildkit solver // Call buildkit solver
@ -113,7 +118,7 @@ func (c *Client) buildfn(ctx context.Context, deployment *Deployment, fn ClientD
Msg("spawning buildkit job") Msg("spawning buildkit job")
resp, err := c.c.Build(ctx, opts, "", func(ctx context.Context, gw bkgw.Client) (*bkgw.Result, error) { 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) s := NewSolver(c.c, gw, ch, auth, c.noCache)
lg.Debug().Msg("loading configuration") lg.Debug().Msg("loading configuration")
if err := deployment.LoadPlan(ctx, s); err != nil { if err := deployment.LoadPlan(ctx, s); err != nil {

View File

@ -197,6 +197,8 @@ func (p *Pipeline) doOp(ctx context.Context, op *compiler.Value, st llb.State) (
return p.Exec(ctx, op, st) return p.Exec(ctx, op, st)
case "export": case "export":
return p.Export(ctx, op, st) return p.Export(ctx, op, st)
case "docker-login":
return p.DockerLogin(ctx, op, st)
case "fetch-container": case "fetch-container":
return p.FetchContainer(ctx, op, st) return p.FetchContainer(ctx, op, st)
case "push-container": case "push-container":
@ -559,6 +561,32 @@ func (p *Pipeline) Load(ctx context.Context, op *compiler.Value, st llb.State) (
return from.State(), nil 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) { func (p *Pipeline) FetchContainer(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
rawRef, err := op.Lookup("ref").String() rawRef, err := op.Lookup("ref").String()
if err != nil { if err != nil {

86
dagger/registryauth.go Normal file
View File

@ -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 &registryAuthProvider{
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")
}

View File

@ -13,7 +13,6 @@ import (
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb" "github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
bkgw "github.com/moby/buildkit/frontend/gateway/client" bkgw "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/session" "github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/auth/authprovider"
bkpb "github.com/moby/buildkit/solver/pb" bkpb "github.com/moby/buildkit/solver/pb"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -23,14 +22,16 @@ type Solver struct {
events chan *bk.SolveStatus events chan *bk.SolveStatus
control *bk.Client control *bk.Client
gw bkgw.Client gw bkgw.Client
auth *registryAuthProvider
noCache bool 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{ return Solver{
events: events, events: events,
control: control, control: control,
gw: gw, gw: gw,
auth: auth,
noCache: noCache, noCache: noCache,
} }
} }
@ -148,9 +149,7 @@ func (s Solver) Export(ctx context.Context, st llb.State, img *dockerfile2llb.Im
opts := bk.SolveOpt{ opts := bk.SolveOpt{
Exports: []bk.ExportEntry{output}, Exports: []bk.ExportEntry{output},
Session: []session.Attachable{ Session: []session.Attachable{s.auth},
authprovider.NewDockerAuthProvider(log.Ctx(ctx)),
},
} }
ch := make(chan *bk.SolveStatus) ch := make(chan *bk.SolveStatus)

View File

@ -17,12 +17,9 @@ import (
awsConfig: aws.#Config awsConfig: aws.#Config
buildArgs: [string]: string buildArgs: [string]: string
pushTarget: "\(repository):\(tag)"
// Use these credentials to push // Use these credentials to push
ecrCreds: ecr.#Credentials & { ecrCreds: ecr.#Credentials & {
config: awsConfig config: awsConfig
target: pushTarget
} }
ref: { ref: {
@ -37,9 +34,15 @@ import (
} }
buildArg: buildArgs buildArg: buildArgs
}, },
// Login to Registry
op.#DockerLogin & {
target: repository
username: ecrCreds.username
secret: ecrCreds.secret
},
// Push the image to the registry // Push the image to the registry
op.#PushContainer & { op.#PushContainer & {
ref: pushTarget ref: "\(repository):\(tag)"
}, },
op.#Export & { op.#Export & {
source: "/dagger/image_ref" source: "/dagger/image_ref"

1
go.mod
View File

@ -29,6 +29,7 @@ require (
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1
google.golang.org/grpc v1.29.1
gopkg.in/yaml.v3 v3.0.0-20210107172259-749611fa9fcc gopkg.in/yaml.v3 v3.0.0-20210107172259-749611fa9fcc
) )

1
go.sum
View File

@ -305,7 +305,6 @@ github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r
github.com/docker/docker v17.12.0-ce-rc1.0.20200730172259-9f28837c1d93+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v17.12.0-ce-rc1.0.20200730172259-9f28837c1d93+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.0-beta1.0.20201110211921-af34b94a78a1+incompatible h1:J2OhsbfqoBRRT048iD/tqXBvEQWQATQ8vew6LqQmDSU= github.com/docker/docker v20.10.0-beta1.0.20201110211921-af34b94a78a1+incompatible h1:J2OhsbfqoBRRT048iD/tqXBvEQWQATQ8vew6LqQmDSU=
github.com/docker/docker v20.10.0-beta1.0.20201110211921-af34b94a78a1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.0-beta1.0.20201110211921-af34b94a78a1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ=
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=

View File

@ -7,30 +7,21 @@ import (
// Credentials retriever for ECR // Credentials retriever for ECR
#Credentials: { #Credentials: {
// AWS Config // AWS Config
config: aws.#Config config: aws.#Config
// Target is the ECR image
target: string
out: dagger.#Secret out: dagger.#Secret
// ECR credentials // ECR credentials
credentials: dagger.#RegistryCredentials & { username: "AWS"
username: "AWS" secret: out
secret: out
}
aws.#Script & { aws.#Script & {
always: true
"config": config "config": config
export: "/out" export: "/out"
code: """ code: """
aws ecr get-login-password > /out aws ecr get-login-password > /out
""" """
} }
// Authentication for ECR Registries
auth: dagger.#RegistryAuth
auth: "\(target)": credentials
} }

View File

@ -51,6 +51,14 @@ package op
mount: [string]: "tmpfs" | "cache" | {from: _, path: string | *"/"} mount: [string]: "tmpfs" | "cache" | {from: _, path: string | *"/"}
} }
#DockerLogin: {
do: "docker-login"
target: string | *"https://index.docker.io/v1/"
username: string
// FIXME: should be a #Secret (circular import)
secret: string | bytes
}
#FetchContainer: { #FetchContainer: {
do: "fetch-container" do: "fetch-container"
ref: string ref: string

View File

@ -84,6 +84,11 @@ setup() {
@test "op.#PushContainer" { @test "op.#PushContainer" {
skip_unless_secrets_available "$TESTDIR"/ops/push-container/inputs.yaml skip_unless_secrets_available "$TESTDIR"/ops/push-container/inputs.yaml
# ensure the tests fail without credentials
run "$DAGGER" compute "$TESTDIR"/ops/push-container/valid
assert_failure
# check that they succeed with the credentials
run "$DAGGER" compute --input-yaml "$TESTDIR"/ops/push-container/inputs.yaml "$TESTDIR"/ops/push-container run "$DAGGER" compute --input-yaml "$TESTDIR"/ops/push-container/inputs.yaml "$TESTDIR"/ops/push-container
assert_success assert_success
} }

View File

@ -1,16 +1,17 @@
registry: registry:
username: ENC[AES256_GCM,data:8AH6p9WHidanCA==,iv:ezThCQJv+bVBf8SdfSa2HFoP+eu6IZMPl5xvMOGDcps=,tag:mzR7xTKeQNDvkyd2Dm3AKw==,type:str] username: ENC[AES256_GCM,data:8AH6p9WHidanCA==,iv:ezThCQJv+bVBf8SdfSa2HFoP+eu6IZMPl5xvMOGDcps=,tag:mzR7xTKeQNDvkyd2Dm3AKw==,type:str]
token: ENC[AES256_GCM,data:68d31b3EfnQJofIt6j+iBCtDyLOBWjFqvVmejyDjIOh8oBXP,iv:PMghC2nd7jqAzrQzm/PW1YdbE0VAbEBkK0/Ri1WwduI=,tag:0JH4WbcJHvgzF4VIK4deBg==,type:str] secret: ENC[AES256_GCM,data:GtuaBAhFBw2JFaeuOm6mUr3m1j5fvCJjcWAzjsdU2xASFxwO,iv:YAXcRzBoemmef5PBdAOBa5acNPo4BoKH7Ngud/CWYfA=,tag:MCCUCOSutjRCI92raYrxdg==,type:str]
sops: sops:
kms: [] kms: []
gcp_kms: [] gcp_kms: []
azure_kv: [] azure_kv: []
hc_vault: [] hc_vault: []
lastmodified: '2021-03-18T22:59:59Z' age: []
mac: ENC[AES256_GCM,data:3++nHOAJaYFCEuUXim4/gOsG1ZVWt8Ab88qaqHM6jpCA2gLSyADWpB5iQfU9bM7Sq3PgCcWd5+mDHxl5Q8r9fiozrS025OLtsn7qQQQ84WaiFz9Y4Trsbe4EJXNpxYDXjLZEkEtkKs4/Dl+y2Ey3nVyIWKZEX9cPogJ64zfFS9Q=,iv:jvSwxJ8Of2Nfp1ijKItOraDO8aS6aGHQKFY61kF8JS8=,tag:I+AWPIZsPeXU30zxbgq2eQ==,type:str] lastmodified: "2021-04-27T00:59:33Z"
mac: ENC[AES256_GCM,data:qk+oo4m5OpfuQ+R3pZUuvn+gqAk15OAJzOULrlYqt1FIDRk/Q5ah5QpIbVxeP1EDVyuY/V/E0ZngRlSV7Dyx6Cp/moMd8AFBHNgnTB+Lq+NmZ9HR1QMOxpbMpJmUGn7MqQ1Ys4wy0p2q2Y2+TuUpKwmRGJbGVYEVmqvV5OT3jhc=,iv:QsUFa2GVzy6iqqLXRz8HascQZPIIzKBhxHdlabov02k=,tag:7lk63FeXsOlTCgfmWd7zrg==,type:str]
pgp: pgp:
- created_at: '2021-03-18T22:59:59Z' - created_at: "2021-03-18T22:59:59Z"
enc: | enc: |
-----BEGIN PGP MESSAGE----- -----BEGIN PGP MESSAGE-----
hQIMAzqVY590vudzAQ//etnfnpfCo9rAkctR+Fwg/7VdVL3Rov+6gnyjUnoN1BS1 hQIMAzqVY590vudzAQ//etnfnpfCo9rAkctR+Fwg/7VdVL3Rov+6gnyjUnoN1BS1
@ -28,6 +29,6 @@ sops:
Xd3gV3smg5xZ7/rfvzKTzJ1a5yH6D3xI05UtnUWdqojONcXS9NS+P7RArngJwSs= Xd3gV3smg5xZ7/rfvzKTzJ1a5yH6D3xI05UtnUWdqojONcXS9NS+P7RArngJwSs=
=m0OS =m0OS
-----END PGP MESSAGE----- -----END PGP MESSAGE-----
fp: 6CB37404020B5F0A0B41B5BB225EBAB0B936AC65 fp: 6CB37404020B5F0A0B41B5BB225EBAB0B936AC65
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.6.1 version: 3.7.1

View File

@ -1,10 +1,16 @@
package testing package testing
import ( import (
"dagger.io/dagger"
"dagger.io/dagger/op" "dagger.io/dagger/op"
"dagger.io/alpine" "dagger.io/alpine"
) )
registry: {
username: string
secret: dagger.#Secret
}
TestPushContainer: { TestPushContainer: {
// Generate a random number // Generate a random number
random: { random: {
@ -24,6 +30,9 @@ TestPushContainer: {
push: { push: {
ref: "daggerio/ci-test:\(random)" ref: "daggerio/ci-test:\(random)"
#up: [ #up: [
op.#DockerLogin & {
registry
},
op.#WriteFile & { op.#WriteFile & {
content: random content: random
dest: "/rand" dest: "/rand"