github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/builtin/providers/docker/resource_docker_container.go (about)

     1  package docker
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  
     7  	"regexp"
     8  
     9  	"github.com/hashicorp/terraform/helper/hashcode"
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  )
    12  
    13  func resourceDockerContainer() *schema.Resource {
    14  	return &schema.Resource{
    15  		Create: resourceDockerContainerCreate,
    16  		Read:   resourceDockerContainerRead,
    17  		Update: resourceDockerContainerUpdate,
    18  		Delete: resourceDockerContainerDelete,
    19  
    20  		Schema: map[string]*schema.Schema{
    21  			"name": &schema.Schema{
    22  				Type:     schema.TypeString,
    23  				Required: true,
    24  				ForceNew: true,
    25  			},
    26  
    27  			// Indicates whether the container must be running.
    28  			//
    29  			// An assumption is made that configured containers
    30  			// should be running; if not, they should not be in
    31  			// the configuration. Therefore a stopped container
    32  			// should be started. Set to false to have the
    33  			// provider leave the container alone.
    34  			//
    35  			// Actively-debugged containers are likely to be
    36  			// stopped and started manually, and Docker has
    37  			// some provisions for restarting containers that
    38  			// stop. The utility here comes from the fact that
    39  			// this will delete and re-create the container
    40  			// following the principle that the containers
    41  			// should be pristine when started.
    42  			"must_run": &schema.Schema{
    43  				Type:     schema.TypeBool,
    44  				Default:  true,
    45  				Optional: true,
    46  			},
    47  
    48  			// ForceNew is not true for image because we need to
    49  			// sane this against Docker image IDs, as each image
    50  			// can have multiple names/tags attached do it.
    51  			"image": &schema.Schema{
    52  				Type:     schema.TypeString,
    53  				Required: true,
    54  				ForceNew: true,
    55  			},
    56  
    57  			"hostname": &schema.Schema{
    58  				Type:     schema.TypeString,
    59  				Optional: true,
    60  				ForceNew: true,
    61  			},
    62  
    63  			"domainname": &schema.Schema{
    64  				Type:     schema.TypeString,
    65  				Optional: true,
    66  				ForceNew: true,
    67  			},
    68  
    69  			"command": &schema.Schema{
    70  				Type:     schema.TypeList,
    71  				Optional: true,
    72  				ForceNew: true,
    73  				Elem:     &schema.Schema{Type: schema.TypeString},
    74  			},
    75  
    76  			"entrypoint": &schema.Schema{
    77  				Type:     schema.TypeList,
    78  				Optional: true,
    79  				ForceNew: true,
    80  				Elem:     &schema.Schema{Type: schema.TypeString},
    81  			},
    82  
    83  			"user": &schema.Schema{
    84  				Type:     schema.TypeString,
    85  				Optional: true,
    86  				ForceNew: true,
    87  				Elem:     &schema.Schema{Type: schema.TypeString},
    88  			},
    89  
    90  			"dns": &schema.Schema{
    91  				Type:     schema.TypeSet,
    92  				Optional: true,
    93  				ForceNew: true,
    94  				Elem:     &schema.Schema{Type: schema.TypeString},
    95  				Set:      schema.HashString,
    96  			},
    97  
    98  			"dns_opts": &schema.Schema{
    99  				Type:     schema.TypeSet,
   100  				Optional: true,
   101  				ForceNew: true,
   102  				Elem:     &schema.Schema{Type: schema.TypeString},
   103  				Set:      schema.HashString,
   104  			},
   105  
   106  			"dns_search": &schema.Schema{
   107  				Type:     schema.TypeSet,
   108  				Optional: true,
   109  				ForceNew: true,
   110  				Elem:     &schema.Schema{Type: schema.TypeString},
   111  				Set:      schema.HashString,
   112  			},
   113  
   114  			"publish_all_ports": &schema.Schema{
   115  				Type:     schema.TypeBool,
   116  				Optional: true,
   117  				ForceNew: true,
   118  			},
   119  
   120  			"restart": &schema.Schema{
   121  				Type:     schema.TypeString,
   122  				Optional: true,
   123  				ForceNew: true,
   124  				Default:  "no",
   125  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   126  					value := v.(string)
   127  					if !regexp.MustCompile(`^(no|on-failure|always|unless-stopped)$`).MatchString(value) {
   128  						es = append(es, fmt.Errorf(
   129  							"%q must be one of \"no\", \"on-failure\", \"always\" or \"unless-stopped\"", k))
   130  					}
   131  					return
   132  				},
   133  			},
   134  
   135  			"max_retry_count": &schema.Schema{
   136  				Type:     schema.TypeInt,
   137  				Optional: true,
   138  				ForceNew: true,
   139  			},
   140  
   141  			"capabilities": &schema.Schema{
   142  				Type:     schema.TypeSet,
   143  				Optional: true,
   144  				ForceNew: true,
   145  				MaxItems: 1,
   146  				Elem: &schema.Resource{
   147  					Schema: map[string]*schema.Schema{
   148  						"add": &schema.Schema{
   149  							Type:     schema.TypeSet,
   150  							Optional: true,
   151  							ForceNew: true,
   152  							Elem:     &schema.Schema{Type: schema.TypeString},
   153  							Set:      schema.HashString,
   154  						},
   155  
   156  						"drop": &schema.Schema{
   157  							Type:     schema.TypeSet,
   158  							Optional: true,
   159  							ForceNew: true,
   160  							Elem:     &schema.Schema{Type: schema.TypeString},
   161  							Set:      schema.HashString,
   162  						},
   163  					},
   164  				},
   165  				Set: resourceDockerCapabilitiesHash,
   166  			},
   167  
   168  			"volumes": &schema.Schema{
   169  				Type:     schema.TypeSet,
   170  				Optional: true,
   171  				ForceNew: true,
   172  				Elem: &schema.Resource{
   173  					Schema: map[string]*schema.Schema{
   174  						"from_container": &schema.Schema{
   175  							Type:     schema.TypeString,
   176  							Optional: true,
   177  							ForceNew: true,
   178  						},
   179  
   180  						"container_path": &schema.Schema{
   181  							Type:     schema.TypeString,
   182  							Optional: true,
   183  							ForceNew: true,
   184  						},
   185  
   186  						"host_path": &schema.Schema{
   187  							Type:         schema.TypeString,
   188  							Optional:     true,
   189  							ForceNew:     true,
   190  							ValidateFunc: validateDockerContainerPath,
   191  						},
   192  
   193  						"volume_name": &schema.Schema{
   194  							Type:     schema.TypeString,
   195  							Optional: true,
   196  							ForceNew: true,
   197  						},
   198  
   199  						"read_only": &schema.Schema{
   200  							Type:     schema.TypeBool,
   201  							Optional: true,
   202  							ForceNew: true,
   203  						},
   204  					},
   205  				},
   206  				Set: resourceDockerVolumesHash,
   207  			},
   208  
   209  			"ports": &schema.Schema{
   210  				Type:     schema.TypeSet,
   211  				Optional: true,
   212  				ForceNew: true,
   213  				Elem: &schema.Resource{
   214  					Schema: map[string]*schema.Schema{
   215  						"internal": &schema.Schema{
   216  							Type:     schema.TypeInt,
   217  							Required: true,
   218  							ForceNew: true,
   219  						},
   220  
   221  						"external": &schema.Schema{
   222  							Type:     schema.TypeInt,
   223  							Optional: true,
   224  							ForceNew: true,
   225  						},
   226  
   227  						"ip": &schema.Schema{
   228  							Type:     schema.TypeString,
   229  							Optional: true,
   230  							ForceNew: true,
   231  						},
   232  
   233  						"protocol": &schema.Schema{
   234  							Type:     schema.TypeString,
   235  							Default:  "tcp",
   236  							Optional: true,
   237  							ForceNew: true,
   238  						},
   239  					},
   240  				},
   241  				Set: resourceDockerPortsHash,
   242  			},
   243  
   244  			"host": &schema.Schema{
   245  				Type:     schema.TypeSet,
   246  				Optional: true,
   247  				ForceNew: true,
   248  				Elem: &schema.Resource{
   249  					Schema: map[string]*schema.Schema{
   250  						"ip": &schema.Schema{
   251  							Type:     schema.TypeString,
   252  							Required: true,
   253  							ForceNew: true,
   254  						},
   255  
   256  						"host": &schema.Schema{
   257  							Type:     schema.TypeString,
   258  							Required: true,
   259  							ForceNew: true,
   260  						},
   261  					},
   262  				},
   263  				Set: resourceDockerHostsHash,
   264  			},
   265  
   266  			"env": &schema.Schema{
   267  				Type:     schema.TypeSet,
   268  				Optional: true,
   269  				ForceNew: true,
   270  				Elem:     &schema.Schema{Type: schema.TypeString},
   271  				Set:      schema.HashString,
   272  			},
   273  
   274  			"links": &schema.Schema{
   275  				Type:     schema.TypeSet,
   276  				Optional: true,
   277  				ForceNew: true,
   278  				Elem:     &schema.Schema{Type: schema.TypeString},
   279  				Set:      schema.HashString,
   280  			},
   281  
   282  			"ip_address": &schema.Schema{
   283  				Type:     schema.TypeString,
   284  				Computed: true,
   285  			},
   286  
   287  			"ip_prefix_length": &schema.Schema{
   288  				Type:     schema.TypeInt,
   289  				Computed: true,
   290  			},
   291  
   292  			"gateway": &schema.Schema{
   293  				Type:     schema.TypeString,
   294  				Computed: true,
   295  			},
   296  
   297  			"bridge": &schema.Schema{
   298  				Type:     schema.TypeString,
   299  				Computed: true,
   300  			},
   301  
   302  			"privileged": &schema.Schema{
   303  				Type:     schema.TypeBool,
   304  				Optional: true,
   305  				ForceNew: true,
   306  			},
   307  
   308  			"destroy_grace_seconds": &schema.Schema{
   309  				Type:     schema.TypeInt,
   310  				Optional: true,
   311  			},
   312  
   313  			"labels": &schema.Schema{
   314  				Type:     schema.TypeMap,
   315  				Optional: true,
   316  				ForceNew: true,
   317  			},
   318  
   319  			"memory": &schema.Schema{
   320  				Type:     schema.TypeInt,
   321  				Optional: true,
   322  				ForceNew: true,
   323  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   324  					value := v.(int)
   325  					if value < 0 {
   326  						es = append(es, fmt.Errorf("%q must be greater than or equal to 0", k))
   327  					}
   328  					return
   329  				},
   330  			},
   331  
   332  			"memory_swap": &schema.Schema{
   333  				Type:     schema.TypeInt,
   334  				Optional: true,
   335  				ForceNew: true,
   336  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   337  					value := v.(int)
   338  					if value < -1 {
   339  						es = append(es, fmt.Errorf("%q must be greater than or equal to -1", k))
   340  					}
   341  					return
   342  				},
   343  			},
   344  
   345  			"cpu_shares": &schema.Schema{
   346  				Type:     schema.TypeInt,
   347  				Optional: true,
   348  				ForceNew: true,
   349  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   350  					value := v.(int)
   351  					if value < 0 {
   352  						es = append(es, fmt.Errorf("%q must be greater than or equal to 0", k))
   353  					}
   354  					return
   355  				},
   356  			},
   357  
   358  			"log_driver": &schema.Schema{
   359  				Type:     schema.TypeString,
   360  				Optional: true,
   361  				ForceNew: true,
   362  				Default:  "json-file",
   363  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   364  					value := v.(string)
   365  					if !regexp.MustCompile(`^(json-file|syslog|journald|gelf|fluentd)$`).MatchString(value) {
   366  						es = append(es, fmt.Errorf(
   367  							"%q must be one of \"json-file\", \"syslog\", \"journald\", \"gelf\", or \"fluentd\"", k))
   368  					}
   369  					return
   370  				},
   371  			},
   372  
   373  			"log_opts": &schema.Schema{
   374  				Type:     schema.TypeMap,
   375  				Optional: true,
   376  				ForceNew: true,
   377  			},
   378  
   379  			"network_alias": &schema.Schema{
   380  				Type:     schema.TypeSet,
   381  				Optional: true,
   382  				ForceNew: true,
   383  				Elem:     &schema.Schema{Type: schema.TypeString},
   384  				Set:      schema.HashString,
   385  			},
   386  
   387  			"network_mode": &schema.Schema{
   388  				Type:     schema.TypeString,
   389  				Optional: true,
   390  				ForceNew: true,
   391  			},
   392  
   393  			"networks": &schema.Schema{
   394  				Type:     schema.TypeSet,
   395  				Optional: true,
   396  				ForceNew: true,
   397  				Elem:     &schema.Schema{Type: schema.TypeString},
   398  				Set:      schema.HashString,
   399  			},
   400  
   401  			"upload": &schema.Schema{
   402  				Type:     schema.TypeSet,
   403  				Optional: true,
   404  				ForceNew: true,
   405  				Elem: &schema.Resource{
   406  					Schema: map[string]*schema.Schema{
   407  						"content": &schema.Schema{
   408  							Type:     schema.TypeString,
   409  							Required: true,
   410  							// This is intentional. The container is mutated once, and never updated later.
   411  							// New configuration forces a new deployment, even with the same binaries.
   412  							ForceNew: true,
   413  						},
   414  						"file": &schema.Schema{
   415  							Type:     schema.TypeString,
   416  							Required: true,
   417  							ForceNew: true,
   418  						},
   419  					},
   420  				},
   421  				Set: resourceDockerUploadHash,
   422  			},
   423  		},
   424  	}
   425  }
   426  
   427  func resourceDockerCapabilitiesHash(v interface{}) int {
   428  	var buf bytes.Buffer
   429  	m := v.(map[string]interface{})
   430  
   431  	if v, ok := m["add"]; ok {
   432  		buf.WriteString(fmt.Sprintf("%v-", v))
   433  	}
   434  
   435  	if v, ok := m["remove"]; ok {
   436  		buf.WriteString(fmt.Sprintf("%v-", v))
   437  	}
   438  
   439  	return hashcode.String(buf.String())
   440  }
   441  
   442  func resourceDockerPortsHash(v interface{}) int {
   443  	var buf bytes.Buffer
   444  	m := v.(map[string]interface{})
   445  
   446  	buf.WriteString(fmt.Sprintf("%v-", m["internal"].(int)))
   447  
   448  	if v, ok := m["external"]; ok {
   449  		buf.WriteString(fmt.Sprintf("%v-", v.(int)))
   450  	}
   451  
   452  	if v, ok := m["ip"]; ok {
   453  		buf.WriteString(fmt.Sprintf("%v-", v.(string)))
   454  	}
   455  
   456  	if v, ok := m["protocol"]; ok {
   457  		buf.WriteString(fmt.Sprintf("%v-", v.(string)))
   458  	}
   459  
   460  	return hashcode.String(buf.String())
   461  }
   462  
   463  func resourceDockerHostsHash(v interface{}) int {
   464  	var buf bytes.Buffer
   465  	m := v.(map[string]interface{})
   466  
   467  	if v, ok := m["ip"]; ok {
   468  		buf.WriteString(fmt.Sprintf("%v-", v.(string)))
   469  	}
   470  
   471  	if v, ok := m["host"]; ok {
   472  		buf.WriteString(fmt.Sprintf("%v-", v.(string)))
   473  	}
   474  
   475  	return hashcode.String(buf.String())
   476  }
   477  
   478  func resourceDockerVolumesHash(v interface{}) int {
   479  	var buf bytes.Buffer
   480  	m := v.(map[string]interface{})
   481  
   482  	if v, ok := m["from_container"]; ok {
   483  		buf.WriteString(fmt.Sprintf("%v-", v.(string)))
   484  	}
   485  
   486  	if v, ok := m["container_path"]; ok {
   487  		buf.WriteString(fmt.Sprintf("%v-", v.(string)))
   488  	}
   489  
   490  	if v, ok := m["host_path"]; ok {
   491  		buf.WriteString(fmt.Sprintf("%v-", v.(string)))
   492  	}
   493  
   494  	if v, ok := m["volume_name"]; ok {
   495  		buf.WriteString(fmt.Sprintf("%v-", v.(string)))
   496  	}
   497  
   498  	if v, ok := m["read_only"]; ok {
   499  		buf.WriteString(fmt.Sprintf("%v-", v.(bool)))
   500  	}
   501  
   502  	return hashcode.String(buf.String())
   503  }
   504  
   505  func resourceDockerUploadHash(v interface{}) int {
   506  	var buf bytes.Buffer
   507  	m := v.(map[string]interface{})
   508  
   509  	if v, ok := m["content"]; ok {
   510  		buf.WriteString(fmt.Sprintf("%v-", v.(string)))
   511  	}
   512  
   513  	if v, ok := m["file"]; ok {
   514  		buf.WriteString(fmt.Sprintf("%v-", v.(string)))
   515  	}
   516  
   517  	return hashcode.String(buf.String())
   518  }
   519  
   520  func validateDockerContainerPath(v interface{}, k string) (ws []string, errors []error) {
   521  
   522  	value := v.(string)
   523  	if !regexp.MustCompile(`^[a-zA-Z]:\\|^/`).MatchString(value) {
   524  		errors = append(errors, fmt.Errorf("%q must be an absolute path", k))
   525  	}
   526  
   527  	return
   528  }