runservice: update docker registry auth

This commit is contained in:
Simone Gotti 2019-04-22 14:38:25 +02:00
parent d91bb36ccb
commit dfeba334f6
13 changed files with 400 additions and 218 deletions

1
go.mod
View File

@ -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

4
go.sum
View File

@ -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=

View File

@ -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) {

View File

@ -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"},

View File

@ -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

View File

@ -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{},
},
},

View File

@ -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
}

View File

@ -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 {

View File

@ -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,
},
},
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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"`