Skip to content

Commit 50d7abc

Browse files
committed
Merge pull request hashicorp#3761 from ryane/f-provider-docker-improvements
provider/docker: support additional arguments for `docker_container` resource
2 parents 1d71ffa + 4fc60c9 commit 50d7abc

4 files changed

Lines changed: 264 additions & 10 deletions

File tree

builtin/providers/docker/resource_docker_container.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/hashicorp/terraform/helper/hashcode"
88
"github.com/hashicorp/terraform/helper/schema"
9+
"regexp"
910
)
1011

1112
func resourceDockerContainer() *schema.Resource {
@@ -71,6 +72,13 @@ func resourceDockerContainer() *schema.Resource {
7172
Elem: &schema.Schema{Type: schema.TypeString},
7273
},
7374

75+
"entrypoint": &schema.Schema{
76+
Type: schema.TypeList,
77+
Optional: true,
78+
ForceNew: true,
79+
Elem: &schema.Schema{Type: schema.TypeString},
80+
},
81+
7482
"dns": &schema.Schema{
7583
Type: schema.TypeSet,
7684
Optional: true,
@@ -85,6 +93,27 @@ func resourceDockerContainer() *schema.Resource {
8593
ForceNew: true,
8694
},
8795

96+
"restart": &schema.Schema{
97+
Type: schema.TypeString,
98+
Optional: true,
99+
ForceNew: true,
100+
Default: "no",
101+
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
102+
value := v.(string)
103+
if !regexp.MustCompile(`^(no|on-failure|always)$`).MatchString(value) {
104+
es = append(es, fmt.Errorf(
105+
"%q must be one of \"no\", \"on-failure\", or \"always\"", k))
106+
}
107+
return
108+
},
109+
},
110+
111+
"max_retry_count": &schema.Schema{
112+
Type: schema.TypeInt,
113+
Optional: true,
114+
ForceNew: true,
115+
},
116+
88117
"volumes": &schema.Schema{
89118
Type: schema.TypeSet,
90119
Optional: true,
@@ -142,6 +171,72 @@ func resourceDockerContainer() *schema.Resource {
142171
Optional: true,
143172
ForceNew: true,
144173
},
174+
175+
"labels": &schema.Schema{
176+
Type: schema.TypeMap,
177+
Optional: true,
178+
ForceNew: true,
179+
},
180+
181+
"memory": &schema.Schema{
182+
Type: schema.TypeInt,
183+
Optional: true,
184+
ForceNew: true,
185+
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
186+
value := v.(int)
187+
if value < 0 {
188+
es = append(es, fmt.Errorf("%q must be greater than or equal to 0", k))
189+
}
190+
return
191+
},
192+
},
193+
194+
"memory_swap": &schema.Schema{
195+
Type: schema.TypeInt,
196+
Optional: true,
197+
ForceNew: true,
198+
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
199+
value := v.(int)
200+
if value < -1 {
201+
es = append(es, fmt.Errorf("%q must be greater than or equal to -1", k))
202+
}
203+
return
204+
},
205+
},
206+
207+
"cpu_shares": &schema.Schema{
208+
Type: schema.TypeInt,
209+
Optional: true,
210+
ForceNew: true,
211+
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
212+
value := v.(int)
213+
if value < 0 {
214+
es = append(es, fmt.Errorf("%q must be greater than or equal to 0", k))
215+
}
216+
return
217+
},
218+
},
219+
220+
"log_driver": &schema.Schema{
221+
Type: schema.TypeString,
222+
Optional: true,
223+
ForceNew: true,
224+
Default: "json-file",
225+
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
226+
value := v.(string)
227+
if !regexp.MustCompile(`^(json-file|syslog|journald|gelf|fluentd)$`).MatchString(value) {
228+
es = append(es, fmt.Errorf(
229+
"%q must be one of \"json-file\", \"syslog\", \"journald\", \"gelf\", or \"fluentd\"", k))
230+
}
231+
return
232+
},
233+
},
234+
235+
"log_opts": &schema.Schema{
236+
Type: schema.TypeMap,
237+
Optional: true,
238+
ForceNew: true,
239+
},
145240
},
146241
}
147242
}

