Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Kubernetes Pull secrets and Pull policies #1617

Merged
merged 2 commits into from
Jan 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 94 additions & 15 deletions cli/command/stack/kubernetes/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,23 @@ 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"
// pullPolicyExtraField is an extra field on ServiceConfigs usable to specify a pull policy
pullPolicyExtraField = "x-pull-policy"
)

// 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 +50,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 +71,26 @@ func (s stackV1Beta1Converter) FromCompose(stderr io.Writer, name string, cfg *c
return st, nil
}

type stackV1Beta2OrHigherConverter struct{}
type stackV1Beta2Converter struct{}

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

func fromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
type stackV1Alpha3Converter struct{}

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, capabilities composeCapabilities) (Stack, error) {
spec, err := fromComposeConfig(stderr, cfg, capabilities)
if err != nil {
return Stack{}, err
}
return Stack{
Name: name,
Spec: fromComposeConfig(stderr, cfg),
Spec: spec,
}, nil
}

Expand All @@ -95,11 +114,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 +185,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, capabilities 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, capabilities)
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,15 +243,34 @@ func fromComposeConfigs(s map[string]composeTypes.ConfigObjConfig) map[string]la
return m
}

func fromComposeServiceConfig(s composeTypes.ServiceConfig) latest.ServiceConfig {
var userID *int64
func fromComposeServiceConfig(s composeTypes.ServiceConfig, capabilities composeCapabilities) (latest.ServiceConfig, error) {
var (
userID *int64
pullSecret string
pullPolicy string
err error
)
if s.User != "" {
numerical, err := strconv.Atoi(s.User)
if err == nil {
unixUserID := int64(numerical)
userID = &unixUserID
}
}
pullSecret, err = resolveServiceExtra(s, pullSecretExtraField)
if err != nil {
return latest.ServiceConfig{}, err
}
pullPolicy, err = resolveServiceExtra(s, pullPolicyExtraField)
if err != nil {
return latest.ServiceConfig{}, err
}
if pullSecret != "" && !capabilities.hasPullSecrets {
return latest.ServiceConfig{}, errors.Errorf("stack API version %s does not support pull secrets (field %q), please use version v1alpha3 or higher", capabilities.apiVersion, pullSecretExtraField)
}
if pullPolicy != "" && !capabilities.hasPullPolicies {
return latest.ServiceConfig{}, errors.Errorf("stack API version %s does not support pull policies (field %q), please use version v1alpha3 or higher", capabilities.apiVersion, pullPolicyExtraField)
}
return latest.ServiceConfig{
Name: s.Name,
CapAdd: s.CapAdd,
Expand Down Expand Up @@ -260,7 +306,20 @@ func fromComposeServiceConfig(s composeTypes.ServiceConfig) latest.ServiceConfig
User: userID,
Volumes: fromComposeServiceVolumeConfig(s.Volumes),
WorkingDir: s.WorkingDir,
PullSecret: pullSecret,
PullPolicy: pullPolicy,
}, nil
}

func resolveServiceExtra(s composeTypes.ServiceConfig, field string) (string, error) {
if iface, ok := s.Extras[field]; ok {
value, ok := iface.(string)
if !ok {
return "", errors.Errorf("field %q: value %v type is %T, should be a string", field, iface, iface)
}
return value, nil
}
return "", nil
}

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

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

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

import (
"fmt"
"io/ioutil"
"path/filepath"
"testing"

"github.com/docker/cli/cli/compose/loader"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/compose-on-kubernetes/api/compose/v1alpha3"
"github.com/docker/compose-on-kubernetes/api/compose/v1beta1"
"github.com/docker/compose-on-kubernetes/api/compose/v1beta2"
Expand Down Expand Up @@ -161,3 +165,73 @@ func TestConvertFromToV1alpha3(t *testing.T) {
gotBack := stackToV1alpha3(result)
assert.DeepEqual(t, stackv1alpha3, gotBack)
}

func loadTestStackWith(t *testing.T, with string) *composetypes.Config {
t.Helper()
filePath := fmt.Sprintf("testdata/compose-with-%s.yml", with)
data, err := ioutil.ReadFile(filePath)
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: filePath},
},
})
assert.NilError(t, err)
return cfg
}

func TestHandlePullSecret(t *testing.T) {
testData := loadTestStackWith(t, "pull-secret")
cases := []struct {
version string
err string
}{
{version: "v1beta1", err: `stack API version v1beta1 does not support pull secrets (field "x-pull-secret"), please use version v1alpha3 or higher`},
{version: "v1beta2", err: `stack API version v1beta2 does not support pull secrets (field "x-pull-secret"), please use version v1alpha3 or higher`},
{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")
}
})
}
}

func TestHandlePullPolicy(t *testing.T) {
testData := loadTestStackWith(t, "pull-policy")
cases := []struct {
version string
err string
}{
{version: "v1beta1", err: `stack API version v1beta1 does not support pull policies (field "x-pull-policy"), please use version v1alpha3 or higher`},
{version: "v1beta2", err: `stack API version v1beta2 does not support pull policies (field "x-pull-policy"), please use version v1alpha3 or higher`},
{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].PullPolicy, "Never")
}
})
}
}
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-image"
x-pull-policy: "Never"
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"
2 changes: 1 addition & 1 deletion vendor.conf
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 # v1.1.0
github.com/dgrijalva/jwt-go a2c85815a77d0f951e33ba4db5ae93629a1530af
github.com/docker/distribution 83389a148052d74ac602f5f1d62f86ff2f3c4aa5
github.com/docker/docker f76d6a078d881f410c00e8d900dcdfc2e026c841
github.com/docker/compose-on-kubernetes 1559927c6b456d56cc9c9b05438252ebb646640b # master w/ v1alpha3
github.com/docker/compose-on-kubernetes 356b2919c496f7e988f6e0dfe7e67d919602e14e # master w/ v1alpha3+pullsecrets+pull-policy
github.com/docker/docker-credential-helpers 5241b46610f2491efdf9d1c85f1ddf5b02f6d962
# the docker/go package contains a customized version of canonical/json
# and is used by Notary. The package is periodically rebased on current Go versions.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.