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