Skip to content

Commit

Permalink
Merge pull request #3761 from ryane/f-provider-docker-improvements
Browse files Browse the repository at this point in the history
provider/docker: support additional arguments for `docker_container` resource
  • Loading branch information
jen20 committed Dec 2, 2015
2 parents 1d71ffa + 4fc60c9 commit 50d7abc
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 10 deletions.
95 changes: 95 additions & 0 deletions builtin/providers/docker/resource_docker_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
"regexp"
)

func resourceDockerContainer() *schema.Resource {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
},
},
}
}
Expand Down
61 changes: 53 additions & 8 deletions builtin/providers/docker/resource_docker_container_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}

Expand All @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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})

Expand Down
100 changes: 98 additions & 2 deletions builtin/providers/docker/resource_docker_container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,87 @@ import (
)

func TestAccDockerContainer_basic(t *testing.T) {
var c dc.Container
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
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 {
Expand All @@ -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
}
}
Expand All @@ -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
}
}
`
18 changes: 18 additions & 0 deletions website/source/docs/providers/docker/r/container.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,37 @@ 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.
* `ports` - (Optional) See [Ports](#ports) below for details.
* `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
Expand Down

0 comments on commit 50d7abc

Please sign in to comment.