mirror of
https://github.com/docker/cli.git
synced 2026-01-26 15:41:42 +03:00
Add support for kubernetes in docker cli
- Add support for kubernetes for docker stack command - Update to go 1.9 - Add kubernetes to vendors - Print orchestrator in docker version command Signed-off-by: Vincent Demeester <vincent@sbr.pm> Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
This commit is contained in:
committed by
Silvin Lubecki
parent
70db7cc0fc
commit
8417e49792
@@ -3,6 +3,9 @@ package stack
|
||||
import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/orchestrator"
|
||||
"github.com/docker/cli/cli/command/stack/kubernetes"
|
||||
"github.com/docker/cli/cli/command/stack/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -15,19 +18,24 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newDeployCommand(dockerCli),
|
||||
newListCommand(dockerCli),
|
||||
newRemoveCommand(dockerCli),
|
||||
newServicesCommand(dockerCli),
|
||||
newPsCommand(dockerCli),
|
||||
)
|
||||
switch orchestrator.GetOrchestrator(dockerCli) {
|
||||
case orchestrator.Kubernetes:
|
||||
kubernetes.AddStackCommands(cmd, dockerCli)
|
||||
case orchestrator.Swarm:
|
||||
swarm.AddStackCommands(cmd, dockerCli)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewTopLevelDeployCommand returns a command for `docker deploy`
|
||||
func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := newDeployCommand(dockerCli)
|
||||
var cmd *cobra.Command
|
||||
switch orchestrator.GetOrchestrator(dockerCli) {
|
||||
case orchestrator.Kubernetes:
|
||||
cmd = kubernetes.NewTopLevelDeployCommand(dockerCli)
|
||||
case orchestrator.Swarm:
|
||||
cmd = swarm.NewTopLevelDeployCommand(dockerCli)
|
||||
}
|
||||
// Remove the aliases at the top level
|
||||
cmd.Aliases = []string{}
|
||||
cmd.Annotations = map[string]string{"experimental": "", "version": "1.25"}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -10,21 +10,25 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func addComposefileFlag(opt *string, flags *pflag.FlagSet) {
|
||||
// AddComposefileFlag adds compose-file file to the specified flagset
|
||||
func AddComposefileFlag(opt *string, flags *pflag.FlagSet) {
|
||||
flags.StringVarP(opt, "compose-file", "c", "", "Path to a Compose file")
|
||||
flags.SetAnnotation("compose-file", "version", []string{"1.25"})
|
||||
}
|
||||
|
||||
func addBundlefileFlag(opt *string, flags *pflag.FlagSet) {
|
||||
// AddBundlefileFlag adds bundle-file file to the specified flagset
|
||||
func AddBundlefileFlag(opt *string, flags *pflag.FlagSet) {
|
||||
flags.StringVar(opt, "bundle-file", "", "Path to a Distributed Application Bundle file")
|
||||
flags.SetAnnotation("bundle-file", "experimental", nil)
|
||||
}
|
||||
|
||||
func addRegistryAuthFlag(opt *bool, flags *pflag.FlagSet) {
|
||||
// AddRegistryAuthFlag adds with-registry-auth file to the specified flagset
|
||||
func AddRegistryAuthFlag(opt *bool, flags *pflag.FlagSet) {
|
||||
flags.BoolVar(opt, "with-registry-auth", false, "Send registry authentication details to Swarm agents")
|
||||
}
|
||||
|
||||
func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefile.Bundlefile, error) {
|
||||
// LoadBundlefile loads a bundle-file from the specified path
|
||||
func LoadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefile.Bundlefile, error) {
|
||||
defaultPath := fmt.Sprintf("%s.dab", namespace)
|
||||
|
||||
if path == "" {
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -32,7 +32,7 @@ func TestLoadBundlefileErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
_, err := loadBundlefile(&bytes.Buffer{}, tc.namespace, tc.path)
|
||||
_, err := LoadBundlefile(&bytes.Buffer{}, tc.namespace, tc.path)
|
||||
assert.Error(t, err, tc.expectedError)
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func TestLoadBundlefile(t *testing.T) {
|
||||
|
||||
namespace := ""
|
||||
path := filepath.Join("testdata", "bundlefile_with_two_services.dab")
|
||||
bundleFile, err := loadBundlefile(buf, namespace, path)
|
||||
bundleFile, err := LoadBundlefile(buf, namespace, path)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(bundleFile.Services), 2)
|
||||
@@ -0,0 +1,88 @@
|
||||
package clientset
|
||||
|
||||
import (
|
||||
composev1beta1 "github.com/docker/cli/cli/command/stack/kubernetes/api/client/clientset_generated/clientset/typed/compose/v1beta1"
|
||||
glog "github.com/golang/glog"
|
||||
discovery "k8s.io/client-go/discovery"
|
||||
rest "k8s.io/client-go/rest"
|
||||
flowcontrol "k8s.io/client-go/util/flowcontrol"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Discovery() discovery.DiscoveryInterface
|
||||
ComposeV1beta1() composev1beta1.ComposeV1beta1Interface
|
||||
// Deprecated: please explicitly pick a version if possible.
|
||||
Compose() composev1beta1.ComposeV1beta1Interface
|
||||
}
|
||||
|
||||
// Clientset contains the clients for groups. Each group has exactly one
|
||||
// version included in a Clientset.
|
||||
type Clientset struct {
|
||||
*discovery.DiscoveryClient
|
||||
*composev1beta1.ComposeV1beta1Client
|
||||
}
|
||||
|
||||
// ComposeV1beta1 retrieves the ComposeV1beta1Client
|
||||
func (c *Clientset) ComposeV1beta1() composev1beta1.ComposeV1beta1Interface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.ComposeV1beta1Client
|
||||
}
|
||||
|
||||
// Deprecated: Compose retrieves the default version of ComposeClient.
|
||||
// Please explicitly pick a version.
|
||||
func (c *Clientset) Compose() composev1beta1.ComposeV1beta1Interface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.ComposeV1beta1Client
|
||||
}
|
||||
|
||||
// Discovery retrieves the DiscoveryClient
|
||||
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.DiscoveryClient
|
||||
}
|
||||
|
||||
// NewForConfig creates a new Clientset for the given config.
|
||||
func NewForConfig(c *rest.Config) (*Clientset, error) {
|
||||
configShallowCopy := *c
|
||||
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
|
||||
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
|
||||
}
|
||||
var cs Clientset
|
||||
var err error
|
||||
cs.ComposeV1beta1Client, err = composev1beta1.NewForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to create the DiscoveryClient: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return &cs, nil
|
||||
}
|
||||
|
||||
// NewForConfigOrDie creates a new Clientset for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewForConfigOrDie(c *rest.Config) *Clientset {
|
||||
var cs Clientset
|
||||
cs.ComposeV1beta1Client = composev1beta1.NewForConfigOrDie(c)
|
||||
|
||||
cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
|
||||
return &cs
|
||||
}
|
||||
|
||||
// New creates a new Clientset for the given RESTClient.
|
||||
func New(c rest.Interface) *Clientset {
|
||||
var cs Clientset
|
||||
cs.ComposeV1beta1Client = composev1beta1.New(c)
|
||||
|
||||
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
|
||||
return &cs
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// This package is generated by client-gen with custom arguments.
|
||||
|
||||
// This package has the automatically generated clientset.
|
||||
package clientset
|
||||
@@ -0,0 +1,4 @@
|
||||
// This package is generated by client-gen with custom arguments.
|
||||
|
||||
// This package contains the scheme of the automatically generated clientset.
|
||||
package scheme
|
||||
@@ -0,0 +1,37 @@
|
||||
package scheme
|
||||
|
||||
import (
|
||||
composev1beta1 "github.com/docker/cli/cli/command/stack/kubernetes/api/compose/v1beta1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
)
|
||||
|
||||
var Scheme = runtime.NewScheme()
|
||||
var Codecs = serializer.NewCodecFactory(Scheme)
|
||||
var ParameterCodec = runtime.NewParameterCodec(Scheme)
|
||||
|
||||
func init() {
|
||||
v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
|
||||
AddToScheme(Scheme)
|
||||
}
|
||||
|
||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||
// of clientsets, like in:
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kuberentes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
//
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
//
|
||||
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
||||
// correctly.
|
||||
func AddToScheme(scheme *runtime.Scheme) {
|
||||
composev1beta1.AddToScheme(scheme)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/cli/command/stack/kubernetes/api/client/clientset_generated/clientset/scheme"
|
||||
v1beta1 "github.com/docker/cli/cli/command/stack/kubernetes/api/compose/v1beta1"
|
||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type ComposeV1beta1Interface interface {
|
||||
RESTClient() rest.Interface
|
||||
StacksGetter
|
||||
}
|
||||
|
||||
// ComposeV1beta1Client is used to interact with features provided by the compose.docker.com group.
|
||||
type ComposeV1beta1Client struct {
|
||||
restClient rest.Interface
|
||||
}
|
||||
|
||||
func (c *ComposeV1beta1Client) Stacks(namespace string) StackInterface {
|
||||
return newStacks(c, namespace)
|
||||
}
|
||||
|
||||
// NewForConfig creates a new ComposeV1beta1Client for the given config.
|
||||
func NewForConfig(c *rest.Config) (*ComposeV1beta1Client, error) {
|
||||
config := *c
|
||||
if err := setConfigDefaults(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := rest.RESTClientFor(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ComposeV1beta1Client{client}, nil
|
||||
}
|
||||
|
||||
// NewForConfigOrDie creates a new ComposeV1beta1Client for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewForConfigOrDie(c *rest.Config) *ComposeV1beta1Client {
|
||||
client, err := NewForConfig(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// New creates a new ComposeV1beta1Client for the given RESTClient.
|
||||
func New(c rest.Interface) *ComposeV1beta1Client {
|
||||
return &ComposeV1beta1Client{c}
|
||||
}
|
||||
|
||||
func setConfigDefaults(config *rest.Config) error {
|
||||
gv := v1beta1.SchemeGroupVersion
|
||||
config.GroupVersion = &gv
|
||||
config.APIPath = "/apis"
|
||||
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
|
||||
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *ComposeV1beta1Client) RESTClient() rest.Interface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.restClient
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// This package is generated by client-gen with custom arguments.
|
||||
|
||||
// This package has the automatically generated typed clients.
|
||||
package v1beta1
|
||||
@@ -0,0 +1,3 @@
|
||||
package v1beta1
|
||||
|
||||
type StackExpansion interface{}
|
||||
@@ -0,0 +1,158 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
scheme "github.com/docker/cli/cli/command/stack/kubernetes/api/client/clientset_generated/clientset/scheme"
|
||||
v1beta1 "github.com/docker/cli/cli/command/stack/kubernetes/api/compose/v1beta1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// StacksGetter has a method to return a StackInterface.
|
||||
// A group's client should implement this interface.
|
||||
type StacksGetter interface {
|
||||
Stacks(namespace string) StackInterface
|
||||
}
|
||||
|
||||
// StackInterface has methods to work with Stack resources.
|
||||
type StackInterface interface {
|
||||
Create(*v1beta1.Stack) (*v1beta1.Stack, error)
|
||||
Update(*v1beta1.Stack) (*v1beta1.Stack, error)
|
||||
UpdateStatus(*v1beta1.Stack) (*v1beta1.Stack, error)
|
||||
Delete(name string, options *v1.DeleteOptions) error
|
||||
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
|
||||
Get(name string, options v1.GetOptions) (*v1beta1.Stack, error)
|
||||
List(opts v1.ListOptions) (*v1beta1.StackList, error)
|
||||
Watch(opts v1.ListOptions) (watch.Interface, error)
|
||||
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Stack, err error)
|
||||
StackExpansion
|
||||
}
|
||||
|
||||
var _ StackInterface = &stacks{}
|
||||
|
||||
// stacks implements StackInterface
|
||||
type stacks struct {
|
||||
client rest.Interface
|
||||
ns string
|
||||
}
|
||||
|
||||
// newStacks returns a Stacks
|
||||
func newStacks(c *ComposeV1beta1Client, namespace string) *stacks {
|
||||
return &stacks{
|
||||
client: c.RESTClient(),
|
||||
ns: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Create takes the representation of a stack and creates it. Returns the server's representation of the stack, and an error, if there is any.
|
||||
func (c *stacks) Create(stack *v1beta1.Stack) (result *v1beta1.Stack, err error) {
|
||||
result = &v1beta1.Stack{}
|
||||
err = c.client.Post().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
Body(stack).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Update takes the representation of a stack and updates it. Returns the server's representation of the stack, and an error, if there is any.
|
||||
func (c *stacks) Update(stack *v1beta1.Stack) (result *v1beta1.Stack, err error) {
|
||||
result = &v1beta1.Stack{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
Name(stack.Name).
|
||||
Body(stack).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclientstatus=false comment above the type to avoid generating UpdateStatus().
|
||||
|
||||
func (c *stacks) UpdateStatus(stack *v1beta1.Stack) (result *v1beta1.Stack, err error) {
|
||||
result = &v1beta1.Stack{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
Name(stack.Name).
|
||||
SubResource("status").
|
||||
Body(stack).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the stack and deletes it. Returns an error if one occurs.
|
||||
func (c *stacks) Delete(name string, options *v1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
Name(name).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *stacks) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
VersionedParams(&listOptions, scheme.ParameterCodec).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// Get takes name of the stack, and returns the corresponding stack object, and an error if there is any.
|
||||
func (c *stacks) Get(name string, options v1.GetOptions) (result *v1beta1.Stack, err error) {
|
||||
result = &v1beta1.Stack{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
Name(name).
|
||||
VersionedParams(&options, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of Stacks that match those selectors.
|
||||
func (c *stacks) List(opts v1.ListOptions) (result *v1beta1.StackList, err error) {
|
||||
result = &v1beta1.StackList{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested stacks.
|
||||
func (c *stacks) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||
opts.Watch = true
|
||||
return c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Watch()
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched stack.
|
||||
func (c *stacks) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Stack, err error) {
|
||||
result = &v1beta1.Stack{}
|
||||
err = c.client.Patch(pt).
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
SubResource(subresources...).
|
||||
Name(name).
|
||||
Body(data).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
5
cli/command/stack/kubernetes/api/compose/doc.go
Normal file
5
cli/command/stack/kubernetes/api/compose/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// +k8s:deepcopy-gen=package,register
|
||||
// +groupName=compose.docker.com
|
||||
|
||||
// Package compose is the internal version of the API.
|
||||
package compose
|
||||
43
cli/command/stack/kubernetes/api/compose/register.go
Normal file
43
cli/command/stack/kubernetes/api/compose/register.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name used to register these objects
|
||||
const GroupName = "compose.docker.com"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
|
||||
func Kind(kind string) schema.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns back a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
var (
|
||||
// SchemeBuilder collects functions that add things to a scheme. It's to allow
|
||||
// code to compile without explicitly referencing generated types. You should
|
||||
// declare one in each package that will have generated deep copy or conversion
|
||||
// functions.
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
|
||||
// AddToScheme applies all the stored functions to the scheme. A non-nil error
|
||||
// indicates that one function failed and the attempt was abandoned.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// adds the list of known types to api.Scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Stack{},
|
||||
&StackList{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
117
cli/command/stack/kubernetes/api/compose/types.go
Normal file
117
cli/command/stack/kubernetes/api/compose/types.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ImpersonationConfig holds information use to impersonate calls from the compose controller
|
||||
type ImpersonationConfig struct {
|
||||
// UserName is the username to impersonate on each request.
|
||||
UserName string
|
||||
// Groups are the groups to impersonate on each request.
|
||||
Groups []string
|
||||
// Extra is a free-form field which can be used to link some authentication information
|
||||
// to authorization information. This field allows you to impersonate it.
|
||||
Extra map[string][]string
|
||||
}
|
||||
|
||||
// Stack defines a stack object to be register in the kubernetes API
|
||||
// +genclient=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type Stack struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ObjectMeta
|
||||
Spec StackSpec
|
||||
Status StackStatus
|
||||
}
|
||||
|
||||
// StackStatus defines the observed state of Stack
|
||||
type StackStatus struct {
|
||||
Phase StackPhase
|
||||
Message string
|
||||
}
|
||||
|
||||
// StackSpec defines the desired state of Stack
|
||||
type StackSpec struct {
|
||||
ComposeFile string
|
||||
Owner ImpersonationConfig
|
||||
}
|
||||
|
||||
// StackPhase defines the status phase in which the stack is.
|
||||
type StackPhase string
|
||||
|
||||
// These are valid conditions of a stack.
|
||||
const (
|
||||
// Available means the stack is available.
|
||||
StackAvailable StackPhase = "Available"
|
||||
// Progressing means the deployment is progressing.
|
||||
StackProgressing StackPhase = "Progressing"
|
||||
// StackFailure is added in a stack when one of its members fails to be created
|
||||
// or deleted.
|
||||
StackFailure StackPhase = "Failure"
|
||||
)
|
||||
|
||||
// StackList defines a list of stacks
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type StackList struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ListMeta
|
||||
Items []Stack
|
||||
}
|
||||
|
||||
// Owner defines the owner of a stack. It is used to impersonate the controller calls
|
||||
// to kubernetes api.
|
||||
type Owner struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ObjectMeta
|
||||
Owner ImpersonationConfig
|
||||
}
|
||||
|
||||
// OwnerList defines a list of owner.
|
||||
type OwnerList struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ListMeta
|
||||
Items []Owner
|
||||
}
|
||||
|
||||
// FIXME(vdemeester) are those necessary ??
|
||||
|
||||
// NewStatus is newStatus
|
||||
func (Stack) NewStatus() interface{} {
|
||||
return StackStatus{}
|
||||
}
|
||||
|
||||
// GetStatus returns the status
|
||||
func (pc *Stack) GetStatus() interface{} {
|
||||
return pc.Status
|
||||
}
|
||||
|
||||
// SetStatus sets the status
|
||||
func (pc *Stack) SetStatus(s interface{}) {
|
||||
pc.Status = s.(StackStatus)
|
||||
}
|
||||
|
||||
// GetSpec returns the spec
|
||||
func (pc *Stack) GetSpec() interface{} {
|
||||
return pc.Spec
|
||||
}
|
||||
|
||||
// SetSpec sets the spec
|
||||
func (pc *Stack) SetSpec(s interface{}) {
|
||||
pc.Spec = s.(StackSpec)
|
||||
}
|
||||
|
||||
// GetObjectMeta returns the ObjectMeta
|
||||
func (pc *Stack) GetObjectMeta() *metav1.ObjectMeta {
|
||||
return &pc.ObjectMeta
|
||||
}
|
||||
|
||||
// SetGeneration sets the Generation
|
||||
func (pc *Stack) SetGeneration(generation int64) {
|
||||
pc.ObjectMeta.Generation = generation
|
||||
}
|
||||
|
||||
// GetGeneration returns the Generation
|
||||
func (pc Stack) GetGeneration() int64 {
|
||||
return pc.ObjectMeta.Generation
|
||||
}
|
||||
11
cli/command/stack/kubernetes/api/compose/v1beta1/doc.go
Normal file
11
cli/command/stack/kubernetes/api/compose/v1beta1/doc.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// Package v1beta1 holds the v1beta1 versions of our stack structures.
|
||||
// API versions allow the api contract for a resource to be changed while keeping
|
||||
// backward compatibility by support multiple concurrent versions
|
||||
// of the same resource
|
||||
//
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:deepcopy-gen=package,register
|
||||
// +k8s:conversion-gen=github.com/docker/cli/cli/command/stack/kubernetes/api/compose
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +groupName=compose.docker.com
|
||||
package v1beta1 // import "github.com/docker/cli/cli/command/stack/kubernetes/api/compose/v1beta1"
|
||||
@@ -0,0 +1,25 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/docker/cli/cli/command/stack/kubernetes/api/compose"
|
||||
)
|
||||
|
||||
// Owner defines the owner of a stack. It is used to impersonate the controller calls
|
||||
// to kubernetes api.
|
||||
// +genclient=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +subresource-request
|
||||
type Owner struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Owner compose.ImpersonationConfig `json:"owner,omitempty"`
|
||||
}
|
||||
|
||||
// OwnerList defines a list of owner.
|
||||
type OwnerList struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ListMeta
|
||||
Items []Owner
|
||||
}
|
||||
47
cli/command/stack/kubernetes/api/compose/v1beta1/register.go
Normal file
47
cli/command/stack/kubernetes/api/compose/v1beta1/register.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name used to register these objects
|
||||
const GroupName = "compose.docker.com"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"}
|
||||
|
||||
var (
|
||||
// SchemeBuilder collects functions that add things to a scheme. It's to allow
|
||||
// code to compile without explicitly referencing generated types. You should
|
||||
// declare one in each package that will have generated deep copy or conversion
|
||||
// functions.
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
|
||||
// AddToScheme applies all the stored functions to the scheme. A non-nil error
|
||||
// indicates that one function failed and the attempt was abandoned.
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(addKnownTypes)
|
||||
}
|
||||
|
||||
// Adds the list of known types to api.Scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Stack{},
|
||||
&StackList{},
|
||||
&Owner{},
|
||||
&OwnerList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// StackList defines a list of stacks
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type StackList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
Items []Stack `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||
}
|
||||
|
||||
// +genclient=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Stack defines a stack object to be register in the kubernetes API
|
||||
// +k8s:openapi-gen=true
|
||||
// +resource:path=stacks,strategy=StackStrategy
|
||||
// +subresource:request=Owner,path=owner,rest=OwnerStackREST
|
||||
type Stack struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec StackSpec `json:"spec,omitempty"`
|
||||
Status StackStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// StackSpec defines the desired state of Stack
|
||||
type StackSpec struct {
|
||||
ComposeFile string `json:"composeFile,omitempty"`
|
||||
}
|
||||
|
||||
// StackPhase defines the status phase in which the stack is.
|
||||
type StackPhase string
|
||||
|
||||
// These are valid conditions of a stack.
|
||||
const (
|
||||
// Available means the stack is available.
|
||||
StackAvailable StackPhase = "Available"
|
||||
// Progressing means the deployment is progressing.
|
||||
StackProgressing StackPhase = "Progressing"
|
||||
// StackFailure is added in a stack when one of its members fails to be created
|
||||
// or deleted.
|
||||
StackFailure StackPhase = "Failure"
|
||||
)
|
||||
|
||||
// StackStatus defines the observed state of Stack
|
||||
type StackStatus struct {
|
||||
// Current condition of the stack.
|
||||
// +optional
|
||||
Phase StackPhase `json:"phase,omitempty" protobuf:"bytes,1,opt,name=phase,casttype=StackPhase"`
|
||||
// A human readable message indicating details about the stack.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"`
|
||||
}
|
||||
|
||||
// Clone implements the Cloner interface for kubernetes
|
||||
func (s *Stack) Clone() (*Stack, error) {
|
||||
scheme := runtime.NewScheme()
|
||||
if err := AddToScheme(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.DeepCopy(), nil
|
||||
}
|
||||
9
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/redis-nginx.input.yaml
vendored
Normal file
9
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/redis-nginx.input.yaml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
nginx:
|
||||
image: nginx
|
||||
deploy:
|
||||
replicas: 2
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
10
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/redis-nginx.output.yaml
vendored
Normal file
10
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/redis-nginx.output.yaml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
nginx:
|
||||
image: nginx
|
||||
deploy:
|
||||
replicas: 2
|
||||
redis:
|
||||
image: redis:alpine
|
||||
deploy:
|
||||
replicas: 5
|
||||
10
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/redis-with-memory.input.yaml
vendored
Normal file
10
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/redis-with-memory.input.yaml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 64M
|
||||
reservations:
|
||||
memory: 64M
|
||||
11
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/redis-with-memory.output.yaml
vendored
Normal file
11
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/redis-with-memory.output.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 64M
|
||||
reservations:
|
||||
memory: 64M
|
||||
replicas: 5
|
||||
6
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/redis-with-replicas.input.yaml
vendored
Normal file
6
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/redis-with-replicas.input.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
deploy:
|
||||
replicas: 2
|
||||
6
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/redis-with-replicas.output.yaml
vendored
Normal file
6
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/redis-with-replicas.output.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
deploy:
|
||||
replicas: 5
|
||||
4
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/single-redis.input.yaml
vendored
Normal file
4
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/single-redis.input.yaml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
6
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/single-redis.output.yaml
vendored
Normal file
6
cli/command/stack/kubernetes/api/compose/v1beta1/testdata/single-redis.output.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
deploy:
|
||||
replicas: 5
|
||||
@@ -0,0 +1,169 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// This file was autogenerated by conversion-gen. Do not edit it manually!
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
compose "github.com/docker/cli/cli/command/stack/kubernetes/api/compose"
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(RegisterConversions)
|
||||
}
|
||||
|
||||
// RegisterConversions adds conversion functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterConversions(scheme *runtime.Scheme) error {
|
||||
return scheme.AddGeneratedConversionFuncs(
|
||||
Convert_v1beta1_Owner_To_compose_Owner,
|
||||
Convert_compose_Owner_To_v1beta1_Owner,
|
||||
Convert_v1beta1_Stack_To_compose_Stack,
|
||||
Convert_compose_Stack_To_v1beta1_Stack,
|
||||
Convert_v1beta1_StackList_To_compose_StackList,
|
||||
Convert_compose_StackList_To_v1beta1_StackList,
|
||||
Convert_v1beta1_StackSpec_To_compose_StackSpec,
|
||||
Convert_compose_StackSpec_To_v1beta1_StackSpec,
|
||||
Convert_v1beta1_StackStatus_To_compose_StackStatus,
|
||||
Convert_compose_StackStatus_To_v1beta1_StackStatus,
|
||||
)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_Owner_To_compose_Owner(in *Owner, out *compose.Owner, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
out.Owner = in.Owner
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1beta1_Owner_To_compose_Owner is an autogenerated conversion function.
|
||||
func Convert_v1beta1_Owner_To_compose_Owner(in *Owner, out *compose.Owner, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_Owner_To_compose_Owner(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_compose_Owner_To_v1beta1_Owner(in *compose.Owner, out *Owner, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
out.Owner = in.Owner
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_compose_Owner_To_v1beta1_Owner is an autogenerated conversion function.
|
||||
func Convert_compose_Owner_To_v1beta1_Owner(in *compose.Owner, out *Owner, s conversion.Scope) error {
|
||||
return autoConvert_compose_Owner_To_v1beta1_Owner(in, out, s)
|
||||
}
|
||||
|
||||
|
||||
func autoConvert_v1beta1_Stack_To_compose_Stack(in *Stack, out *compose.Stack, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
if err := Convert_v1beta1_StackSpec_To_compose_StackSpec(&in.Spec, &out.Spec, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_v1beta1_StackStatus_To_compose_StackStatus(&in.Status, &out.Status, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1beta1_Stack_To_compose_Stack is an autogenerated conversion function.
|
||||
func Convert_v1beta1_Stack_To_compose_Stack(in *Stack, out *compose.Stack, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_Stack_To_compose_Stack(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_compose_Stack_To_v1beta1_Stack(in *compose.Stack, out *Stack, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
if err := Convert_compose_StackSpec_To_v1beta1_StackSpec(&in.Spec, &out.Spec, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_compose_StackStatus_To_v1beta1_StackStatus(&in.Status, &out.Status, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_compose_Stack_To_v1beta1_Stack is an autogenerated conversion function.
|
||||
func Convert_compose_Stack_To_v1beta1_Stack(in *compose.Stack, out *Stack, s conversion.Scope) error {
|
||||
return autoConvert_compose_Stack_To_v1beta1_Stack(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_StackList_To_compose_StackList(in *StackList, out *compose.StackList, s conversion.Scope) error {
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]compose.Stack, len(*in))
|
||||
for i := range *in {
|
||||
if err := Convert_v1beta1_Stack_To_compose_Stack(&(*in)[i], &(*out)[i], s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.Items = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1beta1_StackList_To_compose_StackList is an autogenerated conversion function.
|
||||
func Convert_v1beta1_StackList_To_compose_StackList(in *StackList, out *compose.StackList, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_StackList_To_compose_StackList(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_compose_StackList_To_v1beta1_StackList(in *compose.StackList, out *StackList, s conversion.Scope) error {
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Stack, len(*in))
|
||||
for i := range *in {
|
||||
if err := Convert_compose_Stack_To_v1beta1_Stack(&(*in)[i], &(*out)[i], s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.Items = make([]Stack, 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_compose_StackList_To_v1beta1_StackList is an autogenerated conversion function.
|
||||
func Convert_compose_StackList_To_v1beta1_StackList(in *compose.StackList, out *StackList, s conversion.Scope) error {
|
||||
return autoConvert_compose_StackList_To_v1beta1_StackList(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_StackSpec_To_compose_StackSpec(in *StackSpec, out *compose.StackSpec, s conversion.Scope) error {
|
||||
out.ComposeFile = in.ComposeFile
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_compose_StackSpec_To_v1beta1_StackSpec(in *compose.StackSpec, out *StackSpec, s conversion.Scope) error {
|
||||
return autoConvert_compose_StackSpec_To_v1beta1_StackSpec(in, out, s)
|
||||
}
|
||||
// Convert_v1beta1_StackSpec_To_compose_StackSpec is an autogenerated conversion function.
|
||||
func Convert_v1beta1_StackSpec_To_compose_StackSpec(in *StackSpec, out *compose.StackSpec, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_StackSpec_To_compose_StackSpec(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_compose_StackSpec_To_v1beta1_StackSpec(in *compose.StackSpec, out *StackSpec, s conversion.Scope) error {
|
||||
out.ComposeFile = in.ComposeFile
|
||||
return nil
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_StackStatus_To_compose_StackStatus(in *StackStatus, out *compose.StackStatus, s conversion.Scope) error {
|
||||
out.Phase = compose.StackPhase(in.Phase)
|
||||
out.Message = in.Message
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1beta1_StackStatus_To_compose_StackStatus is an autogenerated conversion function.
|
||||
func Convert_v1beta1_StackStatus_To_compose_StackStatus(in *StackStatus, out *compose.StackStatus, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_StackStatus_To_compose_StackStatus(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_compose_StackStatus_To_v1beta1_StackStatus(in *compose.StackStatus, out *StackStatus, s conversion.Scope) error {
|
||||
out.Phase = StackPhase(in.Phase)
|
||||
out.Message = in.Message
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_compose_StackStatus_To_v1beta1_StackStatus is an autogenerated conversion function.
|
||||
func Convert_compose_StackStatus_To_v1beta1_StackStatus(in *compose.StackStatus, out *StackStatus, s conversion.Scope) error {
|
||||
return autoConvert_compose_StackStatus_To_v1beta1_StackStatus(in, out, s)
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// Deprecated: register deep-copy functions.
|
||||
func init() {
|
||||
SchemeBuilder.Register(RegisterDeepCopies)
|
||||
}
|
||||
|
||||
// Deprecated: RegisterDeepCopies adds deep-copy functions to the given scheme. Public
|
||||
// to allow building arbitrary schemes.
|
||||
func RegisterDeepCopies(scheme *runtime.Scheme) error {
|
||||
return scheme.AddGeneratedDeepCopyFuncs(
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*Owner).DeepCopyInto(out.(*Owner))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&Owner{})},
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*OwnerList).DeepCopyInto(out.(*OwnerList))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&OwnerList{})},
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*Stack).DeepCopyInto(out.(*Stack))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&Stack{})},
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*StackList).DeepCopyInto(out.(*StackList))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&StackList{})},
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*StackSpec).DeepCopyInto(out.(*StackSpec))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&StackSpec{})},
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*StackStatus).DeepCopyInto(out.(*StackStatus))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&StackStatus{})},
|
||||
)
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Owner) DeepCopyInto(out *Owner) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Owner.DeepCopyInto(&out.Owner)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new Owner.
|
||||
func (x *Owner) DeepCopy() *Owner {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Owner)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (x *Owner) DeepCopyObject() runtime.Object {
|
||||
if c := x.DeepCopy(); c != nil {
|
||||
return c
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OwnerList) DeepCopyInto(out *OwnerList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Owner, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new OwnerList.
|
||||
func (x *OwnerList) DeepCopy() *OwnerList {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OwnerList)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (x *OwnerList) DeepCopyObject() runtime.Object {
|
||||
if c := x.DeepCopy(); c != nil {
|
||||
return c
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Stack) DeepCopyInto(out *Stack) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
out.Status = in.Status
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new Stack.
|
||||
func (x *Stack) DeepCopy() *Stack {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Stack)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (x *Stack) DeepCopyObject() runtime.Object {
|
||||
if c := x.DeepCopy(); c != nil {
|
||||
return c
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StackList) DeepCopyInto(out *StackList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Stack, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new StackList.
|
||||
func (x *StackList) DeepCopy() *StackList {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StackList)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (x *StackList) DeepCopyObject() runtime.Object {
|
||||
if c := x.DeepCopy(); c != nil {
|
||||
return c
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StackSpec) DeepCopyInto(out *StackSpec) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new StackSpec.
|
||||
func (x *StackSpec) DeepCopy() *StackSpec {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StackSpec)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StackStatus) DeepCopyInto(out *StackStatus) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new StackStatus.
|
||||
func (x *StackStatus) DeepCopy() *StackStatus {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StackStatus)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// Deprecated: register deep-copy functions.
|
||||
func init() {
|
||||
SchemeBuilder.Register(RegisterDeepCopies)
|
||||
}
|
||||
|
||||
// Deprecated: RegisterDeepCopies adds deep-copy functions to the given scheme. Public
|
||||
// to allow building arbitrary schemes.
|
||||
func RegisterDeepCopies(scheme *runtime.Scheme) error {
|
||||
return scheme.AddGeneratedDeepCopyFuncs(
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*ImpersonationConfig).DeepCopyInto(out.(*ImpersonationConfig))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&ImpersonationConfig{})},
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*Owner).DeepCopyInto(out.(*Owner))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&Owner{})},
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*OwnerList).DeepCopyInto(out.(*OwnerList))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&OwnerList{})},
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*Stack).DeepCopyInto(out.(*Stack))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&Stack{})},
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*StackList).DeepCopyInto(out.(*StackList))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&StackList{})},
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*StackSpec).DeepCopyInto(out.(*StackSpec))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&StackSpec{})},
|
||||
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*StackStatus).DeepCopyInto(out.(*StackStatus))
|
||||
return nil
|
||||
}, InType: reflect.TypeOf(&StackStatus{})},
|
||||
)
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImpersonationConfig) DeepCopyInto(out *ImpersonationConfig) {
|
||||
*out = *in
|
||||
if in.Groups != nil {
|
||||
in, out := &in.Groups, &out.Groups
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Extra != nil {
|
||||
in, out := &in.Extra, &out.Extra
|
||||
*out = make(map[string][]string, len(*in))
|
||||
for key, val := range *in {
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
(*out)[key] = make([]string, len(val))
|
||||
copy((*out)[key], val)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationConfig.
|
||||
func (x *ImpersonationConfig) DeepCopy() *ImpersonationConfig {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImpersonationConfig)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Owner) DeepCopyInto(out *Owner) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Owner.DeepCopyInto(&out.Owner)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new Owner.
|
||||
func (x *Owner) DeepCopy() *Owner {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Owner)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func (x *Owner) DeepCopyObject() runtime.Object {
|
||||
if c := x.DeepCopy(); c != nil {
|
||||
return c
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OwnerList) DeepCopyInto(out *OwnerList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Owner, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new OwnerList.
|
||||
func (x *OwnerList) DeepCopy() *OwnerList {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OwnerList)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Stack) DeepCopyInto(out *Stack) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new Stack.
|
||||
func (x *Stack) DeepCopy() *Stack {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Stack)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (x *Stack) DeepCopyObject() runtime.Object {
|
||||
if c := x.DeepCopy(); c != nil {
|
||||
return c
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StackList) DeepCopyInto(out *StackList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Stack, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new StackList.
|
||||
func (x *StackList) DeepCopy() *StackList {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StackList)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (x *StackList) DeepCopyObject() runtime.Object {
|
||||
if c := x.DeepCopy(); c != nil {
|
||||
return c
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StackSpec) DeepCopyInto(out *StackSpec) {
|
||||
*out = *in
|
||||
in.Owner.DeepCopyInto(&out.Owner)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new StackSpec.
|
||||
func (x *StackSpec) DeepCopy() *StackSpec {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StackSpec)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StackStatus) DeepCopyInto(out *StackStatus) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, creating a new StackStatus.
|
||||
func (x *StackStatus) DeepCopy() *StackStatus {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StackStatus)
|
||||
x.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
4
cli/command/stack/kubernetes/api/doc.go
Normal file
4
cli/command/stack/kubernetes/api/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
//
|
||||
// +domain=docker.com
|
||||
|
||||
package apis
|
||||
58
cli/command/stack/kubernetes/api/labels/labels.go
Normal file
58
cli/command/stack/kubernetes/api/labels/labels.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package labels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// ForServiceName is the label for the service name.
|
||||
ForServiceName = "com.docker.service.name"
|
||||
// ForStackName is the label for the stack name.
|
||||
ForStackName = "com.docker.stack.namespace"
|
||||
// ForServiceID is the label for the service id.
|
||||
ForServiceID = "com.docker.service.id"
|
||||
)
|
||||
|
||||
// ForService gives the labels to select a given service in a stack.
|
||||
func ForService(stackName, serviceName string) map[string]string {
|
||||
labels := map[string]string{}
|
||||
|
||||
if serviceName != "" {
|
||||
labels[ForServiceName] = serviceName
|
||||
}
|
||||
if stackName != "" {
|
||||
labels[ForStackName] = stackName
|
||||
}
|
||||
if serviceName != "" && stackName != "" {
|
||||
labels[ForServiceID] = stackName + "-" + serviceName
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
// Merge merges multiple lists of labels.
|
||||
func Merge(labelsList ...map[string]string) map[string]string {
|
||||
merged := map[string]string{}
|
||||
|
||||
for _, labels := range labelsList {
|
||||
for k, v := range labels {
|
||||
merged[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
// SelectorForStack gives the labelSelector to use for a given stack.
|
||||
// Specific service names can be passed to narrow down the selection.
|
||||
func SelectorForStack(stackName string, serviceNames ...string) string {
|
||||
switch len(serviceNames) {
|
||||
case 0:
|
||||
return fmt.Sprintf("%s=%s", ForStackName, stackName)
|
||||
case 1:
|
||||
return fmt.Sprintf("%s=%s,%s=%s", ForStackName, stackName, ForServiceName, serviceNames[0])
|
||||
default:
|
||||
return fmt.Sprintf("%s=%s,%s in (%s)", ForStackName, stackName, ForServiceName, strings.Join(serviceNames, ","))
|
||||
}
|
||||
}
|
||||
22
cli/command/stack/kubernetes/api/labels/labels_test.go
Normal file
22
cli/command/stack/kubernetes/api/labels/labels_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package labels
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestForService(t *testing.T) {
|
||||
labels := ForService("stack", "service")
|
||||
|
||||
assert.Len(t, labels, 3)
|
||||
assert.Equal(t, "stack", labels["com.docker.stack.namespace"])
|
||||
assert.Equal(t, "service", labels["com.docker.service.name"])
|
||||
assert.Equal(t, "stack-service", labels["com.docker.service.id"])
|
||||
}
|
||||
|
||||
func TestSelectorForStack(t *testing.T) {
|
||||
assert.Equal(t, "com.docker.stack.namespace=demostack", SelectorForStack("demostack"))
|
||||
assert.Equal(t, "com.docker.stack.namespace=stack,com.docker.service.name=service", SelectorForStack("stack", "service"))
|
||||
assert.Equal(t, "com.docker.stack.namespace=stack,com.docker.service.name in (service1,service2)", SelectorForStack("stack", "service1", "service2"))
|
||||
}
|
||||
32
cli/command/stack/kubernetes/check.go
Normal file
32
cli/command/stack/kubernetes/check.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
apiv1beta1 "github.com/docker/cli/cli/command/stack/kubernetes/api/compose/v1beta1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// APIPresent checks that an API is installed.
|
||||
func APIPresent(config *rest.Config) error {
|
||||
log.Debugf("check API present at %s", config.Host)
|
||||
clients, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groups, err := clients.Discovery().ServerGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, group := range groups.Groups {
|
||||
if group.Name == apiv1beta1.SchemeGroupVersion.Group {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("could not find %s api. Install it on your cluster first", apiv1beta1.SchemeGroupVersion.Group)
|
||||
}
|
||||
91
cli/command/stack/kubernetes/client.go
Normal file
91
cli/command/stack/kubernetes/client.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
appsv1beta2 "k8s.io/client-go/kubernetes/typed/apps/v1beta2"
|
||||
typesappsv1beta2 "k8s.io/client-go/kubernetes/typed/apps/v1beta2"
|
||||
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// Factory is the kubernetes client factory
|
||||
type Factory struct {
|
||||
namespace string
|
||||
config *restclient.Config
|
||||
coreClientSet *corev1.CoreV1Client
|
||||
appsClientSet *appsv1beta2.AppsV1beta2Client
|
||||
}
|
||||
|
||||
// NewFactory creates a kubernetes client factory
|
||||
func NewFactory(namespace string, config *restclient.Config) (*Factory, error) {
|
||||
coreClientSet, err := corev1.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
appsClientSet, err := appsv1beta2.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Factory{
|
||||
namespace: namespace,
|
||||
config: config,
|
||||
coreClientSet: coreClientSet,
|
||||
appsClientSet: appsClientSet,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RESTMapper returns a PriorityRESTMapper based on the discovered
|
||||
// groups and resources passed in.
|
||||
func (s *Factory) RESTMapper() (meta.RESTMapper, error) {
|
||||
clientSet, err := kubernetes.NewForConfig(s.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groupResources, err := discovery.GetAPIGroupResources(clientSet.DiscoveryClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructured)
|
||||
|
||||
return mapper, nil
|
||||
}
|
||||
|
||||
// ConfigMaps returns a client for kubernetes's config maps
|
||||
func (s *Factory) ConfigMaps() corev1.ConfigMapInterface {
|
||||
return s.coreClientSet.ConfigMaps(s.namespace)
|
||||
}
|
||||
|
||||
// Secrets returns a client for kubernetes's secrets
|
||||
func (s *Factory) Secrets() corev1.SecretInterface {
|
||||
return s.coreClientSet.Secrets(s.namespace)
|
||||
}
|
||||
|
||||
// Services returns a client for kubernetes's secrets
|
||||
func (s *Factory) Services() corev1.ServiceInterface {
|
||||
return s.coreClientSet.Services(s.namespace)
|
||||
}
|
||||
|
||||
// Pods returns a client for kubernetes's pods
|
||||
func (s *Factory) Pods() corev1.PodInterface {
|
||||
return s.coreClientSet.Pods(s.namespace)
|
||||
}
|
||||
|
||||
// Nodes returns a client for kubernetes's nodes
|
||||
func (s *Factory) Nodes() corev1.NodeInterface {
|
||||
return s.coreClientSet.Nodes()
|
||||
}
|
||||
|
||||
// ReplicationControllers returns a client for kubernetes replication controllers
|
||||
func (s *Factory) ReplicationControllers() corev1.ReplicationControllerInterface {
|
||||
return s.coreClientSet.ReplicationControllers(s.namespace)
|
||||
}
|
||||
|
||||
// ReplicaSets return a client for kubernetes replace sets
|
||||
func (s *Factory) ReplicaSets() typesappsv1beta2.ReplicaSetInterface {
|
||||
return s.appsClientSet.ReplicaSets(s.namespace)
|
||||
}
|
||||
107
cli/command/stack/kubernetes/cmd.go
Normal file
107
cli/command/stack/kubernetes/cmd.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
composev1beta1 "github.com/docker/cli/cli/command/stack/kubernetes/api/client/clientset_generated/clientset/typed/compose/v1beta1"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// AddStackCommands adds `stack` subcommands
|
||||
func AddStackCommands(root *cobra.Command, dockerCli command.Cli) {
|
||||
var kubeCli kubeCli
|
||||
configureCommand(root, &kubeCli)
|
||||
root.AddCommand(
|
||||
newDeployCommand(dockerCli, &kubeCli),
|
||||
newListCommand(dockerCli, &kubeCli),
|
||||
newRemoveCommand(dockerCli, &kubeCli),
|
||||
newServicesCommand(dockerCli, &kubeCli),
|
||||
newPsCommand(dockerCli, &kubeCli),
|
||||
)
|
||||
}
|
||||
|
||||
// NewTopLevelDeployCommand returns a command for `docker deploy`
|
||||
func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var kubeCli kubeCli
|
||||
cmd := newDeployCommand(dockerCli, &kubeCli)
|
||||
configureCommand(cmd, &kubeCli)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func configureCommand(root *cobra.Command, kubeCli *kubeCli) {
|
||||
var (
|
||||
kubeOpts kubeOptions
|
||||
)
|
||||
kubeOpts.installFlags(root.PersistentFlags())
|
||||
preRunE := root.PersistentPreRunE
|
||||
root.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
if preRunE != nil {
|
||||
if err := preRunE(cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
kubeCli.kubeNamespace = kubeOpts.namespace
|
||||
if kubeCli.kubeNamespace == "" {
|
||||
kubeCli.kubeNamespace = "default"
|
||||
}
|
||||
// Read kube config flag and environment variable
|
||||
if kubeOpts.kubeconfig == "" {
|
||||
if config := os.Getenv("KUBECONFIG"); config != "" {
|
||||
kubeOpts.kubeconfig = config
|
||||
} else {
|
||||
kubeOpts.kubeconfig = filepath.Join(homedir.Get(), ".kube/config")
|
||||
}
|
||||
}
|
||||
config, err := clientcmd.BuildConfigFromFlags("", kubeOpts.kubeconfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kubeCli.kubeConfig = config
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// KubeOptions are options specific to kubernetes
|
||||
type kubeOptions struct {
|
||||
namespace string
|
||||
kubeconfig string
|
||||
}
|
||||
|
||||
// InstallFlags adds flags for the common options on the FlagSet
|
||||
func (opts *kubeOptions) installFlags(flags *pflag.FlagSet) {
|
||||
flags.StringVar(&opts.namespace, "namespace", "default", "Kubernetes namespace to use")
|
||||
flags.StringVar(&opts.kubeconfig, "kubeconfig", "", "Kubernetes config file")
|
||||
}
|
||||
|
||||
type kubeCli struct {
|
||||
kubeConfig *restclient.Config
|
||||
kubeNamespace string
|
||||
}
|
||||
|
||||
func (c *kubeCli) ComposeClient() (*Factory, error) {
|
||||
return NewFactory(c.kubeNamespace, c.kubeConfig)
|
||||
}
|
||||
|
||||
func (c *kubeCli) KubeConfig() *restclient.Config {
|
||||
return c.kubeConfig
|
||||
}
|
||||
|
||||
func (c *kubeCli) Stacks() (composev1beta1.StackInterface, error) {
|
||||
err := APIPresent(c.kubeConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientSet, err := composev1beta1.NewForConfig(c.kubeConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return clientSet.Stacks(c.kubeNamespace), nil
|
||||
}
|
||||
71
cli/command/stack/kubernetes/collision.go
Normal file
71
cli/command/stack/kubernetes/collision.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
apiv1beta1 "github.com/docker/cli/cli/command/stack/kubernetes/api/compose/v1beta1"
|
||||
"github.com/docker/cli/cli/command/stack/kubernetes/api/labels"
|
||||
"github.com/docker/cli/cli/compose/loader"
|
||||
"github.com/docker/cli/cli/compose/types"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
)
|
||||
|
||||
// IsColliding verify that services defined in the stack collides with already deployed services
|
||||
func IsColliding(services corev1.ServiceInterface, stack *apiv1beta1.Stack) error {
|
||||
stackObjects, err := getServices(stack.Spec.ComposeFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, srv := range stackObjects {
|
||||
if err := verify(services, stack.Name, srv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func verify(services corev1.ServiceInterface, stackName string, service string) error {
|
||||
svc, err := services.Get(service, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
if key, ok := svc.ObjectMeta.Labels[labels.ForStackName]; ok {
|
||||
if key != stackName {
|
||||
return fmt.Errorf("service %s already present in stack named %s", service, key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("service %s already present in the cluster", service)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getServices(composeFile string) ([]string, error) {
|
||||
parsed, err := loader.ParseYAML([]byte(composeFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := loader.Load(types.ConfigDetails{
|
||||
WorkingDir: ".",
|
||||
ConfigFiles: []types.ConfigFile{
|
||||
{
|
||||
Config: parsed,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services := make([]string, len(config.Services))
|
||||
for i := range config.Services {
|
||||
services[i] = config.Services[i].Name
|
||||
}
|
||||
sort.Strings(services)
|
||||
return services, nil
|
||||
}
|
||||
171
cli/command/stack/kubernetes/conversion.go
Normal file
171
cli/command/stack/kubernetes/conversion.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/command/stack/kubernetes/api/labels"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
appsv1beta2 "k8s.io/api/apps/v1beta2"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
)
|
||||
|
||||
// Pod conversion
|
||||
func podToTask(pod apiv1.Pod) swarm.Task {
|
||||
var startTime time.Time
|
||||
if pod.Status.StartTime != nil {
|
||||
startTime = (*pod.Status.StartTime).Time
|
||||
}
|
||||
task := swarm.Task{
|
||||
ID: string(pod.UID),
|
||||
NodeID: pod.Spec.NodeName,
|
||||
Spec: swarm.TaskSpec{
|
||||
ContainerSpec: &swarm.ContainerSpec{
|
||||
Image: getContainerImage(pod.Spec.Containers),
|
||||
},
|
||||
},
|
||||
DesiredState: podPhaseToState(pod.Status.Phase),
|
||||
Status: swarm.TaskStatus{
|
||||
State: podPhaseToState(pod.Status.Phase),
|
||||
Timestamp: startTime,
|
||||
PortStatus: swarm.PortStatus{
|
||||
Ports: getPorts(pod.Spec.Containers),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pod.GetLabels()
|
||||
return task
|
||||
}
|
||||
|
||||
func podPhaseToState(phase apiv1.PodPhase) swarm.TaskState {
|
||||
switch phase {
|
||||
case apiv1.PodPending:
|
||||
return swarm.TaskStatePending
|
||||
case apiv1.PodRunning:
|
||||
return swarm.TaskStateRunning
|
||||
case apiv1.PodSucceeded:
|
||||
return swarm.TaskStateComplete
|
||||
case apiv1.PodFailed:
|
||||
return swarm.TaskStateFailed
|
||||
default:
|
||||
return swarm.TaskState("unknown")
|
||||
}
|
||||
}
|
||||
|
||||
func toSwarmProtocol(protocol apiv1.Protocol) swarm.PortConfigProtocol {
|
||||
switch protocol {
|
||||
case apiv1.ProtocolTCP:
|
||||
return swarm.PortConfigProtocolTCP
|
||||
case apiv1.ProtocolUDP:
|
||||
return swarm.PortConfigProtocolUDP
|
||||
}
|
||||
return swarm.PortConfigProtocol("unknown")
|
||||
}
|
||||
|
||||
func fetchPods(namespace string, pods corev1.PodInterface) ([]apiv1.Pod, error) {
|
||||
labelSelector := labels.SelectorForStack(namespace)
|
||||
podsList, err := pods.List(metav1.ListOptions{LabelSelector: labelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return podsList.Items, nil
|
||||
}
|
||||
|
||||
func getContainerImage(containers []apiv1.Container) string {
|
||||
if len(containers) == 0 {
|
||||
return ""
|
||||
}
|
||||
return containers[0].Image
|
||||
}
|
||||
|
||||
func getPorts(containers []apiv1.Container) []swarm.PortConfig {
|
||||
if len(containers) == 0 || len(containers[0].Ports) == 0 {
|
||||
return nil
|
||||
}
|
||||
ports := make([]swarm.PortConfig, len(containers[0].Ports))
|
||||
for i, port := range containers[0].Ports {
|
||||
ports[i] = swarm.PortConfig{
|
||||
PublishedPort: uint32(port.HostPort),
|
||||
TargetPort: uint32(port.ContainerPort),
|
||||
Protocol: toSwarmProtocol(port.Protocol),
|
||||
}
|
||||
}
|
||||
return ports
|
||||
}
|
||||
|
||||
type tasksBySlot []swarm.Task
|
||||
|
||||
func (t tasksBySlot) Len() int {
|
||||
return len(t)
|
||||
}
|
||||
|
||||
func (t tasksBySlot) Swap(i, j int) {
|
||||
t[i], t[j] = t[j], t[i]
|
||||
}
|
||||
|
||||
func (t tasksBySlot) Less(i, j int) bool {
|
||||
// Sort by slot.
|
||||
if t[i].Slot != t[j].Slot {
|
||||
return t[i].Slot < t[j].Slot
|
||||
}
|
||||
|
||||
// If same slot, sort by most recent.
|
||||
return t[j].Meta.CreatedAt.Before(t[i].CreatedAt)
|
||||
}
|
||||
|
||||
// Replicas conversion
|
||||
func replicasToServices(replicas *appsv1beta2.ReplicaSetList, services *apiv1.ServiceList) ([]swarm.Service, map[string]formatter.ServiceListInfo, error) {
|
||||
result := make([]swarm.Service, len(replicas.Items))
|
||||
infos := make(map[string]formatter.ServiceListInfo, len(replicas.Items))
|
||||
for i, r := range replicas.Items {
|
||||
service, ok := findService(services, r.Labels[labels.ForServiceName])
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("could not find service '%s'", r.Labels[labels.ForServiceName])
|
||||
}
|
||||
uid := string(service.UID)
|
||||
s := swarm.Service{
|
||||
ID: uid,
|
||||
Spec: swarm.ServiceSpec{
|
||||
Annotations: swarm.Annotations{
|
||||
Name: service.Name,
|
||||
},
|
||||
TaskTemplate: swarm.TaskSpec{
|
||||
ContainerSpec: &swarm.ContainerSpec{
|
||||
Image: getContainerImage(r.Spec.Template.Spec.Containers),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if service.Spec.Type == apiv1.ServiceTypeLoadBalancer {
|
||||
configs := make([]swarm.PortConfig, len(service.Spec.Ports))
|
||||
for i, p := range service.Spec.Ports {
|
||||
configs[i] = swarm.PortConfig{
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
PublishedPort: uint32(p.Port),
|
||||
TargetPort: uint32(p.TargetPort.IntValue()),
|
||||
Protocol: toSwarmProtocol(p.Protocol),
|
||||
}
|
||||
}
|
||||
s.Endpoint = swarm.Endpoint{Ports: configs}
|
||||
}
|
||||
result[i] = s
|
||||
infos[uid] = formatter.ServiceListInfo{
|
||||
Mode: "replicated",
|
||||
Replicas: fmt.Sprintf("%d/%d", r.Status.AvailableReplicas, r.Status.Replicas),
|
||||
}
|
||||
}
|
||||
return result, infos, nil
|
||||
}
|
||||
|
||||
func findService(services *apiv1.ServiceList, name string) (apiv1.Service, bool) {
|
||||
for _, s := range services.Items {
|
||||
if s.Name == name {
|
||||
return s, true
|
||||
}
|
||||
}
|
||||
return apiv1.Service{}, false
|
||||
}
|
||||
41
cli/command/stack/kubernetes/convert.go
Normal file
41
cli/command/stack/kubernetes/convert.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/cli/command/stack/kubernetes/api/labels"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// toConfigMap converts a Compose Config to a Kube ConfigMap.
|
||||
func toConfigMap(stackName, name, key string, content []byte) *apiv1.ConfigMap {
|
||||
return &apiv1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ConfigMap",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: map[string]string{
|
||||
labels.ForStackName: stackName,
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
key: string(content),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// toSecret converts a Compose Secret to a Kube Secret.
|
||||
func toSecret(stackName, name, key string, content []byte) *apiv1.Secret {
|
||||
return &apiv1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: map[string]string{
|
||||
labels.ForStackName: stackName,
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
key: content,
|
||||
},
|
||||
}
|
||||
}
|
||||
158
cli/command/stack/kubernetes/deploy.go
Normal file
158
cli/command/stack/kubernetes/deploy.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/stack/common"
|
||||
composeTypes "github.com/docker/cli/cli/compose/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
)
|
||||
|
||||
type deployOptions struct {
|
||||
composefile string
|
||||
stack string
|
||||
}
|
||||
|
||||
func newDeployCommand(dockerCli command.Cli, kubeCli *kubeCli) *cobra.Command {
|
||||
var opts deployOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "deploy [OPTIONS] STACK",
|
||||
Aliases: []string{"up"},
|
||||
Short: "Deploy a new stack or update an existing stack",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.stack = args[0]
|
||||
return runDeploy(dockerCli, kubeCli, opts)
|
||||
},
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
common.AddComposefileFlag(&opts.composefile, flags)
|
||||
// FIXME(vdemeester) other flags ? (bundlefile, registry-auth, prune, resolve-image) ?
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runDeploy(dockerCli command.Cli, kubeCli *kubeCli, opts deployOptions) error {
|
||||
cmdOut := dockerCli.Out()
|
||||
// Check arguments
|
||||
if opts.composefile == "" {
|
||||
return errors.Errorf("Please specify a Compose file (with --compose-file).")
|
||||
}
|
||||
// Initialize clients
|
||||
stacks, err := kubeCli.Stacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
composeClient, err := kubeCli.ComposeClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configMaps := composeClient.ConfigMaps()
|
||||
secrets := composeClient.Secrets()
|
||||
services := composeClient.Services()
|
||||
pods := composeClient.Pods()
|
||||
watcher := DeployWatcher{
|
||||
Pods: pods,
|
||||
}
|
||||
|
||||
// Parse the compose file
|
||||
stack, cfg, err := LoadStack(opts.stack, opts.composefile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME(vdemeester) handle warnings server-side
|
||||
|
||||
if err = IsColliding(services, stack); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = createFileBasedConfigMaps(stack.Name, cfg.Configs, configMaps); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = createFileBasedSecrets(stack.Name, cfg.Secrets, secrets); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if in, err := stacks.Get(stack.Name, metav1.GetOptions{}); err == nil {
|
||||
in.Spec = stack.Spec
|
||||
|
||||
if _, err = stacks.Update(in); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Stack %s was updated\n", stack.Name)
|
||||
} else {
|
||||
if _, err = stacks.Create(stack); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(cmdOut, "Stack %s was created\n", stack.Name)
|
||||
}
|
||||
|
||||
fmt.Fprintln(cmdOut, "Waiting for the stack to be stable and running...")
|
||||
|
||||
<-watcher.Watch(stack, serviceNames(cfg))
|
||||
|
||||
fmt.Fprintf(cmdOut, "Stack %s is stable and running\n\n", stack.Name)
|
||||
// fmt.Fprintf(cmdOut, "Read the logs with:\n $ %s stack logs %s\n", filepath.Base(os.Args[0]), stack.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createFileBasedConfigMaps creates a Kubernetes ConfigMap for each Compose global file-based config.
|
||||
func createFileBasedConfigMaps(stackName string, globalConfigs map[string]composeTypes.ConfigObjConfig, configMaps corev1.ConfigMapInterface) error {
|
||||
for name, config := range globalConfigs {
|
||||
if config.File == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fileName := path.Base(config.File)
|
||||
content, err := ioutil.ReadFile(config.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configMap := toConfigMap(stackName, name, fileName, content)
|
||||
configMaps.Create(configMap)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func serviceNames(cfg *composeTypes.Config) []string {
|
||||
names := []string{}
|
||||
|
||||
for _, service := range cfg.Services {
|
||||
names = append(names, service.Name)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// createFileBasedSecrets creates a Kubernetes Secret for each Compose global file-based secret.
|
||||
func createFileBasedSecrets(stackName string, globalSecrets map[string]composeTypes.SecretConfig, secrets corev1.SecretInterface) error {
|
||||
for name, secret := range globalSecrets {
|
||||
if secret.File == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fileName := path.Base(secret.File)
|
||||
content, err := ioutil.ReadFile(secret.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secret := toSecret(stackName, name, fileName, content)
|
||||
secrets.Create(secret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
81
cli/command/stack/kubernetes/list.go
Normal file
81
cli/command/stack/kubernetes/list.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"vbom.ml/util/sortorder"
|
||||
)
|
||||
|
||||
type listOptions struct {
|
||||
format string
|
||||
}
|
||||
|
||||
func newListCommand(dockerCli command.Cli, kubeCli *kubeCli) *cobra.Command {
|
||||
opts := listOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "ls",
|
||||
Aliases: []string{"list"},
|
||||
Short: "List stacks",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runList(dockerCli, kubeCli, opts)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.format, "format", "", "Pretty-print stacks using a Go template")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runList(dockerCli command.Cli, kubeCli *kubeCli, opts listOptions) error {
|
||||
stacks, err := getStacks(kubeCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
format := opts.format
|
||||
if len(format) == 0 {
|
||||
format = formatter.TableFormatKey
|
||||
}
|
||||
stackCtx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.NewStackFormat(format),
|
||||
}
|
||||
sort.Sort(byName(stacks))
|
||||
return formatter.StackWrite(stackCtx, stacks)
|
||||
}
|
||||
|
||||
type byName []*formatter.Stack
|
||||
|
||||
func (n byName) Len() int { return len(n) }
|
||||
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
|
||||
func (n byName) Less(i, j int) bool { return sortorder.NaturalLess(n[i].Name, n[j].Name) }
|
||||
|
||||
func getStacks(kubeCli *kubeCli) ([]*formatter.Stack, error) {
|
||||
stackSvc, err := kubeCli.Stacks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stacks, err := stackSvc.List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var formattedStacks []*formatter.Stack
|
||||
for _, stack := range stacks.Items {
|
||||
services, err := getServices(stack.Spec.ComposeFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
formattedStacks = append(formattedStacks, &formatter.Stack{
|
||||
Name: stack.Name,
|
||||
Services: len(services),
|
||||
})
|
||||
}
|
||||
return formattedStacks, nil
|
||||
}
|
||||
167
cli/command/stack/kubernetes/loader.go
Normal file
167
cli/command/stack/kubernetes/loader.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
apiv1beta1 "github.com/docker/cli/cli/command/stack/kubernetes/api/compose/v1beta1"
|
||||
"github.com/docker/cli/cli/compose/loader"
|
||||
"github.com/docker/cli/cli/compose/template"
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
"github.com/pkg/errors"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// LoadStack loads a stack from a Compose file, with a given name.
|
||||
func LoadStack(name, composeFile string) (*apiv1beta1.Stack, *composetypes.Config, error) {
|
||||
if composeFile == "" {
|
||||
return nil, nil, errors.New("compose-file must be set")
|
||||
}
|
||||
|
||||
workingDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
composePath := composeFile
|
||||
if !strings.HasPrefix(composePath, "/") {
|
||||
composePath = filepath.Join(workingDir, composeFile)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(composePath); os.IsNotExist(err) {
|
||||
return nil, nil, errors.Errorf("no compose file found in %s", filepath.Dir(composePath))
|
||||
}
|
||||
|
||||
binary, err := ioutil.ReadFile(composePath)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot load compose file")
|
||||
}
|
||||
|
||||
return load(name, binary, env())
|
||||
}
|
||||
|
||||
func load(name string, binary []byte, env map[string]string) (*apiv1beta1.Stack, *composetypes.Config, error) {
|
||||
processed, err := template.Substitute(string(binary), func(key string) (string, bool) { return env[key], true })
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot load compose file")
|
||||
}
|
||||
|
||||
parsed, err := loader.ParseYAML([]byte(processed))
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "cannot load compose file")
|
||||
}
|
||||
|
||||
cfg, err := loader.Load(composetypes.ConfigDetails{
|
||||
WorkingDir: ".",
|
||||
ConfigFiles: []composetypes.ConfigFile{
|
||||
{
|
||||
Config: parsed,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "cannot load compose file")
|
||||
}
|
||||
|
||||
result, err := processEnvFiles(processed, parsed, cfg)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "cannot load compose file")
|
||||
}
|
||||
|
||||
return &apiv1beta1.Stack{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: apiv1beta1.StackSpec{
|
||||
ComposeFile: result,
|
||||
},
|
||||
}, cfg, nil
|
||||
}
|
||||
|
||||
type iMap = map[string]interface{}
|
||||
|
||||
func processEnvFiles(input string, parsed map[string]interface{}, config *composetypes.Config) (string, error) {
|
||||
changed := false
|
||||
|
||||
for _, svc := range config.Services {
|
||||
if len(svc.EnvFile) == 0 {
|
||||
continue
|
||||
}
|
||||
// Load() processed the env_file for us, we just need to inject back into
|
||||
// the intermediate representation
|
||||
env := iMap{}
|
||||
for k, v := range svc.Environment {
|
||||
env[k] = v
|
||||
}
|
||||
parsed["services"].(iMap)[svc.Name].(iMap)["environment"] = env
|
||||
delete(parsed["services"].(iMap)[svc.Name].(iMap), "env_file")
|
||||
changed = true
|
||||
}
|
||||
if !changed {
|
||||
return input, nil
|
||||
}
|
||||
res, err := yaml.Marshal(parsed)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(res), nil
|
||||
}
|
||||
|
||||
func env() map[string]string {
|
||||
// Apply .env file first
|
||||
config := readEnvFile(".env")
|
||||
|
||||
// Apply env variables
|
||||
for k, v := range envToMap(os.Environ()) {
|
||||
config[k] = v
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func readEnvFile(path string) map[string]string {
|
||||
config := map[string]string{}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return config // Ignore
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
key := parts[0]
|
||||
value := parts[1]
|
||||
|
||||
config[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func envToMap(env []string) map[string]string {
|
||||
config := map[string]string{}
|
||||
|
||||
for _, value := range env {
|
||||
parts := strings.SplitN(value, "=", 2)
|
||||
|
||||
key := parts[0]
|
||||
value := parts[1]
|
||||
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
34
cli/command/stack/kubernetes/loader_test.go
Normal file
34
cli/command/stack/kubernetes/loader_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPlaceholders(t *testing.T) {
|
||||
env := map[string]string{
|
||||
"TAG": "_latest_",
|
||||
"K1": "V1",
|
||||
"K2": "V2",
|
||||
}
|
||||
|
||||
prefix := "version: '3'\nvolumes:\n data:\n external:\n name: "
|
||||
var tests = []struct {
|
||||
input string
|
||||
expectedOutput string
|
||||
}{
|
||||
{prefix + "BEFORE${TAG}AFTER", prefix + "BEFORE_latest_AFTER"},
|
||||
{prefix + "BEFORE${K1}${K2}AFTER", prefix + "BEFOREV1V2AFTER"},
|
||||
{prefix + "BEFORE$TAG AFTER", prefix + "BEFORE_latest_ AFTER"},
|
||||
{prefix + "BEFORE$$TAG AFTER", prefix + "BEFORE$TAG AFTER"},
|
||||
{prefix + "BEFORE $UNKNOWN AFTER", prefix + "BEFORE AFTER"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
output, _, err := load("stack", []byte(test.input), env)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedOutput, output.Spec.ComposeFile)
|
||||
}
|
||||
}
|
||||
138
cli/command/stack/kubernetes/ps.go
Normal file
138
cli/command/stack/kubernetes/ps.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/command/task"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
type psOptions struct {
|
||||
filter opts.FilterOpt
|
||||
noTrunc bool
|
||||
namespace string
|
||||
noResolve bool
|
||||
quiet bool
|
||||
format string
|
||||
}
|
||||
|
||||
func newPsCommand(dockerCli command.Cli, kubeCli *kubeCli) *cobra.Command {
|
||||
options := psOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "ps [OPTIONS] STACK",
|
||||
Short: "List the tasks in the stack",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.namespace = args[0]
|
||||
return runPS(dockerCli, kubeCli, options)
|
||||
},
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output")
|
||||
flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
||||
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs")
|
||||
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPS(dockerCli command.Cli, kubeCli *kubeCli, options psOptions) error {
|
||||
namespace := options.namespace
|
||||
|
||||
// Initialize clients
|
||||
client, err := kubeCli.ComposeClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stacks, err := kubeCli.Stacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
podsClient := client.Pods()
|
||||
|
||||
// Fetch pods
|
||||
if _, err := stacks.Get(namespace, metav1.GetOptions{}); err != nil {
|
||||
return fmt.Errorf("nothing found in stack: %s", namespace)
|
||||
}
|
||||
|
||||
pods, err := fetchPods(namespace, podsClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(pods) == 0 {
|
||||
return fmt.Errorf("nothing found in stack: %s", namespace)
|
||||
}
|
||||
|
||||
format := options.format
|
||||
if len(format) == 0 {
|
||||
format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet)
|
||||
}
|
||||
nodeResolver := makeNodeResolver(options.noResolve, client.Nodes())
|
||||
|
||||
tasks := make([]swarm.Task, len(pods))
|
||||
for i, pod := range pods {
|
||||
tasks[i] = podToTask(pod)
|
||||
}
|
||||
return print(dockerCli, tasks, pods, nodeResolver, !options.noTrunc, options.quiet, format)
|
||||
}
|
||||
|
||||
type idResolver func(name string) (string, error)
|
||||
|
||||
func print(dockerCli command.Cli, tasks []swarm.Task, pods []apiv1.Pod, nodeResolver idResolver, trunc, quiet bool, format string) error {
|
||||
sort.Stable(tasksBySlot(tasks))
|
||||
|
||||
names := map[string]string{}
|
||||
nodes := map[string]string{}
|
||||
|
||||
tasksCtx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.NewTaskFormat(format, quiet),
|
||||
Trunc: trunc,
|
||||
}
|
||||
|
||||
for i, task := range tasks {
|
||||
nodeValue, err := nodeResolver(pods[i].Spec.NodeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
names[task.ID] = pods[i].Name
|
||||
nodes[task.ID] = nodeValue
|
||||
}
|
||||
|
||||
return formatter.TaskWrite(tasksCtx, tasks, names, nodes)
|
||||
}
|
||||
|
||||
func makeNodeResolver(noResolve bool, nodes corev1.NodeInterface) func(string) (string, error) {
|
||||
// Here we have a name and we need to resolve its identifier. To mimic swarm behavior
|
||||
// we need to resolve the id when noresolve is set, otherwise we return the name.
|
||||
if noResolve {
|
||||
return func(name string) (string, error) {
|
||||
n, err := nodes.List(metav1.ListOptions{
|
||||
FieldSelector: fields.OneTermEqualSelector(api.ObjectNameField, name).String(),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(n.Items) != 1 {
|
||||
return "", fmt.Errorf("could not find node '%s'", name)
|
||||
}
|
||||
return string(n.Items[0].UID), nil
|
||||
}
|
||||
}
|
||||
return func(name string) (string, error) { return name, nil }
|
||||
}
|
||||
46
cli/command/stack/kubernetes/remove.go
Normal file
46
cli/command/stack/kubernetes/remove.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type removeOptions struct {
|
||||
stacks []string
|
||||
}
|
||||
|
||||
func newRemoveCommand(dockerCli command.Cli, kubeCli *kubeCli) *cobra.Command {
|
||||
var opts removeOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm STACK [STACK...]",
|
||||
Aliases: []string{"remove", "down"},
|
||||
Short: "Remove one or more stacks",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.stacks = args
|
||||
return runRemove(dockerCli, kubeCli, opts)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRemove(dockerCli command.Cli, kubeCli *kubeCli, opts removeOptions) error {
|
||||
stacks, err := kubeCli.Stacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stack := range opts.stacks {
|
||||
fmt.Fprintf(dockerCli.Out(), "Removing stack: %s\n", stack)
|
||||
err := stacks.Delete(stack, &metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
fmt.Fprintf(dockerCli.Out(), "Failed to remove stack %s: %s\n", stack, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
90
cli/command/stack/kubernetes/services.go
Normal file
90
cli/command/stack/kubernetes/services.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/command/stack/kubernetes/api/labels"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type servicesOptions struct {
|
||||
quiet bool
|
||||
format string
|
||||
namespace string
|
||||
}
|
||||
|
||||
func newServicesCommand(dockerCli command.Cli, kubeCli *kubeCli) *cobra.Command {
|
||||
var options servicesOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "services [OPTIONS] STACK",
|
||||
Short: "List the services in the stack",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.namespace = args[0]
|
||||
return runServices(dockerCli, kubeCli, options)
|
||||
},
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
|
||||
flags.StringVar(&options.format, "format", "", "Pretty-print services using a Go template")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runServices(dockerCli command.Cli, kubeCli *kubeCli, options servicesOptions) error {
|
||||
// Initialize clients
|
||||
client, err := kubeCli.ComposeClient()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
stacks, err := kubeCli.Stacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
replicas := client.ReplicaSets()
|
||||
|
||||
if _, err := stacks.Get(options.namespace, metav1.GetOptions{}); err != nil {
|
||||
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", options.namespace)
|
||||
return nil
|
||||
}
|
||||
|
||||
replicasList, err := replicas.List(metav1.ListOptions{LabelSelector: labels.SelectorForStack(options.namespace)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
servicesList, err := client.Services().List(metav1.ListOptions{LabelSelector: labels.SelectorForStack(options.namespace)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert Replicas sets and kubernetes services to swam services and formatter informations
|
||||
services, info, err := replicasToServices(replicasList, servicesList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.quiet {
|
||||
info = map[string]formatter.ServiceListInfo{}
|
||||
}
|
||||
|
||||
format := options.format
|
||||
if len(format) == 0 {
|
||||
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !options.quiet {
|
||||
format = dockerCli.ConfigFile().ServicesFormat
|
||||
} else {
|
||||
format = formatter.TableFormatKey
|
||||
}
|
||||
}
|
||||
|
||||
servicesCtx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.NewServiceListFormat(format, options.quiet),
|
||||
}
|
||||
return formatter.ServiceListWrite(servicesCtx, services, info)
|
||||
}
|
||||
117
cli/command/stack/kubernetes/watcher.go
Normal file
117
cli/command/stack/kubernetes/watcher.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
apiv1beta1 "github.com/docker/cli/cli/command/stack/kubernetes/api/compose/v1beta1"
|
||||
"github.com/docker/cli/cli/command/stack/kubernetes/api/labels"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
)
|
||||
|
||||
// DeployWatcher watches a stack deployement
|
||||
type DeployWatcher struct {
|
||||
Pods corev1.PodInterface
|
||||
}
|
||||
|
||||
// Watch watches a stuck deployement and return a chan that will holds the state of the stack
|
||||
func (w DeployWatcher) Watch(stack *apiv1beta1.Stack, serviceNames []string) chan bool {
|
||||
stop := make(chan bool)
|
||||
|
||||
go w.waitForPods(stack.Name, serviceNames, stop)
|
||||
|
||||
return stop
|
||||
}
|
||||
|
||||
func (w DeployWatcher) waitForPods(stackName string, serviceNames []string, stop chan bool) {
|
||||
starts := map[string]int32{}
|
||||
|
||||
for {
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
list, err := w.Pods.List(metav1.ListOptions{
|
||||
LabelSelector: labels.SelectorForStack(stackName),
|
||||
IncludeUninitialized: true,
|
||||
})
|
||||
if err != nil {
|
||||
stop <- true
|
||||
return
|
||||
}
|
||||
|
||||
for i := range list.Items {
|
||||
pod := list.Items[i]
|
||||
if pod.Status.Phase != apiv1.PodRunning {
|
||||
continue
|
||||
}
|
||||
|
||||
startCount := startCount(pod)
|
||||
serviceName := pod.Labels[labels.ForServiceName]
|
||||
if startCount != starts[serviceName] {
|
||||
if startCount == 1 {
|
||||
fmt.Printf(" - Service %s has one container running\n", serviceName)
|
||||
} else {
|
||||
fmt.Printf(" - Service %s was restarted %d %s\n", serviceName, startCount-1, timeTimes(startCount-1))
|
||||
}
|
||||
|
||||
starts[serviceName] = startCount
|
||||
}
|
||||
}
|
||||
|
||||
if allReady(list.Items, serviceNames) {
|
||||
stop <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func startCount(pod apiv1.Pod) int32 {
|
||||
restart := int32(0)
|
||||
|
||||
for _, status := range pod.Status.ContainerStatuses {
|
||||
restart += status.RestartCount
|
||||
}
|
||||
|
||||
return 1 + restart
|
||||
}
|
||||
|
||||
func allReady(pods []apiv1.Pod, serviceNames []string) bool {
|
||||
serviceUp := map[string]bool{}
|
||||
|
||||
for _, pod := range pods {
|
||||
if time.Since(pod.GetCreationTimestamp().Time) < 10*time.Second {
|
||||
return false
|
||||
}
|
||||
|
||||
ready := false
|
||||
for _, cond := range pod.Status.Conditions {
|
||||
if cond.Type == apiv1.PodReady && cond.Status == apiv1.ConditionTrue {
|
||||
ready = true
|
||||
}
|
||||
}
|
||||
|
||||
if !ready {
|
||||
return false
|
||||
}
|
||||
|
||||
serviceName := pod.Labels[labels.ForServiceName]
|
||||
serviceUp[serviceName] = true
|
||||
}
|
||||
|
||||
for _, serviceName := range serviceNames {
|
||||
if !serviceUp[serviceName] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func timeTimes(n int32) string {
|
||||
if n == 1 {
|
||||
return "time"
|
||||
}
|
||||
|
||||
return "times"
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
22
cli/command/stack/swarm/cmd.go
Normal file
22
cli/command/stack/swarm/cmd.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// AddStackCommands adds `stack` subcommands
|
||||
func AddStackCommands(root *cobra.Command, dockerCli command.Cli) {
|
||||
root.AddCommand(
|
||||
newDeployCommand(dockerCli),
|
||||
newListCommand(dockerCli),
|
||||
newRemoveCommand(dockerCli),
|
||||
newServicesCommand(dockerCli),
|
||||
newPsCommand(dockerCli),
|
||||
)
|
||||
}
|
||||
|
||||
// NewTopLevelDeployCommand returns a command for `docker deploy`
|
||||
func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return newDeployCommand(dockerCli)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/cli/compose/convert"
|
||||
@@ -1,10 +1,11 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/stack/common"
|
||||
"github.com/docker/cli/cli/compose/convert"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
@@ -44,9 +45,9 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
addBundlefileFlag(&opts.bundlefile, flags)
|
||||
addComposefileFlag(&opts.composefile, flags)
|
||||
addRegistryAuthFlag(&opts.sendRegistryAuth, flags)
|
||||
common.AddBundlefileFlag(&opts.bundlefile, flags)
|
||||
common.AddComposefileFlag(&opts.composefile, flags)
|
||||
common.AddRegistryAuthFlag(&opts.sendRegistryAuth, flags)
|
||||
flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced")
|
||||
flags.SetAnnotation("prune", "version", []string{"1.27"})
|
||||
flags.StringVar(&opts.resolveImage, "resolve-image", resolveImageAlways,
|
||||
@@ -1,16 +1,17 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/stack/common"
|
||||
"github.com/docker/cli/cli/compose/convert"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
)
|
||||
|
||||
func deployBundle(ctx context.Context, dockerCli command.Cli, opts deployOptions) error {
|
||||
bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
|
||||
bundle, err := common.LoadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"sort"
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
Reference in New Issue
Block a user