Skip to content

Commit f2e6ee6

Browse files
committed
Extract StackConverter from the StackClient
It makes it easier to get the correct stack from a compose config struct without requiring the client (and thus talking to k8s API) Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 204ab4c commit f2e6ee6

File tree

7 files changed

+140
-102
lines changed

7 files changed

+140
-102
lines changed

cli/command/stack/kubernetes/convert.go

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,72 @@ import (
88
"strings"
99

1010
"github.com/docker/cli/cli/compose/loader"
11+
"github.com/docker/cli/cli/compose/schema"
1112
composeTypes "github.com/docker/cli/cli/compose/types"
1213
composetypes "github.com/docker/cli/cli/compose/types"
1314
"github.com/docker/cli/kubernetes/compose/v1beta1"
1415
"github.com/docker/cli/kubernetes/compose/v1beta2"
16+
"github.com/pkg/errors"
17+
yaml "gopkg.in/yaml.v2"
1518
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1619
)
1720

21+
// NewStackConverter returns a converter from types.Config (compose) to the specified
22+
// stack version or error out if the version is not supported or existent.
23+
func NewStackConverter(version string) (StackConverter, error) {
24+
switch version {
25+
case "v1beta1":
26+
return stackV1Beta1Converter{}, nil
27+
case "v1beta2":
28+
return stackV1Beta2Converter{}, nil
29+
default:
30+
return nil, errors.Errorf("stack version %s unsupported", version)
31+
}
32+
}
33+
34+
// StackConverter converts a compose types.Config to a Stack
35+
type StackConverter interface {
36+
FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error)
37+
}
38+
39+
type stackV1Beta1Converter struct{}
40+
41+
func (s stackV1Beta1Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
42+
cfg.Version = v1beta1.MaxComposeVersion
43+
st, err := fromCompose(stderr, name, cfg)
44+
if err != nil {
45+
return Stack{}, err
46+
}
47+
res, err := yaml.Marshal(cfg)
48+
if err != nil {
49+
return Stack{}, err
50+
}
51+
// reload the result to check that it produced a valid 3.5 compose file
52+
resparsedConfig, err := loader.ParseYAML(res)
53+
if err != nil {
54+
return Stack{}, err
55+
}
56+
if err = schema.Validate(resparsedConfig, v1beta1.MaxComposeVersion); err != nil {
57+
return Stack{}, errors.Wrapf(err, "the compose yaml file is invalid with v%s", v1beta1.MaxComposeVersion)
58+
}
59+
60+
st.ComposeFile = string(res)
61+
return st, nil
62+
}
63+
64+
type stackV1Beta2Converter struct{}
65+
66+
func (s stackV1Beta2Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
67+
return fromCompose(stderr, name, cfg)
68+
}
69+
70+
func fromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
71+
return Stack{
72+
Name: name,
73+
Spec: fromComposeConfig(stderr, cfg),
74+
}, nil
75+
}
76+
1877
func loadStackData(composefile string) (*composetypes.Config, error) {
1978
parsed, err := loader.ParseYAML([]byte(composefile))
2079
if err != nil {
@@ -30,44 +89,44 @@ func loadStackData(composefile string) (*composetypes.Config, error) {
3089
}
3190

3291
// Conversions from internal stack to different stack compose component versions.
33-
func stackFromV1beta1(in *v1beta1.Stack) (stack, error) {
92+
func stackFromV1beta1(in *v1beta1.Stack) (Stack, error) {
3493
cfg, err := loadStackData(in.Spec.ComposeFile)
3594
if err != nil {
36-
return stack{}, err
95+
return Stack{}, err
3796
}
38-
return stack{
39-
name: in.ObjectMeta.Name,
40-
namespace: in.ObjectMeta.Namespace,
41-
composeFile: in.Spec.ComposeFile,
42-
spec: fromComposeConfig(ioutil.Discard, cfg),
97+
return Stack{
98+
Name: in.ObjectMeta.Name,
99+
Namespace: in.ObjectMeta.Namespace,
100+
ComposeFile: in.Spec.ComposeFile,
101+
Spec: fromComposeConfig(ioutil.Discard, cfg),
43102
}, nil
44103
}
45104

46-
func stackToV1beta1(s stack) *v1beta1.Stack {
105+
func stackToV1beta1(s Stack) *v1beta1.Stack {
47106
return &v1beta1.Stack{
48107
ObjectMeta: metav1.ObjectMeta{
49-
Name: s.name,
108+
Name: s.Name,
50109
},
51110
Spec: v1beta1.StackSpec{
52-
ComposeFile: s.composeFile,
111+
ComposeFile: s.ComposeFile,
53112
},
54113
}
55114
}
56115

57-
func stackFromV1beta2(in *v1beta2.Stack) stack {
58-
return stack{
59-
name: in.ObjectMeta.Name,
60-
namespace: in.ObjectMeta.Namespace,
61-
spec: in.Spec,
116+
func stackFromV1beta2(in *v1beta2.Stack) Stack {
117+
return Stack{
118+
Name: in.ObjectMeta.Name,
119+
Namespace: in.ObjectMeta.Namespace,
120+
Spec: in.Spec,
62121
}
63122
}
64123

65-
func stackToV1beta2(s stack) *v1beta2.Stack {
124+
func stackToV1beta2(s Stack) *v1beta2.Stack {
66125
return &v1beta2.Stack{
67126
ObjectMeta: metav1.ObjectMeta{
68-
Name: s.name,
127+
Name: s.Name,
69128
},
70-
Spec: s.spec,
129+
Spec: s.Spec,
71130
}
72131
}
73132

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package kubernetes
2+
3+
import (
4+
"testing"
5+
6+
"gotest.tools/assert"
7+
is "gotest.tools/assert/cmp"
8+
)
9+
10+
func TestNewStackConverter(t *testing.T) {
11+
_, err := NewStackConverter("v1alpha1")
12+
assert.Check(t, is.ErrorContains(err, "stack version v1alpha1 unsupported"))
13+
14+
_, err = NewStackConverter("v1beta1")
15+
assert.NilError(t, err)
16+
_, err = NewStackConverter("v1beta2")
17+
assert.NilError(t, err)
18+
}

cli/command/stack/kubernetes/deploy.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ func RunDeploy(dockerCli *KubeCli, opts options.Deploy, cfg *composetypes.Config
7575
}
7676
}()
7777

78-
err = watcher.Watch(stack.name, stack.getServices(), statusUpdates)
78+
err = watcher.Watch(stack.Name, stack.getServices(), statusUpdates)
7979
close(statusUpdates)
8080
<-displayDone
8181
if err != nil {
8282
return err
8383
}
84-
fmt.Fprintf(cmdOut, "\nStack %s is stable and running\n\n", stack.name)
84+
fmt.Fprintf(cmdOut, "\nStack %s is stable and running\n\n", stack.Name)
8585
return nil
8686

8787
}

cli/command/stack/kubernetes/list.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ func getStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error)
4848
var formattedStacks []*formatter.Stack
4949
for _, stack := range stacks {
5050
formattedStacks = append(formattedStacks, &formatter.Stack{
51-
Name: stack.name,
51+
Name: stack.Name,
5252
Services: len(stack.getServices()),
5353
Orchestrator: "Kubernetes",
54-
Namespace: stack.namespace,
54+
Namespace: stack.Namespace,
5555
})
5656
}
5757
return formattedStacks, nil

cli/command/stack/kubernetes/stack.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,27 @@ import (
1212
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
1313
)
1414

15-
// stack is the main type used by stack commands so they remain independent from kubernetes compose component version.
16-
type stack struct {
17-
name string
18-
namespace string
19-
composeFile string
20-
spec *v1beta2.StackSpec
15+
// Stack is the main type used by stack commands so they remain independent from kubernetes compose component version.
16+
type Stack struct {
17+
Name string
18+
Namespace string
19+
ComposeFile string
20+
Spec *v1beta2.StackSpec
2121
}
2222

2323
// getServices returns all the stack service names, sorted lexicographically
24-
func (s *stack) getServices() []string {
25-
services := make([]string, len(s.spec.Services))
26-
for i, service := range s.spec.Services {
24+
func (s *Stack) getServices() []string {
25+
services := make([]string, len(s.Spec.Services))
26+
for i, service := range s.Spec.Services {
2727
services[i] = service.Name
2828
}
2929
sort.Strings(services)
3030
return services
3131
}
3232

3333
// createFileBasedConfigMaps creates a Kubernetes ConfigMap for each Compose global file-based config.
34-
func (s *stack) createFileBasedConfigMaps(configMaps corev1.ConfigMapInterface) error {
35-
for name, config := range s.spec.Configs {
34+
func (s *Stack) createFileBasedConfigMaps(configMaps corev1.ConfigMapInterface) error {
35+
for name, config := range s.Spec.Configs {
3636
if config.File == "" {
3737
continue
3838
}
@@ -43,7 +43,7 @@ func (s *stack) createFileBasedConfigMaps(configMaps corev1.ConfigMapInterface)
4343
return err
4444
}
4545

46-
if _, err := configMaps.Create(toConfigMap(s.name, name, fileName, content)); err != nil {
46+
if _, err := configMaps.Create(toConfigMap(s.Name, name, fileName, content)); err != nil {
4747
return err
4848
}
4949
}
@@ -71,8 +71,8 @@ func toConfigMap(stackName, name, key string, content []byte) *apiv1.ConfigMap {
7171
}
7272

7373
// createFileBasedSecrets creates a Kubernetes Secret for each Compose global file-based secret.
74-
func (s *stack) createFileBasedSecrets(secrets corev1.SecretInterface) error {
75-
for name, secret := range s.spec.Secrets {
74+
func (s *Stack) createFileBasedSecrets(secrets corev1.SecretInterface) error {
75+
for name, secret := range s.Spec.Secrets {
7676
if secret.File == "" {
7777
continue
7878
}
@@ -83,7 +83,7 @@ func (s *stack) createFileBasedSecrets(secrets corev1.SecretInterface) error {
8383
return err
8484
}
8585

86-
if _, err := secrets.Create(toSecret(s.name, name, fileName, content)); err != nil {
86+
if _, err := secrets.Create(toSecret(s.Name, name, fileName, content)); err != nil {
8787
return err
8888
}
8989
}

0 commit comments

Comments
 (0)