mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-18 23:06:10 -05:00
feat: Add support for build-secrets (#604)
Enables passing of build-secrets through the 'secrets' block inside 'build'. The feature is only available when using Buildkit. Co-authored-by: Martin <Junkern@users.noreply.github.com>
This commit is contained in:
parent
cfcb6f2cf6
commit
64b95701e3
7 changed files with 153 additions and 2 deletions
|
|
@ -131,6 +131,7 @@ Optional:
|
|||
- `pull_parent` (Boolean) Attempt to pull the image even if an older image exists locally
|
||||
- `remote_context` (String) A Git repository URI or HTTP/HTTPS context URI
|
||||
- `remove` (Boolean) Remove intermediate containers after a successful build. Defaults to `true`.
|
||||
- `secrets` (Block List) Set build-time secrets (see [below for nested schema](#nestedblock--build--secrets))
|
||||
- `security_opt` (List of String) The security options
|
||||
- `session_id` (String) Set an ID for the build session
|
||||
- `shm_size` (Number) Size of /dev/shm in bytes. The size must be greater than 0
|
||||
|
|
@ -159,6 +160,19 @@ Optional:
|
|||
- `user_name` (String) the registry user name
|
||||
|
||||
|
||||
<a id="nestedblock--build--secrets"></a>
|
||||
### Nested Schema for `build.secrets`
|
||||
|
||||
Required:
|
||||
|
||||
- `id` (String) ID of the secret. By default, secrets are mounted to /run/secrets/<id>
|
||||
|
||||
Optional:
|
||||
|
||||
- `env` (String) Environment variable source of the secret
|
||||
- `src` (String) File source of the secret
|
||||
|
||||
|
||||
<a id="nestedblock--build--ulimit"></a>
|
||||
### Nested Schema for `build.ulimit`
|
||||
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -229,6 +229,7 @@ require (
|
|||
github.com/timonwong/loggercheck v0.9.4 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.8.3 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/ultraware/funlen v0.1.0 // indirect
|
||||
github.com/ultraware/whitespace v0.1.1 // indirect
|
||||
github.com/uudashr/gocognit v1.1.2 // indirect
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -1184,6 +1184,8 @@ github.com/tomarrell/wrapcheck/v2 v2.8.3 h1:5ov+Cbhlgi7s/a42BprYoxsr73CbdMUTzE3b
|
|||
github.com/tomarrell/wrapcheck/v2 v2.8.3/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo=
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw=
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI=
|
||||
github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4=
|
||||
|
|
|
|||
|
|
@ -88,6 +88,36 @@ func resourceDockerImage() *schema.Resource {
|
|||
Default: true,
|
||||
Optional: true,
|
||||
},
|
||||
"secrets": {
|
||||
Type: schema.TypeList,
|
||||
Description: "Set build-time secrets",
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"id": {
|
||||
Type: schema.TypeString,
|
||||
Description: "ID of the secret. By default, secrets are mounted to /run/secrets/<id>",
|
||||
Optional: false,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"src": {
|
||||
Type: schema.TypeString,
|
||||
Description: "File source of the secret",
|
||||
Optional: true,
|
||||
Required: false,
|
||||
ForceNew: true,
|
||||
},
|
||||
"env": {
|
||||
Type: schema.TypeString,
|
||||
Description: "Environment variable source of the secret",
|
||||
Optional: true,
|
||||
Required: false,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"label": {
|
||||
Type: schema.TypeMap,
|
||||
Description: "Set metadata for an image",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
|
@ -333,7 +334,22 @@ func buildDockerImage(ctx context.Context, rawBuild map[string]interface{}, imag
|
|||
|
||||
buildContext := rawBuild["context"].(string)
|
||||
|
||||
enableBuildKitIfSupported(ctx, client, &buildOptions)
|
||||
buildKitSession := enableBuildKitIfSupported(ctx, client, &buildOptions)
|
||||
|
||||
// If Buildkit is enabled, try to parse and use secrets if present.
|
||||
if buildKitSession != nil {
|
||||
if secretsRaw, secretsDefined := rawBuild["secrets"]; secretsDefined {
|
||||
parsedSecrets := parseBuildSecrets(secretsRaw)
|
||||
|
||||
store, err := secretsprovider.NewStore(parsedSecrets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
provider := secretsprovider.NewSecretProvider(store)
|
||||
buildKitSession.Allow(provider)
|
||||
}
|
||||
}
|
||||
|
||||
buildCtx, relDockerfile, err := prepareBuildContext(buildContext, buildOptions.Dockerfile)
|
||||
if err != nil {
|
||||
|
|
@ -355,7 +371,11 @@ func buildDockerImage(ctx context.Context, rawBuild map[string]interface{}, imag
|
|||
return nil
|
||||
}
|
||||
|
||||
func enableBuildKitIfSupported(ctx context.Context, client *client.Client, buildOptions *types.ImageBuildOptions) {
|
||||
func enableBuildKitIfSupported(
|
||||
ctx context.Context,
|
||||
client *client.Client,
|
||||
buildOptions *types.ImageBuildOptions,
|
||||
) *session.Session {
|
||||
dockerClientVersion := client.ClientVersion()
|
||||
log.Printf("[DEBUG] DockerClientVersion: %v, minBuildKitDockerVersion: %v\n", dockerClientVersion, minBuildkitDockerVersion)
|
||||
if versions.GreaterThanOrEqualTo(dockerClientVersion, minBuildkitDockerVersion) {
|
||||
|
|
@ -369,8 +389,10 @@ func enableBuildKitIfSupported(ctx context.Context, client *client.Client, build
|
|||
defer s.Close()
|
||||
buildOptions.SessionID = s.ID()
|
||||
buildOptions.Version = types.BuilderBuildKit
|
||||
return s
|
||||
} else {
|
||||
buildOptions.Version = types.BuilderV1
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -461,3 +483,20 @@ func decodeBuildMessages(response types.ImageBuildResponse) (string, error) {
|
|||
|
||||
return buf.String(), buildErr
|
||||
}
|
||||
|
||||
func parseBuildSecrets(secretsRaw interface{}) []secretsprovider.Source {
|
||||
options := secretsRaw.([]interface{})
|
||||
|
||||
secrets := make([]secretsprovider.Source, len(options))
|
||||
for i, option := range options {
|
||||
secretRaw := option.(map[string]interface{})
|
||||
source := secretsprovider.Source{
|
||||
ID: secretRaw["id"].(string),
|
||||
FilePath: secretRaw["src"].(string),
|
||||
Env: secretRaw["env"].(string),
|
||||
}
|
||||
secrets[i] = source
|
||||
}
|
||||
|
||||
return secrets
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,6 +449,53 @@ func TestAccDockerImage_build(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccDockerImageSecrets_build(t *testing.T) {
|
||||
const testDockerFileWithSecret = `
|
||||
FROM python:3-bookworm
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ARG test_arg
|
||||
|
||||
RUN echo ${test_arg} > test_arg.txt
|
||||
|
||||
RUN --mount=type=secret,id=TEST_SECRET_SRC \
|
||||
--mount=type=secret,id=TEST_SECRET_ENV \
|
||||
apt-get update -qq`
|
||||
|
||||
ctx := context.Background()
|
||||
wd, _ := os.Getwd()
|
||||
dfPath := filepath.Join(wd, "Dockerfile")
|
||||
if err := os.WriteFile(dfPath, []byte(testDockerFileWithSecret), 0o644); err != nil {
|
||||
t.Fatalf("failed to create a Dockerfile %s for test: %+v", dfPath, err)
|
||||
}
|
||||
defer os.Remove(dfPath)
|
||||
|
||||
const secretContent = "THIS IS A SECRET"
|
||||
sPath := filepath.Join(wd, "secret")
|
||||
if err := os.WriteFile(sPath, []byte(secretContent), 0o644); err != nil {
|
||||
t.Fatalf("failed to create a secret file %s for test: %+v", sPath, err)
|
||||
}
|
||||
|
||||
defer os.Remove(sPath)
|
||||
|
||||
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", "testDockerImageBuildSecrets"),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestMatchResourceAttr("docker_image.test", "name", contentDigestRegexp),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const testDockerFileExample = `
|
||||
FROM python:3-bookworm
|
||||
|
||||
|
|
|
|||
18
testdata/resources/docker_image/testDockerImageBuildSecrets.tf
vendored
Normal file
18
testdata/resources/docker_image/testDockerImageBuildSecrets.tf
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
resource "docker_image" "test" {
|
||||
name = "ubuntu:11"
|
||||
build {
|
||||
context = "."
|
||||
dockerfile = "Dockerfile"
|
||||
force_remove = true
|
||||
|
||||
secrets {
|
||||
id = "TEST_SECRET_SRC"
|
||||
src = "./secret"
|
||||
}
|
||||
|
||||
secrets {
|
||||
id = "TEST_SECRET_ENV"
|
||||
env = "PATH"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue