2017-11-20 09:30:52 -05:00
|
|
|
package kubernetes
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"path"
|
|
|
|
|
2018-01-29 16:18:43 -05:00
|
|
|
"github.com/docker/cli/cli/command/stack/loader"
|
2017-12-04 06:30:39 -05:00
|
|
|
"github.com/docker/cli/cli/command/stack/options"
|
2018-01-29 16:18:43 -05:00
|
|
|
composetypes "github.com/docker/cli/cli/compose/types"
|
2017-11-20 09:30:52 -05:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
|
|
|
)
|
|
|
|
|
2017-12-04 06:30:39 -05:00
|
|
|
// RunDeploy is the kubernetes implementation of docker stack deploy
|
|
|
|
func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error {
|
2017-11-20 09:30:52 -05:00
|
|
|
cmdOut := dockerCli.Out()
|
|
|
|
// Check arguments
|
2017-09-29 08:21:40 -04:00
|
|
|
if len(opts.Composefiles) == 0 {
|
|
|
|
return errors.Errorf("Please specify only one compose file (with --compose-file).")
|
2017-11-20 09:30:52 -05:00
|
|
|
}
|
2018-01-29 16:18:43 -05:00
|
|
|
|
|
|
|
// Parse the compose file
|
2018-02-21 12:31:52 -05:00
|
|
|
cfg, err := loader.LoadComposefile(dockerCli, opts)
|
2018-01-29 16:18:43 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-02-21 12:31:52 -05:00
|
|
|
stack, err := LoadStack(opts.Namespace, *cfg)
|
2018-01-29 16:18:43 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-11-20 09:30:52 -05:00
|
|
|
// Initialize clients
|
2018-01-02 17:56:07 -05:00
|
|
|
stacks, err := dockerCli.stacks()
|
2017-11-20 09:30:52 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-12-04 06:30:39 -05:00
|
|
|
composeClient, err := dockerCli.composeClient()
|
2017-11-20 09:30:52 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-01-02 17:56:07 -05:00
|
|
|
configMaps := composeClient.ConfigMaps()
|
|
|
|
secrets := composeClient.Secrets()
|
|
|
|
services := composeClient.Services()
|
|
|
|
pods := composeClient.Pods()
|
2017-11-20 09:30:52 -05:00
|
|
|
watcher := DeployWatcher{
|
2018-01-02 17:56:07 -05:00
|
|
|
Pods: pods,
|
2017-11-20 09:30:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME(vdemeester) handle warnings server-side
|
2018-01-02 17:56:07 -05:00
|
|
|
if err = IsColliding(services, stack, cfg); err != nil {
|
2017-11-20 09:30:52 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-01-02 17:56:07 -05:00
|
|
|
if err = createFileBasedConfigMaps(stack.Name, cfg.Configs, configMaps); err != nil {
|
2017-11-20 09:30:52 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-01-02 17:56:07 -05:00
|
|
|
if err = createFileBasedSecrets(stack.Name, cfg.Secrets, secrets); err != nil {
|
2017-11-20 09:30:52 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-01-02 17:56:07 -05:00
|
|
|
if in, err := stacks.Get(stack.Name, metav1.GetOptions{}); err == nil {
|
2017-11-20 09:30:52 -05:00
|
|
|
in.Spec = stack.Spec
|
|
|
|
|
2018-01-02 17:56:07 -05:00
|
|
|
if _, err = stacks.Update(in); err != nil {
|
2017-11-20 09:30:52 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("Stack %s was updated\n", stack.Name)
|
|
|
|
} else {
|
2018-01-02 17:56:07 -05:00
|
|
|
if _, err = stacks.Create(stack); err != nil {
|
2017-11-20 09:30:52 -05:00
|
|
|
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)
|
2018-01-02 17:56:07 -05:00
|
|
|
// TODO: fmt.Fprintf(cmdOut, "Read the logs with:\n $ %s stack logs %s\n", filepath.Base(os.Args[0]), stack.Name)
|
2017-11-20 09:30:52 -05:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// createFileBasedConfigMaps creates a Kubernetes ConfigMap for each Compose global file-based config.
|
2018-01-29 16:18:43 -05:00
|
|
|
func createFileBasedConfigMaps(stackName string, globalConfigs map[string]composetypes.ConfigObjConfig, configMaps corev1.ConfigMapInterface) error {
|
2017-11-20 09:30:52 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-01-03 09:46:19 -05:00
|
|
|
if _, err := configMaps.Create(toConfigMap(stackName, name, fileName, content)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-11-20 09:30:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-01-29 16:18:43 -05:00
|
|
|
func serviceNames(cfg *composetypes.Config) []string {
|
2017-11-20 09:30:52 -05:00
|
|
|
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.
|
2018-01-29 16:18:43 -05:00
|
|
|
func createFileBasedSecrets(stackName string, globalSecrets map[string]composetypes.SecretConfig, secrets corev1.SecretInterface) error {
|
2017-11-20 09:30:52 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-01-03 09:46:19 -05:00
|
|
|
if _, err := secrets.Create(toSecret(stackName, name, fileName, content)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-11-20 09:30:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|