github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/builtin/providers/docker/resource_docker_container_funcs.go (about)

     1  package docker
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strconv"
     7  	"time"
     8  
     9  	dc "github.com/fsouza/go-dockerclient"
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  )
    12  
    13  var (
    14  	creationTime time.Time
    15  )
    16  
    17  func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) error {
    18  	var err error
    19  	client := meta.(*dc.Client)
    20  
    21  	var data Data
    22  	if err := fetchLocalImages(&data, client); err != nil {
    23  		return err
    24  	}
    25  
    26  	image := d.Get("image").(string)
    27  	if _, ok := data.DockerImages[image]; !ok {
    28  		if _, ok := data.DockerImages[image+":latest"]; !ok {
    29  			return fmt.Errorf("Unable to find image %s", image)
    30  		}
    31  		image = image + ":latest"
    32  	}
    33  
    34  	// The awesome, wonderful, splendiferous, sensical
    35  	// Docker API now lets you specify a HostConfig in
    36  	// CreateContainerOptions, but in my testing it still only
    37  	// actually applies HostConfig options set in StartContainer.
    38  	// How cool is that?
    39  	createOpts := dc.CreateContainerOptions{
    40  		Name: d.Get("name").(string),
    41  		Config: &dc.Config{
    42  			Image:      image,
    43  			Hostname:   d.Get("hostname").(string),
    44  			Domainname: d.Get("domainname").(string),
    45  		},
    46  	}
    47  
    48  	if v, ok := d.GetOk("env"); ok {
    49  		createOpts.Config.Env = stringSetToStringSlice(v.(*schema.Set))
    50  	}
    51  
    52  	if v, ok := d.GetOk("command"); ok {
    53  		createOpts.Config.Cmd = stringListToStringSlice(v.([]interface{}))
    54  	}
    55  
    56  	if v, ok := d.GetOk("entrypoint"); ok {
    57  		createOpts.Config.Entrypoint = stringListToStringSlice(v.([]interface{}))
    58  	}
    59  
    60  	exposedPorts := map[dc.Port]struct{}{}
    61  	portBindings := map[dc.Port][]dc.PortBinding{}
    62  
    63  	if v, ok := d.GetOk("ports"); ok {
    64  		exposedPorts, portBindings = portSetToDockerPorts(v.(*schema.Set))
    65  	}
    66  	if len(exposedPorts) != 0 {
    67  		createOpts.Config.ExposedPorts = exposedPorts
    68  	}
    69  
    70  	volumes := map[string]struct{}{}
    71  	binds := []string{}
    72  	volumesFrom := []string{}
    73  
    74  	if v, ok := d.GetOk("volumes"); ok {
    75  		volumes, binds, volumesFrom, err = volumeSetToDockerVolumes(v.(*schema.Set))
    76  		if err != nil {
    77  			return fmt.Errorf("Unable to parse volumes: %s", err)
    78  		}
    79  	}
    80  	if len(volumes) != 0 {
    81  		createOpts.Config.Volumes = volumes
    82  	}
    83  
    84  	if v, ok := d.GetOk("labels"); ok {
    85  		createOpts.Config.Labels = mapTypeMapValsToString(v.(map[string]interface{}))
    86  	}
    87  
    88  	hostConfig := &dc.HostConfig{
    89  		Privileged:      d.Get("privileged").(bool),
    90  		PublishAllPorts: d.Get("publish_all_ports").(bool),
    91  		RestartPolicy: dc.RestartPolicy{
    92  			Name:              d.Get("restart").(string),
    93  			MaximumRetryCount: d.Get("max_retry_count").(int),
    94  		},
    95  		LogConfig: dc.LogConfig{
    96  			Type: d.Get("log_driver").(string),
    97  		},
    98  	}
    99  
   100  	if len(portBindings) != 0 {
   101  		hostConfig.PortBindings = portBindings
   102  	}
   103  
   104  	if len(binds) != 0 {
   105  		hostConfig.Binds = binds
   106  	}
   107  	if len(volumesFrom) != 0 {
   108  		hostConfig.VolumesFrom = volumesFrom
   109  	}
   110  
   111  	if v, ok := d.GetOk("dns"); ok {
   112  		hostConfig.DNS = stringSetToStringSlice(v.(*schema.Set))
   113  	}
   114  
   115  	if v, ok := d.GetOk("links"); ok {
   116  		hostConfig.Links = stringSetToStringSlice(v.(*schema.Set))
   117  	}
   118  
   119  	if v, ok := d.GetOk("memory"); ok {
   120  		hostConfig.Memory = int64(v.(int)) * 1024 * 1024
   121  	}
   122  
   123  	if v, ok := d.GetOk("memory_swap"); ok {
   124  		swap := int64(v.(int))
   125  		if swap > 0 {
   126  			swap = swap * 1024 * 1024
   127  		}
   128  		hostConfig.MemorySwap = swap
   129  	}
   130  
   131  	if v, ok := d.GetOk("cpu_shares"); ok {
   132  		hostConfig.CPUShares = int64(v.(int))
   133  	}
   134  
   135  	if v, ok := d.GetOk("log_opts"); ok {
   136  		hostConfig.LogConfig.Config = mapTypeMapValsToString(v.(map[string]interface{}))
   137  	}
   138  
   139  	createOpts.HostConfig = hostConfig
   140  
   141  	var retContainer *dc.Container
   142  	if retContainer, err = client.CreateContainer(createOpts); err != nil {
   143  		return fmt.Errorf("Unable to create container: %s", err)
   144  	}
   145  	if retContainer == nil {
   146  		return fmt.Errorf("Returned container is nil")
   147  	}
   148  
   149  	d.SetId(retContainer.ID)
   150  
   151  	creationTime = time.Now()
   152  	if err := client.StartContainer(retContainer.ID, hostConfig); err != nil {
   153  		return fmt.Errorf("Unable to start container: %s", err)
   154  	}
   155  
   156  	return resourceDockerContainerRead(d, meta)
   157  }
   158  
   159  func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error {
   160  	client := meta.(*dc.Client)
   161  
   162  	apiContainer, err := fetchDockerContainer(d.Id(), client)
   163  	if err != nil {
   164  		return err
   165  	}
   166  	if apiContainer == nil {
   167  		// This container doesn't exist anymore
   168  		d.SetId("")
   169  		return nil
   170  	}
   171  
   172  	var container *dc.Container
   173  
   174  	loops := 1 // if it hasn't just been created, don't delay
   175  	if !creationTime.IsZero() {
   176  		loops = 30 // with 500ms spacing, 15 seconds; ought to be plenty
   177  	}
   178  	sleepTime := 500 * time.Millisecond
   179  
   180  	for i := loops; i > 0; i-- {
   181  		container, err = client.InspectContainer(apiContainer.ID)
   182  		if err != nil {
   183  			return fmt.Errorf("Error inspecting container %s: %s", apiContainer.ID, err)
   184  		}
   185  
   186  		if container.State.Running ||
   187  			!container.State.Running && !d.Get("must_run").(bool) {
   188  			break
   189  		}
   190  
   191  		if creationTime.IsZero() { // We didn't just create it, so don't wait around
   192  			return resourceDockerContainerDelete(d, meta)
   193  		}
   194  
   195  		if container.State.FinishedAt.After(creationTime) {
   196  			// It exited immediately, so error out so dependent containers
   197  			// aren't started
   198  			resourceDockerContainerDelete(d, meta)
   199  			return fmt.Errorf("Container %s exited after creation, error was: %s", apiContainer.ID, container.State.Error)
   200  		}
   201  
   202  		time.Sleep(sleepTime)
   203  	}
   204  
   205  	// Handle the case of the for loop above running its course
   206  	if !container.State.Running && d.Get("must_run").(bool) {
   207  		resourceDockerContainerDelete(d, meta)
   208  		return fmt.Errorf("Container %s failed to be in running state", apiContainer.ID)
   209  	}
   210  
   211  	// Read Network Settings
   212  	if container.NetworkSettings != nil {
   213  		d.Set("ip_address", container.NetworkSettings.IPAddress)
   214  		d.Set("ip_prefix_length", container.NetworkSettings.IPPrefixLen)
   215  		d.Set("gateway", container.NetworkSettings.Gateway)
   216  		d.Set("bridge", container.NetworkSettings.Bridge)
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  func resourceDockerContainerUpdate(d *schema.ResourceData, meta interface{}) error {
   223  	return nil
   224  }
   225  
   226  func resourceDockerContainerDelete(d *schema.ResourceData, meta interface{}) error {
   227  	client := meta.(*dc.Client)
   228  
   229  	removeOpts := dc.RemoveContainerOptions{
   230  		ID:            d.Id(),
   231  		RemoveVolumes: true,
   232  		Force:         true,
   233  	}
   234  
   235  	if err := client.RemoveContainer(removeOpts); err != nil {
   236  		return fmt.Errorf("Error deleting container %s: %s", d.Id(), err)
   237  	}
   238  
   239  	d.SetId("")
   240  	return nil
   241  }
   242  
   243  func stringListToStringSlice(stringList []interface{}) []string {
   244  	ret := []string{}
   245  	for _, v := range stringList {
   246  		ret = append(ret, v.(string))
   247  	}
   248  	return ret
   249  }
   250  
   251  func stringSetToStringSlice(stringSet *schema.Set) []string {
   252  	ret := []string{}
   253  	if stringSet == nil {
   254  		return ret
   255  	}
   256  	for _, envVal := range stringSet.List() {
   257  		ret = append(ret, envVal.(string))
   258  	}
   259  	return ret
   260  }
   261  
   262  func mapTypeMapValsToString(typeMap map[string]interface{}) map[string]string {
   263  	mapped := make(map[string]string, len(typeMap))
   264  	for k, v := range typeMap {
   265  		mapped[k] = v.(string)
   266  	}
   267  	return mapped
   268  }
   269  
   270  func fetchDockerContainer(ID string, client *dc.Client) (*dc.APIContainers, error) {
   271  	apiContainers, err := client.ListContainers(dc.ListContainersOptions{All: true})
   272  
   273  	if err != nil {
   274  		return nil, fmt.Errorf("Error fetching container information from Docker: %s\n", err)
   275  	}
   276  
   277  	for _, apiContainer := range apiContainers {
   278  		if apiContainer.ID == ID {
   279  			return &apiContainer, nil
   280  		}
   281  	}
   282  
   283  	return nil, nil
   284  }
   285  
   286  func portSetToDockerPorts(ports *schema.Set) (map[dc.Port]struct{}, map[dc.Port][]dc.PortBinding) {
   287  	retExposedPorts := map[dc.Port]struct{}{}
   288  	retPortBindings := map[dc.Port][]dc.PortBinding{}
   289  
   290  	for _, portInt := range ports.List() {
   291  		port := portInt.(map[string]interface{})
   292  		internal := port["internal"].(int)
   293  		protocol := port["protocol"].(string)
   294  
   295  		exposedPort := dc.Port(strconv.Itoa(internal) + "/" + protocol)
   296  		retExposedPorts[exposedPort] = struct{}{}
   297  
   298  		external, extOk := port["external"].(int)
   299  		ip, ipOk := port["ip"].(string)
   300  
   301  		if extOk {
   302  			portBinding := dc.PortBinding{
   303  				HostPort: strconv.Itoa(external),
   304  			}
   305  			if ipOk {
   306  				portBinding.HostIP = ip
   307  			}
   308  			retPortBindings[exposedPort] = append(retPortBindings[exposedPort], portBinding)
   309  		}
   310  	}
   311  
   312  	return retExposedPorts, retPortBindings
   313  }
   314  
   315  func volumeSetToDockerVolumes(volumes *schema.Set) (map[string]struct{}, []string, []string, error) {
   316  	retVolumeMap := map[string]struct{}{}
   317  	retHostConfigBinds := []string{}
   318  	retVolumeFromContainers := []string{}
   319  
   320  	for _, volumeInt := range volumes.List() {
   321  		volume := volumeInt.(map[string]interface{})
   322  		fromContainer := volume["from_container"].(string)
   323  		containerPath := volume["container_path"].(string)
   324  		hostPath := volume["host_path"].(string)
   325  		readOnly := volume["read_only"].(bool)
   326  
   327  		switch {
   328  		case len(fromContainer) == 0 && len(containerPath) == 0:
   329  			return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Volume entry without container path or source container")
   330  		case len(fromContainer) != 0 && len(containerPath) != 0:
   331  			return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Both a container and a path specified in a volume entry")
   332  		case len(fromContainer) != 0:
   333  			retVolumeFromContainers = append(retVolumeFromContainers, fromContainer)
   334  		case len(hostPath) != 0:
   335  			readWrite := "rw"
   336  			if readOnly {
   337  				readWrite = "ro"
   338  			}
   339  			retVolumeMap[containerPath] = struct{}{}
   340  			retHostConfigBinds = append(retHostConfigBinds, hostPath+":"+containerPath+":"+readWrite)
   341  		default:
   342  			retVolumeMap[containerPath] = struct{}{}
   343  		}
   344  	}
   345  
   346  	return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, nil
   347  }