github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/cli/command/stack/deploy_composefile.go (about)

     1  package stack
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/docker/docker/api/types"
    11  	"github.com/docker/docker/api/types/swarm"
    12  	"github.com/docker/docker/cli/command"
    13  	"github.com/docker/docker/cli/compose/convert"
    14  	"github.com/docker/docker/cli/compose/loader"
    15  	composetypes "github.com/docker/docker/cli/compose/types"
    16  	apiclient "github.com/docker/docker/client"
    17  	dockerclient "github.com/docker/docker/client"
    18  	"golang.org/x/net/context"
    19  )
    20  
    21  func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deployOptions) error {
    22  	configDetails, err := getConfigDetails(opts)
    23  	if err != nil {
    24  		return err
    25  	}
    26  
    27  	config, err := loader.Load(configDetails)
    28  	if err != nil {
    29  		if fpe, ok := err.(*loader.ForbiddenPropertiesError); ok {
    30  			return fmt.Errorf("Compose file contains unsupported options:\n\n%s\n",
    31  				propertyWarnings(fpe.Properties))
    32  		}
    33  
    34  		return err
    35  	}
    36  
    37  	unsupportedProperties := loader.GetUnsupportedProperties(configDetails)
    38  	if len(unsupportedProperties) > 0 {
    39  		fmt.Fprintf(dockerCli.Err(), "Ignoring unsupported options: %s\n\n",
    40  			strings.Join(unsupportedProperties, ", "))
    41  	}
    42  
    43  	deprecatedProperties := loader.GetDeprecatedProperties(configDetails)
    44  	if len(deprecatedProperties) > 0 {
    45  		fmt.Fprintf(dockerCli.Err(), "Ignoring deprecated options:\n\n%s\n\n",
    46  			propertyWarnings(deprecatedProperties))
    47  	}
    48  
    49  	if err := checkDaemonIsSwarmManager(ctx, dockerCli); err != nil {
    50  		return err
    51  	}
    52  
    53  	namespace := convert.NewNamespace(opts.namespace)
    54  
    55  	serviceNetworks := getServicesDeclaredNetworks(config.Services)
    56  
    57  	networks, externalNetworks := convert.Networks(namespace, config.Networks, serviceNetworks)
    58  	if err := validateExternalNetworks(ctx, dockerCli, externalNetworks); err != nil {
    59  		return err
    60  	}
    61  	if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil {
    62  		return err
    63  	}
    64  
    65  	secrets, err := convert.Secrets(namespace, config.Secrets)
    66  	if err != nil {
    67  		return err
    68  	}
    69  	if err := createSecrets(ctx, dockerCli, namespace, secrets); err != nil {
    70  		return err
    71  	}
    72  
    73  	services, err := convert.Services(namespace, config, dockerCli.Client())
    74  	if err != nil {
    75  		return err
    76  	}
    77  	return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth)
    78  }
    79  
    80  func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} {
    81  	serviceNetworks := map[string]struct{}{}
    82  	for _, serviceConfig := range serviceConfigs {
    83  		if len(serviceConfig.Networks) == 0 {
    84  			serviceNetworks["default"] = struct{}{}
    85  			continue
    86  		}
    87  		for network := range serviceConfig.Networks {
    88  			serviceNetworks[network] = struct{}{}
    89  		}
    90  	}
    91  	return serviceNetworks
    92  }
    93  
    94  func propertyWarnings(properties map[string]string) string {
    95  	var msgs []string
    96  	for name, description := range properties {
    97  		msgs = append(msgs, fmt.Sprintf("%s: %s", name, description))
    98  	}
    99  	sort.Strings(msgs)
   100  	return strings.Join(msgs, "\n\n")
   101  }
   102  
   103  func getConfigDetails(opts deployOptions) (composetypes.ConfigDetails, error) {
   104  	var details composetypes.ConfigDetails
   105  	var err error
   106  
   107  	details.WorkingDir, err = os.Getwd()
   108  	if err != nil {
   109  		return details, err
   110  	}
   111  
   112  	configFile, err := getConfigFile(opts.composefile)
   113  	if err != nil {
   114  		return details, err
   115  	}
   116  	// TODO: support multiple files
   117  	details.ConfigFiles = []composetypes.ConfigFile{*configFile}
   118  	return details, nil
   119  }
   120  
   121  func getConfigFile(filename string) (*composetypes.ConfigFile, error) {
   122  	bytes, err := ioutil.ReadFile(filename)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	config, err := loader.ParseYAML(bytes)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	return &composetypes.ConfigFile{
   131  		Filename: filename,
   132  		Config:   config,
   133  	}, nil
   134  }
   135  
   136  func validateExternalNetworks(
   137  	ctx context.Context,
   138  	dockerCli *command.DockerCli,
   139  	externalNetworks []string) error {
   140  	client := dockerCli.Client()
   141  
   142  	for _, networkName := range externalNetworks {
   143  		network, err := client.NetworkInspect(ctx, networkName)
   144  		if err != nil {
   145  			if dockerclient.IsErrNetworkNotFound(err) {
   146  				return fmt.Errorf("network %q is declared as external, but could not be found. You need to create the network before the stack is deployed (with overlay driver)", networkName)
   147  			}
   148  			return err
   149  		}
   150  		if network.Scope != "swarm" {
   151  			return fmt.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of %q", networkName, network.Scope, "swarm")
   152  		}
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  func createSecrets(
   159  	ctx context.Context,
   160  	dockerCli *command.DockerCli,
   161  	namespace convert.Namespace,
   162  	secrets []swarm.SecretSpec,
   163  ) error {
   164  	client := dockerCli.Client()
   165  
   166  	for _, secretSpec := range secrets {
   167  		secret, _, err := client.SecretInspectWithRaw(ctx, secretSpec.Name)
   168  		if err == nil {
   169  			// secret already exists, then we update that
   170  			if err := client.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil {
   171  				return err
   172  			}
   173  		} else if apiclient.IsErrSecretNotFound(err) {
   174  			// secret does not exist, then we create a new one.
   175  			if _, err := client.SecretCreate(ctx, secretSpec); err != nil {
   176  				return err
   177  			}
   178  		} else {
   179  			return err
   180  		}
   181  	}
   182  	return nil
   183  }
   184  
   185  func createNetworks(
   186  	ctx context.Context,
   187  	dockerCli *command.DockerCli,
   188  	namespace convert.Namespace,
   189  	networks map[string]types.NetworkCreate,
   190  ) error {
   191  	client := dockerCli.Client()
   192  
   193  	existingNetworks, err := getStackNetworks(ctx, client, namespace.Name())
   194  	if err != nil {
   195  		return err
   196  	}
   197  
   198  	existingNetworkMap := make(map[string]types.NetworkResource)
   199  	for _, network := range existingNetworks {
   200  		existingNetworkMap[network.Name] = network
   201  	}
   202  
   203  	for internalName, createOpts := range networks {
   204  		name := namespace.Scope(internalName)
   205  		if _, exists := existingNetworkMap[name]; exists {
   206  			continue
   207  		}
   208  
   209  		if createOpts.Driver == "" {
   210  			createOpts.Driver = defaultNetworkDriver
   211  		}
   212  
   213  		fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
   214  		if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
   215  			return err
   216  		}
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  func deployServices(
   223  	ctx context.Context,
   224  	dockerCli *command.DockerCli,
   225  	services map[string]swarm.ServiceSpec,
   226  	namespace convert.Namespace,
   227  	sendAuth bool,
   228  ) error {
   229  	apiClient := dockerCli.Client()
   230  	out := dockerCli.Out()
   231  
   232  	existingServices, err := getServices(ctx, apiClient, namespace.Name())
   233  	if err != nil {
   234  		return err
   235  	}
   236  
   237  	existingServiceMap := make(map[string]swarm.Service)
   238  	for _, service := range existingServices {
   239  		existingServiceMap[service.Spec.Name] = service
   240  	}
   241  
   242  	for internalName, serviceSpec := range services {
   243  		name := namespace.Scope(internalName)
   244  
   245  		encodedAuth := ""
   246  		if sendAuth {
   247  			// Retrieve encoded auth token from the image reference
   248  			image := serviceSpec.TaskTemplate.ContainerSpec.Image
   249  			encodedAuth, err = command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
   250  			if err != nil {
   251  				return err
   252  			}
   253  		}
   254  
   255  		if service, exists := existingServiceMap[name]; exists {
   256  			fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
   257  
   258  			updateOpts := types.ServiceUpdateOptions{}
   259  			if sendAuth {
   260  				updateOpts.EncodedRegistryAuth = encodedAuth
   261  			}
   262  			response, err := apiClient.ServiceUpdate(
   263  				ctx,
   264  				service.ID,
   265  				service.Version,
   266  				serviceSpec,
   267  				updateOpts,
   268  			)
   269  			if err != nil {
   270  				return err
   271  			}
   272  
   273  			for _, warning := range response.Warnings {
   274  				fmt.Fprintln(dockerCli.Err(), warning)
   275  			}
   276  		} else {
   277  			fmt.Fprintf(out, "Creating service %s\n", name)
   278  
   279  			createOpts := types.ServiceCreateOptions{}
   280  			if sendAuth {
   281  				createOpts.EncodedRegistryAuth = encodedAuth
   282  			}
   283  			if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil {
   284  				return err
   285  			}
   286  		}
   287  	}
   288  
   289  	return nil
   290  }