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