From 46d1fc59ebbe1bd4fe2ee6f6cdff199e381fe5a5 Mon Sep 17 00:00:00 2001 From: Sid Verma Date: Fri, 20 Apr 2018 14:44:44 +0530 Subject: [PATCH] Add support to attach devices to containers (#54) --- docker/resource_docker_container.go | 27 +++++++ docker/resource_docker_container_funcs.go | 30 ++++++++ docker/resource_docker_container_test.go | 90 +++++++++++++++++++++++ website/docs/r/container.html.markdown | 16 ++++ 4 files changed, 163 insertions(+) diff --git a/docker/resource_docker_container.go b/docker/resource_docker_container.go index c50aa92b..4f666033 100644 --- a/docker/resource_docker_container.go +++ b/docker/resource_docker_container.go @@ -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, diff --git a/docker/resource_docker_container_funcs.go b/docker/resource_docker_container_funcs.go index 4c9aaf49..df240507 100644 --- a/docker/resource_docker_container_funcs.go +++ b/docker/resource_docker_container_funcs.go @@ -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 +} diff --git a/docker/resource_docker_container_test.go b/docker/resource_docker_container_test.go index b992bf60..35dc6851 100644 --- a/docker/resource_docker_container_test.go +++ b/docker/resource_docker_container_test.go @@ -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" + } +} +` diff --git a/website/docs/r/container.html.markdown b/website/docs/r/container.html.markdown index a267cb24..99913b34 100644 --- a/website/docs/r/container.html.markdown +++ b/website/docs/r/container.html.markdown @@ -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. + +### 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: