...
 
Commits (6)
v11.5.1 (2018-12-06)
- Make k8s object names DNS-1123 compatible !1105
v11.5.0 (2018-11-22)
- Support RAW artifacts !1057
- Docs: changing secret variable to just variable in advanced-configuration.md !1055
......
11.5.0
\ No newline at end of file
11.5.1
package common
import (
"bytes"
"context"
"errors"
"fmt"
......@@ -11,9 +12,11 @@ import (
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/sirupsen/logrus"
"gitlab.com/gitlab-org/gitlab-runner/helpers"
docker_helpers "gitlab.com/gitlab-org/gitlab-runner/helpers/docker"
"gitlab.com/gitlab-org/gitlab-runner/helpers/tls"
"gitlab.com/gitlab-org/gitlab-runner/session"
"gitlab.com/gitlab-org/gitlab-runner/session/terminal"
......@@ -637,8 +640,9 @@ func (b *Build) IsDebugTraceEnabled() bool {
return trace
}
func (b *Build) GetDockerAuthConfig() string {
return b.GetAllVariables().Get("DOCKER_AUTH_CONFIG")
func (b *Build) GetDockerAuthConfig() (map[string]types.AuthConfig, error) {
buf := bytes.NewBufferString(b.GetAllVariables().Get("DOCKER_AUTH_CONFIG"))
return docker_helpers.ReadAuthConfigsFromReader(buf)
}
func (b *Build) GetGetSourcesAttempts() int {
......
......@@ -87,8 +87,7 @@ func (e *executor) getUserAuthConfiguration(indexName string) *types.AuthConfig
return nil
}
buf := bytes.NewBufferString(e.Build.GetDockerAuthConfig())
authConfigs, _ := docker_helpers.ReadAuthConfigsFromReader(buf)
authConfigs, _ := e.Build.GetDockerAuthConfig()
if authConfigs != nil {
return docker_helpers.ResolveDockerAuthConfig(indexName, authConfigs)
}
......
......@@ -393,8 +393,22 @@ type dockerConfigEntry struct {
Username, Password string
}
func (s *executor) setupCredentials() error {
authConfigs := make(map[string]dockerConfigEntry)
func (s *executor) projectUniqueName() string {
return makeDNS1123Compatible(s.Build.ProjectUniqueName())
}
func newAuthConfigs() map[string]dockerConfigEntry {
return make(map[string]dockerConfigEntry)
}
func addDockerConfigEntries(to, from map[string]dockerConfigEntry) {
for reg, dce := range from {
to[reg] = dce
}
}
func (s *executor) getBuildAuthConfiguration() map[string]dockerConfigEntry {
authConfigs := newAuthConfigs()
for _, credentials := range s.Build.Credentials {
if credentials.Type != "registry" {
......@@ -407,17 +421,56 @@ func (s *executor) setupCredentials() error {
}
}
if len(authConfigs) == 0 {
return authConfigs
}
func (s *executor) getUserAuthConfiguration() map[string]dockerConfigEntry {
authConfigs := newAuthConfigs()
dockerAuthConfig, err := s.Build.GetDockerAuthConfig()
if err != nil {
s.Println("Failed to parse DOCKER_AUTH_CONFIG, ignoring")
return authConfigs
}
for reg, dce := range dockerAuthConfig {
authConfig := dockerConfigEntry{
Username: dce.Username,
Password: dce.Password,
}
authConfigs[reg] = authConfig
}
return authConfigs
}
func (s *executor) buildDockerConfigEntries() map[string]dockerConfigEntry {
dockerConfigEntries := newAuthConfigs()
addDockerConfigEntries(dockerConfigEntries, s.getBuildAuthConfiguration())
addDockerConfigEntries(dockerConfigEntries, s.getUserAuthConfiguration())
return dockerConfigEntries
}
func (s *executor) setupCredentials() error {
if s.Build == nil {
return nil
}
dockerConfigEntries := s.buildDockerConfigEntries()
if len(dockerConfigEntries) == 0 {
return nil
}
dockerCfgContent, err := json.Marshal(authConfigs)
dockerCfgContent, err := json.Marshal(dockerConfigEntries)
if err != nil {
return err
}
secret := api.Secret{}
secret.GenerateName = s.Build.ProjectUniqueName()
secret.GenerateName = s.projectUniqueName()
secret.Namespace = s.configurationOverwrites.namespace
secret.Type = api.SecretTypeDockercfg
secret.Data = map[string][]byte{}
......@@ -461,7 +514,7 @@ func (s *executor) setupBuildPod() error {
pod, err := s.kubeClient.CoreV1().Pods(s.configurationOverwrites.namespace).Create(&api.Pod{
ObjectMeta: metav1.ObjectMeta{
GenerateName: s.Build.ProjectUniqueName(),
GenerateName: s.projectUniqueName(),
Namespace: s.configurationOverwrites.namespace,
Labels: labels,
Annotations: annotations,
......
......@@ -892,15 +892,19 @@ func TestSetupCredentials(t *testing.T) {
version, _ := testVersionAndCodec()
type testDef struct {
Credentials []common.Credentials
VerifyFn func(*testing.T, testDef, *api.Secret)
RunnerCredentials *common.RunnerCredentials
Credentials []common.Credentials
DockerAuthConfigVar string
Name string
VerifyFn func(*testing.T, testDef, *api.Secret)
}
tests := []testDef{
{
tests := map[string]testDef{
"no credentials": {
Name: "No Credentials",
// don't execute VerifyFn
VerifyFn: nil,
},
{
"registry credentials": {
Credentials: []common.Credentials{
{
Type: "registry",
......@@ -909,12 +913,13 @@ func TestSetupCredentials(t *testing.T) {
Password: "password",
},
},
Name: "Credentials from config",
VerifyFn: func(t *testing.T, test testDef, secret *api.Secret) {
assert.Equal(t, api.SecretTypeDockercfg, secret.Type)
assert.NotEmpty(t, secret.Data[api.DockerConfigKey])
},
},
{
"other credentials": {
Credentials: []common.Credentials{
{
Type: "other",
......@@ -923,15 +928,45 @@ func TestSetupCredentials(t *testing.T) {
Password: "password",
},
},
Name: "Credentials of the wrong type from config",
// don't execute VerifyFn
VerifyFn: nil,
},
"non-DNS-1123-compatible-token": {
RunnerCredentials: &common.RunnerCredentials{
Token: "ToK3_?OF",
},
Credentials: []common.Credentials{
{
Type: "registry",
URL: "http://example.com",
Username: "user",
Password: "password",
},
},
VerifyFn: func(t *testing.T, test testDef, secret *api.Secret) {
assertDNS1123Compatibility(t, secret.GetGenerateName())
},
},
"docker credentials": {
DockerAuthConfigVar: `{"auths":{"https://registry.domain.tld:5005/v1/":{"auth":"dGVzdF91c2VyOnRlc3RfcGFzc3dvcmQ="}}}`,
Name: "Credentials from DOCKER_AUTH_CONFIG",
VerifyFn: func(t *testing.T, test testDef, secret *api.Secret) {
assert.Equal(t, api.SecretTypeDockercfg, secret.Type)
assert.NotEmpty(t, secret.Data[api.DockerConfigKey])
},
},
"broken docker credentials": {
DockerAuthConfigVar: "broken",
Name: "Broken Credentials from DOCKER_AUTH_CONFIG",
VerifyFn: nil,
},
}
executed := false
fakeClientRoundTripper := func(test testDef) func(req *http.Request) (*http.Response, error) {
return func(req *http.Request) (resp *http.Response, err error) {
podBytes, err := ioutil.ReadAll(req.Body)
secretBytes, err := ioutil.ReadAll(req.Body)
executed = true
if err != nil {
......@@ -941,7 +976,7 @@ func TestSetupCredentials(t *testing.T) {
p := new(api.Secret)
err = json.Unmarshal(podBytes, p)
err = json.Unmarshal(secretBytes, p)
if err != nil {
t.Errorf("error decoding pod: %s", err.Error())
......@@ -953,7 +988,7 @@ func TestSetupCredentials(t *testing.T) {
}
resp = &http.Response{StatusCode: http.StatusOK, Body: FakeReadCloser{
Reader: bytes.NewBuffer(podBytes),
Reader: bytes.NewBuffer(secretBytes),
}}
resp.Header = make(http.Header)
resp.Header.Add("Content-Type", "application/json")
......@@ -962,39 +997,59 @@ func TestSetupCredentials(t *testing.T) {
}
}
for _, test := range tests {
ex := executor{
kubeClient: testKubernetesClient(version, fake.CreateHTTPClient(fakeClientRoundTripper(test))),
options: &kubernetesOptions{},
AbstractExecutor: executors.AbstractExecutor{
Config: common.RunnerConfig{
RunnerSettings: common.RunnerSettings{
Kubernetes: &common.KubernetesConfig{
Namespace: "default",
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
ex := executor{
kubeClient: testKubernetesClient(version, fake.CreateHTTPClient(fakeClientRoundTripper(test))),
options: &kubernetesOptions{},
AbstractExecutor: executors.AbstractExecutor{
Config: common.RunnerConfig{
RunnerSettings: common.RunnerSettings{
Kubernetes: &common.KubernetesConfig{
Namespace: "default",
},
},
},
},
BuildShell: &common.ShellConfiguration{},
Build: &common.Build{
JobResponse: common.JobResponse{
Variables: []common.JobVariable{},
Credentials: test.Credentials,
BuildShell: &common.ShellConfiguration{},
Build: &common.Build{
JobResponse: common.JobResponse{
Variables: []common.JobVariable{},
Credentials: test.Credentials,
},
Runner: &common.RunnerConfig{},
},
Runner: &common.RunnerConfig{},
},
},
}
}
executed = false
err := ex.prepareOverwrites(make(common.JobVariables, 0))
assert.NoError(t, err)
err = ex.setupCredentials()
assert.NoError(t, err)
if test.VerifyFn != nil {
assert.True(t, executed)
} else {
assert.False(t, executed)
}
if test.DockerAuthConfigVar != "" {
ex.Build.Variables = common.JobVariables{
common.JobVariable{
Key: "DOCKER_AUTH_CONFIG",
Value: test.DockerAuthConfigVar,
},
}
}
if test.RunnerCredentials != nil {
ex.Build.Runner = &common.RunnerConfig{
RunnerCredentials: *test.RunnerCredentials,
}
}
executed = false
err := ex.prepareOverwrites(make(common.JobVariables, 0))
assert.NoError(t, err)
err = ex.setupCredentials()
assert.NoError(t, err)
if test.VerifyFn != nil {
assert.True(t, executed)
} else {
assert.False(t, executed)
}
})
}
}
......@@ -1368,6 +1423,21 @@ func TestSetupBuildPod(t *testing.T) {
assert.Equal(t, []string{"argument1", "argument2"}, pod.Spec.Containers[4].Args)
},
},
"non-DNS-1123-compatible-token": {
RunnerConfig: common.RunnerConfig{
RunnerCredentials: common.RunnerCredentials{
Token: "ToK3_?OF",
},
RunnerSettings: common.RunnerSettings{
Kubernetes: &common.KubernetesConfig{
Namespace: "default",
},
},
},
VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
assertDNS1123Compatibility(t, pod.GetGenerateName())
},
},
}
for testName, test := range tests {
......
......@@ -5,6 +5,8 @@ import (
"fmt"
"io"
"net/http"
"regexp"
"strings"
"time"
"golang.org/x/net/context"
......@@ -18,6 +20,12 @@ import (
"gitlab.com/gitlab-org/gitlab-runner/common"
)
const (
DNS1123NameMaximumLength = 63
DNS1123NotAllowedCharacters = "[^-a-z0-9]"
DNS1123NotAllowedStartCharacters = "^[^a-z0-9]+"
)
type kubeConfigProvider func() (*restclient.Config, error)
var (
......@@ -256,3 +264,19 @@ func buildVariables(bv common.JobVariables) []api.EnvVar {
}
return e
}
func makeDNS1123Compatible(name string) string {
name = strings.ToLower(name)
nameNotAllowedChars := regexp.MustCompile(DNS1123NotAllowedCharacters)
name = nameNotAllowedChars.ReplaceAllString(name, "")
nameNotAllowedStartChars := regexp.MustCompile(DNS1123NotAllowedStartCharacters)
name = nameNotAllowedStartChars.ReplaceAllString(name, "")
if len(name) > DNS1123NameMaximumLength {
name = name[0:DNS1123NameMaximumLength]
}
return name
}
......@@ -6,6 +6,7 @@ import (
"io"
"io/ioutil"
"net/http"
"regexp"
"strings"
"testing"
......@@ -400,3 +401,31 @@ func testVersionAndCodec() (version string, codec runtime.Codec) {
return
}
func assertDNS1123Compatibility(t *testing.T, name string) {
dns1123MaxLength := 63
dns1123FormatRegexp := regexp.MustCompile("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")
assert.True(t, len(name) <= dns1123MaxLength, "Name length needs to be shorter than %d", dns1123MaxLength)
assert.Regexp(t, dns1123FormatRegexp, name, "Name needs to be in DNS-1123 allowed format")
}
func TestMakeDNS1123Compatible(t *testing.T) {
examples := []struct {
name string
expected string
}{
{name: "tOk3_?ofTHE-Runner", expected: "tok3ofthe-runner"},
{name: "----tOk3_?ofTHE-Runner", expected: "tok3ofthe-runner"},
{name: "very-long-token-----------------------------------------------end", expected: "very-long-token-----------------------------------------------e"},
}
for _, example := range examples {
t.Run(example.name, func(t *testing.T) {
name := makeDNS1123Compatible(example.name)
assert.Equal(t, example.expected, name)
assertDNS1123Compatibility(t, name)
})
}
}