builtin/providers/docker/resource_docker_container_funcs.go

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
5454
createOpts.Config.Cmd = stringListToStringSlice(v.([]interface{}))
5555
}
5656

57+
if v, ok := d.GetOk("entrypoint"); ok {
58+
createOpts.Config.Entrypoint = stringListToStringSlice(v.([]interface{}))
59+
}
60+
5761
exposedPorts := map[dc.Port]struct{}{}
5862
portBindings := map[dc.Port][]dc.PortBinding{}
5963

@@ -78,19 +82,20 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
7882
createOpts.Config.Volumes = volumes
7983
}
8084

81-
var retContainer *dc.Container
82-
if retContainer, err = client.CreateContainer(createOpts); err != nil {
83-
return fmt.Errorf("Unable to create container: %s", err)
85+
if v, ok := d.GetOk("labels"); ok {
86+
createOpts.Config.Labels = mapTypeMapValsToString(v.(map[string]interface{}))
8487
}
85-
if retContainer == nil {
86-
return fmt.Errorf("Returned container is nil")
87-
}
88-
89-
d.SetId(retContainer.ID)
9088

9189
hostConfig := &dc.HostConfig{
9290
Privileged: d.Get("privileged").(bool),
9391
PublishAllPorts: d.Get("publish_all_ports").(bool),
92+
RestartPolicy: dc.RestartPolicy{
93+
Name: d.Get("restart").(string),
94+
MaximumRetryCount: d.Get("max_retry_count").(int),
95+
},
96+
LogConfig: dc.LogConfig{
97+
Type: d.Get("log_driver").(string),
98+
},
9499
}
95100

96101
if len(portBindings) != 0 {
@@ -112,6 +117,38 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
112117
hostConfig.Links = stringSetToStringSlice(v.(*schema.Set))
113118
}
114119

120+
if v, ok := d.GetOk("memory"); ok {
121+
hostConfig.Memory = int64(v.(int)) * 1024 * 1024
122+
}
123+
124+
if v, ok := d.GetOk("memory_swap"); ok {
125+
swap := int64(v.(int))
126+
if swap > 0 {
127+
swap = swap * 1024 * 1024
128+
}
129+
hostConfig.MemorySwap = swap
130+
}
131+
132+
if v, ok := d.GetOk("cpu_shares"); ok {
133+
hostConfig.CPUShares = int64(v.(int))
134+
}
135+
136+
if v, ok := d.GetOk("log_opts"); ok {
137+
hostConfig.LogConfig.Config = mapTypeMapValsToString(v.(map[string]interface{}))
138+
}
139+
140+
createOpts.HostConfig = hostConfig
141+
142+
var retContainer *dc.Container
143+
if retContainer, err = client.CreateContainer(createOpts); err != nil {
144+
return fmt.Errorf("Unable to create container: %s", err)
145+
}
146+
if retContainer == nil {
147+
return fmt.Errorf("Returned container is nil")
148+
}
149+
150+
d.SetId(retContainer.ID)
151+
115152
creationTime = time.Now()
116153
if err := client.StartContainer(retContainer.ID, hostConfig); err != nil {
117154
return fmt.Errorf("Unable to start container: %s", err)
@@ -223,6 +260,14 @@ func stringSetToStringSlice(stringSet *schema.Set) []string {
223260
return ret
224261
}
225262

263+
func mapTypeMapValsToString(typeMap map[string]interface{}) map[string]string {
264+
mapped := make(map[string]string, len(typeMap))
265+
for k, v := range typeMap {
266+
mapped[k] = v.(string)
267+
}
268+
return mapped
269+
}
270+
226271
func fetchDockerContainer(name string, client *dc.Client) (*dc.APIContainers, error) {
227272
apiContainers, err := client.ListContainers(dc.ListContainersOptions{All: true})
228273

builtin/providers/docker/resource_docker_container_test.go

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,87 @@ import (
1010
)
1111

1212
func TestAccDockerContainer_basic(t *testing.T) {
13+
var c dc.Container
1314
resource.Test(t, resource.TestCase{
1415
PreCheck: func() { testAccPreCheck(t) },
1516
Providers: testAccProviders,
1617
Steps: []resource.TestStep{
1718
resource.TestStep{
1819
Config: testAccDockerContainerConfig,
1920
Check: resource.ComposeTestCheckFunc(
20-
testAccContainerRunning("docker_container.foo"),
21+
testAccContainerRunning("docker_container.foo", &c),
2122
),
2223
},
2324
},
2425
})
2526
}
2627

27-
func testAccContainerRunning(n string) resource.TestCheckFunc {
28+
func TestAccDockerContainer_customized(t *testing.T) {
29+
var c dc.Container
30+
31+
testCheck := func(*terraform.State) error {
32+
if len(c.Config.Entrypoint) < 3 ||
33+
(c.Config.Entrypoint[0] != "/bin/bash" &&
34+
c.Config.Entrypoint[1] != "-c" &&
35+
c.Config.Entrypoint[2] != "ping localhost") {
36+
return fmt.Errorf("Container wrong entrypoint: %s", c.Config.Entrypoint)
37+
}
38+
39+
if c.HostConfig.RestartPolicy.Name == "on-failure" {
40+
if c.HostConfig.RestartPolicy.MaximumRetryCount != 5 {
41+
return fmt.Errorf("Container has wrong restart policy max retry count: %d", c.HostConfig.RestartPolicy.MaximumRetryCount)
42+
}
43+
} else {
44+
return fmt.Errorf("Container has wrong restart policy: %s", c.HostConfig.RestartPolicy.Name)
45+
}
46+
47+
if c.HostConfig.Memory != (512 * 1024 * 1024) {
48+
return fmt.Errorf("Container has wrong memory setting: %d", c.HostConfig.Memory)
49+
}
50+
51+
if c.HostConfig.MemorySwap != (2048 * 1024 * 1024) {
52+
return fmt.Errorf("Container has wrong memory swap setting: %d", c.HostConfig.MemorySwap)
53+
}
54+
55+
if c.HostConfig.CPUShares != 32 {
56+
return fmt.Errorf("Container has wrong cpu shares setting: %d", c.HostConfig.CPUShares)
57+
}
58+
59+
if c.Config.Labels["env"] != "prod" || c.Config.Labels["role"] != "test" {
60+
return fmt.Errorf("Container does not have the correct labels")
61+
}
62+
63+
if c.HostConfig.LogConfig.Type != "json-file" {
64+
return fmt.Errorf("Container does not have the correct log config: %s", c.HostConfig.LogConfig.Type)
65+
}
66+
67+
if c.HostConfig.LogConfig.Config["max-size"] != "10m" {
68+
return fmt.Errorf("Container does not have the correct max-size log option: %v", c.HostConfig.LogConfig.Config["max-size"])
69+
}
70+
71+
if c.HostConfig.LogConfig.Config["max-file"] != "20" {
72+
return fmt.Errorf("Container does not have the correct max-file log option: %v", c.HostConfig.LogConfig.Config["max-file"])
73+
}
74+
75+
return nil
76+
}
77+
78+
resource.Test(t, resource.TestCase{
79+
PreCheck: func() { testAccPreCheck(t) },
80+
Providers: testAccProviders,
81+
Steps: []resource.TestStep{
82+
resource.TestStep{
83+
Config: testAccDockerContainerCustomizedConfig,
84+
Check: resource.ComposeTestCheckFunc(
85+
testAccContainerRunning("docker_container.foo", &c),
86+
testCheck,
87+
),
88+
},
89+
},
90+
})
91+
}
92+
93+
func testAccContainerRunning(n string, container *dc.Container) resource.TestCheckFunc {
2894
return func(s *terraform.State) error {
2995
rs, ok := s.RootModule().Resources[n]
3096
if !ok {
@@ -43,6 +109,11 @@ func testAccContainerRunning(n string) resource.TestCheckFunc {
43109

44110
for _, c := range containers {
45111
if c.ID == rs.Primary.ID {
112+
inspected, err := client.InspectContainer(c.ID)
113+
if err != nil {
114+
return fmt.Errorf("Container could not be inspected: %s", err)
115+
}
116+
*container = *inspected
46117
return nil
47118
}
48119
}
@@ -61,3 +132,28 @@ resource "docker_container" "foo" {
61132
image = "${docker_image.foo.latest}"
62133
}
63134
`
135+
const testAccDockerContainerCustomizedConfig = `
136+
resource "docker_image" "foo" {
137+
name = "nginx:latest"
138+
}
139+
140+
resource "docker_container" "foo" {
141+
name = "tf-test"
142+
image = "${docker_image.foo.latest}"
143+
entrypoint = ["/bin/bash", "-c", "ping localhost"]
144+
restart = "on-failure"
145+
max_retry_count = 5
146+
memory = 512
147+
memory_swap = 2048
148+
cpu_shares = 32
149+
labels {
150+
env = "prod"
151+
role = "test"
152+
}
153+
log_driver = "json-file"
154+
log_opts = {
155+
max-size = "10m"
156+
max-file = 20
157+
}
158+
}
159+
`

website/source/docs/providers/docker/r/container.html.markdown

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,37 @@ The following arguments are supported:
3737
* `command` - (Optional, list of strings) The command to use to start the
3838
container. For example, to run `/usr/bin/myprogram -f baz.conf` set the
3939
command to be `["/usr/bin/myprogram", "-f", "baz.conf"]`.
40+
* `entrypoint` - (Optional, list of strings) The command to use as the
41+
Entrypoint for the container. The Entrypoint allows you to configure a
42+
container to run as an executable. For example, to run `/usr/bin/myprogram`
43+
when starting a container, set the entrypoint to be
44+
`["/usr/bin/myprogram"]`.
4045
* `dns` - (Optional, set of strings) Set of DNS servers.
4146
* `env` - (Optional, set of strings) Environmental variables to set.
47+
* `labels` - (Optional) Key/value pairs to set as labels on the container.
4248
* `links` - (Optional, set of strings) Set of links for link based
4349
connectivity between containers that are running on the same host.
4450
* `hostname` - (Optional, string) Hostname of the container.
4551
* `domainname` - (Optional, string) Domain name of the container.
52+
* `restart` - (Optional, string) The restart policy for the container. Must be
53+
one of "no", "on-failure", "always".
54+
* `max_retry_count` - (Optional, int) The maximum amount of times to an attempt
55+
a restart when `restart` is set to "on-failure"
4656
* `must_run` - (Optional, bool) If true, then the Docker container will be
4757
kept running. If false, then as long as the container exists, Terraform
4858
assumes it is successful.
4959
* `ports` - (Optional) See [Ports](#ports) below for details.
5060
* `privileged` - (Optional, bool) Run container in privileged mode.
5161
* `publish_all_ports` - (Optional, bool) Publish all ports of the container.
5262
* `volumes` - (Optional) See [Volumes](#volumes) below for details.
63+
* `memory` - (Optional, int) The memory limit for the container in MBs.
64+
* `memory_swap` - (Optional, int) The total memory limit (memory + swap) for the
65+
container in MBs.
66+
* `cpu_shares` - (Optional, int) CPU shares (relative weight) for the container.
67+
* `log_driver` - (Optional, string) The logging driver to use for the container.
68+
Defaults to "json-file".
69+
* `log_opts` - (Optional) Key/value pairs to use as options for the logging
70+
driver.
5371

5472
<a id="ports"></a>
5573
## Ports

0 commit comments

Comments
 (0)