Skip to content

Commit 5486cdd

Browse files
authored
Merge pull request #1617 from simonferquel/pull-secrets
Add support for Kubernetes Pull secrets and Pull policies
2 parents b258f45 + d184c09 commit 5486cdd

7 files changed

Lines changed: 183 additions & 18 deletions

File tree

cli/command/stack/kubernetes/convert.go

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,23 @@ import (
1919
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020
)
2121

22+
const (
23+
// pullSecretExtraField is an extra field on ServiceConfigs usable to reference a pull secret
24+
pullSecretExtraField = "x-pull-secret"
25+
// pullPolicyExtraField is an extra field on ServiceConfigs usable to specify a pull policy
26+
pullPolicyExtraField = "x-pull-policy"
27+
)
28+
2229
// NewStackConverter returns a converter from types.Config (compose) to the specified
2330
// stack version or error out if the version is not supported or existent.
2431
func NewStackConverter(version string) (StackConverter, error) {
2532
switch version {
2633
case "v1beta1":
2734
return stackV1Beta1Converter{}, nil
28-
case "v1beta2", "v1alpha3":
29-
return stackV1Beta2OrHigherConverter{}, nil
35+
case "v1beta2":
36+
return stackV1Beta2Converter{}, nil
37+
case "v1alpha3":
38+
return stackV1Alpha3Converter{}, nil
3039
default:
3140
return nil, errors.Errorf("stack version %s unsupported", version)
3241
}
@@ -41,7 +50,7 @@ type stackV1Beta1Converter struct{}
4150

4251
func (s stackV1Beta1Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
4352
cfg.Version = v1beta1.MaxComposeVersion
44-
st, err := fromCompose(stderr, name, cfg)
53+
st, err := fromCompose(stderr, name, cfg, v1beta1Capabilities)
4554
if err != nil {
4655
return Stack{}, err
4756
}
@@ -62,16 +71,26 @@ func (s stackV1Beta1Converter) FromCompose(stderr io.Writer, name string, cfg *c
6271
return st, nil
6372
}
6473

65-
type stackV1Beta2OrHigherConverter struct{}
74+
type stackV1Beta2Converter struct{}
6675

67-
func (s stackV1Beta2OrHigherConverter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
68-
return fromCompose(stderr, name, cfg)
76+
func (s stackV1Beta2Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
77+
return fromCompose(stderr, name, cfg, v1beta2Capabilities)
6978
}
7079

71-
func fromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
80+
type stackV1Alpha3Converter struct{}
81+
82+
func (s stackV1Alpha3Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
83+
return fromCompose(stderr, name, cfg, v1alpha3Capabilities)
84+
}
85+
86+
func fromCompose(stderr io.Writer, name string, cfg *composetypes.Config, capabilities composeCapabilities) (Stack, error) {
87+
spec, err := fromComposeConfig(stderr, cfg, capabilities)
88+
if err != nil {
89+
return Stack{}, err
90+
}
7291
return Stack{
7392
Name: name,
74-
Spec: fromComposeConfig(stderr, cfg),
93+
Spec: spec,
7594
}, nil
7695
}
7796

@@ -95,11 +114,15 @@ func stackFromV1beta1(in *v1beta1.Stack) (Stack, error) {
95114
if err != nil {
96115
return Stack{}, err
97116
}
117+
spec, err := fromComposeConfig(ioutil.Discard, cfg, v1beta1Capabilities)
118+
if err != nil {
119+
return Stack{}, err
120+
}
98121
return Stack{
99122
Name: in.ObjectMeta.Name,
100123
Namespace: in.ObjectMeta.Namespace,
101124
ComposeFile: in.Spec.ComposeFile,
102-
Spec: fromComposeConfig(ioutil.Discard, cfg),
125+
Spec: spec,
103126
}, nil
104127
}
105128

@@ -162,20 +185,24 @@ func stackToV1alpha3(s Stack) *latest.Stack {
162185
}
163186
}
164187

165-
func fromComposeConfig(stderr io.Writer, c *composeTypes.Config) *latest.StackSpec {
188+
func fromComposeConfig(stderr io.Writer, c *composeTypes.Config, capabilities composeCapabilities) (*latest.StackSpec, error) {
166189
if c == nil {
167-
return nil
190+
return nil, nil
168191
}
169192
warnUnsupportedFeatures(stderr, c)
170193
serviceConfigs := make([]latest.ServiceConfig, len(c.Services))
171194
for i, s := range c.Services {
172-
serviceConfigs[i] = fromComposeServiceConfig(s)
195+
svc, err := fromComposeServiceConfig(s, capabilities)
196+
if err != nil {
197+
return nil, err
198+
}
199+
serviceConfigs[i] = svc
173200
}
174201
return &latest.StackSpec{
175202
Services: serviceConfigs,
176203
Secrets: fromComposeSecrets(c.Secrets),
177204
Configs: fromComposeConfigs(c.Configs),
178-
}
205+
}, nil
179206
}
180207

181208
func fromComposeSecrets(s map[string]composeTypes.SecretConfig) map[string]latest.SecretConfig {
@@ -216,15 +243,34 @@ func fromComposeConfigs(s map[string]composeTypes.ConfigObjConfig) map[string]la
216243
return m
217244
}
218245

219-
func fromComposeServiceConfig(s composeTypes.ServiceConfig) latest.ServiceConfig {
220-
var userID *int64
246+
func fromComposeServiceConfig(s composeTypes.ServiceConfig, capabilities composeCapabilities) (latest.ServiceConfig, error) {
247+
var (
248+
userID *int64
249+
pullSecret string
250+
pullPolicy string
251+
err error
252+
)
221253
if s.User != "" {
222254
numerical, err := strconv.Atoi(s.User)
223255
if err == nil {
224256
unixUserID := int64(numerical)
225257
userID = &unixUserID
226258
}
227259
}
260+
pullSecret, err = resolveServiceExtra(s, pullSecretExtraField)
261+
if err != nil {
262+
return latest.ServiceConfig{}, err
263+
}
264+
pullPolicy, err = resolveServiceExtra(s, pullPolicyExtraField)
265+
if err != nil {
266+
return latest.ServiceConfig{}, err
267+
}
268+
if pullSecret != "" && !capabilities.hasPullSecrets {
269+
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)
270+
}
271+
if pullPolicy != "" && !capabilities.hasPullPolicies {
272+
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)
273+
}
228274
return latest.ServiceConfig{
229275
Name: s.Name,
230276
CapAdd: s.CapAdd,
@@ -260,7 +306,20 @@ func fromComposeServiceConfig(s composeTypes.ServiceConfig) latest.ServiceConfig
260306
User: userID,
261307
Volumes: fromComposeServiceVolumeConfig(s.Volumes),
262308
WorkingDir: s.WorkingDir,
309+
PullSecret: pullSecret,
310+
PullPolicy: pullPolicy,
311+
}, nil
312+
}
313+
314+
func resolveServiceExtra(s composeTypes.ServiceConfig, field string) (string, error) {
315+
if iface, ok := s.Extras[field]; ok {
316+
value, ok := iface.(string)
317+
if !ok {
318+
return "", errors.Errorf("field %q: value %v type is %T, should be a string", field, iface, iface)
319+
}
320+
return value, nil
263321
}
322+
return "", nil
264323
}
265324

266325
func fromComposePorts(ports []composeTypes.ServicePortConfig) []latest.ServicePortConfig {
@@ -421,3 +480,23 @@ func fromComposeServiceVolumeConfig(vs []composeTypes.ServiceVolumeConfig) []lat
421480
}
422481
return volumes
423482
}
483+
484+
var (
485+
v1beta1Capabilities = composeCapabilities{
486+
apiVersion: "v1beta1",
487+
}
488+
v1beta2Capabilities = composeCapabilities{
489+
apiVersion: "v1beta2",
490+
}
491+
v1alpha3Capabilities = composeCapabilities{
492+
apiVersion: "v1alpha3",
493+
hasPullSecrets: true,
494+
hasPullPolicies: true,
495+
}
496+
)
497+
498+
type composeCapabilities struct {
499+
apiVersion string
500+
hasPullSecrets bool
501+
hasPullPolicies bool
502+
}

