Add support to attach devices to containers (#54)

This commit is contained in:
Sid Verma 2018-04-20 14:44:44 +05:30 committed by Manuel Vogel
parent 5e85cb68ae
commit 46d1fc59eb
4 changed files with 163 additions and 0 deletions

View file

@ -292,6 +292,33 @@ func resourceDockerContainer() *schema.Resource {
ForceNew: true,
},
"devices": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"host_path": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"container_path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"permissions": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
},
},
"destroy_grace_seconds": &schema.Schema{
Type: schema.TypeInt,
Optional: true,

View file

@ -135,6 +135,10 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
}
}
if v, ok := d.GetOk("devices"); ok {
hostConfig.Devices = deviceSetToDockerDevices(v.(*schema.Set))
}
if v, ok := d.GetOk("dns"); ok {
hostConfig.DNS = stringSetToStringSlice(v.(*schema.Set))
}
@ -463,3 +467,29 @@ func volumeSetToDockerVolumes(volumes *schema.Set) (map[string]struct{}, []strin
return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, nil
}
func deviceSetToDockerDevices(devices *schema.Set) []dc.Device {
retDevices := []dc.Device{}
for _, deviceInt := range devices.List() {
deviceMap := deviceInt.(map[string]interface{})
hostPath := deviceMap["host_path"].(string)
containerPath := deviceMap["container_path"].(string)
permissions := deviceMap["permissions"].(string)
switch {
case len(containerPath) == 0:
containerPath = hostPath
fallthrough
case len(permissions) == 0:
permissions = "rwm"
}
device := dc.Device{
PathOnHost: hostPath,
PathInContainer: containerPath,
CgroupPermissions: permissions,
}
retDevices = append(retDevices, device)
}
return retDevices
}

View file

@ -4,6 +4,7 @@ import (
"archive/tar"
"bytes"
"fmt"
"os"
"testing"
dc "github.com/fsouza/go-dockerclient"
@ -274,6 +275,79 @@ func TestAccDockerContainer_upload(t *testing.T) {
})
}
func TestAccDockerContainer_device(t *testing.T) {
var c dc.Container
testCheck := func(*terraform.State) error {
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
createExecOpts := dc.CreateExecOptions{
Cmd: []string{"dd", "if=/dev/zero_test", "of=/tmp/test.txt", "count=10", "bs=1"},
Container: c.ID,
}
exec, err := client.CreateExec(createExecOpts)
if err != nil {
return fmt.Errorf("Unable to create a exec instance on container: %s", err)
}
startExecOpts := dc.StartExecOptions{
OutputStream: os.Stdout,
ErrorStream: os.Stdout,
}
if err := client.StartExec(exec.ID, startExecOpts); err != nil {
return fmt.Errorf("Unable to run exec a instance on container: %s", err)
}
buf := new(bytes.Buffer)
downloadFileOpts := dc.DownloadFromContainerOptions{
OutputStream: buf,
Path: "/tmp/test.txt",
}
if err := client.DownloadFromContainer(c.ID, downloadFileOpts); err != nil {
return fmt.Errorf("Unable to download a file from container: %s", err)
}
r := bytes.NewReader(buf.Bytes())
tr := tar.NewReader(r)
if _, err := tr.Next(); err != nil {
return fmt.Errorf("Unable to read content of tar archive: %s", err)
}
fbuf := new(bytes.Buffer)
fbuf.ReadFrom(tr)
content := fbuf.Bytes()
if len(content) != 10 {
return fmt.Errorf("Incorrect size of file: %d", len(content))
}
for _, value := range content {
if value != 0 {
return fmt.Errorf("Incorrect content in file: %v", content)
}
}
return nil
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDockerContainerDeviceConfig,
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]
@ -408,3 +482,19 @@ resource "docker_container" "foo" {
}
}
`
const testAccDockerContainerDeviceConfig = `
resource "docker_image" "foo" {
name = "nginx:latest"
}
resource "docker_container" "foo" {
name = "tf-test"
image = "${docker_image.foo.latest}"
devices {
host_path = "/dev/zero"
container_path = "/dev/zero_test"
}
}
`

View file

@ -67,6 +67,7 @@ The following arguments are supported:
* `host` - (Optional, block) See [Extra Hosts](#extra_hosts) below for
details.
* `privileged` - (Optional, bool) Run container in privileged mode.
* `devices` - (Optional, bool) See [Devices](#devices) below for details.
* `publish_all_ports` - (Optional, bool) Publish all ports of the container.
* `volumes` - (Optional, block) See [Volumes](#volumes) below for details.
* `memory` - (Optional, int) The memory limit for the container in MBs.
@ -161,6 +162,21 @@ Each `upload` supports the following
* `content` - (Required, string) A content of a file to upload.
* `file` - (Required, string) path to a file in the container.
<a id="devices"></a>
### Devices
`devices` is a block within the configuration that can be repeated to specify
the devices exposed to a container. Each `devices` block supports
the following:
* `host_path` - (Required, string) The path on the host where the device
is located.
* `container_path` - (Optional, string) The path in the container where the
device will be binded.
* `permissions` - (Optional, string) The cgroup permissions given to the
container to access the device.
Defaults to `rwm`.
## Attributes Reference
The following attributes are exported: