Skip to content

Commit

Permalink
Added support for Kubernetes pull secrets
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
  • Loading branch information
simonferquel committed Jan 16, 2019
1 parent 73af592 commit 1905a6f
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 17 deletions.
83 changes: 68 additions & 15 deletions cli/command/stack/kubernetes/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,21 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
// PullSecretExtraField is an extra field on ServiceConfigs usable to reference a pull secret
PullSecretExtraField = "x-pull-secret"
)

// NewStackConverter returns a converter from types.Config (compose) to the specified
// stack version or error out if the version is not supported or existent.
func NewStackConverter(version string) (StackConverter, error) {
switch version {
case "v1beta1":
return stackV1Beta1Converter{}, nil
case "v1beta2", "v1alpha3":
return stackV1Beta2OrHigherConverter{}, nil
case "v1beta2":
return stackV1Beta2Converter{}, nil
case "v1alpha3":
return stackV1Alpha3Converter{}, nil
default:
return nil, errors.Errorf("stack version %s unsupported", version)
}
Expand All @@ -41,7 +48,7 @@ type stackV1Beta1Converter struct{}

func (s stackV1Beta1Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
cfg.Version = v1beta1.MaxComposeVersion
st, err := fromCompose(stderr, name, cfg)
st, err := fromCompose(stderr, name, cfg, v1beta1Capabilities)
if err != nil {
return Stack{}, err
}
Expand All @@ -62,16 +69,26 @@ func (s stackV1Beta1Converter) FromCompose(stderr io.Writer, name string, cfg *c
return st, nil
}

type stackV1Beta2OrHigherConverter struct{}
type stackV1Beta2Converter struct{}

func (s stackV1Beta2Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
return fromCompose(stderr, name, cfg, v1beta2Capabilities)
}

type stackV1Alpha3Converter struct{}

func (s stackV1Beta2OrHigherConverter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
return fromCompose(stderr, name, cfg)
func (s stackV1Alpha3Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
return fromCompose(stderr, name, cfg, v1alpha3Capabilities)
}

func fromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
func fromCompose(stderr io.Writer, name string, cfg *composetypes.Config, caps composeCapabilities) (Stack, error) {
spec, err := fromComposeConfig(stderr, cfg, caps)
if err != nil {
return Stack{}, err
}
return Stack{
Name: name,
Spec: fromComposeConfig(stderr, cfg),
Spec: spec,
}, nil
}

Expand All @@ -95,11 +112,15 @@ func stackFromV1beta1(in *v1beta1.Stack) (Stack, error) {
if err != nil {
return Stack{}, err
}
spec, err := fromComposeConfig(ioutil.Discard, cfg, v1beta1Capabilities)
if err != nil {
return Stack{}, err
}
return Stack{
Name: in.ObjectMeta.Name,
Namespace: in.ObjectMeta.Namespace,
ComposeFile: in.Spec.ComposeFile,
Spec: fromComposeConfig(ioutil.Discard, cfg),
Spec: spec,
}, nil
}

Expand Down Expand Up @@ -162,20 +183,24 @@ func stackToV1alpha3(s Stack) *latest.Stack {
}
}

func fromComposeConfig(stderr io.Writer, c *composeTypes.Config) *latest.StackSpec {
func fromComposeConfig(stderr io.Writer, c *composeTypes.Config, caps composeCapabilities) (*latest.StackSpec, error) {
if c == nil {
return nil
return nil, nil
}
warnUnsupportedFeatures(stderr, c)
serviceConfigs := make([]latest.ServiceConfig, len(c.Services))
for i, s := range c.Services {
serviceConfigs[i] = fromComposeServiceConfig(s)
svc, err := fromComposeServiceConfig(s, caps)
if err != nil {
return nil, err
}
serviceConfigs[i] = svc
}
return &latest.StackSpec{
Services: serviceConfigs,
Secrets: fromComposeSecrets(c.Secrets),
Configs: fromComposeConfigs(c.Configs),
}
}, nil
}

func fromComposeSecrets(s map[string]composeTypes.SecretConfig) map[string]latest.SecretConfig {
Expand Down Expand Up @@ -216,7 +241,7 @@ func fromComposeConfigs(s map[string]composeTypes.ConfigObjConfig) map[string]la
return m
}

func fromComposeServiceConfig(s composeTypes.ServiceConfig) latest.ServiceConfig {
func fromComposeServiceConfig(s composeTypes.ServiceConfig, caps composeCapabilities) (latest.ServiceConfig, error) {
var userID *int64
if s.User != "" {
numerical, err := strconv.Atoi(s.User)
Expand All @@ -225,6 +250,15 @@ func fromComposeServiceConfig(s composeTypes.ServiceConfig) latest.ServiceConfig
userID = &unixUserID
}
}
var pullSecret string
if psInterface, ok := s.Extras[PullSecretExtraField]; ok {
if !caps.hasPullSecrets {
return latest.ServiceConfig{}, errors.Errorf("stack API version %s does not support pull secrets, please use version v1alpha3", caps.apiVersion)
}
if pullSecret, ok = psInterface.(string); !ok {
return latest.ServiceConfig{}, errors.Errorf("pull secret %v type is %T, should be a string", psInterface, psInterface)
}
}
return latest.ServiceConfig{
Name: s.Name,
CapAdd: s.CapAdd,
Expand Down Expand Up @@ -260,7 +294,8 @@ func fromComposeServiceConfig(s composeTypes.ServiceConfig) latest.ServiceConfig
User: userID,
Volumes: fromComposeServiceVolumeConfig(s.Volumes),
WorkingDir: s.WorkingDir,
}
PullSecret: pullSecret,
}, nil
}

func fromComposePorts(ports []composeTypes.ServicePortConfig) []latest.ServicePortConfig {
Expand Down Expand Up @@ -421,3 +456,21 @@ func fromComposeServiceVolumeConfig(vs []composeTypes.ServiceVolumeConfig) []lat
}
return volumes
}

