Merge pull request #3761 from ryane/f-provider-docker-improvements
provider/docker: support additional arguments for `docker_container` resource
This commit is contained in:
commit
50d7abcd8a
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform/helper/hashcode"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func resourceDockerContainer() *schema.Resource {
|
||||
|
@ -71,6 +72,13 @@ func resourceDockerContainer() *schema.Resource {
|
|||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"entrypoint": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"dns": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
|
@ -85,6 +93,27 @@ func resourceDockerContainer() *schema.Resource {
|
|||
ForceNew: true,
|
||||
},
|
||||
|
||||
"restart": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Default: "no",
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
||||
value := v.(string)
|
||||
if !regexp.MustCompile(`^(no|on-failure|always)$`).MatchString(value) {
|
||||
es = append(es, fmt.Errorf(
|
||||
"%q must be one of \"no\", \"on-failure\", or \"always\"", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
|
||||
"max_retry_count": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"volumes": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
|
@ -142,6 +171,72 @@ func resourceDockerContainer() *schema.Resource {
|
|||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"labels": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"memory": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
||||
value := v.(int)
|
||||
if value < 0 {
|
||||
es = append(es, fmt.Errorf("%q must be greater than or equal to 0", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
|
||||
"memory_swap": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
||||
value := v.(int)
|
||||
if value < -1 {
|
||||
es = append(es, fmt.Errorf("%q must be greater than or equal to -1", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
|
||||
"cpu_shares": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
||||
value := v.(int)
|
||||
if value < 0 {
|
||||
es = append(es, fmt.Errorf("%q must be greater than or equal to 0", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
|
||||
"log_driver": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Default: "json-file",
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
||||
value := v.(string)
|
||||
if !regexp.MustCompile(`^(json-file|syslog|journald|gelf|fluentd)$`).MatchString(value) {
|
||||
es = append(es, fmt.Errorf(
|
||||
"%q must be one of \"json-file\", \"syslog\", \"journald\", \"gelf\", or \"fluentd\"", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
|
||||
"log_opts": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,10 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
|
|||
createOpts.Config.Cmd = stringListToStringSlice(v.([]interface{}))
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("entrypoint"); ok {
|
||||
createOpts.Config.Entrypoint = stringListToStringSlice(v.([]interface{}))
|
||||
}
|
||||
|
||||
exposedPorts := map[dc.Port]struct{}{}
|
||||
portBindings := map[dc.Port][]dc.PortBinding{}
|
||||
|
||||
|
@ -78,19 +82,20 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
|
|||
createOpts.Config.Volumes = volumes
|
||||
}
|
||||
|
||||
var retContainer *dc.Container
|
||||
if retContainer, err = client.CreateContainer(createOpts); err != nil {
|
||||
return fmt.Errorf("Unable to create container: %s", err)
|
||||
if v, ok := d.GetOk("labels"); ok {
|
||||
createOpts.Config.Labels = mapTypeMapValsToString(v.(map[string]interface{}))
|
||||
}
|
||||
if retContainer == nil {
|
||||
return fmt.Errorf("Returned container is nil")
|
||||
}
|
||||
|
||||
d.SetId(retContainer.ID)
|
||||
|
||||
hostConfig := &dc.HostConfig{
|
||||
Privileged: d.Get("privileged").(bool),
|
||||
PublishAllPorts: d.Get("publish_all_ports").(bool),
|
||||
RestartPolicy: dc.RestartPolicy{
|
||||
Name: d.Get("restart").(string),
|
||||
MaximumRetryCount: d.Get("max_retry_count").(int),
|
||||
},
|
||||
LogConfig: dc.LogConfig{
|
||||
Type: d.Get("log_driver").(string),
|
||||
},
|
||||
}
|
||||
|
||||
if len(portBindings) != 0 {
|
||||
|
@ -112,6 +117,38 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
|
|||
hostConfig.Links = stringSetToStringSlice(v.(*schema.Set))
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("memory"); ok {
|
||||
hostConfig.Memory = int64(v.(int)) * 1024 * 1024
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("memory_swap"); ok {
|
||||
swap := int64(v.(int))
|
||||
if swap > 0 {
|
||||
swap = swap * 1024 * 1024
|
||||
}
|
||||
hostConfig.MemorySwap = swap
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("cpu_shares"); ok {
|
||||
hostConfig.CPUShares = int64(v.(int))
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("log_opts"); ok {
|
||||
hostConfig.LogConfig.Config = mapTypeMapValsToString(v.(map[string]interface{}))
|
||||
}
|
||||
|
||||
createOpts.HostConfig = hostConfig
|
||||
|
||||
var retContainer *dc.Container
|
||||
if retContainer, err = client.CreateContainer(createOpts); err != nil {
|
||||
return fmt.Errorf("Unable to create container: %s", err)
|
||||
}
|
||||
if retContainer == nil {
|
||||
return fmt.Errorf("Returned container is nil")
|
||||
}
|
||||
|
||||
d.SetId(retContainer.ID)
|
||||
|
||||
creationTime = time.Now()
|
||||
if err := client.StartContainer(retContainer.ID, hostConfig); err != nil {
|
||||
return fmt.Errorf("Unable to start container: %s", err)
|
||||
|
@ -223,6 +260,14 @@ func stringSetToStringSlice(stringSet *schema.Set) []string {
|
|||
return ret
|
||||
}
|
||||
|
||||
func mapTypeMapValsToString(typeMap map[string]interface{}) map[string]string {
|
||||
mapped := make(map[string]string, len(typeMap))
|
||||
for k, v := range typeMap {
|
||||
mapped[k] = v.(string)
|
||||
}
|
||||
return mapped
|
||||
}
|
||||
|
||||
func fetchDockerContainer(name string, client *dc.Client) (*dc.APIContainers, error) {
|
||||
apiContainers, err := client.ListContainers(dc.ListContainersOptions{All: true})
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestAccDockerContainer_basic(t *testing.T) {
|
||||
var c dc.Container
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
|
@ -17,14 +18,79 @@ func TestAccDockerContainer_basic(t *testing.T) {
|
|||
resource.TestStep{
|
||||
Config: testAccDockerContainerConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccContainerRunning("docker_container.foo"),
|
||||
testAccContainerRunning("docker_container.foo", &c),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccContainerRunning(n string) resource.TestCheckFunc {
|
||||
func TestAccDockerContainer_customized(t *testing.T) {
|
||||
var c dc.Container
|
||||
|
||||
testCheck := func(*terraform.State) error {
|
||||
if len(c.Config.Entrypoint) < 3 ||
|
||||
(c.Config.Entrypoint[0] != "/bin/bash" &&
|
||||
c.Config.Entrypoint[1] != "-c" &&
|
||||
c.Config.Entrypoint[2] != "ping localhost") {
|
||||
return fmt.Errorf("Container wrong entrypoint: %s", c.Config.Entrypoint)
|
||||
}
|
||||
|
||||
if c.HostConfig.RestartPolicy.Name == "on-failure" {
|
||||
if c.HostConfig.RestartPolicy.MaximumRetryCount != 5 {
|
||||
return fmt.Errorf("Container has wrong restart policy max retry count: %d", c.HostConfig.RestartPolicy.MaximumRetryCount)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Container has wrong restart policy: %s", c.HostConfig.RestartPolicy.Name)
|
||||
}
|
||||
|
||||
if c.HostConfig.Memory != (512 * 1024 * 1024) {
|
||||
return fmt.Errorf("Container has wrong memory setting: %d", c.HostConfig.Memory)
|
||||
}
|
||||
|
||||
if c.HostConfig.MemorySwap != (2048 * 1024 * 1024) {
|
||||
return fmt.Errorf("Container has wrong memory swap setting: %d", c.HostConfig.MemorySwap)
|
||||
}
|
||||
|
||||
if c.HostConfig.CPUShares != 32 {
|
||||
return fmt.Errorf("Container has wrong cpu shares setting: %d", c.HostConfig.CPUShares)
|
||||
}
|
||||
|
||||
if c.Config.Labels["env"] != "prod" || c.Config.Labels["role"] != "test" {
|
||||
return fmt.Errorf("Container does not have the correct labels")
|
||||
}
|
||||
|
||||
if c.HostConfig.LogConfig.Type != "json-file" {
|
||||
return fmt.Errorf("Container does not have the correct log config: %s", c.HostConfig.LogConfig.Type)
|
||||
}
|
||||
|
||||
if c.HostConfig.LogConfig.Config["max-size"] != "10m" {
|
||||
return fmt.Errorf("Container does not have the correct max-size log option: %v", c.HostConfig.LogConfig.Config["max-size"])
|
||||
}
|
||||
|
||||
if c.HostConfig.LogConfig.Config["max-file"] != "20" {
|
||||
return fmt.Errorf("Container does not have the correct max-file log option: %v", c.HostConfig.LogConfig.Config["max-file"])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccDockerContainerCustomizedConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccContainerRunning("docker_container.foo", &c),
|
||||
testCheck,
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccContainerRunning(n string, container *dc.Container) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
|
@ -43,6 +109,11 @@ func testAccContainerRunning(n string) resource.TestCheckFunc {
|
|||
|
||||
for _, c := range containers {
|
||||
if c.ID == rs.Primary.ID {
|
||||
inspected, err := client.InspectContainer(c.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Container could not be inspected: %s", err)
|
||||
}
|
||||
*container = *inspected
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -61,3 +132,28 @@ resource "docker_container" "foo" {
|
|||
image = "${docker_image.foo.latest}"
|
||||
}
|
||||
`
|
||||
const testAccDockerContainerCustomizedConfig = `
|
||||
resource "docker_image" "foo" {
|
||||
name = "nginx:latest"
|
||||
}
|
||||
|
||||
resource "docker_container" "foo" {
|
||||
name = "tf-test"
|
||||
image = "${docker_image.foo.latest}"
|
||||
entrypoint = ["/bin/bash", "-c", "ping localhost"]
|
||||
restart = "on-failure"
|
||||
max_retry_count = 5
|
||||
memory = 512
|
||||
memory_swap = 2048
|
||||
cpu_shares = 32
|
||||
labels {
|
||||
env = "prod"
|
||||
role = "test"
|
||||
}
|
||||
log_driver = "json-file"
|
||||
log_opts = {
|
||||
max-size = "10m"
|
||||
max-file = 20
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -37,12 +37,22 @@ The following arguments are supported:
|
|||
* `command` - (Optional, list of strings) The command to use to start the
|
||||
container. For example, to run `/usr/bin/myprogram -f baz.conf` set the
|
||||
command to be `["/usr/bin/myprogram", "-f", "baz.conf"]`.
|
||||
* `entrypoint` - (Optional, list of strings) The command to use as the
|
||||
Entrypoint for the container. The Entrypoint allows you to configure a
|
||||
container to run as an executable. For example, to run `/usr/bin/myprogram`
|
||||
when starting a container, set the entrypoint to be
|
||||
`["/usr/bin/myprogram"]`.
|
||||
* `dns` - (Optional, set of strings) Set of DNS servers.
|
||||
* `env` - (Optional, set of strings) Environmental variables to set.
|
||||
* `labels` - (Optional) Key/value pairs to set as labels on the container.
|
||||
* `links` - (Optional, set of strings) Set of links for link based
|
||||
connectivity between containers that are running on the same host.
|
||||
* `hostname` - (Optional, string) Hostname of the container.
|
||||
* `domainname` - (Optional, string) Domain name of the container.
|
||||
* `restart` - (Optional, string) The restart policy for the container. Must be
|
||||
one of "no", "on-failure", "always".
|
||||
* `max_retry_count` - (Optional, int) The maximum amount of times to an attempt
|
||||
a restart when `restart` is set to "on-failure"
|
||||
* `must_run` - (Optional, bool) If true, then the Docker container will be
|
||||
kept running. If false, then as long as the container exists, Terraform
|
||||
assumes it is successful.
|
||||
|
@ -50,6 +60,14 @@ The following arguments are supported:
|
|||
* `privileged` - (Optional, bool) Run container in privileged mode.
|
||||
* `publish_all_ports` - (Optional, bool) Publish all ports of the container.
|
||||
* `volumes` - (Optional) See [Volumes](#volumes) below for details.
|
||||
* `memory` - (Optional, int) The memory limit for the container in MBs.
|
||||
* `memory_swap` - (Optional, int) The total memory limit (memory + swap) for the
|
||||
container in MBs.
|
||||
* `cpu_shares` - (Optional, int) CPU shares (relative weight) for the container.
|
||||
* `log_driver` - (Optional, string) The logging driver to use for the container.
|
||||
Defaults to "json-file".
|
||||
* `log_opts` - (Optional) Key/value pairs to use as options for the logging
|
||||
driver.
|
||||
|
||||
<a id="ports"></a>
|
||||
## Ports
|
||||
|
|
Loading…
Reference in New Issue