Handle version v1alpha3

Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
This commit is contained in:
Simon Ferquel 2019-01-16 12:56:37 +01:00
parent c863dbabf7
commit 2e5981d613
10 changed files with 375 additions and 79 deletions

View File

@ -103,7 +103,7 @@ func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) {
}
func (c *KubeCli) composeClient() (*Factory, error) {
return NewFactory(c.kubeNamespace, c.kubeConfig, c.clientSet)
return NewFactory(c.kubeNamespace, c.kubeConfig, c.clientSet, c.ClientInfo().HasExperimental)
}
func (c *KubeCli) checkHostsMatch() error {

View File

@ -1,7 +1,7 @@
package kubernetes
import (
kubernetes "github.com/docker/compose-on-kubernetes/api"
"github.com/docker/cli/kubernetes"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeclient "k8s.io/client-go/kubernetes"
@ -18,10 +18,11 @@ type Factory struct {
coreClientSet corev1.CoreV1Interface
appsClientSet appsv1beta2.AppsV1beta2Interface
clientSet *kubeclient.Clientset
experimental bool
}
// NewFactory creates a kubernetes client factory
func NewFactory(namespace string, config *restclient.Config, clientSet *kubeclient.Clientset) (*Factory, error) {
func NewFactory(namespace string, config *restclient.Config, clientSet *kubeclient.Clientset, experimental bool) (*Factory, error) {
coreClientSet, err := corev1.NewForConfig(config)
if err != nil {
return nil, err
@ -38,6 +39,7 @@ func NewFactory(namespace string, config *restclient.Config, clientSet *kubeclie
coreClientSet: coreClientSet,
appsClientSet: appsClientSet,
clientSet: clientSet,
experimental: experimental,
}, nil
}
@ -83,7 +85,7 @@ func (s *Factory) DaemonSets() typesappsv1beta2.DaemonSetInterface {
// Stacks returns a client for Docker's Stack on Kubernetes
func (s *Factory) Stacks(allNamespaces bool) (StackClient, error) {
version, err := kubernetes.GetStackAPIVersion(s.clientSet)
version, err := kubernetes.GetStackAPIVersion(s.clientSet.Discovery(), s.experimental)
if err != nil {
return nil, err
}
@ -97,7 +99,9 @@ func (s *Factory) Stacks(allNamespaces bool) (StackClient, error) {
return newStackV1Beta1(s.config, namespace)
case kubernetes.StackAPIV1Beta2:
return newStackV1Beta2(s.config, namespace)
case kubernetes.StackAPIV1Alpha3:
return newStackV1Alpha3(s.config, namespace)
default:
return nil, errors.Errorf("no supported Stack API version")
return nil, errors.Errorf("unsupported stack API version: %q", version)
}
}

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/cli/cli/compose/schema"
composeTypes "github.com/docker/cli/cli/compose/types"
composetypes "github.com/docker/cli/cli/compose/types"
latest "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"
"github.com/pkg/errors"
@ -24,8 +25,8 @@ func NewStackConverter(version string) (StackConverter, error) {
switch version {
case "v1beta1":
return stackV1Beta1Converter{}, nil
case "v1beta2":
return stackV1Beta2Converter{}, nil
case "v1beta2", "v1alpha3":
return stackV1Beta2OrHigherConverter{}, nil
default:
return nil, errors.Errorf("stack version %s unsupported", version)
}
@ -61,9 +62,9 @@ func (s stackV1Beta1Converter) FromCompose(stderr io.Writer, name string, cfg *c
return st, nil
}
type stackV1Beta2Converter struct{}
type stackV1Beta2OrHigherConverter struct{}
func (s stackV1Beta2Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
func (s stackV1Beta2OrHigherConverter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
return fromCompose(stderr, name, cfg)
}
@ -113,7 +114,38 @@ func stackToV1beta1(s Stack) *v1beta1.Stack {
}
}
func stackFromV1beta2(in *v1beta2.Stack) Stack {
func stackFromV1beta2(in *v1beta2.Stack) (Stack, error) {
var spec *latest.StackSpec
if in.Spec != nil {
spec = &latest.StackSpec{}
if err := latest.Convert_v1beta2_StackSpec_To_v1alpha3_StackSpec(in.Spec, spec, nil); err != nil {
return Stack{}, err
}
}
return Stack{
Name: in.ObjectMeta.Name,
Namespace: in.ObjectMeta.Namespace,
Spec: spec,
}, nil
}
func stackToV1beta2(s Stack) (*v1beta2.Stack, error) {
var spec *v1beta2.StackSpec
if s.Spec != nil {
spec = &v1beta2.StackSpec{}
if err := latest.Convert_v1alpha3_StackSpec_To_v1beta2_StackSpec(s.Spec, spec, nil); err != nil {
return nil, err
}
}
return &v1beta2.Stack{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
},
Spec: spec,
}, nil
}
func stackFromV1alpha3(in *latest.Stack) Stack {
return Stack{
Name: in.ObjectMeta.Name,
Namespace: in.ObjectMeta.Namespace,
@ -121,8 +153,8 @@ func stackFromV1beta2(in *v1beta2.Stack) Stack {
}
}
func stackToV1beta2(s Stack) *v1beta2.Stack {
return &v1beta2.Stack{
func stackToV1alpha3(s Stack) *latest.Stack {
return &latest.Stack{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
},
@ -130,32 +162,32 @@ func stackToV1beta2(s Stack) *v1beta2.Stack {
}
}
func fromComposeConfig(stderr io.Writer, c *composeTypes.Config) *v1beta2.StackSpec {
func fromComposeConfig(stderr io.Writer, c *composeTypes.Config) *latest.StackSpec {
if c == nil {
return nil
}
warnUnsupportedFeatures(stderr, c)
serviceConfigs := make([]v1beta2.ServiceConfig, len(c.Services))
serviceConfigs := make([]latest.ServiceConfig, len(c.Services))
for i, s := range c.Services {
serviceConfigs[i] = fromComposeServiceConfig(s)
}
return &v1beta2.StackSpec{
return &latest.StackSpec{
Services: serviceConfigs,
Secrets: fromComposeSecrets(c.Secrets),
Configs: fromComposeConfigs(c.Configs),
}
}
func fromComposeSecrets(s map[string]composeTypes.SecretConfig) map[string]v1beta2.SecretConfig {
func fromComposeSecrets(s map[string]composeTypes.SecretConfig) map[string]latest.SecretConfig {
if s == nil {
return nil
}
m := map[string]v1beta2.SecretConfig{}
m := map[string]latest.SecretConfig{}
for key, value := range s {
m[key] = v1beta2.SecretConfig{
m[key] = latest.SecretConfig{
Name: value.Name,
File: value.File,
External: v1beta2.External{
External: latest.External{
Name: value.External.Name,
External: value.External.External,
},
@ -165,16 +197,16 @@ func fromComposeSecrets(s map[string]composeTypes.SecretConfig) map[string]v1bet
return m
}
func fromComposeConfigs(s map[string]composeTypes.ConfigObjConfig) map[string]v1beta2.ConfigObjConfig {
func fromComposeConfigs(s map[string]composeTypes.ConfigObjConfig) map[string]latest.ConfigObjConfig {
if s == nil {
return nil
}
m := map[string]v1beta2.ConfigObjConfig{}
m := map[string]latest.ConfigObjConfig{}
for key, value := range s {
m[key] = v1beta2.ConfigObjConfig{
m[key] = latest.ConfigObjConfig{
Name: value.Name,
File: value.File,
External: v1beta2.External{
External: latest.External{
Name: value.External.Name,
External: value.External.External,
},
@ -184,7 +216,7 @@ func fromComposeConfigs(s map[string]composeTypes.ConfigObjConfig) map[string]v1
return m
}
func fromComposeServiceConfig(s composeTypes.ServiceConfig) v1beta2.ServiceConfig {
func fromComposeServiceConfig(s composeTypes.ServiceConfig) latest.ServiceConfig {
var userID *int64
if s.User != "" {
numerical, err := strconv.Atoi(s.User)
@ -193,13 +225,13 @@ func fromComposeServiceConfig(s composeTypes.ServiceConfig) v1beta2.ServiceConfi
userID = &unixUserID
}
}
return v1beta2.ServiceConfig{
return latest.ServiceConfig{
Name: s.Name,
CapAdd: s.CapAdd,
CapDrop: s.CapDrop,
Command: s.Command,
Configs: fromComposeServiceConfigs(s.Configs),
Deploy: v1beta2.DeployConfig{
Deploy: latest.DeployConfig{
Mode: s.Deploy.Mode,
Replicas: s.Deploy.Replicas,
Labels: s.Deploy.Labels,
@ -231,13 +263,13 @@ func fromComposeServiceConfig(s composeTypes.ServiceConfig) v1beta2.ServiceConfi
}
}
func fromComposePorts(ports []composeTypes.ServicePortConfig) []v1beta2.ServicePortConfig {
func fromComposePorts(ports []composeTypes.ServicePortConfig) []latest.ServicePortConfig {
if ports == nil {
return nil
}
p := make([]v1beta2.ServicePortConfig, len(ports))
p := make([]latest.ServicePortConfig, len(ports))
for i, port := range ports {
p[i] = v1beta2.ServicePortConfig{
p[i] = latest.ServicePortConfig{
Mode: port.Mode,
Target: port.Target,
Published: port.Published,
@ -247,13 +279,13 @@ func fromComposePorts(ports []composeTypes.ServicePortConfig) []v1beta2.ServiceP
return p
}
func fromComposeServiceSecrets(secrets []composeTypes.ServiceSecretConfig) []v1beta2.ServiceSecretConfig {
func fromComposeServiceSecrets(secrets []composeTypes.ServiceSecretConfig) []latest.ServiceSecretConfig {
if secrets == nil {
return nil
}
c := make([]v1beta2.ServiceSecretConfig, len(secrets))
c := make([]latest.ServiceSecretConfig, len(secrets))
for i, secret := range secrets {
c[i] = v1beta2.ServiceSecretConfig{
c[i] = latest.ServiceSecretConfig{
Source: secret.Source,
Target: secret.Target,
UID: secret.UID,
@ -263,13 +295,13 @@ func fromComposeServiceSecrets(secrets []composeTypes.ServiceSecretConfig) []v1b
return c
}
func fromComposeServiceConfigs(configs []composeTypes.ServiceConfigObjConfig) []v1beta2.ServiceConfigObjConfig {
func fromComposeServiceConfigs(configs []composeTypes.ServiceConfigObjConfig) []latest.ServiceConfigObjConfig {
if configs == nil {
return nil
}
c := make([]v1beta2.ServiceConfigObjConfig, len(configs))
c := make([]latest.ServiceConfigObjConfig, len(configs))
for i, config := range configs {
c[i] = v1beta2.ServiceConfigObjConfig{
c[i] = latest.ServiceConfigObjConfig{
Source: config.Source,
Target: config.Target,
UID: config.UID,
@ -279,11 +311,11 @@ func fromComposeServiceConfigs(configs []composeTypes.ServiceConfigObjConfig) []
return c
}
func fromComposeHealthcheck(h *composeTypes.HealthCheckConfig) *v1beta2.HealthCheckConfig {
func fromComposeHealthcheck(h *composeTypes.HealthCheckConfig) *latest.HealthCheckConfig {
if h == nil {
return nil
}
return &v1beta2.HealthCheckConfig{
return &latest.HealthCheckConfig{
Test: h.Test,
Timeout: composetypes.ConvertDurationPtr(h.Timeout),
Interval: composetypes.ConvertDurationPtr(h.Interval),
@ -291,8 +323,8 @@ func fromComposeHealthcheck(h *composeTypes.HealthCheckConfig) *v1beta2.HealthCh
}
}
func fromComposePlacement(p composeTypes.Placement) v1beta2.Placement {
return v1beta2.Placement{
func fromComposePlacement(p composeTypes.Placement) latest.Placement {
return latest.Placement{
Constraints: fromComposeConstraints(p.Constraints),
}
}
@ -306,18 +338,18 @@ const (
swarmLabelPrefix = "node.labels."
)
func fromComposeConstraints(s []string) *v1beta2.Constraints {
func fromComposeConstraints(s []string) *latest.Constraints {
if len(s) == 0 {
return nil
}
constraints := &v1beta2.Constraints{}
constraints := &latest.Constraints{}
for _, constraint := range s {
matches := constraintEquals.FindStringSubmatch(constraint)
if len(matches) == 4 {
key := matches[1]
operator := matches[2]
value := matches[3]
constraint := &v1beta2.Constraint{
constraint := &latest.Constraint{
Operator: operator,
Value: value,
}
@ -330,7 +362,7 @@ func fromComposeConstraints(s []string) *v1beta2.Constraints {
constraints.Hostname = constraint
case strings.HasPrefix(key, swarmLabelPrefix):
if constraints.MatchLabels == nil {
constraints.MatchLabels = map[string]v1beta2.Constraint{}
constraints.MatchLabels = map[string]latest.Constraint{}
}
constraints.MatchLabels[strings.TrimPrefix(key, swarmLabelPrefix)] = *constraint
}
@ -339,48 +371,48 @@ func fromComposeConstraints(s []string) *v1beta2.Constraints {
return constraints
}
func fromComposeResources(r composeTypes.Resources) v1beta2.Resources {
return v1beta2.Resources{
func fromComposeResources(r composeTypes.Resources) latest.Resources {
return latest.Resources{
Limits: fromComposeResourcesResource(r.Limits),
Reservations: fromComposeResourcesResource(r.Reservations),
}
}
func fromComposeResourcesResource(r *composeTypes.Resource) *v1beta2.Resource {
func fromComposeResourcesResource(r *composeTypes.Resource) *latest.Resource {
if r == nil {
return nil
}
return &v1beta2.Resource{
return &latest.Resource{
MemoryBytes: int64(r.MemoryBytes),
NanoCPUs: r.NanoCPUs,
}
}
func fromComposeUpdateConfig(u *composeTypes.UpdateConfig) *v1beta2.UpdateConfig {
func fromComposeUpdateConfig(u *composeTypes.UpdateConfig) *latest.UpdateConfig {
if u == nil {
return nil
}
return &v1beta2.UpdateConfig{
return &latest.UpdateConfig{
Parallelism: u.Parallelism,
}
}
func fromComposeRestartPolicy(r *composeTypes.RestartPolicy) *v1beta2.RestartPolicy {
func fromComposeRestartPolicy(r *composeTypes.RestartPolicy) *latest.RestartPolicy {
if r == nil {
return nil
}
return &v1beta2.RestartPolicy{
return &latest.RestartPolicy{
Condition: r.Condition,
}
}
func fromComposeServiceVolumeConfig(vs []composeTypes.ServiceVolumeConfig) []v1beta2.ServiceVolumeConfig {
func fromComposeServiceVolumeConfig(vs []composeTypes.ServiceVolumeConfig) []latest.ServiceVolumeConfig {
if vs == nil {
return nil
}
volumes := []v1beta2.ServiceVolumeConfig{}
volumes := []latest.ServiceVolumeConfig{}
for _, v := range vs {
volumes = append(volumes, v1beta2.ServiceVolumeConfig{
volumes = append(volumes, latest.ServiceVolumeConfig{
Type: v.Type,
Source: v.Source,
Target: v.Target,

View File

@ -15,4 +15,6 @@ func TestNewStackConverter(t *testing.T) {
assert.NilError(t, err)
_, err = NewStackConverter("v1beta2")
assert.NilError(t, err)
_, err = NewStackConverter("v1alpha3")
assert.NilError(t, err)
}

View File

@ -4,8 +4,10 @@ import (
"errors"
"testing"
composev1alpha3 "github.com/docker/compose-on-kubernetes/api/client/clientset/typed/compose/v1alpha3"
composev1beta1 "github.com/docker/compose-on-kubernetes/api/client/clientset/typed/compose/v1beta1"
composev1beta2 "github.com/docker/compose-on-kubernetes/api/client/clientset/typed/compose/v1beta2"
"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"
"gotest.tools/assert"
@ -31,11 +33,11 @@ configs:
test:
file: testdata/config
`,
Spec: &v1beta2.StackSpec{
Configs: map[string]v1beta2.ConfigObjConfig{
Spec: &v1alpha3.StackSpec{
Configs: map[string]v1alpha3.ConfigObjConfig{
"test": {Name: "test", File: "testdata/config"},
},
Secrets: map[string]v1beta2.SecretConfig{
Secrets: map[string]v1alpha3.SecretConfig{
"test": {Name: "test", File: "testdata/secret"},
},
},
@ -86,6 +88,24 @@ func TestCreateChildResourcesV1Beta2(t *testing.T) {
checkOwnerReferences(t, s.ObjectMeta, "test", v1beta2.SchemeGroupVersion.String())
}
func TestCreateChildResourcesV1Alpha3(t *testing.T) {
k8sclientSet := fake.NewSimpleClientset()
stack := testStack()
configs := k8sclientSet.CoreV1().ConfigMaps("test")
secrets := k8sclientSet.CoreV1().Secrets("test")
assert.NilError(t, createResources(
stack,
&stackV1Alpha3{stacks: &fakeV1alpha3Client{}},
configs,
secrets))
c, err := configs.Get("test", metav1.GetOptions{})
assert.NilError(t, err)
checkOwnerReferences(t, c.ObjectMeta, "test", v1alpha3.SchemeGroupVersion.String())
s, err := secrets.Get("test", metav1.GetOptions{})
assert.NilError(t, err)
checkOwnerReferences(t, s.ObjectMeta, "test", v1alpha3.SchemeGroupVersion.String())
}
func TestCreateChildResourcesWithStackCreationErrorV1Beta1(t *testing.T) {
k8sclientSet := fake.NewSimpleClientset()
stack := testStack()
@ -120,6 +140,23 @@ func TestCreateChildResourcesWithStackCreationErrorV1Beta2(t *testing.T) {
assert.Check(t, kerrors.IsNotFound(err))
}
func TestCreateChildResourcesWithStackCreationErrorV1Alpha3(t *testing.T) {
k8sclientSet := fake.NewSimpleClientset()
stack := testStack()
configs := k8sclientSet.CoreV1().ConfigMaps("test")
secrets := k8sclientSet.CoreV1().Secrets("test")
err := createResources(
stack,
&stackV1Alpha3{stacks: &fakeV1alpha3Client{errorOnCreate: true}},
configs,
secrets)
assert.Error(t, err, "some error")
_, err = configs.Get("test", metav1.GetOptions{})
assert.Check(t, kerrors.IsNotFound(err))
_, err = secrets.Get("test", metav1.GetOptions{})
assert.Check(t, kerrors.IsNotFound(err))
}
type fakeV1beta1Client struct {
errorOnCreate bool
}
@ -213,3 +250,50 @@ func (c *fakeV1beta2Client) Patch(name string, pt types.PatchType, data []byte,
func (c *fakeV1beta2Client) WithSkipValidation() composev1beta2.StackInterface {
return c
}
type fakeV1alpha3Client struct {
errorOnCreate bool
}
func (c *fakeV1alpha3Client) Create(s *v1alpha3.Stack) (*v1alpha3.Stack, error) {
if c.errorOnCreate {
return nil, errors.New("some error")
}
return s, nil
}
func (c *fakeV1alpha3Client) Update(*v1alpha3.Stack) (*v1alpha3.Stack, error) {
return nil, nil
}
func (c *fakeV1alpha3Client) UpdateStatus(*v1alpha3.Stack) (*v1alpha3.Stack, error) {
return nil, nil
}
func (c *fakeV1alpha3Client) Delete(name string, options *metav1.DeleteOptions) error {
return nil
}
func (c *fakeV1alpha3Client) DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
return nil
}
func (c *fakeV1alpha3Client) Get(name string, options metav1.GetOptions) (*v1alpha3.Stack, error) {
return nil, kerrors.NewNotFound(v1beta1.SchemeGroupVersion.WithResource("stacks").GroupResource(), name)
}
func (c *fakeV1alpha3Client) List(opts metav1.ListOptions) (*v1alpha3.StackList, error) {
return nil, nil
}
func (c *fakeV1alpha3Client) Watch(opts metav1.ListOptions) (watch.Interface, error) {
return nil, nil
}
func (c *fakeV1alpha3Client) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*v1alpha3.Stack, error) {
return nil, nil
}
func (c *fakeV1alpha3Client) WithSkipValidation() composev1alpha3.StackInterface {
return c
}

View File

@ -5,7 +5,7 @@ import (
"path/filepath"
"sort"
"github.com/docker/compose-on-kubernetes/api/compose/v1beta2"
latest "github.com/docker/compose-on-kubernetes/api/compose/v1alpha3"
"github.com/docker/compose-on-kubernetes/api/labels"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -17,7 +17,7 @@ type Stack struct {
Name string
Namespace string
ComposeFile string
Spec *v1beta2.StackSpec
Spec *latest.StackSpec
}
type childResource interface {

View File

@ -3,8 +3,10 @@ package kubernetes
import (
"fmt"
composev1alpha3 "github.com/docker/compose-on-kubernetes/api/client/clientset/typed/compose/v1alpha3"
composev1beta1 "github.com/docker/compose-on-kubernetes/api/client/clientset/typed/compose/v1beta1"
composev1beta2 "github.com/docker/compose-on-kubernetes/api/client/clientset/typed/compose/v1beta2"
"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"
"github.com/docker/compose-on-kubernetes/api/labels"
@ -123,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 {
stackV1Beta2Converter
stackV1Beta2OrHigherConverter
stacks composev1beta2.StackInterface
}
@ -136,17 +138,21 @@ func newStackV1Beta2(config *rest.Config, namespace string) (*stackV1Beta2, erro
}
func (s *stackV1Beta2) CreateOrUpdate(internalStack Stack, childResources []childResource) error {
// If it already exists, update the stack
var (
stack *v1beta2.Stack
err error
)
resolved, err := stackToV1beta2(internalStack)
if err != nil {
deleteChildResources(childResources)
return err
}
if stack, err = s.stacks.Get(internalStack.Name, metav1.GetOptions{}); err == nil {
stack.Spec = internalStack.Spec
stack.Spec = resolved.Spec
stack, err = s.stacks.Update(stack)
} else {
// Or create it
stack, err = s.stacks.Create(stackToV1beta2(internalStack))
stack, err = s.stacks.Create(resolved)
}
if err != nil {
deleteChildResources(childResources)
@ -173,7 +179,7 @@ func (s *stackV1Beta2) Get(name string) (Stack, error) {
if err != nil {
return Stack{}, err
}
return stackFromV1beta2(stackBeta2), nil
return stackFromV1beta2(stackBeta2)
}
func (s *stackV1Beta2) List(opts metav1.ListOptions) ([]Stack, error) {
@ -183,7 +189,9 @@ func (s *stackV1Beta2) List(opts metav1.ListOptions) ([]Stack, error) {
}
stacks := make([]Stack, len(list.Items))
for i := range list.Items {
stacks[i] = stackFromV1beta2(&list.Items[i])
if stacks[i], err = stackFromV1beta2(&list.Items[i]); err != nil {
return nil, err
}
}
return stacks, nil
}
@ -192,3 +200,75 @@ func (s *stackV1Beta2) List(opts metav1.ListOptions) ([]Stack, error) {
func (s *stackV1Beta2) IsColliding(servicesClient corev1.ServiceInterface, st Stack) error {
return nil
}
// stackV1Beta2 implements stackClient interface and talks to compose component v1beta2.
type stackV1Alpha3 struct {
stackV1Beta2OrHigherConverter
stacks composev1alpha3.StackInterface
}
func newStackV1Alpha3(config *rest.Config, namespace string) (*stackV1Alpha3, error) {
client, err := composev1alpha3.NewForConfig(config)
if err != nil {
return nil, err
}
return &stackV1Alpha3{stacks: client.Stacks(namespace)}, nil
}
func (s *stackV1Alpha3) CreateOrUpdate(internalStack Stack, childResources []childResource) error {
var (
stack *v1alpha3.Stack
err error
)
resolved := stackToV1alpha3(internalStack)
if stack, err = s.stacks.Get(internalStack.Name, metav1.GetOptions{}); err == nil {
stack.Spec = resolved.Spec
stack, err = s.stacks.Update(stack)
} else {
// Or create it
stack, err = s.stacks.Create(resolved)
}
if err != nil {
deleteChildResources(childResources)
return err
}
blockOwnerDeletion := true
isController := true
return setChildResourcesOwner(childResources, metav1.OwnerReference{
APIVersion: v1alpha3.SchemeGroupVersion.String(),
Kind: "Stack",
Name: stack.Name,
UID: stack.UID,
BlockOwnerDeletion: &blockOwnerDeletion,
Controller: &isController,
})
}
func (s *stackV1Alpha3) Delete(name string) error {
return s.stacks.Delete(name, &metav1.DeleteOptions{})
}
func (s *stackV1Alpha3) Get(name string) (Stack, error) {
stackAlpha3, err := s.stacks.Get(name, metav1.GetOptions{})
if err != nil {
return Stack{}, err
}
return stackFromV1alpha3(stackAlpha3), nil
}
func (s *stackV1Alpha3) List(opts metav1.ListOptions) ([]Stack, error) {
list, err := s.stacks.List(opts)
if err != nil {
return nil, err
}
stacks := make([]Stack, len(list.Items))
for i := range list.Items {
stacks[i] = stackFromV1alpha3(&list.Items[i])
}
return stacks, nil
}
// IsColliding is handle server side with the compose api v1beta2, so nothing to do here
func (s *stackV1Alpha3) IsColliding(servicesClient corev1.ServiceInterface, st Stack) error {
return nil
}

View File

@ -12,8 +12,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
kubecontext "github.com/docker/cli/cli/context/kubernetes"
"github.com/docker/cli/kubernetes"
"github.com/docker/cli/templates"
kubernetes "github.com/docker/compose-on-kubernetes/api"
"github.com/docker/docker/api/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -260,13 +260,13 @@ func getKubernetesVersion(dockerCli command.Cli, kubeConfig string) *kubernetesV
logrus.Debugf("failed to get Kubernetes client: %s", err)
return &version
}
version.StackAPI = getStackVersion(kubeClient)
version.StackAPI = getStackVersion(kubeClient, dockerCli.ClientInfo().HasExperimental)
version.Kubernetes = getKubernetesServerVersion(kubeClient)
return &version
}
func getStackVersion(client *kubernetesClient.Clientset) string {
apiVersion, err := kubernetes.GetStackAPIVersion(client)
func getStackVersion(client *kubernetesClient.Clientset, experimental bool) string {
apiVersion, err := kubernetes.GetStackAPIVersion(client, experimental)
if err != nil {
logrus.Debugf("failed to get Stack API version: %s", err)
return "Unknown"

View File

@ -1,20 +1,60 @@
package kubernetes
import api "github.com/docker/compose-on-kubernetes/api"
import (
apiv1alpha3 "github.com/docker/compose-on-kubernetes/api/compose/v1alpha3"
apiv1beta1 "github.com/docker/compose-on-kubernetes/api/compose/v1beta1"
apiv1beta2 "github.com/docker/compose-on-kubernetes/api/compose/v1beta2"
"github.com/pkg/errors"
apimachinerymetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
)
// StackVersion represents the detected Compose Component on Kubernetes side.
// Deprecated: Use github.com/docker/compose-on-kubernetes/api.StackVersion instead
type StackVersion = api.StackVersion
type StackVersion string
const (
// StackAPIV1Beta1 is returned if it's the most recent version available.
// Deprecated: Use github.com/docker/compose-on-kubernetes/api.StackAPIV1Beta1 instead
StackAPIV1Beta1 = api.StackAPIV1Beta1
StackAPIV1Beta1 = StackVersion("v1beta1")
// StackAPIV1Beta2 is returned if it's the most recent version available.
// Deprecated: Use github.com/docker/compose-on-kubernetes/api.StackAPIV1Beta2 instead
StackAPIV1Beta2 = api.StackAPIV1Beta2
StackAPIV1Beta2 = StackVersion("v1beta2")
// StackAPIV1Alpha3 is returned if it's the most recent version available, and experimental flag is on.
StackAPIV1Alpha3 = StackVersion("v1alpha3")
)
// GetStackAPIVersion returns the most recent stack API installed.
// Deprecated: Use github.com/docker/compose-on-kubernetes/api.GetStackAPIVersion instead
var GetStackAPIVersion = api.GetStackAPIVersion
// GetStackAPIVersion returns the most appropriate stack API version installed.
func GetStackAPIVersion(serverGroups discovery.ServerGroupsInterface, experimental bool) (StackVersion, error) {
groups, err := serverGroups.ServerGroups()
if err != nil {
return "", err
}
return getAPIVersion(groups, experimental)
}
func getAPIVersion(groups *metav1.APIGroupList, experimental bool) (StackVersion, error) {
switch {
case experimental && findVersion(apiv1alpha3.SchemeGroupVersion, groups.Groups):
return StackAPIV1Alpha3, nil
case findVersion(apiv1beta2.SchemeGroupVersion, groups.Groups):
return StackAPIV1Beta2, nil
case findVersion(apiv1beta1.SchemeGroupVersion, groups.Groups):
return StackAPIV1Beta1, nil
default:
return "", errors.New("failed to find a Stack API version")
}
}
func findVersion(stackAPI schema.GroupVersion, groups []apimachinerymetav1.APIGroup) bool {
for _, group := range groups {
if group.Name == stackAPI.Group {
for _, version := range group.Versions {
if version.Version == stackAPI.Version {
return true
}
}
}
}
return false
}

54
kubernetes/check_test.go Normal file
View File

@ -0,0 +1,54 @@
package kubernetes
import (
"testing"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestGetStackAPIVersion(t *testing.T) {
var tests = []struct {
description string
groups *metav1.APIGroupList
experimental bool
err bool
expectedStack StackVersion
}{
{"no stack api", makeGroups(), false, true, ""},
{"v1beta1", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta1"}}), false, false, StackAPIV1Beta1},
{"v1beta2", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta2"}}), false, false, StackAPIV1Beta2},
{"most recent has precedence", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta1", "v1beta2"}}), false, false, StackAPIV1Beta2},
{"most recent has precedence", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta1", "v1beta2", "v1alpha3"}}), false, false, StackAPIV1Beta2},
{"most recent has precedence", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta1", "v1beta2", "v1alpha3"}}), true, false, StackAPIV1Alpha3},
}
for _, test := range tests {
version, err := getAPIVersion(test.groups, test.experimental)
if test.err {
assert.ErrorContains(t, err, "")
} else {
assert.NilError(t, err)
}
assert.Check(t, is.Equal(test.expectedStack, version))
}
}
type groupVersion struct {
name string
versions []string
}
func makeGroups(versions ...groupVersion) *metav1.APIGroupList {
groups := make([]metav1.APIGroup, len(versions))
for i := range versions {
groups[i].Name = versions[i].name
for _, v := range versions[i].versions {
groups[i].Versions = append(groups[i].Versions, metav1.GroupVersionForDiscovery{Version: v})
}
}
return &metav1.APIGroupList{
Groups: groups,
}
}