mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-20 22:59:42 -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"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
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 {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
buildOptions := types.ImageBuildOptions{}
|
||||
|
||||
buildOptions.Dockerfile = rawBuild["dockerfile"].(string)
|
||||
|
|
@ -323,11 +329,32 @@ func buildDockerImage(ctx context.Context, rawBuild map[string]interface{}, imag
|
|||
buildOptions.Labels = 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()
|
||||
log.Printf("[DEBUG] DockerClientVersion: %v, minBuildKitDockerVersion: %v\n", dockerClientVersion, minBuildkitDockerVersion)
|
||||
|
||||
if versions.GreaterThanOrEqualTo(dockerClientVersion, minBuildkitDockerVersion) {
|
||||
// docker client supports BuildKit
|
||||
log.Printf("[DEBUG] Enabling BuildKit")
|
||||
s, _ := session.NewSession(ctx, "docker-provider", "")
|
||||
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 {
|
||||
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)
|
||||
//TarWithOptions works only with absolute paths in Windows.
|
||||
filePath, err := filepath.Abs(filePath)
|
||||
|
|
|
|||
|
|
@ -324,6 +324,32 @@ 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()
|
||||
|
|
|
|||
|
|
@ -248,13 +248,15 @@ func buildDockerRegistryImage(ctx context.Context, client *client.Client, buildO
|
|||
buildContext = buildContext[:lastIndex]
|
||||
}
|
||||
|
||||
excludes, err := build.ReadDockerignore(buildContext)
|
||||
enableBuildKitIfSupported(ctx, client, &imageBuildOptions)
|
||||
|
||||
buildCtx, relDockerfile, err := prepareBuildContext(buildContext, imageBuildOptions.Dockerfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read dockerignore: %v", err)
|
||||
return err
|
||||
}
|
||||
excludes = build.TrimBuildFilesFromExcludes(excludes, imageBuildOptions.Dockerfile, false)
|
||||
log.Printf("[DEBUG] Excludes: %v", excludes)
|
||||
buildResponse, err := client.ImageBuild(ctx, getBuildContext(buildContext, excludes), imageBuildOptions)
|
||||
imageBuildOptions.Dockerfile = relDockerfile
|
||||
|
||||
buildResponse, err := client.ImageBuild(ctx, buildCtx, imageBuildOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to build image for docker_registry_image: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package provider
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
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