Add experimental docker stack commands
Signed-off-by: Daniel Nephin <dnephin@docker.com> Upstream-commit: 71104bb592dc98467d3828394eabcbe50ca22ae4 Component: engine
This commit is contained in:
205
components/engine/api/client/stack/deploy.go
Normal file
205
components/engine/api/client/stack/deploy.go
Normal file
@ -0,0 +1,205 @@
|
||||
// +build experimental
|
||||
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/api/client/bundlefile"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/network"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultNetworkDriver = "overlay"
|
||||
)
|
||||
|
||||
type deployOptions struct {
|
||||
bundlefile string
|
||||
namespace string
|
||||
}
|
||||
|
||||
func newDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var opts deployOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "deploy [OPTIONS] STACK",
|
||||
Aliases: []string{"up"},
|
||||
Short: "Create and update a stack",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.namespace = args[0]
|
||||
return runDeploy(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
addBundlefileFlag(&opts.bundlefile, flags)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runDeploy(dockerCli *client.DockerCli, opts deployOptions) error {
|
||||
bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networks := getUniqueNetworkNames(bundle.Services)
|
||||
ctx := context.Background()
|
||||
|
||||
if err := updateNetworks(ctx, dockerCli, networks, opts.namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
return deployServices(ctx, dockerCli, bundle.Services, opts.namespace)
|
||||
}
|
||||
|
||||
func getUniqueNetworkNames(services map[string]bundlefile.Service) []string {
|
||||
networkSet := make(map[string]bool)
|
||||
for _, service := range services {
|
||||
for _, network := range service.Networks {
|
||||
networkSet[network] = true
|
||||
}
|
||||
}
|
||||
|
||||
networks := []string{}
|
||||
for network := range networkSet {
|
||||
networks = append(networks, network)
|
||||
}
|
||||
return networks
|
||||
}
|
||||
|
||||
func updateNetworks(
|
||||
ctx context.Context,
|
||||
dockerCli *client.DockerCli,
|
||||
networks []string,
|
||||
namespace string,
|
||||
) error {
|
||||
client := dockerCli.Client()
|
||||
|
||||
existingNetworks, err := getNetworks(ctx, client, namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existingNetworkMap := make(map[string]types.NetworkResource)
|
||||
for _, network := range existingNetworks {
|
||||
existingNetworkMap[network.Name] = network
|
||||
}
|
||||
|
||||
createOpts := types.NetworkCreate{
|
||||
Labels: getStackLabels(namespace, nil),
|
||||
Driver: defaultNetworkDriver,
|
||||
// TODO: remove when engine-api uses omitempty for IPAM
|
||||
IPAM: network.IPAM{Driver: "default"},
|
||||
}
|
||||
|
||||
for _, internalName := range networks {
|
||||
name := fmt.Sprintf("%s_%s", namespace, internalName)
|
||||
|
||||
if _, exists := existingNetworkMap[name]; exists {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
|
||||
if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertNetworks(networks []string, namespace string, name string) []swarm.NetworkAttachmentConfig {
|
||||
nets := []swarm.NetworkAttachmentConfig{}
|
||||
for _, network := range networks {
|
||||
nets = append(nets, swarm.NetworkAttachmentConfig{
|
||||
Target: namespace + "_" + network,
|
||||
Aliases: []string{name},
|
||||
})
|
||||
}
|
||||
return nets
|
||||
}
|
||||
|
||||
func deployServices(
|
||||
ctx context.Context,
|
||||
dockerCli *client.DockerCli,
|
||||
services map[string]bundlefile.Service,
|
||||
namespace string,
|
||||
) error {
|
||||
apiClient := dockerCli.Client()
|
||||
out := dockerCli.Out()
|
||||
|
||||
existingServices, err := getServices(ctx, apiClient, namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existingServiceMap := make(map[string]swarm.Service)
|
||||
for _, service := range existingServices {
|
||||
existingServiceMap[service.Spec.Name] = service
|
||||
}
|
||||
|
||||
for internalName, service := range services {
|
||||
name := fmt.Sprintf("%s_%s", namespace, internalName)
|
||||
|
||||
var ports []swarm.PortConfig
|
||||
for _, portSpec := range service.Ports {
|
||||
ports = append(ports, swarm.PortConfig{
|
||||
Protocol: swarm.PortConfigProtocol(portSpec.Protocol),
|
||||
TargetPort: portSpec.Port,
|
||||
})
|
||||
}
|
||||
|
||||
serviceSpec := swarm.ServiceSpec{
|
||||
Annotations: swarm.Annotations{
|
||||
Name: name,
|
||||
Labels: getStackLabels(namespace, service.Labels),
|
||||
},
|
||||
TaskTemplate: swarm.TaskSpec{
|
||||
ContainerSpec: swarm.ContainerSpec{
|
||||
Image: service.Image,
|
||||
Command: service.Command,
|
||||
Args: service.Args,
|
||||
Env: service.Env,
|
||||
},
|
||||
},
|
||||
EndpointSpec: &swarm.EndpointSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
Networks: convertNetworks(service.Networks, namespace, internalName),
|
||||
}
|
||||
|
||||
cspec := &serviceSpec.TaskTemplate.ContainerSpec
|
||||
if service.WorkingDir != nil {
|
||||
cspec.Dir = *service.WorkingDir
|
||||
}
|
||||
if service.User != nil {
|
||||
cspec.User = *service.User
|
||||
}
|
||||
|
||||
if service, exists := existingServiceMap[name]; exists {
|
||||
fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
|
||||
|
||||
if err := apiClient.ServiceUpdate(
|
||||
ctx,
|
||||
service.ID,
|
||||
service.Version,
|
||||
serviceSpec,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(out, "Creating service %s\n", name)
|
||||
|
||||
if _, err := apiClient.ServiceCreate(ctx, serviceSpec); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user