mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-31 04:00:06 -05:00
fix: binary upload as base 64 content (#194)
Closes #48 * feat: adds new content_base64 property for upload * feat: adds logic for base64 content processing * test: for new upload property * docs: for new upload property * docs: adapts container upload to aws example docs * fix: adds a comment for non-working conflicts with within lists * docs: updates for non-supported conflict within lists and sets * tests: updates notes on disabled checks * fix: validation for container upload configs * docs: validation for container upload configs * fix(test): add must not run flag for invalid upload config container
This commit is contained in:
parent
cb9c327ae4
commit
2ec6bba9b7
4 changed files with 206 additions and 5 deletions
|
|
@ -659,11 +659,17 @@ func resourceDockerContainer() *schema.Resource {
|
|||
Schema: map[string]*schema.Schema{
|
||||
"content": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Optional: 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,
|
||||
},
|
||||
"content_base64": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validateStringIsBase64Encoded(),
|
||||
},
|
||||
"file": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -382,6 +383,21 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
|
|||
var mode int64
|
||||
for _, upload := range v.(*schema.Set).List() {
|
||||
content := upload.(map[string]interface{})["content"].(string)
|
||||
contentBase64 := upload.(map[string]interface{})["content_base64"].(string)
|
||||
if content == "" && contentBase64 == "" {
|
||||
return fmt.Errorf("Error with upload content: neither 'content', nor 'content_base64' was set")
|
||||
}
|
||||
if content != "" && contentBase64 != "" {
|
||||
return fmt.Errorf("Error with upload content: only one of 'content' or 'content_base64' can be specified")
|
||||
}
|
||||
var contentToUpload string
|
||||
if content != "" {
|
||||
contentToUpload = content
|
||||
}
|
||||
if contentBase64 != "" {
|
||||
decoded, _ := base64.StdEncoding.DecodeString(contentBase64)
|
||||
contentToUpload = string(decoded)
|
||||
}
|
||||
file := upload.(map[string]interface{})["file"].(string)
|
||||
executable := upload.(map[string]interface{})["executable"].(bool)
|
||||
|
||||
|
|
@ -395,12 +411,12 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
|
|||
hdr := &tar.Header{
|
||||
Name: file,
|
||||
Mode: mode,
|
||||
Size: int64(len(content)),
|
||||
Size: int64(len(contentToUpload)),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return fmt.Errorf("Error creating tar archive: %s", err)
|
||||
}
|
||||
if _, err := tw.Write([]byte(content)); err != nil {
|
||||
if _, err := tw.Write([]byte(contentToUpload)); err != nil {
|
||||
return fmt.Errorf("Error creating tar archive: %s", err)
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -610,12 +611,164 @@ func TestAccDockerContainer_upload(t *testing.T) {
|
|||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccContainerRunning("docker_container.foo", &c),
|
||||
testCheck,
|
||||
resource.TestCheckResourceAttr("docker_container.foo", "name", "tf-test"),
|
||||
resource.TestCheckResourceAttr("docker_container.foo", "upload.#", "1"),
|
||||
// NOTE mavogel: current the terraform-plugin-sdk it's likely that
|
||||
// the acceptance testing framework shims (still using the older flatmap-style addressing)
|
||||
// are missing a conversion with the hashes.
|
||||
// See https://github.com/hashicorp/terraform-plugin-sdk/issues/196
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.0.content", "foo"),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.0.content_base64", ""),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.0.executable", "true"),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.0.file", "/terraform/test.txt"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDockerContainer_uploadAsBase64(t *testing.T) {
|
||||
var c types.ContainerJSON
|
||||
|
||||
testCheck := func(srcPath, wantedContent, filePerm string) func(*terraform.State) error {
|
||||
return func(*terraform.State) error {
|
||||
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
|
||||
|
||||
r, _, err := client.CopyFromContainer(context.Background(), c.ID, srcPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to download a file from container: %s", err)
|
||||
}
|
||||
|
||||
tr := tar.NewReader(r)
|
||||
if header, err := tr.Next(); err != nil {
|
||||
return fmt.Errorf("Unable to read content of tar archive: %s", err)
|
||||
} else {
|
||||
mode := strconv.FormatInt(header.Mode, 8)
|
||||
if !strings.HasSuffix(mode, filePerm) {
|
||||
return fmt.Errorf("File permissions are incorrect: %s", mode)
|
||||
}
|
||||
}
|
||||
|
||||
fbuf := new(bytes.Buffer)
|
||||
fbuf.ReadFrom(tr)
|
||||
gotContent := fbuf.String()
|
||||
|
||||
if wantedContent != gotContent {
|
||||
return fmt.Errorf("file content is invalid: want: %q, got: %q", wantedContent, gotContent)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccDockerContainerUploadBase64Config,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccContainerRunning("docker_container.foo", &c),
|
||||
testCheck("/terraform/test1.txt", "894fc3f56edf2d3a4c5fb5cb71df910f958a2ed8", "744"),
|
||||
testCheck("/terraform/test2.txt", "foobar", "100644"),
|
||||
resource.TestCheckResourceAttr("docker_container.foo", "name", "tf-test"),
|
||||
resource.TestCheckResourceAttr("docker_container.foo", "upload.#", "2"),
|
||||
// NOTE: see comment above
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.0.content", ""),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.0.content_base64", "ODk0ZmMzZjU2ZWRmMmQzYTRjNWZiNWNiNzFkZjkxMGY5NThhMmVkOA=="),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.0.executable", "true"),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.0.file", "/terraform/test1.txt"),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.1.content", "foo"),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.1.content_base64", ""),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.1.executable", "false"),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.1.file", "/terraform/test2.txt"),
|
||||
),
|
||||
},
|
||||
// We add a second on purpose to detect if there is a dirty plan
|
||||
// although the file content did not change
|
||||
{
|
||||
Config: testAccDockerContainerUploadBase64Config,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccContainerRunning("docker_container.foo", &c),
|
||||
testCheck("/terraform/test1.txt", "894fc3f56edf2d3a4c5fb5cb71df910f958a2ed8", "744"),
|
||||
testCheck("/terraform/test2.txt", "foobar", "100644"),
|
||||
resource.TestCheckResourceAttr("docker_container.foo", "name", "tf-test"),
|
||||
resource.TestCheckResourceAttr("docker_container.foo", "upload.#", "2"),
|
||||
// NOTE: see comment above
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.0.content", ""),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.0.content_base64", "ODk0ZmMzZjU2ZWRmMmQzYTRjNWZiNWNiNzFkZjkxMGY5NThhMmVkOA=="),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.0.executable", "true"),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.0.file", "/terraform/test1.txt"),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.1.content", "foo"),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.1.content_base64", ""),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.1.executable", "false"),
|
||||
// resource.TestCheckResourceAttr("docker_container.foo", "upload.1.file", "/terraform/test2.txt"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDockerContainer_multipleUploadContentsConfig(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: `
|
||||
resource "docker_image" "foo" {
|
||||
name = "nginx:latest"
|
||||
keep_locally = true
|
||||
}
|
||||
|
||||
resource "docker_container" "foo" {
|
||||
name = "tf-test"
|
||||
image = "${docker_image.foo.latest}"
|
||||
must_run = "false"
|
||||
|
||||
upload {
|
||||
content = "foobar"
|
||||
content_base64 = "${base64encode("barbaz")}"
|
||||
file = "/terraform/test1.txt"
|
||||
executable = true
|
||||
}
|
||||
}
|
||||
`,
|
||||
ExpectError: regexp.MustCompile(`.*only one of 'content' or 'content_base64' can be specified.*`),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDockerContainer_noUploadContentsConfig(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: `
|
||||
resource "docker_image" "foo" {
|
||||
name = "nginx:latest"
|
||||
keep_locally = true
|
||||
}
|
||||
|
||||
resource "docker_container" "foo" {
|
||||
name = "tf-test"
|
||||
image = "${docker_image.foo.latest}"
|
||||
must_run = "false"
|
||||
|
||||
upload {
|
||||
file = "/terraform/test1.txt"
|
||||
executable = true
|
||||
}
|
||||
}
|
||||
`,
|
||||
ExpectError: regexp.MustCompile(`.* neither 'content', nor 'content_base64' was set.*`),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDockerContainer_device(t *testing.T) {
|
||||
var c types.ContainerJSON
|
||||
|
||||
|
|
@ -1531,6 +1684,7 @@ resource "docker_network" "test_network" {
|
|||
const testAccDockerContainerUploadConfig = `
|
||||
resource "docker_image" "foo" {
|
||||
name = "nginx:latest"
|
||||
keep_locally = true
|
||||
}
|
||||
|
||||
resource "docker_container" "foo" {
|
||||
|
|
@ -1545,6 +1699,29 @@ resource "docker_container" "foo" {
|
|||
}
|
||||
`
|
||||
|
||||
const testAccDockerContainerUploadBase64Config = `
|
||||
resource "docker_image" "foo" {
|
||||
name = "nginx:latest"
|
||||
keep_locally = true
|
||||
}
|
||||
|
||||
resource "docker_container" "foo" {
|
||||
name = "tf-test"
|
||||
image = "${docker_image.foo.latest}"
|
||||
|
||||
upload {
|
||||
content_base64 = "${base64encode("894fc3f56edf2d3a4c5fb5cb71df910f958a2ed8")}"
|
||||
file = "/terraform/test1.txt"
|
||||
executable = true
|
||||
}
|
||||
|
||||
upload {
|
||||
content = "foobar"
|
||||
file = "/terraform/test2.txt"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const testAccDockerContainerDeviceConfig = `
|
||||
resource "docker_image" "foo" {
|
||||
name = "nginx:latest"
|
||||
|
|
|
|||
|
|
@ -204,10 +204,12 @@ 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.
|
||||
files to upload to the container before starting it. Only one of `content` or `content_base64` can be set and at least
|
||||
one of them hast to be set.
|
||||
Each `upload` supports the following
|
||||
|
||||
* `content` - (Required, string) A content of a file to upload.
|
||||
* `content` - (Optional, string, conflicts with `content_base64`) Literal string value to use as the object content, which will be uploaded as UTF-8-encoded text.
|
||||
* `content_base64` - (Optional, string, conflicts with `content`) Base64-encoded data that will be decoded and uploaded as raw bytes for the object content. This allows safely uploading non-UTF8 binary data, but is recommended only for larger binary content such as the result of the `base64encode` interpolation function. See [here](https://github.com/terraform-providers/terraform-provider-docker/issues/48#issuecomment-374174588) for the reason.
|
||||
* `file` - (Required, string) path to a file in the container.
|
||||
* `executable` - (Optional, bool) If true, the file will be uploaded with user
|
||||
executable permission.
|
||||
|
|
|
|||
Loading…
Reference in a new issue