mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2026-01-23 23:22:58 -05:00
docker_container resource mounts support (#147)
* Add mounts support to docker container resource * Source is not a required mount attribute. * Add tmpfs map support to docker container resource * Add tests around tmpfs map
This commit is contained in:
parent
a6fdf4c2a6
commit
a7b3f314d6
4 changed files with 294 additions and 2 deletions
|
|
@ -189,7 +189,104 @@ func resourceDockerContainer() *schema.Resource {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
"mounts": {
|
||||
Type: schema.TypeSet,
|
||||
Description: "Specification for mounts to be added to containers created as part of the service",
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"target": {
|
||||
Type: schema.TypeString,
|
||||
Description: "Container path",
|
||||
Required: true,
|
||||
},
|
||||
"source": {
|
||||
Type: schema.TypeString,
|
||||
Description: "Mount source (e.g. a volume name, a host path)",
|
||||
Optional: true,
|
||||
},
|
||||
"type": {
|
||||
Type: schema.TypeString,
|
||||
Description: "The mount type",
|
||||
Required: true,
|
||||
ValidateFunc: validateStringMatchesPattern(`^(bind|volume|tmpfs)$`),
|
||||
},
|
||||
"read_only": {
|
||||
Type: schema.TypeBool,
|
||||
Description: "Whether the mount should be read-only",
|
||||
Optional: true,
|
||||
},
|
||||
"bind_options": {
|
||||
Type: schema.TypeList,
|
||||
Description: "Optional configuration for the bind type",
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"propagation": {
|
||||
Type: schema.TypeString,
|
||||
Description: "A propagation mode with the value",
|
||||
Optional: true,
|
||||
ValidateFunc: validateStringMatchesPattern(`^(private|rprivate|shared|rshared|slave|rslave)$`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"volume_options": {
|
||||
Type: schema.TypeList,
|
||||
Description: "Optional configuration for the volume type",
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"no_copy": {
|
||||
Type: schema.TypeBool,
|
||||
Description: "Populate volume with data from the target",
|
||||
Optional: true,
|
||||
},
|
||||
"labels": {
|
||||
Type: schema.TypeMap,
|
||||
Description: "User-defined key/value metadata",
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
"driver_name": {
|
||||
Type: schema.TypeString,
|
||||
Description: "Name of the driver to use to create the volume.",
|
||||
Optional: true,
|
||||
},
|
||||
"driver_options": {
|
||||
Type: schema.TypeMap,
|
||||
Description: "key/value map of driver specific options",
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"tmpfs_options": {
|
||||
Type: schema.TypeList,
|
||||
Description: "Optional configuration for the tmpfs type",
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"size_bytes": {
|
||||
Type: schema.TypeInt,
|
||||
Description: "The size for the tmpfs mount in bytes",
|
||||
Optional: true,
|
||||
},
|
||||
"mode": {
|
||||
Type: schema.TypeInt,
|
||||
Description: "The permission mode for the tmpfs mount in an integer",
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"volumes": {
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
|
|
@ -230,6 +327,11 @@ func resourceDockerContainer() *schema.Resource {
|
|||
},
|
||||
},
|
||||
|
||||
"tmpfs": {
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
"ports": {
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -18,6 +19,7 @@ import (
|
|||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
|
|
@ -127,6 +129,82 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
|
|||
}
|
||||
}
|
||||
|
||||
mounts := []mount.Mount{}
|
||||
|
||||
if value, ok := d.GetOk("mounts"); ok {
|
||||
for _, rawMount := range value.(*schema.Set).List() {
|
||||
rawMount := rawMount.(map[string]interface{})
|
||||
mountType := mount.Type(rawMount["type"].(string))
|
||||
mountInstance := mount.Mount{
|
||||
Type: mountType,
|
||||
Target: rawMount["target"].(string),
|
||||
Source: rawMount["source"].(string),
|
||||
}
|
||||
if value, ok := rawMount["read_only"]; ok {
|
||||
mountInstance.ReadOnly = value.(bool)
|
||||
}
|
||||
|
||||
if mountType == mount.TypeBind {
|
||||
if value, ok := rawMount["bind_options"]; ok {
|
||||
if len(value.([]interface{})) > 0 {
|
||||
mountInstance.BindOptions = &mount.BindOptions{}
|
||||
for _, rawBindOptions := range value.([]interface{}) {
|
||||
rawBindOptions := rawBindOptions.(map[string]interface{})
|
||||
if value, ok := rawBindOptions["propagation"]; ok {
|
||||
mountInstance.BindOptions.Propagation = mount.Propagation(value.(string))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if mountType == mount.TypeVolume {
|
||||
if value, ok := rawMount["volume_options"]; ok {
|
||||
if len(value.([]interface{})) > 0 {
|
||||
mountInstance.VolumeOptions = &mount.VolumeOptions{}
|
||||
for _, rawVolumeOptions := range value.([]interface{}) {
|
||||
rawVolumeOptions := rawVolumeOptions.(map[string]interface{})
|
||||
if value, ok := rawVolumeOptions["no_copy"]; ok {
|
||||
mountInstance.VolumeOptions.NoCopy = value.(bool)
|
||||
}
|
||||
if value, ok := rawVolumeOptions["labels"]; ok {
|
||||
mountInstance.VolumeOptions.Labels = mapTypeMapValsToString(value.(map[string]interface{}))
|
||||
}
|
||||
// because it is not possible to nest maps
|
||||
if value, ok := rawVolumeOptions["driver_name"]; ok {
|
||||
if mountInstance.VolumeOptions.DriverConfig == nil {
|
||||
mountInstance.VolumeOptions.DriverConfig = &mount.Driver{}
|
||||
}
|
||||
mountInstance.VolumeOptions.DriverConfig.Name = value.(string)
|
||||
}
|
||||
if value, ok := rawVolumeOptions["driver_options"]; ok {
|
||||
if mountInstance.VolumeOptions.DriverConfig == nil {
|
||||
mountInstance.VolumeOptions.DriverConfig = &mount.Driver{}
|
||||
}
|
||||
mountInstance.VolumeOptions.DriverConfig.Options = mapTypeMapValsToString(value.(map[string]interface{}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if mountType == mount.TypeTmpfs {
|
||||
if value, ok := rawMount["tmpfs_options"]; ok {
|
||||
if len(value.([]interface{})) > 0 {
|
||||
mountInstance.TmpfsOptions = &mount.TmpfsOptions{}
|
||||
for _, rawTmpfsOptions := range value.([]interface{}) {
|
||||
rawTmpfsOptions := rawTmpfsOptions.(map[string]interface{})
|
||||
if value, ok := rawTmpfsOptions["size_bytes"]; ok {
|
||||
mountInstance.TmpfsOptions.SizeBytes = value.(int64)
|
||||
}
|
||||
if value, ok := rawTmpfsOptions["mode"]; ok {
|
||||
mountInstance.TmpfsOptions.Mode = os.FileMode(value.(int))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mounts = append(mounts, mountInstance)
|
||||
}
|
||||
}
|
||||
|
||||
hostConfig := &container.HostConfig{
|
||||
Privileged: d.Get("privileged").(bool),
|
||||
PublishAllPorts: d.Get("publish_all_ports").(bool),
|
||||
|
|
@ -134,12 +212,17 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
|
|||
Name: d.Get("restart").(string),
|
||||
MaximumRetryCount: d.Get("max_retry_count").(int),
|
||||
},
|
||||
Mounts: mounts,
|
||||
AutoRemove: d.Get("rm").(bool),
|
||||
LogConfig: container.LogConfig{
|
||||
Type: d.Get("log_driver").(string),
|
||||
},
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("tmpfs"); ok {
|
||||
hostConfig.Tmpfs = mapTypeMapValsToString(v.(map[string]interface{}))
|
||||
}
|
||||
|
||||
if len(portBindings) != 0 {
|
||||
hostConfig.PortBindings = portBindings
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,6 +200,70 @@ func TestAccDockerContainer_volume(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccDockerContainer_mounts(t *testing.T) {
|
||||
var c types.ContainerJSON
|
||||
|
||||
testCheck := func(*terraform.State) error {
|
||||
if len(c.Mounts) != 2 {
|
||||
return fmt.Errorf("Incorrect number of mounts: expected 2, got %d", len(c.Mounts))
|
||||
}
|
||||
|
||||
for _, v := range c.Mounts {
|
||||
if v.Destination != "/mount/test" && v.Destination != "/mount/tmpfs" {
|
||||
return fmt.Errorf("Bad destination on mount: expected /mount/test or /mount/tmpfs, got %q", v.Destination)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccDockerContainerMountsConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccContainerRunning("docker_container.foo_mounts", &c),
|
||||
testCheck,
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDockerContainer_tmpfs(t *testing.T) {
|
||||
var c types.ContainerJSON
|
||||
|
||||
testCheck := func(*terraform.State) error {
|
||||
if len(c.HostConfig.Tmpfs) != 1 {
|
||||
return fmt.Errorf("Incorrect number of tmpfs: expected 1, got %d", len(c.HostConfig.Tmpfs))
|
||||
}
|
||||
|
||||
for mountPath, _ := range c.HostConfig.Tmpfs {
|
||||
if mountPath != "/mount/tmpfs" {
|
||||
return fmt.Errorf("Bad destination on tmpfs: expected /mount/tmpfs, got %q", mountPath)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccDockerContainerTmpfsConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccContainerRunning("docker_container.foo", &c),
|
||||
testCheck,
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDockerContainer_customized(t *testing.T) {
|
||||
var c types.ContainerJSON
|
||||
|
||||
|
|
@ -1219,6 +1283,49 @@ resource "docker_container" "foo" {
|
|||
}
|
||||
`
|
||||
|
||||
const testAccDockerContainerMountsConfig = `
|
||||
resource "docker_image" "foo_mounts" {
|
||||
name = "nginx:latest"
|
||||
}
|
||||
|
||||
resource "docker_volume" "foo_mounts" {
|
||||
name = "testAccDockerContainerMounts_volume"
|
||||
}
|
||||
|
||||
resource "docker_container" "foo_mounts" {
|
||||
name = "tf-test"
|
||||
image = "${docker_image.foo_mounts.latest}"
|
||||
|
||||
mounts = [
|
||||
{
|
||||
target = "/mount/test"
|
||||
source = "${docker_volume.foo_mounts.name}"
|
||||
type = "volume"
|
||||
read_only = true
|
||||
},
|
||||
{
|
||||
target = "/mount/tmpfs"
|
||||
type = "tmpfs"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
const testAccDockerContainerTmpfsConfig = `
|
||||
resource "docker_image" "foo" {
|
||||
name = "nginx:latest"
|
||||
}
|
||||
|
||||
resource "docker_container" "foo" {
|
||||
name = "tf-test"
|
||||
image = "${docker_image.foo.latest}"
|
||||
|
||||
tmpfs = {
|
||||
"/mount/tmpfs" = "rw,noexec,nosuid"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const testAccDockerContainerCustomizedConfig = `
|
||||
resource "docker_image" "foo" {
|
||||
name = "nginx:latest"
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ func resourceDockerService() *schema.Resource {
|
|||
"source": {
|
||||
Type: schema.TypeString,
|
||||
Description: "Mount source (e.g. a volume name, a host path)",
|
||||
Required: true,
|
||||
Optional: true,
|
||||
},
|
||||
"type": {
|
||||
Type: schema.TypeString,
|
||||
|
|
|
|||
Loading…
Reference in a new issue