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 }