github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/builtin/providers/docker/resource_docker_container_funcs.go (about) 1 package docker 2 3 import ( 4 "errors" 5 "fmt" 6 "strconv" 7 "time" 8 9 dc "github.com/fsouza/go-dockerclient" 10 "github.com/hashicorp/terraform/helper/schema" 11 ) 12 13 var ( 14 creationTime time.Time 15 ) 16 17 func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) error { 18 var err error 19 client := meta.(*dc.Client) 20 21 var data Data 22 if err := fetchLocalImages(&data, client); err != nil { 23 return err 24 } 25 26 image := d.Get("image").(string) 27 if _, ok := data.DockerImages[image]; !ok { 28 if _, ok := data.DockerImages[image+":latest"]; !ok { 29 return fmt.Errorf("Unable to find image %s", image) 30 } 31 image = image + ":latest" 32 } 33 34 // The awesome, wonderful, splendiferous, sensical 35 // Docker API now lets you specify a HostConfig in 36 // CreateContainerOptions, but in my testing it still only 37 // actually applies HostConfig options set in StartContainer. 38 // How cool is that? 39 createOpts := dc.CreateContainerOptions{ 40 Name: d.Get("name").(string), 41 Config: &dc.Config{ 42 Image: image, 43 Hostname: d.Get("hostname").(string), 44 Domainname: d.Get("domainname").(string), 45 }, 46 } 47 48 if v, ok := d.GetOk("env"); ok { 49 createOpts.Config.Env = stringSetToStringSlice(v.(*schema.Set)) 50 } 51 52 if v, ok := d.GetOk("command"); ok { 53 createOpts.Config.Cmd = stringListToStringSlice(v.([]interface{})) 54 for _, v := range createOpts.Config.Cmd { 55 if v == "" { 56 return fmt.Errorf("values for command may not be empty") 57 } 58 } 59 } 60 61 if v, ok := d.GetOk("entrypoint"); ok { 62 createOpts.Config.Entrypoint = stringListToStringSlice(v.([]interface{})) 63 } 64 65 if v, ok := d.GetOk("user"); ok { 66 createOpts.Config.User = v.(string) 67 } 68 69 exposedPorts := map[dc.Port]struct{}{} 70 portBindings := map[dc.Port][]dc.PortBinding{} 71 72 if v, ok := d.GetOk("ports"); ok { 73 exposedPorts, portBindings = portSetToDockerPorts(v.(*schema.Set)) 74 } 75 if len(exposedPorts) != 0 { 76 createOpts.Config.ExposedPorts = exposedPorts 77 } 78 79 extraHosts := []string{} 80 if v, ok := d.GetOk("host"); ok { 81 extraHosts = extraHostsSetToDockerExtraHosts(v.(*schema.Set)) 82 } 83 84 volumes := map[string]struct{}{} 85 binds := []string{} 86 volumesFrom := []string{} 87 88 if v, ok := d.GetOk("volumes"); ok { 89 volumes, binds, volumesFrom, err = volumeSetToDockerVolumes(v.(*schema.Set)) 90 if err != nil { 91 return fmt.Errorf("Unable to parse volumes: %s", err) 92 } 93 } 94 if len(volumes) != 0 { 95 createOpts.Config.Volumes = volumes 96 } 97 98 if v, ok := d.GetOk("labels"); ok { 99 createOpts.Config.Labels = mapTypeMapValsToString(v.(map[string]interface{})) 100 } 101 102 hostConfig := &dc.HostConfig{ 103 Privileged: d.Get("privileged").(bool), 104 PublishAllPorts: d.Get("publish_all_ports").(bool), 105 RestartPolicy: dc.RestartPolicy{ 106 Name: d.Get("restart").(string), 107 MaximumRetryCount: d.Get("max_retry_count").(int), 108 }, 109 LogConfig: dc.LogConfig{ 110 Type: d.Get("log_driver").(string), 111 }, 112 } 113 114 if len(portBindings) != 0 { 115 hostConfig.PortBindings = portBindings 116 } 117 if len(extraHosts) != 0 { 118 hostConfig.ExtraHosts = extraHosts 119 } 120 if len(binds) != 0 { 121 hostConfig.Binds = binds 122 } 123 if len(volumesFrom) != 0 { 124 hostConfig.VolumesFrom = volumesFrom 125 } 126 127 if v, ok := d.GetOk("dns"); ok { 128 hostConfig.DNS = stringSetToStringSlice(v.(*schema.Set)) 129 } 130 131 if v, ok := d.GetOk("dns_opts"); ok { 132 hostConfig.DNSOptions = stringSetToStringSlice(v.(*schema.Set)) 133 } 134 135 if v, ok := d.GetOk("dns_search"); ok { 136 hostConfig.DNSSearch = stringSetToStringSlice(v.(*schema.Set)) 137 } 138 139 if v, ok := d.GetOk("links"); ok { 140 hostConfig.Links = stringSetToStringSlice(v.(*schema.Set)) 141 } 142 143 if v, ok := d.GetOk("memory"); ok { 144 hostConfig.Memory = int64(v.(int)) * 1024 * 1024 145 } 146 147 if v, ok := d.GetOk("memory_swap"); ok { 148 swap := int64(v.(int)) 149 if swap > 0 { 150 swap = swap * 1024 * 1024 151 } 152 hostConfig.MemorySwap = swap 153 } 154 155 if v, ok := d.GetOk("cpu_shares"); ok { 156 hostConfig.CPUShares = int64(v.(int)) 157 } 158 159 if v, ok := d.GetOk("log_opts"); ok { 160 hostConfig.LogConfig.Config = mapTypeMapValsToString(v.(map[string]interface{})) 161 } 162 163 if v, ok := d.GetOk("network_mode"); ok { 164 hostConfig.NetworkMode = v.(string) 165 } 166 167 createOpts.HostConfig = hostConfig 168 169 var retContainer *dc.Container 170 if retContainer, err = client.CreateContainer(createOpts); err != nil { 171 return fmt.Errorf("Unable to create container: %s", err) 172 } 173 if retContainer == nil { 174 return fmt.Errorf("Returned container is nil") 175 } 176 177 d.SetId(retContainer.ID) 178 179 if v, ok := d.GetOk("networks"); ok { 180 connectionOpts := dc.NetworkConnectionOptions{Container: retContainer.ID} 181 182 for _, rawNetwork := range v.(*schema.Set).List() { 183 network := rawNetwork.(string) 184 if err := client.ConnectNetwork(network, connectionOpts); err != nil { 185 return fmt.Errorf("Unable to connect to network '%s': %s", network, err) 186 } 187 } 188 } 189 190 creationTime = time.Now() 191 if err := client.StartContainer(retContainer.ID, nil); err != nil { 192 return fmt.Errorf("Unable to start container: %s", err) 193 } 194 195 return resourceDockerContainerRead(d, meta) 196 } 197 198 func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error { 199 client := meta.(*dc.Client) 200 201 apiContainer, err := fetchDockerContainer(d.Id(), client) 202 if err != nil { 203 return err 204 } 205 if apiContainer == nil { 206 // This container doesn't exist anymore 207 d.SetId("") 208 return nil 209 } 210 211 var container *dc.Container 212 213 loops := 1 // if it hasn't just been created, don't delay 214 if !creationTime.IsZero() { 215 loops = 30 // with 500ms spacing, 15 seconds; ought to be plenty 216 } 217 sleepTime := 500 * time.Millisecond 218 219 for i := loops; i > 0; i-- { 220 container, err = client.InspectContainer(apiContainer.ID) 221 if err != nil { 222 return fmt.Errorf("Error inspecting container %s: %s", apiContainer.ID, err) 223 } 224 225 if container.State.Running || 226 !container.State.Running && !d.Get("must_run").(bool) { 227 break 228 } 229 230 if creationTime.IsZero() { // We didn't just create it, so don't wait around 231 return resourceDockerContainerDelete(d, meta) 232 } 233 234 if container.State.FinishedAt.After(creationTime) { 235 // It exited immediately, so error out so dependent containers 236 // aren't started 237 resourceDockerContainerDelete(d, meta) 238 return fmt.Errorf("Container %s exited after creation, error was: %s", apiContainer.ID, container.State.Error) 239 } 240 241 time.Sleep(sleepTime) 242 } 243 244 // Handle the case of the for loop above running its course 245 if !container.State.Running && d.Get("must_run").(bool) { 246 resourceDockerContainerDelete(d, meta) 247 return fmt.Errorf("Container %s failed to be in running state", apiContainer.ID) 248 } 249 250 // Read Network Settings 251 if container.NetworkSettings != nil { 252 d.Set("ip_address", container.NetworkSettings.IPAddress) 253 d.Set("ip_prefix_length", container.NetworkSettings.IPPrefixLen) 254 d.Set("gateway", container.NetworkSettings.Gateway) 255 d.Set("bridge", container.NetworkSettings.Bridge) 256 } 257 258 return nil 259 } 260 261 func resourceDockerContainerUpdate(d *schema.ResourceData, meta interface{}) error { 262 return nil 263 } 264 265 func resourceDockerContainerDelete(d *schema.ResourceData, meta interface{}) error { 266 client := meta.(*dc.Client) 267 268 // Stop the container before removing if destroy_grace_seconds is defined 269 if d.Get("destroy_grace_seconds").(int) > 0 { 270 var timeout = uint(d.Get("destroy_grace_seconds").(int)) 271 if err := client.StopContainer(d.Id(), timeout); err != nil { 272 return fmt.Errorf("Error stopping container %s: %s", d.Id(), err) 273 } 274 } 275 276 removeOpts := dc.RemoveContainerOptions{ 277 ID: d.Id(), 278 RemoveVolumes: true, 279 Force: true, 280 } 281 282 if err := client.RemoveContainer(removeOpts); err != nil { 283 return fmt.Errorf("Error deleting container %s: %s", d.Id(), err) 284 } 285 286 d.SetId("") 287 return nil 288 } 289 290 func stringListToStringSlice(stringList []interface{}) []string { 291 ret := []string{} 292 for _, v := range stringList { 293 if v == nil { 294 ret = append(ret, "") 295 continue 296 } 297 ret = append(ret, v.(string)) 298 } 299 return ret 300 } 301 302 func stringSetToStringSlice(stringSet *schema.Set) []string { 303 ret := []string{} 304 if stringSet == nil { 305 return ret 306 } 307 for _, envVal := range stringSet.List() { 308 ret = append(ret, envVal.(string)) 309 } 310 return ret 311 } 312 313 func mapTypeMapValsToString(typeMap map[string]interface{}) map[string]string { 314 mapped := make(map[string]string, len(typeMap)) 315 for k, v := range typeMap { 316 mapped[k] = v.(string) 317 } 318 return mapped 319 } 320 321 func fetchDockerContainer(ID string, client *dc.Client) (*dc.APIContainers, error) { 322 apiContainers, err := client.ListContainers(dc.ListContainersOptions{All: true}) 323 324 if err != nil { 325 return nil, fmt.Errorf("Error fetching container information from Docker: %s\n", err) 326 } 327 328 for _, apiContainer := range apiContainers { 329 if apiContainer.ID == ID { 330 return &apiContainer, nil 331 } 332 } 333 334 return nil, nil 335 } 336 337 func portSetToDockerPorts(ports *schema.Set) (map[dc.Port]struct{}, map[dc.Port][]dc.PortBinding) { 338 retExposedPorts := map[dc.Port]struct{}{} 339 retPortBindings := map[dc.Port][]dc.PortBinding{} 340 341 for _, portInt := range ports.List() { 342 port := portInt.(map[string]interface{}) 343 internal := port["internal"].(int) 344 protocol := port["protocol"].(string) 345 346 exposedPort := dc.Port(strconv.Itoa(internal) + "/" + protocol) 347 retExposedPorts[exposedPort] = struct{}{} 348 349 external, extOk := port["external"].(int) 350 ip, ipOk := port["ip"].(string) 351 352 if extOk { 353 portBinding := dc.PortBinding{ 354 HostPort: strconv.Itoa(external), 355 } 356 if ipOk { 357 portBinding.HostIP = ip 358 } 359 retPortBindings[exposedPort] = append(retPortBindings[exposedPort], portBinding) 360 } 361 } 362 363 return retExposedPorts, retPortBindings 364 } 365 366 func extraHostsSetToDockerExtraHosts(extraHosts *schema.Set) []string { 367 retExtraHosts := []string{} 368 369 for _, hostInt := range extraHosts.List() { 370 host := hostInt.(map[string]interface{}) 371 ip := host["ip"].(string) 372 hostname := host["host"].(string) 373 retExtraHosts = append(retExtraHosts, hostname+":"+ip) 374 } 375 376 return retExtraHosts 377 } 378 379 func volumeSetToDockerVolumes(volumes *schema.Set) (map[string]struct{}, []string, []string, error) { 380 retVolumeMap := map[string]struct{}{} 381 retHostConfigBinds := []string{} 382 retVolumeFromContainers := []string{} 383 384 for _, volumeInt := range volumes.List() { 385 volume := volumeInt.(map[string]interface{}) 386 fromContainer := volume["from_container"].(string) 387 containerPath := volume["container_path"].(string) 388 volumeName := volume["volume_name"].(string) 389 if len(volumeName) == 0 { 390 volumeName = volume["host_path"].(string) 391 } 392 readOnly := volume["read_only"].(bool) 393 394 switch { 395 case len(fromContainer) == 0 && len(containerPath) == 0: 396 return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Volume entry without container path or source container") 397 case len(fromContainer) != 0 && len(containerPath) != 0: 398 return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Both a container and a path specified in a volume entry") 399 case len(fromContainer) != 0: 400 retVolumeFromContainers = append(retVolumeFromContainers, fromContainer) 401 case len(volumeName) != 0: 402 readWrite := "rw" 403 if readOnly { 404 readWrite = "ro" 405 } 406 retVolumeMap[containerPath] = struct{}{} 407 retHostConfigBinds = append(retHostConfigBinds, volumeName+":"+containerPath+":"+readWrite) 408 default: 409 retVolumeMap[containerPath] = struct{}{} 410 } 411 } 412 413 return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, nil 414 }