diff --git a/cmd/drone-server/server.go b/cmd/drone-server/server.go index d2fe0b214..e4c3e9de1 100644 --- a/cmd/drone-server/server.go +++ b/cmd/drone-server/server.go @@ -138,6 +138,10 @@ var flags = []cli.Flag{ EnvVar: "DRONE_VOLUME", Name: "volume", }, + cli.StringFlag{ + EnvVar: "DRONE_DOCKER_CONFIG", + Name: "docker-config", + }, cli.StringSliceFlag{ EnvVar: "DRONE_ENVIRONMENT", Name: "environment", diff --git a/cmd/drone-server/setup.go b/cmd/drone-server/setup.go index 4fb8a844c..09a051203 100644 --- a/cmd/drone-server/setup.go +++ b/cmd/drone-server/setup.go @@ -60,7 +60,14 @@ func setupSecretService(c *cli.Context, s store.Store) model.SecretService { } func setupRegistryService(c *cli.Context, s store.Store) model.RegistryService { - return registry.New(s) + if c.String("docker-config") != "" { + return registry.Combined( + registry.New(s), + registry.Filesystem(c.String("docker-config")), + ) + } else { + return registry.New(s) + } } func setupEnvironService(c *cli.Context, s store.Store) model.EnvironService { diff --git a/go.mod b/go.mod index 156635b78..dd841403a 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,10 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/dgrijalva/jwt-go v0.0.0-20150904212456-c1da56349675 github.com/dimfeld/httptreemux v5.0.1+incompatible + github.com/docker/cli v0.0.0-20200303215952-eb310fca4956 github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible + github.com/docker/docker-credential-helpers v0.6.3 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/docker/libcompose v0.4.0 diff --git a/go.sum b/go.sum index e62fc9982..2cdd9f68a 100644 --- a/go.sum +++ b/go.sum @@ -36,10 +36,14 @@ github.com/dgrijalva/jwt-go v0.0.0-20150904212456-c1da56349675 h1:MIkcjohFTgoQVD github.com/dgrijalva/jwt-go v0.0.0-20150904212456-c1da56349675/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimfeld/httptreemux v5.0.1+incompatible h1:Qj3gVcDNoOthBAqftuD596rm4wg/adLLz5xh5CmpiCA= github.com/dimfeld/httptreemux v5.0.1+incompatible/go.mod h1:rbUlSV+CCpv/SuqUTP/8Bk2O3LyUV436/yaRGkhP6Z0= +github.com/docker/cli v0.0.0-20200303215952-eb310fca4956 h1:5/ZRsUbguX7xFNLlbxVQY/yhD3Psy+vylKZrNme5BJs= +github.com/docker/cli v0.0.0-20200303215952-eb310fca4956/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible h1:G2hY8RD7jB9QaSmcb8mYEIg8QbEvVAB7se8+lXHZHfg= github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+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/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= diff --git a/model/registry.go b/model/registry.go index f2ba1d0a8..49f7f7e94 100644 --- a/model/registry.go +++ b/model/registry.go @@ -31,6 +31,12 @@ type RegistryService interface { RegistryDelete(*Repo, string) error } +// RegistryService defines a service for managing registries. +type ReadOnlyRegistryService interface { + RegistryFind(*Repo, string) (*Registry, error) + RegistryList(*Repo) ([]*Registry, error) +} + // RegistryStore persists registry information to storage. type RegistryStore interface { RegistryFind(*Repo, string) (*Registry, error) diff --git a/plugins/registry/combine.go b/plugins/registry/combine.go new file mode 100644 index 000000000..b1efb3541 --- /dev/null +++ b/plugins/registry/combine.go @@ -0,0 +1,55 @@ +package registry + +import ( + "github.com/laszlocph/woodpecker/model" +) + +type combined struct { + registries []model.ReadOnlyRegistryService + dbRegistry model.RegistryService +} + +func Combined(dbRegistry model.RegistryService, registries ...model.ReadOnlyRegistryService) model.RegistryService { + registries = append(registries, dbRegistry) + return &combined{ + registries: registries, + dbRegistry: dbRegistry, + } +} + +func (c combined) RegistryFind(repo *model.Repo, name string) (*model.Registry, error) { + for _, registry := range c.registries { + res, err := registry.RegistryFind(repo, name) + if err != nil { + return nil, err + } + if res != nil { + return res, nil + } + } + return nil, nil +} + +func (c combined) RegistryList(repo *model.Repo) ([]*model.Registry, error) { + var registries []*model.Registry + for _, registry := range c.registries { + list, err := registry.RegistryList(repo) + if err != nil { + return nil, err + } + registries = append(registries, list...) + } + return registries, nil +} + +func (c combined) RegistryCreate(repo *model.Repo, registry *model.Registry) error { + return c.dbRegistry.RegistryCreate(repo, registry) +} + +func (c combined) RegistryUpdate(repo *model.Repo, registry *model.Registry) error { + return c.dbRegistry.RegistryUpdate(repo, registry) +} + +func (c combined) RegistryDelete(repo *model.Repo, name string) error { + return c.dbRegistry.RegistryDelete(repo, name) +} diff --git a/plugins/registry/builtin_test.go b/plugins/registry/combine_test.go similarity index 100% rename from plugins/registry/builtin_test.go rename to plugins/registry/combine_test.go diff --git a/plugins/registry/builtin.go b/plugins/registry/db.go similarity index 52% rename from plugins/registry/builtin.go rename to plugins/registry/db.go index 22520d098..6327175e7 100644 --- a/plugins/registry/builtin.go +++ b/plugins/registry/db.go @@ -4,32 +4,32 @@ import ( "github.com/laszlocph/woodpecker/model" ) -type builtin struct { +type db struct { store model.RegistryStore } // New returns a new local registry service. func New(store model.RegistryStore) model.RegistryService { - return &builtin{store} + return &db{store} } -func (b *builtin) RegistryFind(repo *model.Repo, name string) (*model.Registry, error) { +func (b *db) RegistryFind(repo *model.Repo, name string) (*model.Registry, error) { return b.store.RegistryFind(repo, name) } -func (b *builtin) RegistryList(repo *model.Repo) ([]*model.Registry, error) { +func (b *db) RegistryList(repo *model.Repo) ([]*model.Registry, error) { return b.store.RegistryList(repo) } -func (b *builtin) RegistryCreate(repo *model.Repo, in *model.Registry) error { +func (b *db) RegistryCreate(repo *model.Repo, in *model.Registry) error { return b.store.RegistryCreate(in) } -func (b *builtin) RegistryUpdate(repo *model.Repo, in *model.Registry) error { +func (b *db) RegistryUpdate(repo *model.Repo, in *model.Registry) error { return b.store.RegistryUpdate(in) } -func (b *builtin) RegistryDelete(repo *model.Repo, addr string) error { +func (b *db) RegistryDelete(repo *model.Repo, addr string) error { registry, err := b.RegistryFind(repo, addr) if err != nil { return err diff --git a/plugins/registry/db_test.go b/plugins/registry/db_test.go new file mode 100644 index 000000000..b2a276fb4 --- /dev/null +++ b/plugins/registry/db_test.go @@ -0,0 +1 @@ +package registry diff --git a/plugins/registry/filesystem.go b/plugins/registry/filesystem.go new file mode 100644 index 000000000..6dd6e1a29 --- /dev/null +++ b/plugins/registry/filesystem.go @@ -0,0 +1,96 @@ +package registry + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/config/types" + "github.com/laszlocph/woodpecker/model" + "os" + "strings" +) + +type filesystem struct { + path string +} + +func Filesystem(path string) model.ReadOnlyRegistryService { + return &filesystem{path} +} + +func parseDockerConfig(path string) ([]*model.Registry, error) { + if path == "" { + return nil, nil + } + + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + configFile := configfile.ConfigFile{ + AuthConfigs: make(map[string]types.AuthConfig), + } + + if err := json.NewDecoder(f).Decode(&configFile); err != nil { + return nil, err + } + + for addr, ac := range configFile.AuthConfigs { + if ac.Auth != "" { + ac.Username, ac.Password, err = decodeAuth(ac.Auth) + if err != nil { + return nil, err + } + ac.Auth = "" + ac.ServerAddress = addr + configFile.AuthConfigs[addr] = ac + } + } + + auths := []*model.Registry{} + + for key, auth := range configFile.AuthConfigs { + auths = append(auths, &model.Registry{ + Address: key, + Username: auth.Username, + Password: auth.Password, + }) + } + + return auths, nil +} + +func (b *filesystem) RegistryFind(*model.Repo, string) (*model.Registry, error) { + return nil, nil +} + +func (b *filesystem) RegistryList(*model.Repo) ([]*model.Registry, error) { + return parseDockerConfig(b.path) +} + +// decodeAuth decodes a base64 encoded string and returns username and password +func decodeAuth(authStr string) (string, string, error) { + if authStr == "" { + return "", "", nil + } + + decLen := base64.StdEncoding.DecodedLen(len(authStr)) + decoded := make([]byte, decLen) + authByte := []byte(authStr) + n, err := base64.StdEncoding.Decode(decoded, authByte) + if err != nil { + return "", "", err + } + if n > decLen { + return "", "", fmt.Errorf("Something went wrong decoding auth config") + } + arr := strings.SplitN(string(decoded), ":", 2) + if len(arr) != 2 { + return "", "", fmt.Errorf("Invalid auth configuration file") + } + password := strings.Trim(arr[1], "\x00") + return arr[0], password, nil +} \ No newline at end of file diff --git a/plugins/registry/filesystem_test.go b/plugins/registry/filesystem_test.go new file mode 100644 index 000000000..b2a276fb4 --- /dev/null +++ b/plugins/registry/filesystem_test.go @@ -0,0 +1 @@ +package registry