From dfeba334f6dd9d82542bbce11b0fd5975239bde4 Mon Sep 17 00:00:00 2001 From: Simone Gotti Date: Mon, 22 Apr 2019 14:38:25 +0200 Subject: [PATCH] runservice: update docker registry auth --- go.mod | 1 + go.sum | 4 +- internal/config/config.go | 104 +++++++------ internal/config/config_test.go | 34 +++-- internal/runconfig/runconfig.go | 61 ++++---- internal/runconfig/runconfig_test.go | 125 ++++++++++------ .../runservice/executor/driver/docker.go | 21 ++- .../runservice/executor/driver/driver.go | 16 +- .../services/runservice/executor/executor.go | 15 +- .../services/runservice/executor/registry.go | 47 ------ .../runservice/executor/registry/registry.go | 139 ++++++++++++++++++ .../runservice/scheduler/scheduler.go | 1 + internal/services/runservice/types/types.go | 50 ++++--- 13 files changed, 400 insertions(+), 218 deletions(-) delete mode 100644 internal/services/runservice/executor/registry.go create mode 100644 internal/services/runservice/executor/registry/registry.go diff --git a/go.mod b/go.mod index c7b4f3f..283ff6a 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/go-ini/ini v1.42.0 // indirect github.com/go-sql-driver/mysql v1.4.1 // indirect github.com/google/go-cmp v0.3.0 + github.com/google/go-containerregistry v0.0.0-20190412005658-1d38b9cfdb9d github.com/google/go-jsonnet v0.12.1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect github.com/gorilla/handlers v1.4.0 diff --git a/go.sum b/go.sum index a1ea618..742d11b 100644 --- a/go.sum +++ b/go.sum @@ -62,10 +62,10 @@ github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a h1:ZJu5NB1Bk5ms4vw0Xu github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a h1:ZJu5NB1Bk5ms4vw0Xu4i+jD32SE9jQXyfnOvwhHqlT0= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-containerregistry v0.0.0-20190412005658-1d38b9cfdb9d h1:K8AF5hFHsOYRk0CG22FwQk3oCu7CbL2bNfiHoaGuW4Y= +github.com/google/go-containerregistry v0.0.0-20190412005658-1d38b9cfdb9d/go.mod h1:yZAFP63pRshzrEYLXLGPmUt0Ay+2zdjmMN1loCnRLUk= github.com/google/go-jsonnet v0.12.1 h1:v0iUm/b4SBz7lR/diMoz9tLAz8lqtnNRKIwMrmU2HEU= github.com/google/go-jsonnet v0.12.1/go.mod h1:gVu3UVSfOt5fRFq+dh9duBqXa5905QY8S1QvMNcEIVs= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= diff --git a/internal/config/config.go b/internal/config/config.go index ccd846b..4cbc4a2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -59,30 +59,34 @@ const ( RuntimeTypePod RuntimeType = "pod" ) -type RegistryAuthType string +type DockerRegistryAuthType string const ( - RegistryAuthTypeDefault RegistryAuthType = "default" + DockerRegistryAuthTypeBasic DockerRegistryAuthType = "basic" + DockerRegistryAuthTypeEncodedAuth DockerRegistryAuthType = "encodedauth" ) -type RegistryAuth struct { - Type RegistryAuthType `json:"type"` +type DockerRegistryAuth struct { + Type DockerRegistryAuthType `json:"type"` - // default auth + // basic auth Username Value `json:"username"` Password Value `json:"password"` + + // encoded auth string + Auth string `json:"auth"` + + // future auths like aws ecr auth } type Runtime struct { - Type RuntimeType `json:"type,omitempty"` - Auth *RegistryAuth `json:"auth"` - Arch common.Arch `json:"arch,omitempty"` - Containers []*Container `json:"containers,omitempty"` + Type RuntimeType `json:"type,omitempty"` + Arch common.Arch `json:"arch,omitempty"` + Containers []*Container `json:"containers,omitempty"` } type Container struct { Image string `json:"image,omitempty"` - Auth *RegistryAuth `json:"auth"` Environment map[string]Value `json:"environment,omitempty"` User string `json:"user"` Privileged bool `json:"privileged"` @@ -90,22 +94,24 @@ type Container struct { } type Run struct { - Name string `json:"name"` - Tasks []*Task `json:"tasks"` + Name string `json:"name"` + Tasks []*Task `json:"tasks"` + DockerRegistriesAuth map[string]*DockerRegistryAuth `json:"docker_registries_auth"` } type Task struct { - Name string `json:"name"` - Runtime *Runtime `json:"runtime"` - Environment map[string]Value `json:"environment,omitempty"` - WorkingDir string `json:"working_dir"` - Shell string `json:"shell"` - User string `json:"user"` - Steps []interface{} `json:"steps"` - Depends []*Depend `json:"depends"` - IgnoreFailure bool `json:"ignore_failure"` - Approval bool `json:"approval"` - When *types.When `json:"when"` + Name string `json:"name"` + Runtime *Runtime `json:"runtime"` + Environment map[string]Value `json:"environment,omitempty"` + WorkingDir string `json:"working_dir"` + Shell string `json:"shell"` + User string `json:"user"` + Steps []interface{} `json:"steps"` + Depends []*Depend `json:"depends"` + IgnoreFailure bool `json:"ignore_failure"` + Approval bool `json:"approval"` + When *types.When `json:"when"` + DockerRegistriesAuth map[string]*DockerRegistryAuth `json:"docker_registries_auth"` } type DependCondition string @@ -187,17 +193,18 @@ func (t *Task) UnmarshalJSON(b []byte) error { } type runtask struct { - Name string `json:"name"` - Runtime *Runtime `json:"runtime"` - Environment map[string]Value `json:"environment,omitempty"` - WorkingDir string `json:"working_dir"` - Shell string `json:"shell"` - User string `json:"user"` - Steps []map[string]interface{} `json:"steps"` - Depends []interface{} `json:"depends"` - IgnoreFailure bool `json:"ignore_failure"` - Approval bool `json:"approval"` - When *when `json:"when"` + Name string `json:"name"` + Runtime *Runtime `json:"runtime"` + Environment map[string]Value `json:"environment,omitempty"` + WorkingDir string `json:"working_dir"` + Shell string `json:"shell"` + User string `json:"user"` + Steps []map[string]interface{} `json:"steps"` + Depends []interface{} `json:"depends"` + IgnoreFailure bool `json:"ignore_failure"` + Approval bool `json:"approval"` + When *when `json:"when"` + DockerRegistriesAuth map[string]*DockerRegistryAuth `json:"docker_registries_auth"` } var tr *runtask @@ -214,6 +221,7 @@ func (t *Task) UnmarshalJSON(b []byte) error { t.User = tr.User t.IgnoreFailure = tr.IgnoreFailure t.Approval = tr.Approval + t.DockerRegistriesAuth = tr.DockerRegistriesAuth steps := make([]interface{}, len(tr.Steps)) for i, stepEntry := range tr.Steps { @@ -766,27 +774,25 @@ func checkConfig(config *Config) error { // Set defaults for _, run := range config.Runs { + // set auth type to basic if not specified + for _, registryAuth := range run.DockerRegistriesAuth { + if registryAuth.Type == "" { + registryAuth.Type = DockerRegistryAuthTypeBasic + } + } for _, task := range run.Tasks { + // set auth type to basic if not specified + for _, registryAuth := range task.DockerRegistriesAuth { + if registryAuth.Type == "" { + registryAuth.Type = DockerRegistryAuthTypeBasic + } + } + // set task default working dir if task.WorkingDir == "" { task.WorkingDir = defaultWorkingDir } - // set auth type to default if not specified - runtime := task.Runtime - if runtime.Auth != nil { - if runtime.Auth.Type == "" { - runtime.Auth.Type = RegistryAuthTypeDefault - } - } - for _, container := range runtime.Containers { - if container.Auth != nil { - if container.Auth.Type == "" { - container.Auth.Type = RegistryAuthTypeDefault - } - } - } - // set steps defaults for i, s := range task.Steps { switch step := s.(type) { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 3cea1a5..4188b70 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -170,14 +170,20 @@ func TestParseOutput(t *testing.T) { in: ` runs: - name: run01 + docker_registries_auth: + index.docker.io: + username: username + password: + from_variable: password tasks: - name: task01 - runtime: - type: pod - auth: + docker_registries_auth: + index.docker.io: username: username password: from_variable: password + runtime: + type: pod containers: - image: image01 auth: @@ -263,25 +269,29 @@ func TestParseOutput(t *testing.T) { Runs: []*Run{ &Run{ Name: "run01", + DockerRegistriesAuth: map[string]*DockerRegistryAuth{ + "index.docker.io": { + Type: DockerRegistryAuthTypeBasic, + Username: Value{Type: ValueTypeString, Value: "username"}, + Password: Value{Type: ValueTypeFromVariable, Value: "password"}, + }, + }, Tasks: []*Task{ &Task{ Name: "task01", - Runtime: &Runtime{ - Type: "pod", - Auth: &RegistryAuth{ - Type: RegistryAuthTypeDefault, + DockerRegistriesAuth: map[string]*DockerRegistryAuth{ + "index.docker.io": { + Type: DockerRegistryAuthTypeBasic, Username: Value{Type: ValueTypeString, Value: "username"}, Password: Value{Type: ValueTypeFromVariable, Value: "password"}, }, + }, + Runtime: &Runtime{ + Type: "pod", Arch: "", Containers: []*Container{ &Container{ Image: "image01", - Auth: &RegistryAuth{ - Type: RegistryAuthTypeDefault, - Username: Value{Type: ValueTypeFromVariable, Value: "username2"}, - Password: Value{Type: ValueTypeString, Value: "password2"}, - }, Environment: map[string]Value{ "ENV01": Value{Type: ValueTypeString, Value: "ENV01"}, "ENVFROMVARIABLE01": Value{Type: ValueTypeFromVariable, Value: "variable01"}, diff --git a/internal/runconfig/runconfig.go b/internal/runconfig/runconfig.go index 7233a86..111c608 100644 --- a/internal/runconfig/runconfig.go +++ b/internal/runconfig/runconfig.go @@ -37,23 +37,6 @@ func genRuntime(c *config.Config, ce *config.Runtime, variables map[string]strin Entrypoint: cc.Entrypoint, } - // Set container auth - if cc.Auth != nil { - container.Auth = &rstypes.RegistryAuth{ - Type: rstypes.RegistryAuthType(cc.Auth.Type), - Username: genValue(cc.Auth.Username, variables), - Password: genValue(cc.Auth.Password, variables), - } - } - // if container auth is nil use runtime auth - if container.Auth == nil && ce.Auth != nil { - container.Auth = &rstypes.RegistryAuth{ - Type: rstypes.RegistryAuthType(ce.Auth.Type), - Username: genValue(ce.Auth.Username, variables), - Password: genValue(ce.Auth.Password, variables), - } - } - containers = append(containers, container) } @@ -199,17 +182,39 @@ func GenRunConfigTasks(uuid util.UUIDGenerator, c *config.Config, runName string tEnv := genEnv(ct.Environment, variables) t := &rstypes.RunConfigTask{ - ID: uuid.New(ct.Name).String(), - Name: ct.Name, - Runtime: genRuntime(c, ct.Runtime, variables), - Environment: tEnv, - WorkingDir: ct.WorkingDir, - Shell: ct.Shell, - User: ct.User, - Steps: steps, - IgnoreFailure: ct.IgnoreFailure, - Skip: !include, - NeedsApproval: ct.Approval, + ID: uuid.New(ct.Name).String(), + Name: ct.Name, + Runtime: genRuntime(c, ct.Runtime, variables), + Environment: tEnv, + WorkingDir: ct.WorkingDir, + Shell: ct.Shell, + User: ct.User, + Steps: steps, + IgnoreFailure: ct.IgnoreFailure, + Skip: !include, + NeedsApproval: ct.Approval, + DockerRegistriesAuth: make(map[string]rstypes.DockerRegistryAuth), + } + + if cr.DockerRegistriesAuth != nil { + for regname, auth := range cr.DockerRegistriesAuth { + t.DockerRegistriesAuth[regname] = rstypes.DockerRegistryAuth{ + Type: rstypes.DockerRegistryAuthType(auth.Type), + Username: genValue(auth.Username, variables), + Password: genValue(auth.Password, variables), + } + } + } + + // override with per task docker registry auth + if ct.DockerRegistriesAuth != nil { + for regname, auth := range ct.DockerRegistriesAuth { + t.DockerRegistriesAuth[regname] = rstypes.DockerRegistryAuth{ + Type: rstypes.DockerRegistryAuthType(auth.Type), + Username: genValue(auth.Username, variables), + Password: genValue(auth.Password, variables), + } + } } rcts[t.ID] = t diff --git a/internal/runconfig/runconfig_test.go b/internal/runconfig/runconfig_test.go index 1cb016c..6b3e83a 100644 --- a/internal/runconfig/runconfig_test.go +++ b/internal/runconfig/runconfig_test.go @@ -635,25 +635,29 @@ func TestGenRunConfig(t *testing.T) { Runs: []*config.Run{ &config.Run{ Name: "run01", + DockerRegistriesAuth: map[string]*config.DockerRegistryAuth{ + "index.docker.io": { + Type: config.DockerRegistryAuthTypeBasic, + Username: config.Value{Type: config.ValueTypeString, Value: "username"}, + Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"}, + }, + }, Tasks: []*config.Task{ &config.Task{ Name: "task01", + DockerRegistriesAuth: map[string]*config.DockerRegistryAuth{ + "index.docker.io": { + Type: config.DockerRegistryAuthTypeBasic, + Username: config.Value{Type: config.ValueTypeFromVariable, Value: "registry_username"}, + Password: config.Value{Type: config.ValueTypeString, Value: "password2"}, + }, + }, Runtime: &config.Runtime{ Type: "pod", - Auth: &config.RegistryAuth{ - Type: config.RegistryAuthTypeDefault, - Username: config.Value{Type: config.ValueTypeString, Value: "username"}, - Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"}, - }, Arch: "", Containers: []*config.Container{ &config.Container{ Image: "image01", - Auth: &config.RegistryAuth{ - Type: config.RegistryAuthTypeDefault, - Username: config.Value{Type: config.ValueTypeFromVariable, Value: "registry_username"}, - Password: config.Value{Type: config.ValueTypeString, Value: "password2"}, - }, Environment: map[string]config.Value{ "ENV01": config.Value{Type: config.ValueTypeString, Value: "ENV01"}, "ENVFROMVARIABLE01": config.Value{Type: config.ValueTypeFromVariable, Value: "variable01"}, @@ -721,15 +725,17 @@ func TestGenRunConfig(t *testing.T) { uuid.New("task01").String(): &rstypes.RunConfigTask{ ID: uuid.New("task01").String(), Name: "task01", Depends: map[string]*rstypes.RunConfigTaskDepend{}, + DockerRegistriesAuth: map[string]rstypes.DockerRegistryAuth{ + "index.docker.io": { + Type: rstypes.DockerRegistryAuthTypeBasic, + Username: "yourregistryusername", + Password: "password2", + }, + }, Runtime: &rstypes.Runtime{Type: rstypes.RuntimeType("pod"), Containers: []*rstypes.Container{ { Image: "image01", - Auth: &rstypes.RegistryAuth{ - Type: rstypes.RegistryAuthTypeDefault, - Username: "yourregistryusername", - Password: "password2", - }, Environment: map[string]string{ "ENV01": "ENV01", "ENVFROMVARIABLE01": "VARVALUE01", @@ -751,21 +757,23 @@ func TestGenRunConfig(t *testing.T) { }, }, { - name: "test runtime auth used for container nil auth", + name: "test run auth used for task undefined auth", in: &config.Config{ Runs: []*config.Run{ &config.Run{ Name: "run01", + DockerRegistriesAuth: map[string]*config.DockerRegistryAuth{ + "index.docker.io": { + Type: config.DockerRegistryAuthTypeBasic, + Username: config.Value{Type: config.ValueTypeString, Value: "username"}, + Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"}, + }, + }, Tasks: []*config.Task{ &config.Task{ Name: "task01", Runtime: &config.Runtime{ Type: "pod", - Auth: &config.RegistryAuth{ - Type: config.RegistryAuthTypeDefault, - Username: config.Value{Type: config.ValueTypeString, Value: "username"}, - Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"}, - }, Arch: "", Containers: []*config.Container{ &config.Container{ @@ -795,15 +803,17 @@ func TestGenRunConfig(t *testing.T) { uuid.New("task01").String(): &rstypes.RunConfigTask{ ID: uuid.New("task01").String(), Name: "task01", Depends: map[string]*rstypes.RunConfigTaskDepend{}, + DockerRegistriesAuth: map[string]rstypes.DockerRegistryAuth{ + "index.docker.io": { + Type: rstypes.DockerRegistryAuthTypeBasic, + Username: "username", + Password: "yourregistrypassword", + }, + }, Runtime: &rstypes.Runtime{Type: rstypes.RuntimeType("pod"), Containers: []*rstypes.Container{ { - Image: "image01", - Auth: &rstypes.RegistryAuth{ - Type: rstypes.RegistryAuthTypeDefault, - Username: "username", - Password: "yourregistrypassword", - }, + Image: "image01", Environment: map[string]string{}, }, }, @@ -816,30 +826,44 @@ func TestGenRunConfig(t *testing.T) { }, }, { - name: "test runtime auth not used for container with auth", + name: "test run auth override by task auth", in: &config.Config{ Runs: []*config.Run{ &config.Run{ Name: "run01", + DockerRegistriesAuth: map[string]*config.DockerRegistryAuth{ + "index.docker.io": { + Type: config.DockerRegistryAuthTypeBasic, + Username: config.Value{Type: config.ValueTypeString, Value: "username"}, + Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"}, + }, + "https://myregistry.example.com": { + Type: config.DockerRegistryAuthTypeBasic, + Username: config.Value{Type: config.ValueTypeString, Value: "username"}, + Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"}, + }, + }, Tasks: []*config.Task{ &config.Task{ Name: "task01", + DockerRegistriesAuth: map[string]*config.DockerRegistryAuth{ + "index.docker.io": { + Type: config.DockerRegistryAuthTypeBasic, + Username: config.Value{Type: config.ValueTypeFromVariable, Value: "registry_username"}, + Password: config.Value{Type: config.ValueTypeString, Value: "password2"}, + }, + "https://anotherregistry.example.com": { + Type: config.DockerRegistryAuthTypeBasic, + Username: config.Value{Type: config.ValueTypeFromVariable, Value: "registry_username"}, + Password: config.Value{Type: config.ValueTypeString, Value: "password2"}, + }, + }, Runtime: &config.Runtime{ Type: "pod", - Auth: &config.RegistryAuth{ - Type: config.RegistryAuthTypeDefault, - Username: config.Value{Type: config.ValueTypeString, Value: "username"}, - Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"}, - }, Arch: "", Containers: []*config.Container{ &config.Container{ Image: "image01", - Auth: &config.RegistryAuth{ - Type: config.RegistryAuthTypeDefault, - Username: config.Value{Type: config.ValueTypeFromVariable, Value: "registry_username"}, - Password: config.Value{Type: config.ValueTypeString, Value: "password2"}, - }, }, }, }, @@ -860,20 +884,33 @@ func TestGenRunConfig(t *testing.T) { variables: map[string]string{ "variable01": "VARVALUE01", "registry_username": "yourregistryusername", + "password": "myregistrypassword", }, out: map[string]*rstypes.RunConfigTask{ uuid.New("task01").String(): &rstypes.RunConfigTask{ ID: uuid.New("task01").String(), Name: "task01", Depends: map[string]*rstypes.RunConfigTaskDepend{}, + DockerRegistriesAuth: map[string]rstypes.DockerRegistryAuth{ + "index.docker.io": { + Type: rstypes.DockerRegistryAuthTypeBasic, + Username: "yourregistryusername", + Password: "password2", + }, + "https://myregistry.example.com": { + Type: rstypes.DockerRegistryAuthTypeBasic, + Username: "username", + Password: "myregistrypassword", + }, + "https://anotherregistry.example.com": { + Type: rstypes.DockerRegistryAuthTypeBasic, + Username: "yourregistryusername", + Password: "password2", + }, + }, Runtime: &rstypes.Runtime{Type: rstypes.RuntimeType("pod"), Containers: []*rstypes.Container{ { - Image: "image01", - Auth: &rstypes.RegistryAuth{ - Type: rstypes.RegistryAuthTypeDefault, - Username: "yourregistryusername", - Password: "password2", - }, + Image: "image01", Environment: map[string]string{}, }, }, diff --git a/internal/services/runservice/executor/driver/docker.go b/internal/services/runservice/executor/driver/docker.go index f8de7d9..14d41cf 100644 --- a/internal/services/runservice/executor/driver/docker.go +++ b/internal/services/runservice/executor/driver/docker.go @@ -16,6 +16,8 @@ package driver import ( "context" + "encoding/base64" + "encoding/json" "fmt" "io" "io/ioutil" @@ -25,6 +27,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/sorintlab/agola/internal/services/runservice/executor/registry" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -116,9 +119,25 @@ func (d *DockerDriver) NewPod(ctx context.Context, podConfig *PodConfig, out io. containerConfig := podConfig.Containers[0] + regName, err := registry.GetRegistry(containerConfig.Image) + if err != nil { + return nil, err + } + var registryAuth registry.DockerConfigAuth + if podConfig.DockerConfig != nil { + if regauth, ok := podConfig.DockerConfig.Auths[regName]; ok { + registryAuth = regauth + } + } + buf, err := json.Marshal(registryAuth) + if err != nil { + return nil, err + } + registryAuthEnc := base64.URLEncoding.EncodeToString(buf) + // by default always try to pull the image so we are sure only authorized users can fetch them // see https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#alwayspullimages - reader, err := d.client.ImagePull(ctx, containerConfig.Image, types.ImagePullOptions{RegistryAuth: containerConfig.RegistryAuth}) + reader, err := d.client.ImagePull(ctx, containerConfig.Image, types.ImagePullOptions{RegistryAuth: registryAuthEnc}) if err != nil { return nil, err } diff --git a/internal/services/runservice/executor/driver/driver.go b/internal/services/runservice/executor/driver/driver.go index 05c8545..a6693c4 100644 --- a/internal/services/runservice/executor/driver/driver.go +++ b/internal/services/runservice/executor/driver/driver.go @@ -17,6 +17,8 @@ package driver import ( "context" "io" + + "github.com/sorintlab/agola/internal/services/runservice/executor/registry" ) const ( @@ -68,16 +70,16 @@ type PodConfig struct { Labels map[string]string // The container dir where the init volume will be mounted InitVolumeDir string + DockerConfig *registry.DockerConfig } type ContainerConfig struct { - Cmd []string - Env map[string]string - WorkingDir string - Image string - User string - Privileged bool - RegistryAuth string + Cmd []string + Env map[string]string + WorkingDir string + Image string + User string + Privileged bool } type ExecConfig struct { diff --git a/internal/services/runservice/executor/executor.go b/internal/services/runservice/executor/executor.go index adfc25d..d058354 100644 --- a/internal/services/runservice/executor/executor.go +++ b/internal/services/runservice/executor/executor.go @@ -37,6 +37,7 @@ import ( slog "github.com/sorintlab/agola/internal/log" "github.com/sorintlab/agola/internal/services/config" "github.com/sorintlab/agola/internal/services/runservice/executor/driver" + "github.com/sorintlab/agola/internal/services/runservice/executor/registry" rsapi "github.com/sorintlab/agola/internal/services/runservice/scheduler/api" "github.com/sorintlab/agola/internal/services/runservice/types" "github.com/sorintlab/agola/internal/util" @@ -795,7 +796,7 @@ func (e *Executor) setupTask(ctx context.Context, rt *runningTask) error { log.Debugf("starting pod") - registryAuth, err := registryAuthToken(et.Containers[0].Auth) + dockerConfig, err := registry.GenDockerConfig(et.DockerRegistriesAuth, []string{et.Containers[0].Image}) if err != nil { return err } @@ -803,14 +804,14 @@ func (e *Executor) setupTask(ctx context.Context, rt *runningTask) error { podConfig := &driver.PodConfig{ Labels: createTaskLabels(et.ID), InitVolumeDir: toolboxContainerDir, + DockerConfig: dockerConfig, Containers: []*driver.ContainerConfig{ { - Image: et.Containers[0].Image, - Cmd: cmd, - Env: et.Containers[0].Environment, - User: et.Containers[0].User, - Privileged: et.Containers[0].Privileged, - RegistryAuth: registryAuth, + Image: et.Containers[0].Image, + Cmd: cmd, + Env: et.Containers[0].Environment, + User: et.Containers[0].User, + Privileged: et.Containers[0].Privileged, }, }, } diff --git a/internal/services/runservice/executor/registry.go b/internal/services/runservice/executor/registry.go deleted file mode 100644 index 45c56ea..0000000 --- a/internal/services/runservice/executor/registry.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2019 Sorint.lab -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied -// See the License for the specific language governing permissions and -// limitations under the License. - -package executor - -import ( - "encoding/base64" - "encoding/json" - - "github.com/sorintlab/agola/internal/services/runservice/types" - - dtypes "github.com/docker/docker/api/types" - "github.com/pkg/errors" -) - -func registryAuthToken(auth *types.RegistryAuth) (string, error) { - if auth == nil { - return "", nil - } - - switch auth.Type { - case types.RegistryAuthTypeDefault: - authConfig := dtypes.AuthConfig{ - Username: auth.Username, - Password: auth.Password, - } - authConfigj, err := json.Marshal(authConfig) - if err != nil { - panic(err) - } - return base64.URLEncoding.EncodeToString(authConfigj), nil - - default: - return "", errors.Errorf("unsupported registry auth type %q", auth.Type) - } -} diff --git a/internal/services/runservice/executor/registry/registry.go b/internal/services/runservice/executor/registry/registry.go new file mode 100644 index 0000000..9eef2ec --- /dev/null +++ b/internal/services/runservice/executor/registry/registry.go @@ -0,0 +1,139 @@ +// Copyright 2019 Sorint.lab +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied +// See the License for the specific language governing permissions and +// limitations under the License. + +package registry + +import ( + "encoding/base64" + "fmt" + "strings" + + "github.com/pkg/errors" + "github.com/sorintlab/agola/internal/services/runservice/types" + + "github.com/google/go-containerregistry/pkg/name" +) + +//func registryAuthToken(auth *types.DockerRegistryAuth) (string, error) { +// if auth == nil { +// return "", nil +// } +// +// switch auth.Type { +// case types.DockerRegistryAuthTypeBasic: +// authConfig := dtypes.AuthConfig{ +// Username: auth.Username, +// Password: auth.Password, +// } +// authConfigj, err := json.Marshal(authConfig) +// if err != nil { +// panic(err) +// } +// return base64.URLEncoding.EncodeToString(authConfigj), nil +// +// default: +// return "", errors.Errorf("unsupported registry auth type %q", auth.Type) +// } +//} + +// Docker config represents the docker config.json format. We only consider the "auths" part +type DockerConfig struct { + Auths map[string]DockerConfigAuth `json:"auths,omitempty"` +} + +// Docker config represents the docker config.json auth part. We only consider the "auth" token part +type DockerConfigAuth struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Auth string `json:"auth,omitempty"` +} + +// There are a variety of ways a domain may get qualified within the Docker credential file. +// We enumerate them here as format strings. +var ( + domainForms = []string{ + // Allow naked domains + "%s", + // Allow scheme-prefixed. + "https://%s", + "http://%s", + // Allow scheme-prefixes with version in url path. + "https://%s/v1/", + "http://%s/v1/", + "https://%s/v2/", + "http://%s/v2/", + } +) + +func GetRegistry(image string) (string, error) { + ref, err := name.ParseReference(image, name.WeakValidation) + if err != nil { + return "", err + } + regName := ref.Context().RegistryStr() + return regName, nil +} + +// ResolveAuth resolves the auth username and password for the provided registry name +func ResolveAuth(auths map[string]types.DockerRegistryAuth, regname string) (string, string, error) { + if auths != nil { + for _, form := range domainForms { + if auth, ok := auths[fmt.Sprintf(form, regname)]; ok { + switch auth.Type { + case types.DockerRegistryAuthTypeEncodedAuth: + decoded, err := base64.StdEncoding.DecodeString(auth.Auth) + if err != nil { + return "", "", errors.Wrapf(err, "failed to decode docker auth") + } + parts := strings.Split(string(decoded), ":") + if len(parts) != 2 { + return "", "", errors.Wrapf(err, "wrong docker auth") + } + return parts[0], parts[1], nil + case types.DockerRegistryAuthTypeBasic: + return auth.Username, auth.Password, nil + default: + return "", "", fmt.Errorf("unsupported auth type %q", auth.Type) + } + } + } + } + + return "", "", nil +} + +func GenDockerConfig(auths map[string]types.DockerRegistryAuth, images []string) (*DockerConfig, error) { + dockerConfig := &DockerConfig{Auths: make(map[string]DockerConfigAuth)} + for _, image := range images { + ref, err := name.ParseReference(image, name.WeakValidation) + if err != nil { + return nil, err + } + regName := ref.Context().RegistryStr() + + if _, ok := dockerConfig.Auths[regName]; ok { + continue + } + + username, password, err := ResolveAuth(auths, regName) + if err != nil { + return nil, errors.Wrapf(err, "failed to resolve auth") + } + delimited := fmt.Sprintf("%s:%s", username, password) + auth := base64.StdEncoding.EncodeToString([]byte(delimited)) + dockerConfig.Auths[regName] = DockerConfigAuth{Username: username, Password: password, Auth: auth} + } + + return dockerConfig, nil +} diff --git a/internal/services/runservice/scheduler/scheduler.go b/internal/services/runservice/scheduler/scheduler.go index 7ca934a..b60ea1d 100644 --- a/internal/services/runservice/scheduler/scheduler.go +++ b/internal/services/runservice/scheduler/scheduler.go @@ -335,6 +335,7 @@ func (s *Scheduler) genExecutorTask(ctx context.Context, r *types.Run, rt *types Steps: make([]*types.ExecutorTaskStepStatus, len(rct.Steps)), ExecutorID: executor.ID, }, + DockerRegistriesAuth: rct.DockerRegistriesAuth, } for i := range et.Status.Steps { diff --git a/internal/services/runservice/types/types.go b/internal/services/runservice/types/types.go index c8ac433..e9d27dc 100644 --- a/internal/services/runservice/types/types.go +++ b/internal/services/runservice/types/types.go @@ -317,19 +317,20 @@ func (rc *RunConfig) DeepCopy() *RunConfig { } type RunConfigTask struct { - Level int `json:"level,omitempty"` - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Depends map[string]*RunConfigTaskDepend `json:"depends"` - Runtime *Runtime `json:"runtime,omitempty"` - Environment map[string]string `json:"environment,omitempty"` - WorkingDir string `json:"working_dir,omitempty"` - Shell string `json:"shell,omitempty"` - User string `json:"user,omitempty"` - Steps []interface{} `json:"steps,omitempty"` - IgnoreFailure bool `json:"ignore_failure,omitempty"` - NeedsApproval bool `json:"needs_approval,omitempty"` - Skip bool `json:"skip,omitempty"` + Level int `json:"level,omitempty"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Depends map[string]*RunConfigTaskDepend `json:"depends"` + Runtime *Runtime `json:"runtime,omitempty"` + Environment map[string]string `json:"environment,omitempty"` + WorkingDir string `json:"working_dir,omitempty"` + Shell string `json:"shell,omitempty"` + User string `json:"user,omitempty"` + Steps []interface{} `json:"steps,omitempty"` + IgnoreFailure bool `json:"ignore_failure,omitempty"` + NeedsApproval bool `json:"needs_approval,omitempty"` + Skip bool `json:"skip,omitempty"` + DockerRegistriesAuth map[string]DockerRegistryAuth `json:"docker_registries_auth"` } type RunConfigTaskDependCondition string @@ -351,18 +352,24 @@ const ( RuntimeTypePod RuntimeType = "pod" ) -type RegistryAuthType string +type DockerRegistryAuthType string const ( - RegistryAuthTypeDefault RegistryAuthType = "default" + DockerRegistryAuthTypeBasic DockerRegistryAuthType = "basic" + DockerRegistryAuthTypeEncodedAuth DockerRegistryAuthType = "encodedauth" ) -type RegistryAuth struct { - Type RegistryAuthType `json:"type,omitempty"` +type DockerRegistryAuth struct { + Type DockerRegistryAuthType `json:"type"` - // default auth - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` + // basic auth + Username string `json:"username"` + Password string `json:"password"` + + // encoded auth string + Auth string `json:"auth"` + + // future auths like aws ecr auth } type Runtime struct { @@ -502,6 +509,8 @@ type ExecutorTask struct { User string `json:"user,omitempty"` Privileged bool `json:"privileged"` + DockerRegistriesAuth map[string]DockerRegistryAuth `json:"docker_registries_auth"` + Steps []interface{} `json:"steps,omitempty"` Status ExecutorTaskStatus `json:"status,omitempty"` @@ -540,7 +549,6 @@ type ExecutorTaskStepStatus struct { type Container struct { Image string `json:"image,omitempty"` - Auth *RegistryAuth `json:"auth,omitempty"` Environment map[string]string `json:"environment,omitempty"` User string `json:"user,omitempty"` Privileged bool `json:"privileged"`