github.com/anuaimi/terraform@v0.6.4-0.20150904235404-2bf9aec61da8/builtin/providers/docker/resource_docker_container_funcs.go (about) 1 package docker 2 3 import ( 4 "errors" 5 "fmt" 6 "strconv" 7 "strings" 8 "time" 9 10 dc "github.com/fsouza/go-dockerclient" 11 "github.com/hashicorp/terraform/helper/schema" 12 ) 13 14 var ( 15 creationTime time.Time 16 ) 17 18 func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) error { 19 var err error 20 client := meta.(*dc.Client) 21 22 var data Data 23 if err := fetchLocalImages(&data, client); err != nil { 24 return err 25 } 26 27 image := d.Get("image").(string) 28 if _, ok := data.DockerImages[image]; !ok { 29 if _, ok := data.DockerImages[image+":latest"]; !ok { 30 return fmt.Errorf("Unable to find image %s", image) 31 } 32 image = image + ":latest" 33 } 34 35 // The awesome, wonderful, splendiferous, sensical 36 // Docker API now lets you specify a HostConfig in 37 // CreateContainerOptions, but in my testing it still only 38 // actually applies HostConfig options set in StartContainer. 39 // How cool is that? 40 createOpts := dc.CreateContainerOptions{ 41 Name: d.Get("name").(string), 42 Config: &dc.Config{ 43 Image: image, 44 Hostname: d.Get("hostname").(string), 45 Domainname: d.Get("domainname").(string), 46 }, 47 } 48 49 if v, ok := d.GetOk("env"); ok { 50 createOpts.Config.Env = stringSetToStringSlice(v.(*schema.Set)) 51 } 52 53 if v, ok := d.GetOk("command"); ok { 54 createOpts.Config.Cmd = stringListToStringSlice(v.([]interface{})) 55 } 56 57 exposedPorts := map[dc.Port]struct{}{} 58 portBindings := map[dc.Port][]dc.PortBinding{} 59 60 if v, ok := d.GetOk("ports"); ok { 61 exposedPorts, portBindings = portSetToDockerPorts(v.(*schema.Set)) 62 } 63 if len(exposedPorts) != 0 { 64 createOpts.Config.ExposedPorts = exposedPorts 65 } 66 67 volumes := map[string]struct{}{} 68 binds := []string{} 69 volumesFrom := []string{} 70 71 if v, ok := d.GetOk("volumes"); ok { 72 volumes, binds, volumesFrom, err = volumeSetToDockerVolumes(v.(*schema.Set)) 73 if err != nil { 74 return fmt.Errorf("Unable to parse volumes: %s", err) 75 } 76 } 77 if len(volumes) != 0 { 78 createOpts.Config.Volumes = volumes 79 } 80 81 var retContainer *dc.Container 82 if retContainer, err = client.CreateContainer(createOpts); err != nil { 83 return fmt.Errorf("Unable to create container: %s", err) 84 } 85 if retContainer == nil { 86 return fmt.Errorf("Returned container is nil") 87 } 88 89 d.SetId(retContainer.ID) 90 91 hostConfig := &dc.HostConfig{ 92 Privileged: d.Get("privileged").(bool), 93 PublishAllPorts: d.Get("publish_all_ports").(bool), 94 } 95 96 if len(portBindings) != 0 { 97 hostConfig.PortBindings = portBindings 98 } 99 100 if len(binds) != 0 { 101 hostConfig.Binds = binds 102 } 103 if len(volumesFrom) != 0 { 104 hostConfig.VolumesFrom = volumesFrom 105 } 106 107 if v, ok := d.GetOk("dns"); ok { 108 hostConfig.DNS = stringSetToStringSlice(v.(*schema.Set)) 109 } 110 111 if v, ok := d.GetOk("links"); ok { 112 hostConfig.Links = stringSetToStringSlice(v.(*schema.Set)) 113 } 114 115 creationTime = time.Now() 116 if err := client.StartContainer(retContainer.ID, hostConfig); err != nil { 117 return fmt.Errorf("Unable to start container: %s", err) 118 } 119 120 return resourceDockerContainerRead(d, meta) 121 } 122 123 func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error { 124 client := meta.(*dc.Client) 125 126 apiContainer, err := fetchDockerContainer(d.Get("name").(string), client) 127 if err != nil { 128 return err 129 } 130 if apiContainer == nil { 131 // This container doesn't exist anymore 132 d.SetId("") 133 return nil 134 } 135 136 var container *dc.Container 137 138 loops := 1 // if it hasn't just been created, don't delay 139 if !creationTime.IsZero() { 140 loops = 30 // with 500ms spacing, 15 seconds; ought to be plenty 141 } 142 sleepTime := 500 * time.Millisecond 143 144 for i := loops; i > 0; i-- { 145 container, err = client.InspectContainer(apiContainer.ID) 146 if err != nil { 147 return fmt.Errorf("Error inspecting container %s: %s", apiContainer.ID, err) 148 } 149 150 if container.State.Running || 151 (!container.State.Running && !d.Get("must_run").(bool)) { 152 break 153 } 154 155 if creationTime.IsZero() { // We didn't just create it, so don't wait around 156 return resourceDockerContainerDelete(d, meta) 157 } 158 159 if container.State.FinishedAt.After(creationTime) { 160 // It exited immediately, so error out so dependent containers 161 // aren't started 162 resourceDockerContainerDelete(d, meta) 163 return fmt.Errorf("Container %s exited after creation, error was: %s", apiContainer.ID, container.State.Error) 164 } 165 166 time.Sleep(sleepTime) 167 } 168 169 // Handle the case of the for loop above running its course 170 if !container.State.Running && d.Get("must_run").(bool) { 171 resourceDockerContainerDelete(d, meta) 172 return fmt.Errorf("Container %s failed to be in running state", apiContainer.ID) 173 } 174 175 // Read Network Settings 176 if container.NetworkSettings != nil { 177 d.Set("ip_address", container.NetworkSettings.IPAddress) 178 d.Set("ip_prefix_length", container.NetworkSettings.IPPrefixLen) 179 d.Set("gateway", container.NetworkSettings.Gateway) 180 d.Set("bridge", container.NetworkSettings.Bridge) 181 } 182 183 return nil 184 } 185 186 func resourceDockerContainerUpdate(d *schema.ResourceData, meta interface{}) error { 187 return nil 188 } 189 190 func resourceDockerContainerDelete(d *schema.ResourceData, meta interface{}) error { 191 client := meta.(*dc.Client) 192 193 removeOpts := dc.RemoveContainerOptions{ 194 ID: d.Id(), 195 RemoveVolumes: true, 196 Force: true, 197 } 198 199 if err := client.RemoveContainer(removeOpts); err != nil { 200 return fmt.Errorf("Error deleting container %s: %s", d.Id(), err) 201 } 202 203 d.SetId("") 204 return nil 205 } 206 207 func stringListToStringSlice(stringList []interface{}) []string { 208 ret := []string{} 209 for _, v := range stringList { 210 ret = append(ret, v.(string)) 211 } 212 return ret 213 } 214 215 func stringSetToStringSlice(stringSet *schema.Set) []string { 216 ret := []string{} 217 if stringSet == nil { 218 return ret 219 } 220 for _, envVal := range stringSet.List() { 221 ret = append(ret, envVal.(string)) 222 } 223 return ret 224 } 225 226 func fetchDockerContainer(name string, client *dc.Client) (*dc.APIContainers, error) { 227 apiContainers, err := client.ListContainers(dc.ListContainersOptions{All: true}) 228 229 if err != nil { 230 return nil, fmt.Errorf("Error fetching container information from Docker: %s\n", err) 231 } 232 233 for _, apiContainer := range apiContainers { 234 // Sometimes the Docker API prefixes container names with / 235 // like it does in these commands. But if there's no 236 // set name, it just uses the ID without a /...ugh. 237 switch len(apiContainer.Names) { 238 case 0: 239 if apiContainer.ID == name { 240 return &apiContainer, nil 241 } 242 default: 243 for _, containerName := range apiContainer.Names { 244 if strings.TrimLeft(containerName, "/") == name { 245 return &apiContainer, nil 246 } 247 } 248 } 249 } 250 251 return nil, nil 252 } 253 254 func portSetToDockerPorts(ports *schema.Set) (map[dc.Port]struct{}, map[dc.Port][]dc.PortBinding) { 255 retExposedPorts := map[dc.Port]struct{}{} 256 retPortBindings := map[dc.Port][]dc.PortBinding{} 257 258 for _, portInt := range ports.List() { 259 port := portInt.(map[string]interface{}) 260 internal := port["internal"].(int) 261 protocol := port["protocol"].(string) 262 263 exposedPort := dc.Port(strconv.Itoa(internal) + "/" + protocol) 264 retExposedPorts[exposedPort] = struct{}{} 265 266 external, extOk := port["external"].(int) 267 ip, ipOk := port["ip"].(string) 268 269 if extOk { 270 portBinding := dc.PortBinding{ 271 HostPort: strconv.Itoa(external), 272 } 273 if ipOk { 274 portBinding.HostIP = ip 275 } 276 retPortBindings[exposedPort] = append(retPortBindings[exposedPort], portBinding) 277 } 278 } 279 280 return retExposedPorts, retPortBindings 281 } 282 283 func volumeSetToDockerVolumes(volumes *schema.Set) (map[string]struct{}, []string, []string, error) { 284 retVolumeMap := map[string]struct{}{} 285 retHostConfigBinds := []string{} 286 retVolumeFromContainers := []string{} 287 288 for _, volumeInt := range volumes.List() { 289 volume := volumeInt.(map[string]interface{}) 290 fromContainer := volume["from_container"].(string) 291 containerPath := volume["container_path"].(string) 292 hostPath := volume["host_path"].(string) 293 readOnly := volume["read_only"].(bool) 294 295 switch { 296 case len(fromContainer) == 0 && len(containerPath) == 0: 297 return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Volume entry without container path or source container") 298 case len(fromContainer) != 0 && len(containerPath) != 0: 299 return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Both a container and a path specified in a volume entry") 300 case len(fromContainer) != 0: 301 retVolumeFromContainers = append(retVolumeFromContainers, fromContainer) 302 case len(hostPath) != 0: 303 readWrite := "rw" 304 if readOnly { 305 readWrite = "ro" 306 } 307 retVolumeMap[containerPath] = struct{}{} 308 retHostConfigBinds = append(retHostConfigBinds, hostPath+":"+containerPath+":"+readWrite) 309 default: 310 retVolumeMap[containerPath] = struct{}{} 311 } 312 } 313 314 return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, nil 315 }