github.com/jsoriano/terraform@v0.6.7-0.20151026070445-8b70867fdd95/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  }