github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/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  		for _, v := range createOpts.Config.Cmd {
    55  			if v == "" {
    56  				return fmt.Errorf("values for command may not be empty")
    57  			}
    58  		}
    59  	}
    60  
    61  	if v, ok := d.GetOk("entrypoint"); ok {
    62  		createOpts.Config.Entrypoint = stringListToStringSlice(v.([]interface{}))
    63  	}
    64  
    65  	if v, ok := d.GetOk("user"); ok {
    66  		createOpts.Config.User = v.(string)
    67  	}
    68  
    69  	exposedPorts := map[dc.Port]struct{}{}
    70  	portBindings := map[dc.Port][]dc.PortBinding{}
    71  
    72  	if v, ok := d.GetOk("ports"); ok {
    73  		exposedPorts, portBindings = portSetToDockerPorts(v.(*schema.Set))
    74  	}
    75  	if len(exposedPorts) != 0 {
    76  		createOpts.Config.ExposedPorts = exposedPorts
    77  	}
    78  
    79  	extraHosts := []string{}
    80  	if v, ok := d.GetOk("host"); ok {
    81  		extraHosts = extraHostsSetToDockerExtraHosts(v.(*schema.Set))
    82  	}
    83  
    84  	volumes := map[string]struct{}{}
    85  	binds := []string{}
    86  	volumesFrom := []string{}
    87  
    88  	if v, ok := d.GetOk("volumes"); ok {
    89  		volumes, binds, volumesFrom, err = volumeSetToDockerVolumes(v.(*schema.Set))
    90  		if err != nil {
    91  			return fmt.Errorf("Unable to parse volumes: %s", err)
    92  		}
    93  	}
    94  	if len(volumes) != 0 {
    95  		createOpts.Config.Volumes = volumes
    96  	}
    97  
    98  	if v, ok := d.GetOk("labels"); ok {
    99  		createOpts.Config.Labels = mapTypeMapValsToString(v.(map[string]interface{}))
   100  	}
   101  
   102  	hostConfig := &dc.HostConfig{
   103  		Privileged:      d.Get("privileged").(bool),
   104  		PublishAllPorts: d.Get("publish_all_ports").(bool),
   105  		RestartPolicy: dc.RestartPolicy{
   106  			Name:              d.Get("restart").(string),
   107  			MaximumRetryCount: d.Get("max_retry_count").(int),
   108  		},
   109  		LogConfig: dc.LogConfig{
   110  			Type: d.Get("log_driver").(string),
   111  		},
   112  	}
   113  
   114  	if len(portBindings) != 0 {
   115  		hostConfig.PortBindings = portBindings
   116  	}
   117  	if len(extraHosts) != 0 {
   118  		hostConfig.ExtraHosts = extraHosts
   119  	}
   120  	if len(binds) != 0 {
   121  		hostConfig.Binds = binds
   122  	}
   123  	if len(volumesFrom) != 0 {
   124  		hostConfig.VolumesFrom = volumesFrom
   125  	}
   126  
   127  	if v, ok := d.GetOk("dns"); ok {
   128  		hostConfig.DNS = stringSetToStringSlice(v.(*schema.Set))
   129  	}
   130  
   131  	if v, ok := d.GetOk("dns_opts"); ok {
   132  		hostConfig.DNSOptions = stringSetToStringSlice(v.(*schema.Set))
   133  	}
   134  
   135  	if v, ok := d.GetOk("dns_search"); ok {
   136  		hostConfig.DNSSearch = stringSetToStringSlice(v.(*schema.Set))
   137  	}
   138  
   139  	if v, ok := d.GetOk("links"); ok {
   140  		hostConfig.Links = stringSetToStringSlice(v.(*schema.Set))
   141  	}
   142  
   143  	if v, ok := d.GetOk("memory"); ok {
   144  		hostConfig.Memory = int64(v.(int)) * 1024 * 1024
   145  	}
   146  
   147  	if v, ok := d.GetOk("memory_swap"); ok {
   148  		swap := int64(v.(int))
   149  		if swap > 0 {
   150  			swap = swap * 1024 * 1024
   151  		}
   152  		hostConfig.MemorySwap = swap
   153  	}
   154  
   155  	if v, ok := d.GetOk("cpu_shares"); ok {
   156  		hostConfig.CPUShares = int64(v.(int))
   157  	}
   158  
   159  	if v, ok := d.GetOk("log_opts"); ok {
   160  		hostConfig.LogConfig.Config = mapTypeMapValsToString(v.(map[string]interface{}))
   161  	}
   162  
   163  	if v, ok := d.GetOk("network_mode"); ok {
   164  		hostConfig.NetworkMode = v.(string)
   165  	}
   166  
   167  	createOpts.HostConfig = hostConfig
   168  
   169  	var retContainer *dc.Container
   170  	if retContainer, err = client.CreateContainer(createOpts); err != nil {
   171  		return fmt.Errorf("Unable to create container: %s", err)
   172  	}
   173  	if retContainer == nil {
   174  		return fmt.Errorf("Returned container is nil")
   175  	}
   176  
   177  	d.SetId(retContainer.ID)
   178  
   179  	if v, ok := d.GetOk("networks"); ok {
   180  		connectionOpts := dc.NetworkConnectionOptions{Container: retContainer.ID}
   181  
   182  		for _, rawNetwork := range v.(*schema.Set).List() {
   183  			network := rawNetwork.(string)
   184  			if err := client.ConnectNetwork(network, connectionOpts); err != nil {
   185  				return fmt.Errorf("Unable to connect to network '%s': %s", network, err)
   186  			}
   187  		}
   188  	}
   189  
   190  	creationTime = time.Now()
   191  	if err := client.StartContainer(retContainer.ID, nil); err != nil {
   192  		return fmt.Errorf("Unable to start container: %s", err)
   193  	}
   194  
   195  	return resourceDockerContainerRead(d, meta)
   196  }
   197  
   198  func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error {
   199  	client := meta.(*dc.Client)
   200  
   201  	apiContainer, err := fetchDockerContainer(d.Id(), client)
   202  	if err != nil {
   203  		return err
   204  	}
   205  	if apiContainer == nil {
   206  		// This container doesn't exist anymore
   207  		d.SetId("")
   208  		return nil
   209  	}
   210  
   211  	var container *dc.Container
   212  
   213  	loops := 1 // if it hasn't just been created, don't delay
   214  	if !creationTime.IsZero() {
   215  		loops = 30 // with 500ms spacing, 15 seconds; ought to be plenty
   216  	}
   217  	sleepTime := 500 * time.Millisecond
   218  
   219  	for i := loops; i > 0; i-- {
   220  		container, err = client.InspectContainer(apiContainer.ID)
   221  		if err != nil {
   222  			return fmt.Errorf("Error inspecting container %s: %s", apiContainer.ID, err)
   223  		}
   224  
   225  		if container.State.Running ||
   226  			!container.State.Running && !d.Get("must_run").(bool) {
   227  			break
   228  		}
   229  
   230  		if creationTime.IsZero() { // We didn't just create it, so don't wait around
   231  			return resourceDockerContainerDelete(d, meta)
   232  		}
   233  
   234  		if container.State.FinishedAt.After(creationTime) {
   235  			// It exited immediately, so error out so dependent containers
   236  			// aren't started
   237  			resourceDockerContainerDelete(d, meta)
   238  			return fmt.Errorf("Container %s exited after creation, error was: %s", apiContainer.ID, container.State.Error)
   239  		}
   240  
   241  		time.Sleep(sleepTime)
   242  	}
   243  
   244  	// Handle the case of the for loop above running its course
   245  	if !container.State.Running && d.Get("must_run").(bool) {
   246  		resourceDockerContainerDelete(d, meta)
   247  		return fmt.Errorf("Container %s failed to be in running state", apiContainer.ID)
   248  	}
   249  
   250  	// Read Network Settings
   251  	if container.NetworkSettings != nil {
   252  		d.Set("ip_address", container.NetworkSettings.IPAddress)
   253  		d.Set("ip_prefix_length", container.NetworkSettings.IPPrefixLen)
   254  		d.Set("gateway", container.NetworkSettings.Gateway)
   255  		d.Set("bridge", container.NetworkSettings.Bridge)
   256  	}
   257  
   258  	return nil
   259  }
   260  
   261  func resourceDockerContainerUpdate(d *schema.ResourceData, meta interface{}) error {
   262  	return nil
   263  }
   264  
   265  func resourceDockerContainerDelete(d *schema.ResourceData, meta interface{}) error {
   266  	client := meta.(*dc.Client)
   267  
   268  	// Stop the container before removing if destroy_grace_seconds is defined
   269  	if d.Get("destroy_grace_seconds").(int) > 0 {
   270  		var timeout = uint(d.Get("destroy_grace_seconds").(int))
   271  		if err := client.StopContainer(d.Id(), timeout); err != nil {
   272  			return fmt.Errorf("Error stopping container %s: %s", d.Id(), err)
   273  		}
   274  	}
   275  
   276  	removeOpts := dc.RemoveContainerOptions{
   277  		ID:            d.Id(),
   278  		RemoveVolumes: true,
   279  		Force:         true,
   280  	}
   281  
   282  	if err := client.RemoveContainer(removeOpts); err != nil {
   283  		return fmt.Errorf("Error deleting container %s: %s", d.Id(), err)
   284  	}
   285  
   286  	d.SetId("")
   287  	return nil
   288  }
   289  
   290  func stringListToStringSlice(stringList []interface{}) []string {
   291  	ret := []string{}
   292  	for _, v := range stringList {
   293  		if v == nil {
   294  			ret = append(ret, "")
   295  			continue
   296  		}
   297  		ret = append(ret, v.(string))
   298  	}
   299  	return ret
   300  }
   301  
   302  func stringSetToStringSlice(stringSet *schema.Set) []string {
   303  	ret := []string{}
   304  	if stringSet == nil {
   305  		return ret
   306  	}
   307  	for _, envVal := range stringSet.List() {
   308  		ret = append(ret, envVal.(string))
   309  	}
   310  	return ret
   311  }
   312  
   313  func mapTypeMapValsToString(typeMap map[string]interface{}) map[string]string {
   314  	mapped := make(map[string]string, len(typeMap))
   315  	for k, v := range typeMap {
   316  		mapped[k] = v.(string)
   317  	}
   318  	return mapped
   319  }
   320  
   321  func fetchDockerContainer(ID string, client *dc.Client) (*dc.APIContainers, error) {
   322  	apiContainers, err := client.ListContainers(dc.ListContainersOptions{All: true})
   323  
   324  	if err != nil {
   325  		return nil, fmt.Errorf("Error fetching container information from Docker: %s\n", err)
   326  	}
   327  
   328  	for _, apiContainer := range apiContainers {
   329  		if apiContainer.ID == ID {
   330  			return &apiContainer, nil
   331  		}
   332  	}
   333  
   334  	return nil, nil
   335  }
   336  
   337  func portSetToDockerPorts(ports *schema.Set) (map[dc.Port]struct{}, map[dc.Port][]dc.PortBinding) {
   338  	retExposedPorts := map[dc.Port]struct{}{}
   339  	retPortBindings := map[dc.Port][]dc.PortBinding{}
   340  
   341  	for _, portInt := range ports.List() {
   342  		port := portInt.(map[string]interface{})
   343  		internal := port["internal"].(int)
   344  		protocol := port["protocol"].(string)
   345  
   346  		exposedPort := dc.Port(strconv.Itoa(internal) + "/" + protocol)
   347  		retExposedPorts[exposedPort] = struct{}{}
   348  
   349  		external, extOk := port["external"].(int)
   350  		ip, ipOk := port["ip"].(string)
   351  
   352  		if extOk {
   353  			portBinding := dc.PortBinding{
   354  				HostPort: strconv.Itoa(external),
   355  			}
   356  			if ipOk {
   357  				portBinding.HostIP = ip
   358  			}
   359  			retPortBindings[exposedPort] = append(retPortBindings[exposedPort], portBinding)
   360  		}
   361  	}
   362  
   363  	return retExposedPorts, retPortBindings
   364  }
   365  
   366  func extraHostsSetToDockerExtraHosts(extraHosts *schema.Set) []string {
   367  	retExtraHosts := []string{}
   368  
   369  	for _, hostInt := range extraHosts.List() {
   370  		host := hostInt.(map[string]interface{})
   371  		ip := host["ip"].(string)
   372  		hostname := host["host"].(string)
   373  		retExtraHosts = append(retExtraHosts, hostname+":"+ip)
   374  	}
   375  
   376  	return retExtraHosts
   377  }
   378  
   379  func volumeSetToDockerVolumes(volumes *schema.Set) (map[string]struct{}, []string, []string, error) {
   380  	retVolumeMap := map[string]struct{}{}
   381  	retHostConfigBinds := []string{}
   382  	retVolumeFromContainers := []string{}
   383  
   384  	for _, volumeInt := range volumes.List() {
   385  		volume := volumeInt.(map[string]interface{})
   386  		fromContainer := volume["from_container"].(string)
   387  		containerPath := volume["container_path"].(string)
   388  		volumeName := volume["volume_name"].(string)
   389  		if len(volumeName) == 0 {
   390  			volumeName = volume["host_path"].(string)
   391  		}
   392  		readOnly := volume["read_only"].(bool)
   393  
   394  		switch {
   395  		case len(fromContainer) == 0 && len(containerPath) == 0:
   396  			return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Volume entry without container path or source container")
   397  		case len(fromContainer) != 0 && len(containerPath) != 0:
   398  			return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Both a container and a path specified in a volume entry")
   399  		case len(fromContainer) != 0:
   400  			retVolumeFromContainers = append(retVolumeFromContainers, fromContainer)
   401  		case len(volumeName) != 0:
   402  			readWrite := "rw"
   403  			if readOnly {
   404  				readWrite = "ro"
   405  			}
   406  			retVolumeMap[containerPath] = struct{}{}
   407  			retHostConfigBinds = append(retHostConfigBinds, volumeName+":"+containerPath+":"+readWrite)
   408  		default:
   409  			retVolumeMap[containerPath] = struct{}{}
   410  		}
   411  	}
   412  
   413  	return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, nil
   414  }