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