var (
v1beta1Capabilities = composeCapabilities{
apiVersion: "v1beta1",
}
v1beta2Capabilities = composeCapabilities{
apiVersion: "v1beta2",
}
v1alpha3Capabilities = composeCapabilities{
apiVersion: "v1alpha3",
hasPullSecrets: true,
}
)

type composeCapabilities struct {
apiVersion string
hasPullSecrets bool
}
45 changes: 45 additions & 0 deletions cli/command/stack/kubernetes/convert_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package kubernetes

import (
"io/ioutil"
"testing"

"github.com/docker/cli/cli/compose/loader"
composetypes "github.com/docker/cli/cli/compose/types"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
Expand All @@ -18,3 +21,45 @@ func TestNewStackConverter(t *testing.T) {
_, err = NewStackConverter("v1alpha3")
assert.NilError(t, err)
}

func loadTestStackWithPullSecret(t *testing.T) *composetypes.Config {
t.Helper()
data, err := ioutil.ReadFile("testdata/compose-with-pull-secret.yml")
assert.NilError(t, err)
yamlData, err := loader.ParseYAML(data)
assert.NilError(t, err)
cfg, err := loader.Load(composetypes.ConfigDetails{
ConfigFiles: []composetypes.ConfigFile{
{Config: yamlData, Filename: "testdata/compose-with-pull-secret.yml"},
},
})
assert.NilError(t, err)
return cfg
}

func TestHandlePullSecret(t *testing.T) {
testData := loadTestStackWithPullSecret(t)
cases := []struct {
version string
err string
}{
{version: "v1beta1", err: "stack API version v1beta1 does not support pull secrets, please use version v1alpha3"},
{version: "v1beta2", err: "stack API version v1beta2 does not support pull secrets, please use version v1alpha3"},
{version: "v1alpha3"},
}

for _, c := range cases {
t.Run(c.version, func(t *testing.T) {
conv, err := NewStackConverter(c.version)
assert.NilError(t, err)
s, err := conv.FromCompose(ioutil.Discard, "test", testData)
if c.err != "" {
assert.Error(t, err, c.err)

} else {
assert.NilError(t, err)
assert.Equal(t, s.Spec.Services[0].PullSecret, "some-secret")
}
})
}
}
4 changes: 2 additions & 2 deletions cli/command/stack/kubernetes/stackclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func verify(services corev1.ServiceInterface, stackName string, service string)

// stackV1Beta2 implements stackClient interface and talks to compose component v1beta2.
type stackV1Beta2 struct {
stackV1Beta2OrHigherConverter
stackV1Beta2Converter
stacks composev1beta2.StackInterface
}

Expand Down Expand Up @@ -203,7 +203,7 @@ func (s *stackV1Beta2) IsColliding(servicesClient corev1.ServiceInterface, st St

// stackV1Beta2 implements stackClient interface and talks to compose component v1beta2.
type stackV1Alpha3 struct {
stackV1Beta2OrHigherConverter
stackV1Alpha3Converter
stacks composev1alpha3.StackInterface
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: "3.7"
services:
test:
image: "some-private-image"
x-pull-secret: "some-secret"

0 comments on commit 1905a6f

Please sign in to comment.