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  }