Skip to content

Commit

Permalink
feat: Add support for build-secrets
Browse files Browse the repository at this point in the history
Enables passing of build-secrets through the 'secrets' block inside
'build'.
The feature is only available when using Buildkit.
  • Loading branch information
emanuel-skrenkovic committed May 17, 2024
1 parent 3e9c4a5 commit 10c45ad
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 2 deletions.
14 changes: 14 additions & 0 deletions docs/resources/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,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
Expand Down Expand Up @@ -160,6 +161,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`

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ require (
github.com/timonwong/loggercheck v0.9.3 // indirect
github.com/tomarrell/wrapcheck/v2 v2.6.2 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.0 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
github.com/ultraware/funlen v0.0.3 // indirect
github.com/ultraware/whitespace v0.0.5 // indirect
github.com/uudashr/gocognit v1.0.6 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,8 @@ github.com/tomarrell/wrapcheck/v2 v2.6.2 h1:3dI6YNcrJTQ/CJQ6M/DUkc0gnqYSIk6o0rCh
github.com/tomarrell/wrapcheck/v2 v2.6.2/go.mod h1:ao7l5p0aOlUNJKI0qVwB4Yjlqutd0IvAB9Rdwyilxvg=
github.com/tommy-muehle/go-mnd/v2 v2.5.0 h1:iAj0a8e6+dXSL7Liq0aXPox36FiN1dBbjA6lt9fl65s=
github.com/tommy-muehle/go-mnd/v2 v2.5.0/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.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA=
github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
Expand Down
30 changes: 30 additions & 0 deletions internal/provider/resource_docker_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,36 @@ func resourceDockerImage() *schema.Resource {
},
ForceNew: 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",
Expand Down
43 changes: 41 additions & 2 deletions internal/provider/resource_docker_image_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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 {
Expand All @@ -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) {
Expand All @@ -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
}
}

Expand Down Expand Up @@ -452,3 +474,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
}
47 changes: 47 additions & 0 deletions internal/provider/resource_docker_image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,53 @@ func TestAccDockerImage_build(t *testing.T) {
})
}

func TestAccDockerImageSecrets_build(t *testing.T) {
const testDockerFileWithSecret = `
FROM python:3-stretch
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 := ioutil.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 := ioutil.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
Expand Down
18 changes: 18 additions & 0 deletions testdata/resources/docker_image/testDockerImageBuildSecrets.tf
Original file line number Diff line number Diff line change
@@ -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"
}
}
}

0 comments on commit 10c45ad

Please sign in to comment.