mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-11 16:15:33 -06:00
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/hashcode"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceDockerContainer() *schema.Resource {
|
func resourceDockerContainer() *schema.Resource {
|
||||||
@ -71,6 +72,13 @@ func resourceDockerContainer() *schema.Resource {
|
|||||||
Elem: &schema.Schema{Type: schema.TypeString},
|
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{
|
"dns": &schema.Schema{
|
||||||
Type: schema.TypeSet,
|
Type: schema.TypeSet,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
@ -85,6 +93,27 @@ func resourceDockerContainer() *schema.Resource {
|
|||||||
ForceNew: true,
|
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{
|
"volumes": &schema.Schema{
|
||||||
Type: schema.TypeSet,
|
Type: schema.TypeSet,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
@ -142,6 +171,72 @@ func resourceDockerContainer() *schema.Resource {
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: 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{}))
|
createOpts.Config.Cmd = stringListToStringSlice(v.([]interface{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("entrypoint"); ok {
|
||||||
|
createOpts.Config.Entrypoint = stringListToStringSlice(v.([]interface{}))
|
||||||
|
}
|
||||||
|
|
||||||
exposedPorts := map[dc.Port]struct{}{}
|
exposedPorts := map[dc.Port]struct{}{}
|
||||||
portBindings := map[dc.Port][]dc.PortBinding{}
|
portBindings := map[dc.Port][]dc.PortBinding{}
|
||||||
|
|
||||||
@ -78,19 +82,20 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
|
|||||||
createOpts.Config.Volumes = volumes
|
createOpts.Config.Volumes = volumes
|
||||||
}
|
}
|
||||||
|
|
||||||
var retContainer *dc.Container
|
if v, ok := d.GetOk("labels"); ok {
|
||||||
if retContainer, err = client.CreateContainer(createOpts); err != nil {
|
createOpts.Config.Labels = mapTypeMapValsToString(v.(map[string]interface{}))
|
||||||
return fmt.Errorf("Unable to create container: %s", err)
|
|
||||||
}
|
}
|
||||||
if retContainer == nil {
|
|
||||||
return fmt.Errorf("Returned container is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.SetId(retContainer.ID)
|
|
||||||
|
|
||||||
hostConfig := &dc.HostConfig{
|
hostConfig := &dc.HostConfig{
|
||||||
Privileged: d.Get("privileged").(bool),
|
Privileged: d.Get("privileged").(bool),
|
||||||
PublishAllPorts: d.Get("publish_all_ports").(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 {
|
if len(portBindings) != 0 {
|
||||||
@ -112,6 +117,38 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
|
|||||||
hostConfig.Links = stringSetToStringSlice(v.(*schema.Set))
|
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()
|
creationTime = time.Now()
|
||||||
if err := client.StartContainer(retContainer.ID, hostConfig); err != nil {
|
if err := client.StartContainer(retContainer.ID, hostConfig); err != nil {
|
||||||
return fmt.Errorf("Unable to start container: %s", err)
|
return fmt.Errorf("Unable to start container: %s", err)
|
||||||
@ -223,6 +260,14 @@ func stringSetToStringSlice(stringSet *schema.Set) []string {
|
|||||||
return ret
|
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) {
|
func fetchDockerContainer(name string, client *dc.Client) (*dc.APIContainers, error) {
|
||||||
apiContainers, err := client.ListContainers(dc.ListContainersOptions{All: true})
|
apiContainers, err := client.ListContainers(dc.ListContainersOptions{All: true})
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestAccDockerContainer_basic(t *testing.T) {
|
func TestAccDockerContainer_basic(t *testing.T) {
|
||||||
|
var c dc.Container
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
Providers: testAccProviders,
|
Providers: testAccProviders,
|
||||||
@ -17,14 +18,79 @@ func TestAccDockerContainer_basic(t *testing.T) {
|
|||||||
resource.TestStep{
|
resource.TestStep{
|
||||||
Config: testAccDockerContainerConfig,
|
Config: testAccDockerContainerConfig,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
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 {
|
return func(s *terraform.State) error {
|
||||||
rs, ok := s.RootModule().Resources[n]
|
rs, ok := s.RootModule().Resources[n]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -43,6 +109,11 @@ func testAccContainerRunning(n string) resource.TestCheckFunc {
|
|||||||
|
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
if c.ID == rs.Primary.ID {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,3 +132,28 @@ resource "docker_container" "foo" {
|
|||||||
image = "${docker_image.foo.latest}"
|
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
|
* `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
|
container. For example, to run `/usr/bin/myprogram -f baz.conf` set the
|
||||||
command to be `["/usr/bin/myprogram", "-f", "baz.conf"]`.
|
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.
|
* `dns` - (Optional, set of strings) Set of DNS servers.
|
||||||
* `env` - (Optional, set of strings) Environmental variables to set.
|
* `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
|
* `links` - (Optional, set of strings) Set of links for link based
|
||||||
connectivity between containers that are running on the same host.
|
connectivity between containers that are running on the same host.
|
||||||
* `hostname` - (Optional, string) Hostname of the container.
|
* `hostname` - (Optional, string) Hostname of the container.
|
||||||
* `domainname` - (Optional, string) Domain name 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
|
* `must_run` - (Optional, bool) If true, then the Docker container will be
|
||||||
kept running. If false, then as long as the container exists, Terraform
|
kept running. If false, then as long as the container exists, Terraform
|
||||||
assumes it is successful.
|
assumes it is successful.
|
||||||
@ -50,6 +60,14 @@ The following arguments are supported:
|
|||||||
* `privileged` - (Optional, bool) Run container in privileged mode.
|
* `privileged` - (Optional, bool) Run container in privileged mode.
|
||||||
* `publish_all_ports` - (Optional, bool) Publish all ports of the container.
|
* `publish_all_ports` - (Optional, bool) Publish all ports of the container.
|
||||||
* `volumes` - (Optional) See [Volumes](#volumes) below for details.
|
* `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>
|
<a id="ports"></a>
|
||||||
## Ports
|
## Ports
|
||||||
|
Loading…
Reference in New Issue
Block a user