diff --git a/builtin/providers/docker/resource_docker_container.go b/builtin/providers/docker/resource_docker_container.go index 01b2453ad..2fb4efc29 100644 --- a/builtin/providers/docker/resource_docker_container.go +++ b/builtin/providers/docker/resource_docker_container.go @@ -369,6 +369,29 @@ func resourceDockerContainer() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, + + "upload": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "content": &schema.Schema{ + Type: schema.TypeString, + Required: true, + // This is intentional. The container is mutated once, and never updated later. + // New configuration forces a new deployment, even with the same binaries. + ForceNew: true, + }, + "file": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + Set: resourceDockerUploadHash, + }, }, } } @@ -435,3 +458,18 @@ func resourceDockerVolumesHash(v interface{}) int { return hashcode.String(buf.String()) } + +func resourceDockerUploadHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + if v, ok := m["content"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(string))) + } + + if v, ok := m["file"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(string))) + } + + return hashcode.String(buf.String()) +} diff --git a/builtin/providers/docker/resource_docker_container_funcs.go b/builtin/providers/docker/resource_docker_container_funcs.go index 9668fd0a7..f74264a77 100644 --- a/builtin/providers/docker/resource_docker_container_funcs.go +++ b/builtin/providers/docker/resource_docker_container_funcs.go @@ -1,6 +1,8 @@ package docker import ( + "archive/tar" + "bytes" "errors" "fmt" "strconv" @@ -187,6 +189,39 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err } } + if v, ok := d.GetOk("upload"); ok { + for _, upload := range v.(*schema.Set).List() { + content := upload.(map[string]interface{})["content"].(string) + file := upload.(map[string]interface{})["file"].(string) + + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + hdr := &tar.Header{ + Name: file, + Mode: 0644, + Size: int64(len(content)), + } + if err := tw.WriteHeader(hdr); err != nil { + return fmt.Errorf("Error creating tar archive: %s", err) + } + if _, err := tw.Write([]byte(content)); err != nil { + return fmt.Errorf("Error creating tar archive: %s", err) + } + if err := tw.Close(); err != nil { + return fmt.Errorf("Error creating tar archive: %s", err) + } + + uploadOpts := dc.UploadToContainerOptions{ + InputStream: bytes.NewReader(buf.Bytes()), + Path: "/", + } + + if err := client.UploadToContainer(retContainer.ID, uploadOpts); err != nil { + return fmt.Errorf("Unable to upload volume content: %s", err) + } + } + } + creationTime = time.Now() if err := client.StartContainer(retContainer.ID, nil); err != nil { return fmt.Errorf("Unable to start container: %s", err) diff --git a/builtin/providers/docker/resource_docker_container_test.go b/builtin/providers/docker/resource_docker_container_test.go index 1c4da8cdd..99f0ab3e9 100644 --- a/builtin/providers/docker/resource_docker_container_test.go +++ b/builtin/providers/docker/resource_docker_container_test.go @@ -1,6 +1,8 @@ package docker import ( + "archive/tar" + "bytes" "fmt" "testing" @@ -180,6 +182,55 @@ func TestAccDockerContainer_customized(t *testing.T) { }) } +func TestAccDockerContainer_upload(t *testing.T) { + var c dc.Container + + testCheck := func(*terraform.State) error { + client := testAccProvider.Meta().(*dc.Client) + + buf := new(bytes.Buffer) + opts := dc.DownloadFromContainerOptions{ + OutputStream: buf, + Path: "/terraform/test.txt", + } + + if err := client.DownloadFromContainer(c.ID, opts); 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.String() + + if content != "foo" { + return fmt.Errorf("file content is invalid") + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDockerContainerUploadConfig, + 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] @@ -285,3 +336,19 @@ resource "docker_container" "foo" { } } ` + +const testAccDockerContainerUploadConfig = ` +resource "docker_image" "foo" { + name = "nginx:latest" +} + +resource "docker_container" "foo" { + name = "tf-test" + image = "${docker_image.foo.latest}" + + upload { + content = "foo" + file = "/terraform/test.txt" + } +} +` diff --git a/website/source/docs/providers/docker/r/container.html.markdown b/website/source/docs/providers/docker/r/container.html.markdown index 4b928c126..0774edb4a 100644 --- a/website/source/docs/providers/docker/r/container.html.markdown +++ b/website/source/docs/providers/docker/r/container.html.markdown @@ -80,6 +80,7 @@ The following arguments are supported: * `networks` - (Optional, set of strings) Id of the networks in which the container is. * `destroy_grace_seconds` - (Optional, int) If defined will attempt to stop the container before destroying. Container will be destroyed after `n` seconds or on successful stop. +* `upload` - (Optional, block) See [File Upload](#upload) below for details. ### Ports @@ -127,6 +128,16 @@ the following: One of `from_container`, `host_path` or `volume_name` must be set. + +### File Upload + +`upload` is a block within the configuration that can be repeated to specify +files to upload to the container before starting it. +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. + ## Attributes Reference The following attributes are exported: