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