cli/command/stack/kubernetes/convert_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package kubernetes
22

33
import (
4+
"fmt"
5+
"io/ioutil"
46
"path/filepath"
57
"testing"
68

9+
"github.com/docker/cli/cli/compose/loader"
10+
composetypes "github.com/docker/cli/cli/compose/types"
711
"github.com/docker/compose-on-kubernetes/api/compose/v1alpha3"
812
"github.com/docker/compose-on-kubernetes/api/compose/v1beta1"
913
"github.com/docker/compose-on-kubernetes/api/compose/v1beta2"
@@ -161,3 +165,73 @@ func TestConvertFromToV1alpha3(t *testing.T) {
161165
gotBack := stackToV1alpha3(result)
162166
assert.DeepEqual(t, stackv1alpha3, gotBack)
163167
}
168+
169+
func loadTestStackWith(t *testing.T, with string) *composetypes.Config {
170+
t.Helper()
171+
filePath := fmt.Sprintf("testdata/compose-with-%s.yml", with)
172+
data, err := ioutil.ReadFile(filePath)
173+
assert.NilError(t, err)
174+
yamlData, err := loader.ParseYAML(data)
175+
assert.NilError(t, err)
176+
cfg, err := loader.Load(composetypes.ConfigDetails{
177+
ConfigFiles: []composetypes.ConfigFile{
178+
{Config: yamlData, Filename: filePath},
179+
},
180+
})
181+
assert.NilError(t, err)
182+
return cfg
183+
}
184+
185+
func TestHandlePullSecret(t *testing.T) {
186+
testData := loadTestStackWith(t, "pull-secret")
187+
cases := []struct {
188+
version string
189+
err string
190+
}{
191+
{version: "v1beta1", err: `stack API version v1beta1 does not support pull secrets (field "x-pull-secret"), please use version v1alpha3 or higher`},
192+
{version: "v1beta2", err: `stack API version v1beta2 does not support pull secrets (field "x-pull-secret"), please use version v1alpha3 or higher`},
193+
{version: "v1alpha3"},
194+
}
195+
196+
for _, c := range cases {
197+
t.Run(c.version, func(t *testing.T) {
198+
conv, err := NewStackConverter(c.version)
199+
assert.NilError(t, err)
200+
s, err := conv.FromCompose(ioutil.Discard, "test", testData)
201+
if c.err != "" {
202+
assert.Error(t, err, c.err)
203+
204+
} else {
205+
assert.NilError(t, err)
206+
assert.Equal(t, s.Spec.Services[0].PullSecret, "some-secret")
207+
}
208+
})
209+
}
210+
}
211+
212+
func TestHandlePullPolicy(t *testing.T) {
213+
testData := loadTestStackWith(t, "pull-policy")
214+
cases := []struct {
215+
version string
216+
err string
217+
}{
218+
{version: "v1beta1", err: `stack API version v1beta1 does not support pull policies (field "x-pull-policy"), please use version v1alpha3 or higher`},
219+
{version: "v1beta2", err: `stack API version v1beta2 does not support pull policies (field "x-pull-policy"), please use version v1alpha3 or higher`},
220+
{version: "v1alpha3"},
221+
}
222+
223+
for _, c := range cases {
224+
t.Run(c.version, func(t *testing.T) {
225+
conv, err := NewStackConverter(c.version)
226+
assert.NilError(t, err)
227+
s, err := conv.FromCompose(ioutil.Discard, "test", testData)
228+
if c.err != "" {
229+
assert.Error(t, err, c.err)
230+
231+
} else {
232+
assert.NilError(t, err)
233+
assert.Equal(t, s.Spec.Services[0].PullPolicy, "Never")
234+
}
235+
})
236+
}
237+
}

cli/command/stack/kubernetes/stackclient.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ func verify(services corev1.ServiceInterface, stackName string, service string)
125125

126126
// stackV1Beta2 implements stackClient interface and talks to compose component v1beta2.
127127
type stackV1Beta2 struct {
128-
stackV1Beta2OrHigherConverter
128+
stackV1Beta2Converter
129129
stacks composev1beta2.StackInterface
130130
}
131131

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

204204
// stackV1Beta2 implements stackClient interface and talks to compose component v1beta2.
205205
type stackV1Alpha3 struct {
206-
stackV1Beta2OrHigherConverter
206+
stackV1Alpha3Converter
207207
stacks composev1alpha3.StackInterface
208208
}
209209

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
version: "3.7"
2+
services:
3+
test:
4+
image: "some-image"
5+
x-pull-policy: "Never"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
version: "3.7"
2+
services:
3+
test:
4+
image: "some-private-image"
5+
x-pull-secret: "some-secret"

vendor.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 # v1.1.0
1414
github.com/dgrijalva/jwt-go a2c85815a77d0f951e33ba4db5ae93629a1530af
1515
github.com/docker/distribution 83389a148052d74ac602f5f1d62f86ff2f3c4aa5
1616
github.com/docker/docker f76d6a078d881f410c00e8d900dcdfc2e026c841
17-
github.com/docker/compose-on-kubernetes 1559927c6b456d56cc9c9b05438252ebb646640b # master w/ v1alpha3
17+
github.com/docker/compose-on-kubernetes 356b2919c496f7e988f6e0dfe7e67d919602e14e # master w/ v1alpha3+pullsecrets+pull-policy
1818
github.com/docker/docker-credential-helpers 5241b46610f2491efdf9d1c85f1ddf5b02f6d962
1919
# the docker/go package contains a customized version of canonical/json
2020
# and is used by Notary. The package is periodically rebased on current Go versions.

vendor/github.com/docker/compose-on-kubernetes/api/compose/v1alpha3/stack.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)