mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-24 00:29:46 -05:00
fix: Enables having a Dockerfile outside the context (#402)
* tests: Add failing test for Dockerfile outside context. * fix: Enable support for Dockerfiles outside context. Also extract some code to functions. * fix: Adding docker outside context test and implementation for docker_registry_image.
This commit is contained in:
parent
6ac477b3c1
commit
78c42d7657
6 changed files with 155 additions and 27 deletions
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -22,6 +23,7 @@ import (
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/moby/buildkit/session"
|
"github.com/moby/buildkit/session"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const minBuildkitDockerVersion = "1.39"
|
const minBuildkitDockerVersion = "1.39"
|
||||||
|
|
@ -293,6 +295,10 @@ func findImage(ctx context.Context, imageName string, client *client.Client, aut
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDockerImage(ctx context.Context, rawBuild map[string]interface{}, imageName string, client *client.Client) error {
|
func buildDockerImage(ctx context.Context, rawBuild map[string]interface{}, imageName string, client *client.Client) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
buildOptions := types.ImageBuildOptions{}
|
buildOptions := types.ImageBuildOptions{}
|
||||||
|
|
||||||
buildOptions.Dockerfile = rawBuild["dockerfile"].(string)
|
buildOptions.Dockerfile = rawBuild["dockerfile"].(string)
|
||||||
|
|
@ -323,11 +329,32 @@ func buildDockerImage(ctx context.Context, rawBuild map[string]interface{}, imag
|
||||||
buildOptions.Labels = labels
|
buildOptions.Labels = labels
|
||||||
log.Printf("[DEBUG] Labels: %v\n", labels)
|
log.Printf("[DEBUG] Labels: %v\n", labels)
|
||||||
|
|
||||||
|
enableBuildKitIfSupported(ctx, client, &buildOptions)
|
||||||
|
|
||||||
|
buildCtx, relDockerfile, err := prepareBuildContext(rawBuild["path"].(string), buildOptions.Dockerfile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buildOptions.Dockerfile = relDockerfile
|
||||||
|
|
||||||
|
var response types.ImageBuildResponse
|
||||||
|
response, err = client.ImageBuild(ctx, buildCtx, buildOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
buildResult, err := decodeBuildMessages(response)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s\n\n%s", err, buildResult)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableBuildKitIfSupported(ctx context.Context, client *client.Client, buildOptions *types.ImageBuildOptions) {
|
||||||
dockerClientVersion := client.ClientVersion()
|
dockerClientVersion := client.ClientVersion()
|
||||||
log.Printf("[DEBUG] DockerClientVersion: %v, minBuildKitDockerVersion: %v\n", dockerClientVersion, minBuildkitDockerVersion)
|
log.Printf("[DEBUG] DockerClientVersion: %v, minBuildKitDockerVersion: %v\n", dockerClientVersion, minBuildkitDockerVersion)
|
||||||
|
|
||||||
if versions.GreaterThanOrEqualTo(dockerClientVersion, minBuildkitDockerVersion) {
|
if versions.GreaterThanOrEqualTo(dockerClientVersion, minBuildkitDockerVersion) {
|
||||||
// docker client supports BuildKit
|
|
||||||
log.Printf("[DEBUG] Enabling BuildKit")
|
log.Printf("[DEBUG] Enabling BuildKit")
|
||||||
s, _ := session.NewSession(ctx, "docker-provider", "")
|
s, _ := session.NewSession(ctx, "docker-provider", "")
|
||||||
dialSession := func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
dialSession := func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
||||||
|
|
@ -341,28 +368,50 @@ func buildDockerImage(ctx context.Context, rawBuild map[string]interface{}, imag
|
||||||
} else {
|
} else {
|
||||||
buildOptions.Version = types.BuilderV1
|
buildOptions.Version = types.BuilderV1
|
||||||
}
|
}
|
||||||
contextDir := rawBuild["path"].(string)
|
|
||||||
excludes, err := build.ReadDockerignore(contextDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
excludes = build.TrimBuildFilesFromExcludes(excludes, buildOptions.Dockerfile, false)
|
|
||||||
|
|
||||||
var response types.ImageBuildResponse
|
|
||||||
response, err = client.ImageBuild(ctx, getBuildContext(contextDir, excludes), buildOptions)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
buildResult, err := decodeBuildMessages(response)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s\n\n%s", err, buildResult)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBuildContext(filePath string, excludes []string) io.Reader {
|
func prepareBuildContext(specifiedContext string, specifiedDockerfile string) (io.ReadCloser, string, error) {
|
||||||
|
var (
|
||||||
|
dockerfileCtx io.ReadCloser
|
||||||
|
contextDir string
|
||||||
|
relDockerfile string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, specifiedDockerfile)
|
||||||
|
log.Printf("[DEBUG] contextDir %s", contextDir)
|
||||||
|
log.Printf("[DEBUG] relDockerfile %s", relDockerfile)
|
||||||
|
if err == nil && strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
|
||||||
|
// Dockerfile is outside of build-context; read the Dockerfile and pass it as dockerfileCtx
|
||||||
|
log.Printf("[DEBUG] Dockerfile is outside of build-context")
|
||||||
|
dockerfileCtx, err = os.Open(specifiedDockerfile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", errors.Errorf("unable to open Dockerfile: %v", err)
|
||||||
|
}
|
||||||
|
defer dockerfileCtx.Close()
|
||||||
|
}
|
||||||
|
excludes, err := build.ReadDockerignore(contextDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
specifiedDockerfile = archive.CanonicalTarNameForPath(specifiedDockerfile)
|
||||||
|
excludes = build.TrimBuildFilesFromExcludes(excludes, specifiedDockerfile, false)
|
||||||
|
log.Printf("[DEBUG] Excludes: %v", excludes)
|
||||||
|
buildCtx := getBuildContext(contextDir, excludes)
|
||||||
|
|
||||||
|
// replace Dockerfile if it was added from stdin or a file outside the build-context, and there is archive context
|
||||||
|
if dockerfileCtx != nil && buildCtx != nil {
|
||||||
|
log.Printf("[DEBUG] Adding dockerfile to build context")
|
||||||
|
buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return buildCtx, relDockerfile, nil
|
||||||
|
}
|
||||||
|
return buildCtx, specifiedDockerfile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBuildContext(filePath string, excludes []string) io.ReadCloser {
|
||||||
filePath, _ = homedir.Expand(filePath)
|
filePath, _ = homedir.Expand(filePath)
|
||||||
//TarWithOptions works only with absolute paths in Windows.
|
//TarWithOptions works only with absolute paths in Windows.
|
||||||
filePath, err := filepath.Abs(filePath)
|
filePath, err := filepath.Abs(filePath)
|
||||||
|
|
|
||||||
|
|
@ -324,6 +324,32 @@ RUN echo ${test_arg} > test_arg.txt
|
||||||
RUN apt-get update -qq
|
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 {
|
func testAccImageCreated(resourceName string, image *types.ImageInspect) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
|
||||||
|
|
@ -248,13 +248,15 @@ func buildDockerRegistryImage(ctx context.Context, client *client.Client, buildO
|
||||||
buildContext = buildContext[:lastIndex]
|
buildContext = buildContext[:lastIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
excludes, err := build.ReadDockerignore(buildContext)
|
enableBuildKitIfSupported(ctx, client, &imageBuildOptions)
|
||||||
|
|
||||||
|
buildCtx, relDockerfile, err := prepareBuildContext(buildContext, imageBuildOptions.Dockerfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to read dockerignore: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
excludes = build.TrimBuildFilesFromExcludes(excludes, imageBuildOptions.Dockerfile, false)
|
imageBuildOptions.Dockerfile = relDockerfile
|
||||||
log.Printf("[DEBUG] Excludes: %v", excludes)
|
|
||||||
buildResponse, err := client.ImageBuild(ctx, getBuildContext(buildContext, excludes), imageBuildOptions)
|
buildResponse, err := client.ImageBuild(ctx, buildCtx, imageBuildOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to build image for docker_registry_image: %v", err)
|
return fmt.Errorf("unable to build image for docker_registry_image: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package provider
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
@ -249,6 +250,30 @@ func TestAccDockerRegistryImageResource_whitelistDockerignore(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test for https://github.com/kreuzwerker/terraform-provider-docker/issues/249
|
||||||
|
func TestAccDockerRegistryImageResource_DockerfileOutsideContext(t *testing.T) {
|
||||||
|
pushOptions := createPushImageOptions("127.0.0.1:15000/tftest-dockerregistryimage-dockerfileoutsidecontext:1.0")
|
||||||
|
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)
|
||||||
|
context := strings.ReplaceAll((filepath.Join(wd, "..", "..", "scripts", "testing", "docker_registry_image_file_permissions")), "\\", "\\\\")
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
ProviderFactories: providerFactories,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_registry_image", "testDockerRegistryImageDockerfileOutsideContext"), pushOptions.Registry, pushOptions.Name, context),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttrSet("docker_registry_image.outside_context", "sha256_digest"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccDockerRegistryImageResource_pushMissingImage(t *testing.T) {
|
func TestAccDockerRegistryImageResource_pushMissingImage(t *testing.T) {
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
|
|
||||||
9
testdata/resources/docker_image/testDockerImageDockerfileOutsideContext.tf
vendored
Normal file
9
testdata/resources/docker_image/testDockerImageDockerfileOutsideContext.tf
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
resource "docker_image" "outside_context" {
|
||||||
|
name = "outside-context:latest"
|
||||||
|
|
||||||
|
build {
|
||||||
|
path = "."
|
||||||
|
dockerfile = "../Dockerfile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
17
testdata/resources/docker_registry_image/testDockerRegistryImageDockerfileOutsideContext.tf
vendored
Normal file
17
testdata/resources/docker_registry_image/testDockerRegistryImageDockerfileOutsideContext.tf
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
provider "docker" {
|
||||||
|
alias = "private"
|
||||||
|
registry_auth {
|
||||||
|
address = "%s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "docker_registry_image" "outside_context" {
|
||||||
|
provider = "docker.private"
|
||||||
|
name = "%s"
|
||||||
|
insecure_skip_verify = true
|
||||||
|
|
||||||
|
build {
|
||||||
|
context = "%s"
|
||||||
|
dockerfile = "../Dockerfile"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue