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