mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-20 22:59:42 -05:00
* fix: Add ForceTrue to docker_image name attribute. * fix: Test for docker_image name attribute change does not need to check for specific sha sums.
448 lines
16 KiB
Go
448 lines
16 KiB
Go
package provider
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
|
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
|
)
|
|
|
|
var contentDigestRegexp = regexp.MustCompile(`\A[A-Za-z0-9_\+\.-]+:[A-Fa-f0-9]+\z`)
|
|
|
|
func TestAccDockerImage_basic(t *testing.T) {
|
|
// run a Docker container which refers the Docker image to test "force_remove" option
|
|
containerName := "test-docker-image-force-remove"
|
|
ctx := context.Background()
|
|
if err := exec.Command("docker", "run", "--rm", "-d", "--name", containerName, "alpine:3.16.0", "tail", "-f", "/dev/null").Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if err := exec.Command("docker", "stop", containerName).Run(); err != nil {
|
|
t.Logf("failed to stop the Docker container %s: %v", containerName, err)
|
|
}
|
|
}()
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
CheckDestroy: func(state *terraform.State) error {
|
|
return testAccDockerImageDestroy(ctx, state)
|
|
},
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageConfig"),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.foo", "latest", contentDigestRegexp),
|
|
),
|
|
},
|
|
{
|
|
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAccForceRemoveDockerImage"),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.test", "latest", contentDigestRegexp),
|
|
),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestAccDockerImage_private(t *testing.T) {
|
|
ctx := context.Background()
|
|
var i types.ImageInspect
|
|
|
|
testCheckImageInspect := func(*terraform.State) error {
|
|
if len(i.RepoTags) != 1 ||
|
|
i.RepoTags[0] != "gcr.io:443/google_containers/pause:0.8.0" {
|
|
return fmt.Errorf("Image RepoTags is wrong: %v", i.RepoTags)
|
|
}
|
|
|
|
if len(i.RepoDigests) != 1 ||
|
|
i.RepoDigests[0] != "gcr.io:443/google_containers/pause@sha256:bbeaef1d40778579b7b86543fe03e1ec041428a50d21f7a7b25630e357ec9247" {
|
|
return fmt.Errorf("Image RepoDigests is wrong: %v", i.RepoDigests)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
CheckDestroy: func(state *terraform.State) error {
|
|
return testAccDockerImageDestroy(ctx, state)
|
|
},
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAddDockerPrivateImageConfig"),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.foobar", "latest", contentDigestRegexp),
|
|
resource.TestMatchResourceAttr("docker_image.foobar", "repo_digest", imageRepoDigestRegexp),
|
|
testAccImageCreated("docker_image.foobar", &i),
|
|
testCheckImageInspect,
|
|
),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestAccDockerImage_destroy(t *testing.T) {
|
|
ctx := context.Background()
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
CheckDestroy: func(s *terraform.State) error {
|
|
for _, rs := range s.RootModule().Resources {
|
|
if rs.Type != "docker_image" {
|
|
continue
|
|
}
|
|
|
|
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
|
|
_, _, err := client.ImageInspectWithRaw(ctx, rs.Primary.Attributes["name"])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageKeepLocallyConfig"),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.foobarzoo", "latest", contentDigestRegexp),
|
|
resource.TestMatchResourceAttr("docker_image.foobarzoo", "repo_digest", imageRepoDigestRegexp),
|
|
),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestAccDockerImage_data(t *testing.T) {
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
PreventPostDestroyRefresh: true,
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageFromDataConfig"),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.foobarbaz", "latest", contentDigestRegexp),
|
|
resource.TestMatchResourceAttr("docker_image.foobarbaz", "repo_digest", imageRepoDigestRegexp),
|
|
),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestAccDockerImage_data_pull_trigger(t *testing.T) {
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
PreventPostDestroyRefresh: true,
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageFromDataConfigWithPullTrigger"),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.foobarbazoo", "latest", contentDigestRegexp),
|
|
resource.TestMatchResourceAttr("docker_image.foobarbazoo", "repo_digest", imageRepoDigestRegexp),
|
|
),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestAccDockerImage_data_private(t *testing.T) {
|
|
registry := "127.0.0.1:15000"
|
|
image := "127.0.0.1:15000/tftest-service:v1"
|
|
ctx := context.Background()
|
|
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
PreventPostDestroyRefresh: true,
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageFromDataPrivateConfig"), registry, image),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.foo_private", "latest", contentDigestRegexp),
|
|
resource.TestMatchResourceAttr("docker_image.foo_private", "repo_digest", imageRepoDigestRegexp),
|
|
),
|
|
},
|
|
},
|
|
CheckDestroy: func(state *terraform.State) error {
|
|
return checkAndRemoveImages(ctx, state)
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestAccDockerImage_data_private_config_file(t *testing.T) {
|
|
registry := "127.0.0.1:15000"
|
|
image := "127.0.0.1:15000/tftest-service:v1"
|
|
wd, _ := os.Getwd()
|
|
dockerConfig := strings.ReplaceAll(filepath.Join(wd, "..", "..", "scripts", "testing", "dockerconfig.json"), "\\", "\\\\")
|
|
ctx := context.Background()
|
|
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
PreventPostDestroyRefresh: true,
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageFromDataPrivateConfigFile"), registry, dockerConfig, image),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.foo_private", "latest", contentDigestRegexp),
|
|
resource.TestMatchResourceAttr("docker_image.foo_private", "repo_digest", imageRepoDigestRegexp),
|
|
),
|
|
},
|
|
},
|
|
CheckDestroy: func(state *terraform.State) error {
|
|
return checkAndRemoveImages(ctx, state)
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestAccDockerImage_data_private_config_file_content(t *testing.T) {
|
|
registry := "127.0.0.1:15000"
|
|
image := "127.0.0.1:15000/tftest-service:v1"
|
|
wd, _ := os.Getwd()
|
|
dockerConfig := strings.ReplaceAll(filepath.Join(wd, "..", "..", "scripts", "testing", "dockerconfig.json"), "\\", "\\\\")
|
|
ctx := context.Background()
|
|
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
PreventPostDestroyRefresh: true,
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageFromDataPrivateConfigFileContent"), registry, dockerConfig, image),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.foo_private", "latest", contentDigestRegexp),
|
|
resource.TestMatchResourceAttr("docker_image.foo_private", "repo_digest", imageRepoDigestRegexp),
|
|
),
|
|
},
|
|
},
|
|
CheckDestroy: func(state *terraform.State) error {
|
|
return checkAndRemoveImages(ctx, state)
|
|
},
|
|
})
|
|
}
|
|
|
|
// Changing the name attribute should also force a change of the dependent docker container
|
|
// This test fails, if we remove the ForceTrue: true from the name attribute
|
|
func TestAccDockerImage_name_attr_change(t *testing.T) {
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
PreventPostDestroyRefresh: true,
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageName"), "ubuntu:precise@sha256:18305429afa14ea462f810146ba44d4363ae76e4c8dfc38288cf73aa07485005"),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.ubuntu", "latest", contentDigestRegexp),
|
|
resource.TestMatchResourceAttr("docker_image.ubuntu", "repo_digest", imageRepoDigestRegexp),
|
|
),
|
|
},
|
|
{
|
|
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageName"), "ubuntu:jammy@sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac"),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.ubuntu", "latest", contentDigestRegexp),
|
|
resource.TestMatchResourceAttr("docker_image.ubuntu", "repo_digest", imageRepoDigestRegexp),
|
|
),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestAccDockerImage_sha265(t *testing.T) {
|
|
ctx := context.Background()
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
CheckDestroy: func(state *terraform.State) error {
|
|
return testAccDockerImageDestroy(ctx, state)
|
|
},
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAddDockerImageWithSHA256RepoDigest"),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.foobar", "latest", contentDigestRegexp),
|
|
resource.TestMatchResourceAttr("docker_image.foobar", "repo_digest", imageRepoDigestRegexp),
|
|
),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func testAccDockerImageDestroy(ctx context.Context, s *terraform.State) error {
|
|
for _, rs := range s.RootModule().Resources {
|
|
if rs.Type != "docker_image" {
|
|
continue
|
|
}
|
|
|
|
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
|
|
_, _, err := client.ImageInspectWithRaw(ctx, rs.Primary.Attributes["name"])
|
|
if err == nil {
|
|
return fmt.Errorf("Image still exists")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestAccDockerImage_tag_sha265(t *testing.T) {
|
|
ctx := context.Background()
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
CheckDestroy: func(state *terraform.State) error {
|
|
return testAccDockerImageDestroy(ctx, state)
|
|
},
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageWithTagAndSHA256RepoDigest"),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.nginx", "latest", contentDigestRegexp),
|
|
resource.TestMatchResourceAttr("docker_image.nginx", "repo_digest", imageRepoDigestRegexp),
|
|
),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestAccDockerImage_build(t *testing.T) {
|
|
ctx := context.Background()
|
|
wd, _ := os.Getwd()
|
|
dfPath := filepath.Join(wd, "Dockerfile")
|
|
if err := ioutil.WriteFile(dfPath, []byte(testDockerFileExample), 0o644); err != nil {
|
|
t.Fatalf("failed to create a Dockerfile %s for test: %+v", dfPath, err)
|
|
}
|
|
defer os.Remove(dfPath)
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
CheckDestroy: func(state *terraform.State) error {
|
|
return testAccDockerImageDestroy(ctx, state)
|
|
},
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testCreateDockerImage"),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.test", "name", contentDigestRegexp),
|
|
),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
const testDockerFileExample = `
|
|
FROM python:3-stretch
|
|
|
|
WORKDIR /app
|
|
|
|
ARG test_arg
|
|
|
|
RUN echo ${test_arg} > test_arg.txt
|
|
|
|
RUN apt-get update -qq
|
|
`
|
|
|
|
// Test for implementation of https://github.com/kreuzwerker/terraform-provider-docker/issues/401
|
|
func TestAccDockerImage_buildOutsideContext(t *testing.T) {
|
|
ctx := context.Background()
|
|
wd, _ := os.Getwd()
|
|
dfPath := filepath.Join(wd, "..", "Dockerfile")
|
|
if err := ioutil.WriteFile(dfPath, []byte(testDockerFileExample), 0o644); err != nil {
|
|
t.Fatalf("failed to create a Dockerfile %s for test: %+v", dfPath, err)
|
|
}
|
|
defer os.Remove(dfPath)
|
|
resource.Test(t, resource.TestCase{
|
|
PreCheck: func() { testAccPreCheck(t) },
|
|
ProviderFactories: providerFactories,
|
|
CheckDestroy: func(state *terraform.State) error {
|
|
return testAccDockerImageDestroy(ctx, state)
|
|
},
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testDockerImageDockerfileOutsideContext"),
|
|
Check: resource.ComposeTestCheckFunc(
|
|
resource.TestMatchResourceAttr("docker_image.outside_context", "name", regexp.MustCompile(`\Aoutside-context:latest\z`)),
|
|
),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func testAccImageCreated(resourceName string, image *types.ImageInspect) resource.TestCheckFunc {
|
|
return func(s *terraform.State) error {
|
|
ctx := context.Background()
|
|
rs, ok := s.RootModule().Resources[resourceName]
|
|
if !ok {
|
|
return fmt.Errorf("Resource with name '%s' not found in state", resourceName)
|
|
}
|
|
|
|
if rs.Primary.ID == "" {
|
|
return fmt.Errorf("No ID is set")
|
|
}
|
|
|
|
name := rs.Primary.Attributes["name"]
|
|
// TODO mavogel: it's because we set the ID in the format:
|
|
// d.SetId(foundImage.ID + d.Get("name").(string))
|
|
// so we need to strip away the name
|
|
strippedID := strings.Replace(rs.Primary.ID, name, "", -1)
|
|
|
|
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
|
|
inspectedImage, _, err := client.ImageInspectWithRaw(ctx, strippedID)
|
|
if err != nil {
|
|
return fmt.Errorf("Image with ID '%s': %w", strippedID, err)
|
|
}
|
|
|
|
// we set the value to the pointer to be able to use the value
|
|
// outside of the function
|
|
*image = inspectedImage
|
|
return nil
|
|
|
|
}
|
|
}
|
|
|
|
func TestParseImageOptions(t *testing.T) {
|
|
t.Run("Should parse image name with registry", func(t *testing.T) {
|
|
expected := internalPullImageOptions{Registry: "registry.com", Repository: "image", Tag: "tag"}
|
|
result := parseImageOptions("registry.com/image:tag")
|
|
if !reflect.DeepEqual(expected, result) {
|
|
t.Fatalf("Result %#v did not match expectation %#v", result, expected)
|
|
}
|
|
})
|
|
t.Run("Should parse image name with registryPort", func(t *testing.T) {
|
|
expected := internalPullImageOptions{Registry: "registry.com:8080", Repository: "image", Tag: "tag"}
|
|
result := parseImageOptions("registry.com:8080/image:tag")
|
|
if !reflect.DeepEqual(expected, result) {
|
|
t.Fatalf("Result %#v did not match expectation %#v", result, expected)
|
|
}
|
|
})
|
|
t.Run("Should parse image name with registry and proper repository", func(t *testing.T) {
|
|
expected := internalPullImageOptions{Registry: "registry.com", Repository: "repo/image", Tag: "tag"}
|
|
result := parseImageOptions("registry.com/repo/image:tag")
|
|
if !reflect.DeepEqual(expected, result) {
|
|
t.Fatalf("Result %#v did not match expectation %#v", result, expected)
|
|
}
|
|
})
|
|
t.Run("Should parse image with no tag", func(t *testing.T) {
|
|
expected := internalPullImageOptions{Registry: "registry.com", Repository: "repo/image", Tag: "latest"}
|
|
result := parseImageOptions("registry.com/repo/image")
|
|
if !reflect.DeepEqual(expected, result) {
|
|
t.Fatalf("Result %#v did not match expectation %#v", result, expected)
|
|
}
|
|
})
|
|
t.Run("Should parse image name without registry and default to docker registry", func(t *testing.T) {
|
|
expected := internalPullImageOptions{Registry: "registry-1.docker.io", Repository: "library/image", Tag: "tag"}
|
|
result := parseImageOptions("image:tag")
|
|
if !reflect.DeepEqual(expected, result) {
|
|
t.Fatalf("Result %#v did not match expectation %#v", result, expected)
|
|
}
|
|
})
|
|
}
|