feat: allow use of source file instead of content / content_base64 (#240)

Closes #239 

* Added a 'source' and 'source hash' which will reference a file / file hash to load into the container
* Add to docs
* Adding a test, cleaning up another one
This commit is contained in:
stephenliberty 2020-02-03 12:44:46 -08:00 committed by GitHub
parent 5733f00a64
commit 5ad4646537
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 198 additions and 8 deletions

View file

@ -716,6 +716,16 @@ func resourceDockerContainer() *schema.Resource {
ForceNew: true,
Default: false,
},
"source": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"source_hash": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
},
},
@ -1473,6 +1483,16 @@ func resourceDockerContainerV1() *schema.Resource {
ForceNew: true,
Default: false,
},
"source": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"source_hash": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
},
},

View file

@ -8,6 +8,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"sort"
@ -385,12 +386,23 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
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")
source := upload.(map[string]interface{})["source"].(string)
testParams := []string{content, contentBase64, source}
setParams := 0
for _, v := range testParams {
if v != "" {
setParams++
}
}
if content != "" && contentBase64 != "" {
return fmt.Errorf("Error with upload content: only one of 'content' or 'content_base64' can be specified")
if setParams == 0 {
return fmt.Errorf("error with upload content: one of 'content', 'content_base64', or 'source' must be set")
}
if setParams > 1 {
return fmt.Errorf("error with upload content: only one of 'content', 'content_base64', or 'source' can be set")
}
var contentToUpload string
if content != "" {
contentToUpload = content
@ -399,6 +411,13 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
decoded, _ := base64.StdEncoding.DecodeString(contentBase64)
contentToUpload = string(decoded)
}
if source != "" {
sourceContent, err := ioutil.ReadFile(source)
if err != nil {
return fmt.Errorf("could not read file: %s", err)
}
contentToUpload = string(sourceContent)
}
file := upload.(map[string]interface{})["file"].(string)
executable := upload.(map[string]interface{})["executable"].(bool)

View file

@ -4,6 +4,7 @@ import (
"archive/tar"
"bytes"
"fmt"
"io/ioutil"
"os"
"reflect"
"regexp"
@ -658,6 +659,112 @@ func TestAccDockerContainer_upload(t *testing.T) {
})
}
func TestAccDockerContainer_uploadSource(t *testing.T) {
var c types.ContainerJSON
wd, _ := os.Getwd()
testFile := wd + "/../scripts/testing/testingFile"
testFileContent, _ := ioutil.ReadFile(testFile)
testCheck := func(*terraform.State) error {
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
srcPath := "/terraform/test.txt"
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, "744") {
return fmt.Errorf("File permissions are incorrect: %s", mode)
}
}
fbuf := new(bytes.Buffer)
fbuf.ReadFrom(tr)
content := fbuf.String()
if content != string(testFileContent) {
return fmt.Errorf("file content is invalid")
}
return nil
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccDockerContainerUploadSourceConfig, testFile),
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_uploadSourceHash(t *testing.T) {
var c types.ContainerJSON
var firstRunId string
wd, _ := os.Getwd()
testFile := wd + "/../scripts/testing/testingFile"
hash, _ := ioutil.ReadFile(testFile + ".base64")
grabFirstCheck := func(*terraform.State) error {
firstRunId = c.ID
return nil
}
testCheck := func(*terraform.State) error {
if c.ID == firstRunId {
return fmt.Errorf("Container should have been recreated due to changed hash")
}
return nil
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccDockerContainerUploadSourceHashConfig, testFile, string(hash)),
Check: resource.ComposeTestCheckFunc(
testAccContainerRunning("docker_container.foo", &c),
grabFirstCheck,
resource.TestCheckResourceAttr("docker_container.foo", "name", "tf-test"),
resource.TestCheckResourceAttr("docker_container.foo", "upload.#", "1"),
),
},
{
Config: fmt.Sprintf(testAccDockerContainerUploadSourceHashConfig, testFile, string(hash)+"arbitrary"),
Check: resource.ComposeTestCheckFunc(
testAccContainerRunning("docker_container.foo", &c),
testCheck,
resource.TestCheckResourceAttr("docker_container.foo", "name", "tf-test"),
resource.TestCheckResourceAttr("docker_container.foo", "upload.#", "1"),
),
},
},
})
}
func TestAccDockerContainer_uploadAsBase64(t *testing.T) {
var c types.ContainerJSON
@ -765,7 +872,7 @@ func TestAccDockerContainer_multipleUploadContentsConfig(t *testing.T) {
}
}
`,
ExpectError: regexp.MustCompile(`.*only one of 'content' or 'content_base64' can be specified.*`),
ExpectError: regexp.MustCompile(`.*only one of 'content', 'content_base64', or 'source' can be set.*`),
},
},
})
@ -794,7 +901,7 @@ func TestAccDockerContainer_noUploadContentsConfig(t *testing.T) {
}
}
`,
ExpectError: regexp.MustCompile(`.* neither 'content', nor 'content_base64' was set.*`),
ExpectError: regexp.MustCompile(`.* one of 'content', 'content_base64', or 'source' must be set.*`),
},
},
})
@ -1791,6 +1898,43 @@ resource "docker_container" "foo" {
}
`
const testAccDockerContainerUploadSourceConfig = `
resource "docker_image" "foo" {
name = "nginx:latest"
keep_locally = true
}
resource "docker_container" "foo" {
name = "tf-test"
image = "${docker_image.foo.latest}"
upload {
source = "%s"
file = "/terraform/test.txt"
executable = true
}
}
`
const testAccDockerContainerUploadSourceHashConfig = `
resource "docker_image" "foo" {
name = "nginx:latest"
keep_locally = true
}
resource "docker_container" "foo" {
name = "tf-test"
image = "${docker_image.foo.latest}"
upload {
source = "%s"
source_hash = "%s"
file = "/terraform/test.txt"
executable = true
}
}
`
const testAccDockerContainerUploadBase64Config = `
resource "docker_image" "foo" {
name = "nginx:latest"

View file

@ -4,6 +4,8 @@ set -e
for p in $(docker container ls -f 'name=private_registry' -q); do docker stop $p; done
echo "### stopped private registry ###"
rm -f "$(pwd)/scripts/testing/testingFile"
rm -f "$(pwd)/scripts/testing/testingFile.base64"
rm -f "$(pwd)"/scripts/testing/auth/htpasswd
rm -f "$(pwd)"/scripts/testing/certs/registry_auth.*
echo "### removed auth and certs ###"

View file

@ -1,6 +1,9 @@
#!/bin/bash
set -e
echo -n "foo" > "$(pwd)/scripts/testing/testingFile"
echo -n `base64 $(pwd)/scripts/testing/testingFile` > "$(pwd)/scripts/testing/testingFile.base64"
# Create self signed certs
mkdir -p "$(pwd)"/scripts/testing/certs
openssl req \

View file

@ -220,8 +220,10 @@ files to upload to the container before starting it. Only one of `content` or `c
one of them hast to be set.
Each `upload` supports the following
* `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.
* `content` - (Optional, string, conflicts with `content_base64` & `source`) 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` & `source`) 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.
* `source` - (Optional, string, conflicts with `content` & `content_base64`) A filename that references a file which will be uploaded as the object content. This allows for large file uploads that do not get stored in state.
* `source_hash` - (Optional, string) If using `source`, this will force an update if the file content has updated but the filename has not.
* `file` - (Required, string) path to a file in the container.
* `executable` - (Optional, bool) If true, the file will be uploaded with user
executable permission.