provider/docker: Upload files into container before first start (#9520)
* Create uploads section for docker containers * Upload a single file, load its content from state
This commit is contained in:
parent
aba6c904ce
commit
05145dba19
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -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.
|
||||
|
||||
<a id="ports"></a>
|
||||
### Ports
|
||||
|
@ -127,6 +128,16 @@ the following:
|
|||
|
||||
One of `from_container`, `host_path` or `volume_name` must be set.
|
||||
|
||||
<a id="upload"></a>
|
||||
### 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:
|
||||
|
|
Loading…
Reference in New Issue