mirror of https://github.com/docker/cli.git
Take @nass review into account
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
parent
dedd0db51a
commit
b5170bde03
|
@ -3,6 +3,8 @@ package command
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Orchestrator type acts as an enum describing supported orchestrators.
|
// Orchestrator type acts as an enum describing supported orchestrators.
|
||||||
|
@ -43,6 +45,7 @@ func GetOrchestrator(orchestrator string) Orchestrator {
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.Infof("Specified orchestrator %q is invalid. Please use either kubernetes or swarm", orchestrator)
|
||||||
// Nothing set, use default orchestrator
|
// Nothing set, use default orchestrator
|
||||||
return defaultOrchestrator
|
return defaultOrchestrator
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ type KubeCli struct {
|
||||||
kubeNamespace string
|
kubeNamespace string
|
||||||
}
|
}
|
||||||
|
|
||||||
// WrapCli wraps command.Cli with kubernetes specifiecs
|
// WrapCli wraps command.Cli with kubernetes specifics
|
||||||
func WrapCli(dockerCli command.Cli, cmd *cobra.Command) (*KubeCli, error) {
|
func WrapCli(dockerCli command.Cli, cmd *cobra.Command) (*KubeCli, error) {
|
||||||
var err error
|
var err error
|
||||||
cli := &KubeCli{
|
cli := &KubeCli{
|
||||||
|
@ -34,7 +34,7 @@ func WrapCli(dockerCli command.Cli, cmd *cobra.Command) (*KubeCli, error) {
|
||||||
}
|
}
|
||||||
kubeConfig := ""
|
kubeConfig := ""
|
||||||
if cmd.PersistentFlags().Changed("kubeconfig") {
|
if cmd.PersistentFlags().Changed("kubeconfig") {
|
||||||
kubeConfig, err = cmd.PersistentFlags().GetString("namespace")
|
kubeConfig, err = cmd.PersistentFlags().GetString("kubeconfig")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
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"
|
appsv1beta2 "k8s.io/client-go/kubernetes/typed/apps/v1beta2"
|
||||||
typesappsv1beta2 "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"
|
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
|
@ -38,23 +35,6 @@ func NewFactory(namespace string, config *restclient.Config) (*Factory, error) {
|
||||||
}, nil
|
}, 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
|
// ConfigMaps returns a client for kubernetes's config maps
|
||||||
func (s *Factory) ConfigMaps() corev1.ConfigMapInterface {
|
func (s *Factory) ConfigMaps() corev1.ConfigMapInterface {
|
||||||
return s.coreClientSet.ConfigMaps(s.namespace)
|
return s.coreClientSet.ConfigMaps(s.namespace)
|
||||||
|
|
|
@ -4,8 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/compose/loader"
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
"github.com/docker/cli/cli/compose/types"
|
|
||||||
apiv1beta1 "github.com/docker/cli/kubernetes/compose/v1beta1"
|
apiv1beta1 "github.com/docker/cli/kubernetes/compose/v1beta1"
|
||||||
"github.com/docker/cli/kubernetes/labels"
|
"github.com/docker/cli/kubernetes/labels"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
@ -13,11 +12,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsColliding verify that services defined in the stack collides with already deployed services
|
// IsColliding verify that services defined in the stack collides with already deployed services
|
||||||
func IsColliding(services corev1.ServiceInterface, stack *apiv1beta1.Stack) error {
|
func IsColliding(services corev1.ServiceInterface, stack *apiv1beta1.Stack, cfg *composetypes.Config) error {
|
||||||
stackObjects, err := getServices(stack.Spec.ComposeFile)
|
stackObjects := getServices(cfg)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, srv := range stackObjects {
|
for _, srv := range stackObjects {
|
||||||
if err := verify(services, stack.Name, srv); err != nil {
|
if err := verify(services, stack.Name, srv); err != nil {
|
||||||
|
@ -28,6 +24,9 @@ func IsColliding(services corev1.ServiceInterface, stack *apiv1beta1.Stack) erro
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verify checks wether the service is already present in kubernetes.
|
||||||
|
// If we find the service by name but it doesn't have our label or it has a different value
|
||||||
|
// than the stack name for the label, we fail (i.e. it will collide)
|
||||||
func verify(services corev1.ServiceInterface, stackName string, service string) error {
|
func verify(services corev1.ServiceInterface, stackName string, service string) error {
|
||||||
svc, err := services.Get(service, metav1.GetOptions{})
|
svc, err := services.Get(service, metav1.GetOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -45,27 +44,11 @@ func verify(services corev1.ServiceInterface, stackName string, service string)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServices(composeFile string) ([]string, error) {
|
func getServices(cfg *composetypes.Config) []string {
|
||||||
parsed, err := loader.ParseYAML([]byte(composeFile))
|
services := make([]string, len(cfg.Services))
|
||||||
if err != nil {
|
for i := range cfg.Services {
|
||||||
return nil, err
|
services[i] = cfg.Services[i].Name
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
sort.Strings(services)
|
||||||
return services, nil
|
return services
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error {
|
||||||
return errors.Errorf("Please specify a Compose file (with --compose-file).")
|
return errors.Errorf("Please specify a Compose file (with --compose-file).")
|
||||||
}
|
}
|
||||||
// Initialize clients
|
// Initialize clients
|
||||||
stacks, err := dockerCli.stacks()
|
stackInterface, err := dockerCli.stacks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -28,12 +28,12 @@ func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
configMaps := composeClient.ConfigMaps()
|
configMapInterface := composeClient.ConfigMaps()
|
||||||
secrets := composeClient.Secrets()
|
secretInterface := composeClient.Secrets()
|
||||||
services := composeClient.Services()
|
serviceInterface := composeClient.Services()
|
||||||
pods := composeClient.Pods()
|
podInterface := composeClient.Pods()
|
||||||
watcher := DeployWatcher{
|
watcher := DeployWatcher{
|
||||||
Pods: pods,
|
Pods: podInterface,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the compose file
|
// Parse the compose file
|
||||||
|
@ -43,29 +43,28 @@ func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME(vdemeester) handle warnings server-side
|
// FIXME(vdemeester) handle warnings server-side
|
||||||
|
if err = IsColliding(serviceInterface, stack, cfg); err != nil {
|
||||||
if err = IsColliding(services, stack); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = createFileBasedConfigMaps(stack.Name, cfg.Configs, configMaps); err != nil {
|
if err = createFileBasedConfigMaps(stack.Name, cfg.Configs, configMapInterface); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = createFileBasedSecrets(stack.Name, cfg.Secrets, secrets); err != nil {
|
if err = createFileBasedSecrets(stack.Name, cfg.Secrets, secretInterface); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if in, err := stacks.Get(stack.Name, metav1.GetOptions{}); err == nil {
|
if in, err := stackInterface.Get(stack.Name, metav1.GetOptions{}); err == nil {
|
||||||
in.Spec = stack.Spec
|
in.Spec = stack.Spec
|
||||||
|
|
||||||
if _, err = stacks.Update(in); err != nil {
|
if _, err = stackInterface.Update(in); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Stack %s was updated\n", stack.Name)
|
fmt.Printf("Stack %s was updated\n", stack.Name)
|
||||||
} else {
|
} else {
|
||||||
if _, err = stacks.Create(stack); err != nil {
|
if _, err = stackInterface.Create(stack); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/cli/cli/command/stack/options"
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
|
"github.com/docker/cli/cli/compose/loader"
|
||||||
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"vbom.ml/util/sortorder"
|
"vbom.ml/util/sortorder"
|
||||||
)
|
)
|
||||||
|
@ -45,14 +47,28 @@ func getStacks(kubeCli *KubeCli) ([]*formatter.Stack, error) {
|
||||||
}
|
}
|
||||||
var formattedStacks []*formatter.Stack
|
var formattedStacks []*formatter.Stack
|
||||||
for _, stack := range stacks.Items {
|
for _, stack := range stacks.Items {
|
||||||
services, err := getServices(stack.Spec.ComposeFile)
|
cfg, err := loadStack(stack.Spec.ComposeFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
formattedStacks = append(formattedStacks, &formatter.Stack{
|
formattedStacks = append(formattedStacks, &formatter.Stack{
|
||||||
Name: stack.Name,
|
Name: stack.Name,
|
||||||
Services: len(services),
|
Services: len(getServices(cfg)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return formattedStacks, nil
|
return formattedStacks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadStack(composefile string) (*composetypes.Config, error) {
|
||||||
|
parsed, err := loader.ParseYAML([]byte(composefile))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return loader.Load(composetypes.ConfigDetails{
|
||||||
|
ConfigFiles: []composetypes.ConfigFile{
|
||||||
|
{
|
||||||
|
Config: parsed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadStack loads a stack from a Compose file, with a given name.
|
// LoadStack loads a stack from a Compose file, with a given name.
|
||||||
|
// FIXME(vdemeester) remove this and use cli/compose/loader for both swarm and kubernetes
|
||||||
func LoadStack(name, composeFile string) (*apiv1beta1.Stack, *composetypes.Config, error) {
|
func LoadStack(name, composeFile string) (*apiv1beta1.Stack, *composetypes.Config, error) {
|
||||||
if composeFile == "" {
|
if composeFile == "" {
|
||||||
return nil, nil, errors.New("compose-file must be set")
|
return nil, nil, errors.New("compose-file must be set")
|
||||||
|
@ -38,13 +39,14 @@ func LoadStack(name, composeFile string) (*apiv1beta1.Stack, *composetypes.Confi
|
||||||
|
|
||||||
binary, err := ioutil.ReadFile(composePath)
|
binary, err := ioutil.ReadFile(composePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "cannot load compose file")
|
return nil, nil, errors.Wrap(err, "cannot read compose file")
|
||||||
}
|
}
|
||||||
|
|
||||||
return load(name, binary, env())
|
env := env(workingDir)
|
||||||
|
return load(name, binary, workingDir, env)
|
||||||
}
|
}
|
||||||
|
|
||||||
func load(name string, binary []byte, env map[string]string) (*apiv1beta1.Stack, *composetypes.Config, error) {
|
func load(name string, binary []byte, workingDir string, env map[string]string) (*apiv1beta1.Stack, *composetypes.Config, error) {
|
||||||
processed, err := template.Substitute(string(binary), func(key string) (string, bool) { return env[key], true })
|
processed, err := template.Substitute(string(binary), func(key string) (string, bool) { return env[key], true })
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "cannot load compose file")
|
return nil, nil, errors.Wrap(err, "cannot load compose file")
|
||||||
|
@ -56,7 +58,7 @@ func load(name string, binary []byte, env map[string]string) (*apiv1beta1.Stack,
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := loader.Load(composetypes.ConfigDetails{
|
cfg, err := loader.Load(composetypes.ConfigDetails{
|
||||||
WorkingDir: ".",
|
WorkingDir: workingDir,
|
||||||
ConfigFiles: []composetypes.ConfigFile{
|
ConfigFiles: []composetypes.ConfigFile{
|
||||||
{
|
{
|
||||||
Config: parsed,
|
Config: parsed,
|
||||||
|
@ -111,9 +113,9 @@ func processEnvFiles(input string, parsed map[string]interface{}, config *compos
|
||||||
return string(res), nil
|
return string(res), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func env() map[string]string {
|
func env(workingDir string) map[string]string {
|
||||||
// Apply .env file first
|
// Apply .env file first
|
||||||
config := readEnvFile(".env")
|
config := readEnvFile(filepath.Join(workingDir, ".env"))
|
||||||
|
|
||||||
// Apply env variables
|
// Apply env variables
|
||||||
for k, v := range envToMap(os.Environ()) {
|
for k, v := range envToMap(os.Environ()) {
|
||||||
|
|
|
@ -27,7 +27,7 @@ func TestPlaceholders(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
output, _, err := load("stack", []byte(test.input), env)
|
output, _, err := load("stack", []byte(test.input), ".", env)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.expectedOutput, output.Spec.ComposeFile)
|
assert.Equal(t, test.expectedOutput, output.Spec.ComposeFile)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue