github.com/armen/terraform@v0.5.2-0.20150529052519-caa8117a08f1/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  		PublishAllPorts: d.Get("publish_all_ports").(bool),
    89  	}
    90  
    91  	if len(portBindings) != 0 {
    92  		hostConfig.PortBindings = portBindings
    93  	}
    94  
    95  	if len(binds) != 0 {
    96  		hostConfig.Binds = binds
    97  	}
    98  	if len(volumesFrom) != 0 {
    99  		hostConfig.VolumesFrom = volumesFrom
   100  	}
   101  
   102  	if v, ok := d.GetOk("dns"); ok {
   103  		hostConfig.DNS = stringSetToStringSlice(v.(*schema.Set))
   104  	}
   105  
   106  	if v, ok := d.GetOk("links"); ok {
   107  		hostConfig.Links = stringSetToStringSlice(v.(*schema.Set))
   108  	}
   109  
   110  	if err := client.StartContainer(retContainer.ID, hostConfig); err != nil {
   111  		return fmt.Errorf("Unable to start container: %s", err)
   112  	}
   113  
   114  	return resourceDockerContainerRead(d, meta)
   115  }
   116  
   117  func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error {
   118  	client := meta.(*dc.Client)
   119  
   120  	apiContainer, err := fetchDockerContainer(d.Get("name").(string), client)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	if apiContainer == nil {
   126  		// This container doesn't exist anymore
   127  		d.SetId("")
   128  
   129  		return nil
   130  	}
   131  
   132  	container, err := client.InspectContainer(apiContainer.ID)
   133  	if err != nil {
   134  		return fmt.Errorf("Error inspecting container %s: %s", apiContainer.ID, err)
   135  	}
   136  
   137  	if d.Get("must_run").(bool) && !container.State.Running {
   138  		return resourceDockerContainerDelete(d, meta)
   139  	}
   140  
   141  	// Read Network Settings
   142  	if container.NetworkSettings != nil {
   143  		d.Set("ip_address", container.NetworkSettings.IPAddress)
   144  		d.Set("ip_prefix_length", container.NetworkSettings.IPPrefixLen)
   145  		d.Set("gateway", container.NetworkSettings.Gateway)
   146  		d.Set("bridge", container.NetworkSettings.Bridge)
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  func resourceDockerContainerUpdate(d *schema.ResourceData, meta interface{}) error {
   153  	return nil
   154  }
   155  
   156  func resourceDockerContainerDelete(d *schema.ResourceData, meta interface{}) error {
   157  	client := meta.(*dc.Client)
   158  
   159  	removeOpts := dc.RemoveContainerOptions{
   160  		ID:            d.Id(),
   161  		RemoveVolumes: true,
   162  		Force:         true,
   163  	}
   164  
   165  	if err := client.RemoveContainer(removeOpts); err != nil {
   166  		return fmt.Errorf("Error deleting container %s: %s", d.Id(), err)
   167  	}
   168  
   169  	d.SetId("")
   170  	return nil
   171  }
   172  
   173  func stringListToStringSlice(stringList []interface{}) []string {
   174  	ret := []string{}
   175  	for _, v := range stringList {
   176  		ret = append(ret, v.(string))
   177  	}
   178  	return ret
   179  }
   180  
   181  func stringSetToStringSlice(stringSet *schema.Set) []string {
   182  	ret := []string{}
   183  	if stringSet == nil {
   184  		return ret
   185  	}
   186  	for _, envVal := range stringSet.List() {
   187  		ret = append(ret, envVal.(string))
   188  	}
   189  	return ret
   190  }
   191  
   192  func fetchDockerContainer(name string, client *dc.Client) (*dc.APIContainers, error) {
   193  	apiContainers, err := client.ListContainers(dc.ListContainersOptions{All: true})
   194  
   195  	if err != nil {
   196  		return nil, fmt.Errorf("Error fetching container information from Docker: %s\n", err)
   197  	}
   198  
   199  	for _, apiContainer := range apiContainers {
   200  		// Sometimes the Docker API prefixes container names with /
   201  		// like it does in these commands. But if there's no
   202  		// set name, it just uses the ID without a /...ugh.
   203  		var dockerContainerName string
   204  		if len(apiContainer.Names) > 0 {
   205  			dockerContainerName = strings.TrimLeft(apiContainer.Names[0], "/")
   206  		} else {
   207  			dockerContainerName = apiContainer.ID
   208  		}
   209  
   210  		if dockerContainerName == name {
   211  			return &apiContainer, nil
   212  		}
   213  	}
   214  
   215  	return nil, nil
   216  }
   217  
   218  func portSetToDockerPorts(ports *schema.Set) (map[dc.Port]struct{}, map[dc.Port][]dc.PortBinding) {
   219  	retExposedPorts := map[dc.Port]struct{}{}
   220  	retPortBindings := map[dc.Port][]dc.PortBinding{}
   221  
   222  	for _, portInt := range ports.List() {
   223  		port := portInt.(map[string]interface{})
   224  		internal := port["internal"].(int)
   225  		protocol := port["protocol"].(string)
   226  
   227  		exposedPort := dc.Port(strconv.Itoa(internal) + "/" + protocol)
   228  		retExposedPorts[exposedPort] = struct{}{}
   229  
   230  		external, extOk := port["external"].(int)
   231  		ip, ipOk := port["ip"].(string)
   232  
   233  		if extOk {
   234  			portBinding := dc.PortBinding{
   235  				HostPort: strconv.Itoa(external),
   236  			}
   237  			if ipOk {
   238  				portBinding.HostIP = ip
   239  			}
   240  			retPortBindings[exposedPort] = append(retPortBindings[exposedPort], portBinding)
   241  		}
   242  	}
   243  
   244  	return retExposedPorts, retPortBindings
   245  }
   246  
   247  func volumeSetToDockerVolumes(volumes *schema.Set) (map[string]struct{}, []string, []string, error) {
   248  	retVolumeMap := map[string]struct{}{}
   249  	retHostConfigBinds := []string{}
   250  	retVolumeFromContainers := []string{}
   251  
   252  	for _, volumeInt := range volumes.List() {
   253  		volume := volumeInt.(map[string]interface{})
   254  		fromContainer := volume["from_container"].(string)
   255  		containerPath := volume["container_path"].(string)
   256  		hostPath := volume["host_path"].(string)
   257  		readOnly := volume["read_only"].(bool)
   258  
   259  		switch {
   260  		case len(fromContainer) == 0 && len(containerPath) == 0:
   261  			return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Volume entry without container path or source container")
   262  		case len(fromContainer) != 0 && len(containerPath) != 0:
   263  			return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Both a container and a path specified in a volume entry")
   264  		case len(fromContainer) != 0:
   265  			retVolumeFromContainers = append(retVolumeFromContainers, fromContainer)
   266  		case len(hostPath) != 0:
   267  			readWrite := "rw"
   268  			if readOnly {
   269  				readWrite = "ro"
   270  			}
   271  			retVolumeMap[containerPath] = struct{}{}
   272  			retHostConfigBinds = append(retHostConfigBinds, hostPath+":"+containerPath+":"+readWrite)
   273  		default:
   274  			retVolumeMap[containerPath] = struct{}{}
   275  		}
   276  	}
   277  
   278  	return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, nil
   279  }