mirror of
https://github.com/coder/terraform-provider-envbuilder.git
synced 2025-08-19 17:47:10 +00:00
fix(internal/provider): correctly override from extra_env (#44)
Relates to #43 Our previous logic did not pass options from extra_env to envbuilder.RunCacheProbe. This fixes the logic and adds more comprehensive tests around the overriding logic. Future commits will refactor this logic some more.
This commit is contained in:
parent
e35030b39f
commit
23f2cf5f48
4 changed files with 737 additions and 163 deletions
|
@ -16,12 +16,14 @@ import (
|
||||||
"github.com/coder/envbuilder/constants"
|
"github.com/coder/envbuilder/constants"
|
||||||
eblog "github.com/coder/envbuilder/log"
|
eblog "github.com/coder/envbuilder/log"
|
||||||
eboptions "github.com/coder/envbuilder/options"
|
eboptions "github.com/coder/envbuilder/options"
|
||||||
|
"github.com/coder/serpent"
|
||||||
"github.com/go-git/go-billy/v5/osfs"
|
"github.com/go-git/go-billy/v5/osfs"
|
||||||
"github.com/google/go-containerregistry/pkg/authn"
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||||
|
@ -293,128 +295,246 @@ func (r *CachedImageResource) Configure(ctx context.Context, req resource.Config
|
||||||
r.client = client
|
r.client = client
|
||||||
}
|
}
|
||||||
|
|
||||||
// setComputedEnv sets data.Env and data.EnvMap based on the values of the
|
func optionsFromDataModel(data CachedImageResourceModel) (eboptions.Options, diag.Diagnostics) {
|
||||||
// other fields in the model.
|
var diags diag.Diagnostics
|
||||||
func (data *CachedImageResourceModel) setComputedEnv(ctx context.Context) diag.Diagnostics {
|
var opts eboptions.Options
|
||||||
env := make(map[string]string)
|
|
||||||
|
|
||||||
env["ENVBUILDER_CACHE_REPO"] = tfValueToString(data.CacheRepo)
|
// Required options. Cannot be overridden by extra_env.
|
||||||
env["ENVBUILDER_GIT_URL"] = tfValueToString(data.GitURL)
|
opts.CacheRepo = data.CacheRepo.ValueString()
|
||||||
|
opts.GitURL = data.GitURL.ValueString()
|
||||||
|
|
||||||
|
// Other options can be overridden by extra_env, with a warning.
|
||||||
|
// Keep track of which options are overridden.
|
||||||
|
overrides := make(map[string]struct{})
|
||||||
|
|
||||||
if !data.BaseImageCacheDir.IsNull() {
|
if !data.BaseImageCacheDir.IsNull() {
|
||||||
env["ENVBUILDER_BASE_IMAGE_CACHE_DIR"] = tfValueToString(data.BaseImageCacheDir)
|
overrides["ENVBUILDER_BASE_IMAGE_CACHE_DIR"] = struct{}{}
|
||||||
|
opts.BaseImageCacheDir = data.BaseImageCacheDir.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.BuildContextPath.IsNull() {
|
if !data.BuildContextPath.IsNull() {
|
||||||
env["ENVBUILDER_BUILD_CONTEXT_PATH"] = tfValueToString(data.BuildContextPath)
|
overrides["ENVBUILDER_BUILD_CONTEXT_PATH"] = struct{}{}
|
||||||
|
opts.BuildContextPath = data.BuildContextPath.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.CacheTTLDays.IsNull() {
|
if !data.CacheTTLDays.IsNull() {
|
||||||
env["ENVBUILDER_CACHE_TTL_DAYS"] = tfValueToString(data.CacheTTLDays)
|
overrides["ENVBUILDER_CACHE_TTL_DAYS"] = struct{}{}
|
||||||
|
opts.CacheTTLDays = data.CacheTTLDays.ValueInt64()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.DevcontainerDir.IsNull() {
|
if !data.DevcontainerDir.IsNull() {
|
||||||
env["ENVBUILDER_DEVCONTAINER_DIR"] = tfValueToString(data.DevcontainerDir)
|
overrides["ENVBUILDER_DEVCONTAINER_DIR"] = struct{}{}
|
||||||
|
opts.DevcontainerDir = data.DevcontainerDir.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.DevcontainerJSONPath.IsNull() {
|
if !data.DevcontainerJSONPath.IsNull() {
|
||||||
env["ENVBUILDER_DEVCONTAINER_JSON_PATH"] = tfValueToString(data.DevcontainerJSONPath)
|
overrides["ENVBUILDER_DEVCONTAINER_JSON_PATH"] = struct{}{}
|
||||||
|
opts.DevcontainerJSONPath = data.DevcontainerJSONPath.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.DockerfilePath.IsNull() {
|
if !data.DockerfilePath.IsNull() {
|
||||||
env["ENVBUILDER_DOCKERFILE_PATH"] = tfValueToString(data.DockerfilePath)
|
overrides["ENVBUILDER_DOCKERFILE_PATH"] = struct{}{}
|
||||||
|
opts.DockerfilePath = data.DockerfilePath.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.DockerConfigBase64.IsNull() {
|
if !data.DockerConfigBase64.IsNull() {
|
||||||
env["ENVBUILDER_DOCKER_CONFIG_BASE64"] = tfValueToString(data.DockerConfigBase64)
|
overrides["ENVBUILDER_DOCKER_CONFIG_BASE64"] = struct{}{}
|
||||||
|
opts.DockerConfigBase64 = data.DockerConfigBase64.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.ExitOnBuildFailure.IsNull() {
|
if !data.ExitOnBuildFailure.IsNull() {
|
||||||
env["ENVBUILDER_EXIT_ON_BUILD_FAILURE"] = tfValueToString(data.ExitOnBuildFailure)
|
overrides["ENVBUILDER_EXIT_ON_BUILD_FAILURE"] = struct{}{}
|
||||||
|
opts.ExitOnBuildFailure = data.ExitOnBuildFailure.ValueBool()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.FallbackImage.IsNull() {
|
if !data.FallbackImage.IsNull() {
|
||||||
env["ENVBUILDER_FALLBACK_IMAGE"] = tfValueToString(data.FallbackImage)
|
overrides["ENVBUILDER_FALLBACK_IMAGE"] = struct{}{}
|
||||||
|
opts.FallbackImage = data.FallbackImage.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.GitCloneDepth.IsNull() {
|
if !data.GitCloneDepth.IsNull() {
|
||||||
env["ENVBUILDER_GIT_CLONE_DEPTH"] = tfValueToString(data.GitCloneDepth)
|
overrides["ENVBUILDER_GIT_CLONE_DEPTH"] = struct{}{}
|
||||||
|
opts.GitCloneDepth = data.GitCloneDepth.ValueInt64()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.GitCloneSingleBranch.IsNull() {
|
if !data.GitCloneSingleBranch.IsNull() {
|
||||||
env["ENVBUILDER_GIT_CLONE_SINGLE_BRANCH"] = tfValueToString(data.GitCloneSingleBranch)
|
overrides["ENVBUILDER_GIT_CLONE_SINGLE_BRANCH"] = struct{}{}
|
||||||
|
opts.GitCloneSingleBranch = data.GitCloneSingleBranch.ValueBool()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.GitHTTPProxyURL.IsNull() {
|
if !data.GitHTTPProxyURL.IsNull() {
|
||||||
env["ENVBUILDER_GIT_HTTP_PROXY_URL"] = tfValueToString(data.GitHTTPProxyURL)
|
overrides["ENVBUILDER_GIT_HTTP_PROXY_URL"] = struct{}{}
|
||||||
|
opts.GitHTTPProxyURL = data.GitHTTPProxyURL.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.GitSSHPrivateKeyPath.IsNull() {
|
if !data.GitSSHPrivateKeyPath.IsNull() {
|
||||||
env["ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH"] = tfValueToString(data.GitSSHPrivateKeyPath)
|
overrides["ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH"] = struct{}{}
|
||||||
|
opts.GitSSHPrivateKeyPath = data.GitSSHPrivateKeyPath.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.GitUsername.IsNull() {
|
if !data.GitUsername.IsNull() {
|
||||||
env["ENVBUILDER_GIT_USERNAME"] = tfValueToString(data.GitUsername)
|
overrides["ENVBUILDER_GIT_USERNAME"] = struct{}{}
|
||||||
|
opts.GitUsername = data.GitUsername.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.GitPassword.IsNull() {
|
if !data.GitPassword.IsNull() {
|
||||||
env["ENVBUILDER_GIT_PASSWORD"] = tfValueToString(data.GitPassword)
|
overrides["ENVBUILDER_GIT_PASSWORD"] = struct{}{}
|
||||||
|
opts.GitPassword = data.GitPassword.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.IgnorePaths.IsNull() {
|
if !data.IgnorePaths.IsNull() {
|
||||||
env["ENVBUILDER_IGNORE_PATHS"] = strings.Join(tfListToStringSlice(data.IgnorePaths), ",")
|
overrides["ENVBUILDER_IGNORE_PATHS"] = struct{}{}
|
||||||
|
opts.IgnorePaths = tfListToStringSlice(data.IgnorePaths)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.Insecure.IsNull() {
|
if !data.Insecure.IsNull() {
|
||||||
env["ENVBUILDER_INSECURE"] = tfValueToString(data.Insecure)
|
overrides["ENVBUILDER_INSECURE"] = struct{}{}
|
||||||
|
opts.Insecure = data.Insecure.ValueBool()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to remote build mode.
|
|
||||||
if data.RemoteRepoBuildMode.IsNull() {
|
if data.RemoteRepoBuildMode.IsNull() {
|
||||||
env["ENVBUILDER_REMOTE_REPO_BUILD_MODE"] = "true"
|
opts.RemoteRepoBuildMode = true
|
||||||
} else {
|
} else {
|
||||||
env["ENVBUILDER_REMOTE_REPO_BUILD_MODE"] = tfValueToString(data.RemoteRepoBuildMode)
|
overrides["ENVBUILDER_REMOTE_REPO_BUILD_MODE"] = struct{}{}
|
||||||
|
opts.RemoteRepoBuildMode = data.RemoteRepoBuildMode.ValueBool()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.SSLCertBase64.IsNull() {
|
if !data.SSLCertBase64.IsNull() {
|
||||||
env["ENVBUILDER_SSL_CERT_BASE64"] = tfValueToString(data.SSLCertBase64)
|
overrides["ENVBUILDER_SSL_CERT_BASE64"] = struct{}{}
|
||||||
|
opts.SSLCertBase64 = data.SSLCertBase64.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.Verbose.IsNull() {
|
if !data.Verbose.IsNull() {
|
||||||
env["ENVBUILDER_VERBOSE"] = tfValueToString(data.Verbose)
|
overrides["ENVBUILDER_VERBOSE"] = struct{}{}
|
||||||
|
opts.Verbose = data.Verbose.ValueBool()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.WorkspaceFolder.IsNull() {
|
if !data.WorkspaceFolder.IsNull() {
|
||||||
env["ENVBUILDER_WORKSPACE_FOLDER"] = tfValueToString(data.WorkspaceFolder)
|
overrides["ENVBUILDER_WORKSPACE_FOLDER"] = struct{}{}
|
||||||
|
opts.WorkspaceFolder = data.WorkspaceFolder.ValueString()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do ExtraEnv last so that it can override any other values.
|
// convert extraEnv to a map for ease of use.
|
||||||
// With one exception: ENVBUILDER_CACHE_REPO and ENVBUILDER_GIT_URL are required and should not be overridden.
|
extraEnv := make(map[string]string)
|
||||||
// Other values set by the provider may be overridden, but will generate a warning.
|
for k, v := range data.ExtraEnv.Elements() {
|
||||||
var diag, ds diag.Diagnostics
|
extraEnv[k] = tfValueToString(v)
|
||||||
if !data.ExtraEnv.IsNull() {
|
}
|
||||||
for key, elem := range data.ExtraEnv.Elements() {
|
diags = append(diags, overrideOptionsFromExtraEnv(&opts, extraEnv, overrides)...)
|
||||||
switch key {
|
|
||||||
// These are required and should not be overridden.
|
return opts, diags
|
||||||
case "ENVBUILDER_CACHE_REPO", "ENVBUILDER_GIT_URL":
|
}
|
||||||
diag.AddAttributeWarning(path.Root("extra_env"),
|
|
||||||
"Cannot override required environment variable",
|
func overrideOptionsFromExtraEnv(opts *eboptions.Options, extraEnv map[string]string, overrides map[string]struct{}) diag.Diagnostics {
|
||||||
fmt.Sprintf("The key %q in extra_env cannot be overridden.", key),
|
var diags diag.Diagnostics
|
||||||
|
// Make a map of the options for easy lookup.
|
||||||
|
optsMap := make(map[string]pflag.Value)
|
||||||
|
for _, opt := range opts.CLI() {
|
||||||
|
optsMap[opt.Env] = opt.Value
|
||||||
|
}
|
||||||
|
for key, val := range extraEnv {
|
||||||
|
switch key {
|
||||||
|
|
||||||
|
// These options may not be overridden.
|
||||||
|
case "ENVBUILDER_CACHE_REPO", "ENVBUILDER_GIT_URL":
|
||||||
|
diags.AddAttributeWarning(path.Root("extra_env"),
|
||||||
|
"Cannot override required environment variable",
|
||||||
|
fmt.Sprintf("The key %q in extra_env cannot be overridden.", key),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Check if the option was set on the provider data model and generate a warning if so.
|
||||||
|
if _, overridden := overrides[key]; overridden {
|
||||||
|
diags.AddAttributeWarning(path.Root("extra_env"),
|
||||||
|
"Overriding provider environment variable",
|
||||||
|
fmt.Sprintf("The key %q in extra_env overrides an option set on the provider.", key),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: workaround for serpent behaviour where calling Set() on a
|
||||||
|
// string slice will append instead of replace: set to empty first.
|
||||||
|
if key == "ENVBUILDER_IGNORE_PATHS" {
|
||||||
|
_ = optsMap[key].Set("")
|
||||||
|
}
|
||||||
|
|
||||||
|
opt, found := optsMap[key]
|
||||||
|
if !found {
|
||||||
|
// ignore unknown keys
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := opt.Set(val); err != nil {
|
||||||
|
diags.AddAttributeError(path.Root("extra_env"),
|
||||||
|
"Invalid value for environment variable",
|
||||||
|
fmt.Sprintf("The key %q in extra_env has an invalid value: %s", key, err),
|
||||||
)
|
)
|
||||||
default:
|
|
||||||
if _, ok := env[key]; ok {
|
|
||||||
// This is a warning because it's possible that the user wants to override
|
|
||||||
// a value set by the provider.
|
|
||||||
diag.AddAttributeWarning(path.Root("extra_env"),
|
|
||||||
"Overriding provider environment variable",
|
|
||||||
fmt.Sprintf("The key %q in extra_env overrides an environment variable set by the provider.", key),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
env[key] = tfValueToString(elem)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeEnvFromOptions(opts eboptions.Options, extraEnv map[string]string) map[string]string {
|
||||||
|
allEnvKeys := make(map[string]struct{})
|
||||||
|
for _, opt := range opts.CLI() {
|
||||||
|
if opt.Env == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
allEnvKeys[opt.Env] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only set the environment variables from opts that are not legacy options.
|
||||||
|
// Legacy options are those that are not prefixed with ENVBUILDER_.
|
||||||
|
// While we can detect when a legacy option is set, overriding it becomes
|
||||||
|
// problematic. Erring on the side of caution, we will not override legacy options.
|
||||||
|
isEnvbuilderOption := func(key string) bool {
|
||||||
|
switch key {
|
||||||
|
case "CODER_AGENT_URL", "CODER_AGENT_TOKEN", "CODER_AGENT_SUBSYSTEM":
|
||||||
|
return true // kinda
|
||||||
|
default:
|
||||||
|
return strings.HasPrefix(key, "ENVBUILDER_")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
computed := make(map[string]string)
|
||||||
|
for _, opt := range opts.CLI() {
|
||||||
|
if opt.Env == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO: remove this check once support for legacy options is removed.
|
||||||
|
if !isEnvbuilderOption(opt.Env) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var val string
|
||||||
|
if sa, ok := opt.Value.(*serpent.StringArray); ok {
|
||||||
|
val = strings.Join(sa.GetSlice(), ",")
|
||||||
|
} else {
|
||||||
|
val = opt.Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch val {
|
||||||
|
case "", "false", "0":
|
||||||
|
// Skip zero values.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
computed[opt.Env] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge in extraEnv, which may override values from opts.
|
||||||
|
// Skip any keys that are envbuilder options.
|
||||||
|
for key, val := range extraEnv {
|
||||||
|
if isEnvbuilderOption(key) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
computed[key] = val
|
||||||
|
}
|
||||||
|
return computed
|
||||||
|
}
|
||||||
|
|
||||||
|
// setComputedEnv sets data.Env and data.EnvMap based on the values of the
|
||||||
|
// other fields in the model.
|
||||||
|
func (data *CachedImageResourceModel) setComputedEnv(ctx context.Context, env map[string]string) diag.Diagnostics {
|
||||||
|
var diag, ds diag.Diagnostics
|
||||||
data.EnvMap, ds = basetypes.NewMapValueFrom(ctx, types.StringType, env)
|
data.EnvMap, ds = basetypes.NewMapValueFrom(ctx, types.StringType, env)
|
||||||
diag = append(diag, ds...)
|
diag = append(diag, ds...)
|
||||||
data.Env, ds = basetypes.NewListValueFrom(ctx, types.StringType, sortedKeyValues(env))
|
data.Env, ds = basetypes.NewListValueFrom(ctx, types.StringType, sortedKeyValues(env))
|
||||||
|
@ -431,6 +551,16 @@ func (r *CachedImageResource) Read(ctx context.Context, req resource.ReadRequest
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the options from the data model.
|
||||||
|
opts, diags := optionsFromDataModel(data)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Set the expected environment variables.
|
||||||
|
computedEnv := computeEnvFromOptions(opts, tfMapToStringMap(data.ExtraEnv))
|
||||||
|
resp.Diagnostics.Append(data.setComputedEnv(ctx, computedEnv)...)
|
||||||
|
|
||||||
// If the previous state is that Image == BuilderImage, then we previously did
|
// If the previous state is that Image == BuilderImage, then we previously did
|
||||||
// not find the image. We will need to run another cache probe.
|
// not find the image. We will need to run another cache probe.
|
||||||
if data.Image.Equal(data.BuilderImage) {
|
if data.Image.Equal(data.BuilderImage) {
|
||||||
|
@ -478,9 +608,6 @@ func (r *CachedImageResource) Read(ctx context.Context, req resource.ReadRequest
|
||||||
data.Image = types.StringValue(fmt.Sprintf("%s@%s", data.CacheRepo.ValueString(), digest))
|
data.Image = types.StringValue(fmt.Sprintf("%s@%s", data.CacheRepo.ValueString(), digest))
|
||||||
data.Exists = types.BoolValue(true)
|
data.Exists = types.BoolValue(true)
|
||||||
|
|
||||||
// Set the expected environment variables.
|
|
||||||
resp.Diagnostics.Append(data.setComputedEnv(ctx)...)
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,7 +621,18 @@ func (r *CachedImageResource) Create(ctx context.Context, req resource.CreateReq
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedImg, err := r.runCacheProbe(ctx, data)
|
// Get the options from the data model.
|
||||||
|
opts, diags := optionsFromDataModel(data)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the expected environment variables.
|
||||||
|
computedEnv := computeEnvFromOptions(opts, tfMapToStringMap(data.ExtraEnv))
|
||||||
|
resp.Diagnostics.Append(data.setComputedEnv(ctx, computedEnv)...)
|
||||||
|
|
||||||
|
cachedImg, err := runCacheProbe(ctx, data.BuilderImage.ValueString(), opts)
|
||||||
data.ID = types.StringValue(uuid.Nil.String())
|
data.ID = types.StringValue(uuid.Nil.String())
|
||||||
data.Exists = types.BoolValue(err == nil)
|
data.Exists = types.BoolValue(err == nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -517,9 +655,6 @@ func (r *CachedImageResource) Create(ctx context.Context, req resource.CreateReq
|
||||||
data.ID = types.StringValue(digest.String())
|
data.ID = types.StringValue(digest.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the expected environment variables.
|
|
||||||
resp.Diagnostics.Append(data.setComputedEnv(ctx)...)
|
|
||||||
|
|
||||||
// Save data into Terraform state
|
// Save data into Terraform state
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||||
}
|
}
|
||||||
|
@ -553,7 +688,7 @@ func (r *CachedImageResource) Delete(ctx context.Context, req resource.DeleteReq
|
||||||
// runCacheProbe performs a 'fake build' of the requested image and ensures that
|
// runCacheProbe performs a 'fake build' of the requested image and ensures that
|
||||||
// all of the resulting layers of the image are present in the configured cache
|
// all of the resulting layers of the image are present in the configured cache
|
||||||
// repo. Otherwise, returns an error.
|
// repo. Otherwise, returns an error.
|
||||||
func (r *CachedImageResource) runCacheProbe(ctx context.Context, data CachedImageResourceModel) (v1.Image, error) {
|
func runCacheProbe(ctx context.Context, builderImage string, opts eboptions.Options) (v1.Image, error) {
|
||||||
tmpDir, err := os.MkdirTemp(os.TempDir(), "envbuilder-provider-cached-image-data-source")
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "envbuilder-provider-cached-image-data-source")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create temp directory: %s", err.Error())
|
return nil, fmt.Errorf("unable to create temp directory: %s", err.Error())
|
||||||
|
@ -581,69 +716,53 @@ func (r *CachedImageResource) runCacheProbe(ctx context.Context, data CachedImag
|
||||||
// In order to correctly reproduce the final layer of the cached image, we
|
// In order to correctly reproduce the final layer of the cached image, we
|
||||||
// need the envbuilder binary used to originally build the image!
|
// need the envbuilder binary used to originally build the image!
|
||||||
envbuilderPath := filepath.Join(tmpDir, "envbuilder")
|
envbuilderPath := filepath.Join(tmpDir, "envbuilder")
|
||||||
if err := extractEnvbuilderFromImage(ctx, data.BuilderImage.ValueString(), envbuilderPath); err != nil {
|
if err := extractEnvbuilderFromImage(ctx, builderImage, envbuilderPath); err != nil {
|
||||||
tflog.Error(ctx, "failed to fetch envbuilder binary from builder image", map[string]any{"err": err})
|
tflog.Error(ctx, "failed to fetch envbuilder binary from builder image", map[string]any{"err": err})
|
||||||
return nil, fmt.Errorf("failed to fetch the envbuilder binary from the builder image: %s", err.Error())
|
return nil, fmt.Errorf("failed to fetch the envbuilder binary from the builder image: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
opts.BinaryPath = envbuilderPath
|
||||||
|
|
||||||
workspaceFolder := data.WorkspaceFolder.ValueString()
|
// We need a filesystem to work with.
|
||||||
if workspaceFolder == "" {
|
opts.Filesystem = osfs.New("/")
|
||||||
workspaceFolder = filepath.Join(tmpDir, "workspace")
|
// This should never be set to true, as this may be running outside of a container!
|
||||||
tflog.Debug(ctx, "workspace_folder not specified, using temp dir", map[string]any{"workspace_folder": workspaceFolder})
|
opts.ForceSafe = false
|
||||||
|
// We always want to get the cached image.
|
||||||
|
opts.GetCachedImage = true
|
||||||
|
// Log to the Terraform logger.
|
||||||
|
opts.Logger = tfLogFunc(ctx)
|
||||||
|
|
||||||
|
// We don't require users to set a workspace folder, but maybe there's a
|
||||||
|
// reason someone may need to.
|
||||||
|
if opts.WorkspaceFolder == "" {
|
||||||
|
opts.WorkspaceFolder = filepath.Join(tmpDir, "workspace")
|
||||||
|
if err := os.MkdirAll(opts.WorkspaceFolder, 0o755); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create workspace folder: %w", err)
|
||||||
|
}
|
||||||
|
tflog.Debug(ctx, "workspace_folder not specified, using temp dir", map[string]any{"workspace_folder": opts.WorkspaceFolder})
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := eboptions.Options{
|
// We need a place to clone the repo.
|
||||||
// These options are always required
|
repoDir := filepath.Join(tmpDir, "repo")
|
||||||
CacheRepo: data.CacheRepo.ValueString(),
|
if err := os.MkdirAll(repoDir, 0o755); err != nil {
|
||||||
Filesystem: osfs.New("/"),
|
return nil, fmt.Errorf("failed to create repo dir: %w", err)
|
||||||
ForceSafe: false, // This should never be set to true, as this may be running outside of a container!
|
|
||||||
GetCachedImage: true, // always!
|
|
||||||
Logger: tfLogFunc(ctx),
|
|
||||||
Verbose: data.Verbose.ValueBool(),
|
|
||||||
WorkspaceFolder: workspaceFolder,
|
|
||||||
|
|
||||||
// Options related to compiling the devcontainer
|
|
||||||
BuildContextPath: data.BuildContextPath.ValueString(),
|
|
||||||
DevcontainerDir: data.DevcontainerDir.ValueString(),
|
|
||||||
DevcontainerJSONPath: data.DevcontainerJSONPath.ValueString(),
|
|
||||||
DockerfilePath: data.DockerfilePath.ValueString(),
|
|
||||||
DockerConfigBase64: data.DockerConfigBase64.ValueString(),
|
|
||||||
FallbackImage: data.FallbackImage.ValueString(),
|
|
||||||
|
|
||||||
// These options are required for cloning the Git repo
|
|
||||||
CacheTTLDays: data.CacheTTLDays.ValueInt64(),
|
|
||||||
GitURL: data.GitURL.ValueString(),
|
|
||||||
GitCloneDepth: data.GitCloneDepth.ValueInt64(),
|
|
||||||
GitCloneSingleBranch: data.GitCloneSingleBranch.ValueBool(),
|
|
||||||
GitUsername: data.GitUsername.ValueString(),
|
|
||||||
GitPassword: data.GitPassword.ValueString(),
|
|
||||||
GitSSHPrivateKeyPath: data.GitSSHPrivateKeyPath.ValueString(),
|
|
||||||
GitHTTPProxyURL: data.GitHTTPProxyURL.ValueString(),
|
|
||||||
RemoteRepoBuildMode: data.RemoteRepoBuildMode.ValueBool(),
|
|
||||||
RemoteRepoDir: filepath.Join(tmpDir, "repo"),
|
|
||||||
SSLCertBase64: data.SSLCertBase64.ValueString(),
|
|
||||||
|
|
||||||
// Other options
|
|
||||||
BaseImageCacheDir: data.BaseImageCacheDir.ValueString(),
|
|
||||||
BinaryPath: envbuilderPath, // needed to reproduce the final layer.
|
|
||||||
ExitOnBuildFailure: data.ExitOnBuildFailure.ValueBool(), // may wish to do this instead of fallback image?
|
|
||||||
Insecure: data.Insecure.ValueBool(), // might have internal CAs?
|
|
||||||
IgnorePaths: tfListToStringSlice(data.IgnorePaths), // may need to be specified?
|
|
||||||
// The below options are not relevant and are set to their zero value explicitly.
|
|
||||||
// They must be set by extra_env.
|
|
||||||
CoderAgentSubsystem: nil,
|
|
||||||
CoderAgentToken: "",
|
|
||||||
CoderAgentURL: "",
|
|
||||||
ExportEnvFile: "",
|
|
||||||
InitArgs: "",
|
|
||||||
InitCommand: "",
|
|
||||||
InitScript: "",
|
|
||||||
LayerCacheDir: "",
|
|
||||||
PostStartScriptPath: "",
|
|
||||||
PushImage: false, // This is only relevant when building.
|
|
||||||
SetupScript: "",
|
|
||||||
SkipRebuild: false,
|
|
||||||
}
|
}
|
||||||
|
opts.RemoteRepoDir = repoDir
|
||||||
|
|
||||||
|
// The below options are not relevant and are set to their zero value
|
||||||
|
// explicitly.
|
||||||
|
// They must be set by extra_env to be used in the final builder image.
|
||||||
|
opts.CoderAgentSubsystem = nil
|
||||||
|
opts.CoderAgentToken = ""
|
||||||
|
opts.CoderAgentURL = ""
|
||||||
|
opts.ExportEnvFile = ""
|
||||||
|
opts.InitArgs = ""
|
||||||
|
opts.InitCommand = ""
|
||||||
|
opts.InitScript = ""
|
||||||
|
opts.LayerCacheDir = ""
|
||||||
|
opts.PostStartScriptPath = ""
|
||||||
|
opts.PushImage = false
|
||||||
|
opts.SetupScript = ""
|
||||||
|
opts.SkipRebuild = false
|
||||||
|
|
||||||
return envbuilder.RunCacheProbe(ctx, opts)
|
return envbuilder.RunCacheProbe(ctx, opts)
|
||||||
}
|
}
|
||||||
|
@ -764,6 +883,16 @@ func tfListToStringSlice(l types.List) []string {
|
||||||
return ss
|
return ss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tfMapToStringMap converts a types.Map to a map[string]string by calling
|
||||||
|
// tfValueToString on each element.
|
||||||
|
func tfMapToStringMap(m types.Map) map[string]string {
|
||||||
|
res := make(map[string]string)
|
||||||
|
for k, v := range m.Elements() {
|
||||||
|
res[k] = tfValueToString(v)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// tfLogFunc is an adapter to envbuilder/log.Func.
|
// tfLogFunc is an adapter to envbuilder/log.Func.
|
||||||
func tfLogFunc(ctx context.Context) eblog.Func {
|
func tfLogFunc(ctx context.Context) eblog.Func {
|
||||||
return func(level eblog.Level, format string, args ...any) {
|
return func(level eblog.Level, format string, args ...any) {
|
||||||
|
|
|
@ -20,8 +20,10 @@ func TestAccCachedImageResource(t *testing.T) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
files map[string]string
|
files map[string]string
|
||||||
|
extraEnv map[string]string
|
||||||
|
assertEnv func(t *testing.T, deps testDependencies) resource.TestCheckFunc
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
// This test case is the simplest possible case: a devcontainer.json.
|
// This test case is the simplest possible case: a devcontainer.json.
|
||||||
|
@ -31,6 +33,23 @@ func TestAccCachedImageResource(t *testing.T) {
|
||||||
files: map[string]string{
|
files: map[string]string{
|
||||||
".devcontainer/devcontainer.json": `{"image": "localhost:5000/test-ubuntu:latest"}`,
|
".devcontainer/devcontainer.json": `{"image": "localhost:5000/test-ubuntu:latest"}`,
|
||||||
},
|
},
|
||||||
|
extraEnv: map[string]string{
|
||||||
|
"FOO": testEnvValue,
|
||||||
|
"ENVBUILDER_GIT_URL": "https://not.the.real.git/url",
|
||||||
|
"ENVBUILDER_CACHE_REPO": "not-the-real-cache-repo",
|
||||||
|
},
|
||||||
|
assertEnv: func(t *testing.T, deps testDependencies) resource.TestCheckFunc {
|
||||||
|
return resource.ComposeAggregateTestCheckFunc(
|
||||||
|
assertEnv(t,
|
||||||
|
"ENVBUILDER_CACHE_REPO", deps.CacheRepo,
|
||||||
|
"ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key,
|
||||||
|
"ENVBUILDER_GIT_URL", deps.Repo.URL,
|
||||||
|
"ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true",
|
||||||
|
"ENVBUILDER_VERBOSE", "true",
|
||||||
|
"FOO", "bar\nbaz",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// This test case includes a Dockerfile in addition to the devcontainer.json.
|
// This test case includes a Dockerfile in addition to the devcontainer.json.
|
||||||
|
@ -42,14 +61,61 @@ func TestAccCachedImageResource(t *testing.T) {
|
||||||
".devcontainer/Dockerfile": `FROM localhost:5000/test-ubuntu:latest
|
".devcontainer/Dockerfile": `FROM localhost:5000/test-ubuntu:latest
|
||||||
RUN date > /date.txt`,
|
RUN date > /date.txt`,
|
||||||
},
|
},
|
||||||
|
extraEnv: map[string]string{
|
||||||
|
"FOO": testEnvValue,
|
||||||
|
"ENVBUILDER_GIT_URL": "https://not.the.real.git/url",
|
||||||
|
"ENVBUILDER_CACHE_REPO": "not-the-real-cache-repo",
|
||||||
|
},
|
||||||
|
assertEnv: func(t *testing.T, deps testDependencies) resource.TestCheckFunc {
|
||||||
|
return resource.ComposeAggregateTestCheckFunc(
|
||||||
|
assertEnv(t,
|
||||||
|
"ENVBUILDER_CACHE_REPO", deps.CacheRepo,
|
||||||
|
"ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key,
|
||||||
|
"ENVBUILDER_GIT_URL", deps.Repo.URL,
|
||||||
|
"ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true",
|
||||||
|
"ENVBUILDER_VERBOSE", "true",
|
||||||
|
"FOO", "bar\nbaz",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// This test case ensures that parameters passed via extra_env are
|
||||||
|
// handled correctly.
|
||||||
|
name: "extra_env",
|
||||||
|
files: map[string]string{
|
||||||
|
"path/to/.devcontainer/devcontainer.json": `{"build": { "dockerfile": "Dockerfile" }}`,
|
||||||
|
"path/to/.devcontainer/Dockerfile": `FROM localhost:5000/test-ubuntu:latest
|
||||||
|
RUN date > /date.txt`,
|
||||||
|
},
|
||||||
|
extraEnv: map[string]string{
|
||||||
|
"FOO": testEnvValue,
|
||||||
|
"ENVBUILDER_GIT_URL": "https://not.the.real.git/url",
|
||||||
|
"ENVBUILDER_CACHE_REPO": "not-the-real-cache-repo",
|
||||||
|
"ENVBUILDER_DEVCONTAINER_DIR": "path/to/.devcontainer",
|
||||||
|
"ENVBUILDER_DEVCONTAINER_JSON_PATH": "path/to/.devcontainer/devcontainer.json",
|
||||||
|
"ENVBUILDER_DOCKERFILE_PATH": "path/to/.devcontainer/Dockerfile",
|
||||||
|
},
|
||||||
|
assertEnv: func(t *testing.T, deps testDependencies) resource.TestCheckFunc {
|
||||||
|
return resource.ComposeAggregateTestCheckFunc(
|
||||||
|
assertEnv(t,
|
||||||
|
"ENVBUILDER_CACHE_REPO", deps.CacheRepo,
|
||||||
|
"ENVBUILDER_DEVCONTAINER_DIR", "path/to/.devcontainer",
|
||||||
|
"ENVBUILDER_DEVCONTAINER_JSON_PATH", "path/to/.devcontainer/devcontainer.json",
|
||||||
|
"ENVBUILDER_DOCKERFILE_PATH", "path/to/.devcontainer/Dockerfile",
|
||||||
|
"ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key,
|
||||||
|
"ENVBUILDER_GIT_URL", deps.Repo.URL,
|
||||||
|
"ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true",
|
||||||
|
"ENVBUILDER_VERBOSE", "true",
|
||||||
|
"FOO", "bar\nbaz",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
//nolint: paralleltest
|
//nolint: paralleltest
|
||||||
deps := setup(ctx, t, tc.files)
|
deps := setup(ctx, t, tc.extraEnv, tc.files)
|
||||||
deps.ExtraEnv["FOO"] = testEnvValue
|
|
||||||
deps.ExtraEnv["ENVBUILDER_GIT_URL"] = "https://not.the.real.git/url"
|
|
||||||
deps.ExtraEnv["ENVBUILDER_CACHE_REPO"] = "not-the-real-cache-repo"
|
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||||
|
@ -71,14 +137,13 @@ RUN date > /date.txt`,
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "image", deps.BuilderImage),
|
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "image", deps.BuilderImage),
|
||||||
// Inputs should still be present.
|
// Inputs should still be present.
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo),
|
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo),
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "extra_env.FOO", "bar\nbaz"),
|
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL),
|
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL),
|
||||||
// Should be empty
|
// Should be empty
|
||||||
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"),
|
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"),
|
||||||
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"),
|
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"),
|
||||||
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"),
|
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"),
|
||||||
// Environment variables
|
// Environment variables
|
||||||
assertEnv(t, deps),
|
tc.assertEnv(t, deps),
|
||||||
),
|
),
|
||||||
ExpectNonEmptyPlan: true, // TODO: check the plan.
|
ExpectNonEmptyPlan: true, // TODO: check the plan.
|
||||||
},
|
},
|
||||||
|
@ -93,14 +158,13 @@ RUN date > /date.txt`,
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "image", deps.BuilderImage),
|
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "image", deps.BuilderImage),
|
||||||
// Inputs should still be present.
|
// Inputs should still be present.
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo),
|
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo),
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "extra_env.FOO", "bar\nbaz"),
|
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL),
|
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL),
|
||||||
// Should be empty
|
// Should be empty
|
||||||
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"),
|
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"),
|
||||||
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"),
|
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"),
|
||||||
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"),
|
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"),
|
||||||
// Environment variables
|
// Environment variables
|
||||||
assertEnv(t, deps),
|
tc.assertEnv(t, deps),
|
||||||
),
|
),
|
||||||
ExpectNonEmptyPlan: true, // TODO: check the plan.
|
ExpectNonEmptyPlan: true, // TODO: check the plan.
|
||||||
},
|
},
|
||||||
|
@ -113,7 +177,6 @@ RUN date > /date.txt`,
|
||||||
Check: resource.ComposeAggregateTestCheckFunc(
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
// Inputs should still be present.
|
// Inputs should still be present.
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo),
|
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo),
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "extra_env.FOO", "bar\nbaz"),
|
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL),
|
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL),
|
||||||
// Should be empty
|
// Should be empty
|
||||||
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"),
|
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"),
|
||||||
|
@ -125,7 +188,7 @@ RUN date > /date.txt`,
|
||||||
resource.TestCheckResourceAttrSet("envbuilder_cached_image.test", "image"),
|
resource.TestCheckResourceAttrSet("envbuilder_cached_image.test", "image"),
|
||||||
resource.TestCheckResourceAttrWith("envbuilder_cached_image.test", "image", quotedPrefix(deps.CacheRepo)),
|
resource.TestCheckResourceAttrWith("envbuilder_cached_image.test", "image", quotedPrefix(deps.CacheRepo)),
|
||||||
// Environment variables
|
// Environment variables
|
||||||
assertEnv(t, deps),
|
tc.assertEnv(t, deps),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// 5) Should produce an empty plan after apply
|
// 5) Should produce an empty plan after apply
|
||||||
|
@ -144,28 +207,31 @@ RUN date > /date.txt`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// assertEnv is a test helper that checks the environment variables set on the
|
// assertEnv is a test helper that checks the environment variables, in order,
|
||||||
// cached image resource based on the provided test dependencies.
|
// on both the env and env_map attributes of the cached image resource.
|
||||||
func assertEnv(t *testing.T, deps testDependencies) resource.TestCheckFunc {
|
func assertEnv(t *testing.T, kvs ...string) resource.TestCheckFunc {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
return resource.ComposeAggregateTestCheckFunc(
|
if len(kvs)%2 != 0 {
|
||||||
// Check that the environment variables are set correctly.
|
t.Fatalf("assertEnv: expected an even number of key-value pairs, got %d", len(kvs))
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.0", fmt.Sprintf("ENVBUILDER_CACHE_REPO=%s", deps.CacheRepo)),
|
}
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.1", fmt.Sprintf("ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH=%s", deps.Repo.Key)),
|
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.2", fmt.Sprintf("ENVBUILDER_GIT_URL=%s", deps.Repo.URL)),
|
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.3", "ENVBUILDER_REMOTE_REPO_BUILD_MODE=true"),
|
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.4", "ENVBUILDER_VERBOSE=true"),
|
|
||||||
// Check that the extra environment variables are set correctly.
|
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.5", "FOO=bar\nbaz"),
|
|
||||||
// We should not have any other environment variables set.
|
|
||||||
resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "env.6"),
|
|
||||||
|
|
||||||
// Check that the same values are set in env_map.
|
funcs := make([]resource.TestCheckFunc, 0)
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env_map.ENVBUILDER_CACHE_REPO", deps.CacheRepo),
|
for i := 0; i < len(kvs); i += 2 {
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env_map.ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key),
|
resKey := fmt.Sprintf("env.%d", len(funcs))
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env_map.ENVBUILDER_GIT_URL", deps.Repo.URL),
|
resVal := fmt.Sprintf("%s=%s", kvs[i], kvs[i+1])
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env_map.ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true"),
|
fn := resource.TestCheckResourceAttr("envbuilder_cached_image.test", resKey, resVal)
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env_map.ENVBUILDER_VERBOSE", "true"),
|
funcs = append(funcs, fn)
|
||||||
resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env_map.FOO", "bar\nbaz"),
|
}
|
||||||
)
|
|
||||||
|
lastKey := fmt.Sprintf("env.%d", len(funcs))
|
||||||
|
lastFn := resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", lastKey)
|
||||||
|
funcs = append(funcs, lastFn)
|
||||||
|
|
||||||
|
for i := 0; i < len(kvs); i += 2 {
|
||||||
|
resKey := fmt.Sprintf("env_map.%s", kvs[i])
|
||||||
|
fn := resource.TestCheckResourceAttr("envbuilder_cached_image.test", resKey, kvs[i+1])
|
||||||
|
funcs = append(funcs, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resource.ComposeAggregateTestCheckFunc(funcs...)
|
||||||
}
|
}
|
||||||
|
|
359
internal/provider/provider_internal_test.go
Normal file
359
internal/provider/provider_internal_test.go
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
eboptions "github.com/coder/envbuilder/options"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_optionsFromDataModel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
data CachedImageResourceModel
|
||||||
|
expectOpts eboptions.Options
|
||||||
|
expectNumErrorDiags int
|
||||||
|
expectNumWarningDiags int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "required only",
|
||||||
|
data: CachedImageResourceModel{
|
||||||
|
BuilderImage: basetypes.NewStringValue("envbuilder:latest"),
|
||||||
|
CacheRepo: basetypes.NewStringValue("localhost:5000/cache"),
|
||||||
|
GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"),
|
||||||
|
},
|
||||||
|
expectOpts: eboptions.Options{
|
||||||
|
CacheRepo: "localhost:5000/cache",
|
||||||
|
GitURL: "git@git.local/devcontainer.git",
|
||||||
|
RemoteRepoBuildMode: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all options without extra_env",
|
||||||
|
data: CachedImageResourceModel{
|
||||||
|
BuilderImage: basetypes.NewStringValue("envbuilder:latest"),
|
||||||
|
CacheRepo: basetypes.NewStringValue("localhost:5000/cache"),
|
||||||
|
GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"),
|
||||||
|
BaseImageCacheDir: basetypes.NewStringValue("/tmp/cache"),
|
||||||
|
BuildContextPath: basetypes.NewStringValue("."),
|
||||||
|
CacheTTLDays: basetypes.NewInt64Value(7),
|
||||||
|
DevcontainerDir: basetypes.NewStringValue(".devcontainer"),
|
||||||
|
DevcontainerJSONPath: basetypes.NewStringValue(".devcontainer/devcontainer.json"),
|
||||||
|
DockerfilePath: basetypes.NewStringValue("Dockerfile"),
|
||||||
|
DockerConfigBase64: basetypes.NewStringValue("some base64"),
|
||||||
|
ExitOnBuildFailure: basetypes.NewBoolValue(true),
|
||||||
|
// ExtraEnv: map[string]basetypes.Value{},
|
||||||
|
FallbackImage: basetypes.NewStringValue("fallback"),
|
||||||
|
GitCloneDepth: basetypes.NewInt64Value(1),
|
||||||
|
GitCloneSingleBranch: basetypes.NewBoolValue(true),
|
||||||
|
GitHTTPProxyURL: basetypes.NewStringValue("http://proxy"),
|
||||||
|
GitPassword: basetypes.NewStringValue("password"),
|
||||||
|
GitSSHPrivateKeyPath: basetypes.NewStringValue("/tmp/id_rsa"),
|
||||||
|
GitUsername: basetypes.NewStringValue("user"),
|
||||||
|
IgnorePaths: listValue("ignore", "paths"),
|
||||||
|
Insecure: basetypes.NewBoolValue(true),
|
||||||
|
RemoteRepoBuildMode: basetypes.NewBoolValue(false),
|
||||||
|
SSLCertBase64: basetypes.NewStringValue("cert"),
|
||||||
|
Verbose: basetypes.NewBoolValue(true),
|
||||||
|
WorkspaceFolder: basetypes.NewStringValue("workspace"),
|
||||||
|
},
|
||||||
|
expectOpts: eboptions.Options{
|
||||||
|
CacheRepo: "localhost:5000/cache",
|
||||||
|
GitURL: "git@git.local/devcontainer.git",
|
||||||
|
BaseImageCacheDir: "/tmp/cache",
|
||||||
|
BuildContextPath: ".",
|
||||||
|
CacheTTLDays: 7,
|
||||||
|
DevcontainerDir: ".devcontainer",
|
||||||
|
DevcontainerJSONPath: ".devcontainer/devcontainer.json",
|
||||||
|
DockerfilePath: "Dockerfile",
|
||||||
|
DockerConfigBase64: "some base64",
|
||||||
|
ExitOnBuildFailure: true,
|
||||||
|
FallbackImage: "fallback",
|
||||||
|
GitCloneDepth: 1,
|
||||||
|
GitCloneSingleBranch: true,
|
||||||
|
GitHTTPProxyURL: "http://proxy",
|
||||||
|
GitPassword: "password",
|
||||||
|
GitSSHPrivateKeyPath: "/tmp/id_rsa",
|
||||||
|
GitUsername: "user",
|
||||||
|
IgnorePaths: []string{"ignore", "paths"},
|
||||||
|
Insecure: true,
|
||||||
|
RemoteRepoBuildMode: false,
|
||||||
|
SSLCertBase64: "cert",
|
||||||
|
Verbose: true,
|
||||||
|
WorkspaceFolder: "workspace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "extra env override",
|
||||||
|
data: CachedImageResourceModel{
|
||||||
|
BuilderImage: basetypes.NewStringValue("envbuilder:latest"),
|
||||||
|
CacheRepo: basetypes.NewStringValue("localhost:5000/cache"),
|
||||||
|
GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"),
|
||||||
|
ExtraEnv: extraEnvMap(t,
|
||||||
|
"CODER_AGENT_TOKEN", "token",
|
||||||
|
"CODER_AGENT_URL", "http://coder",
|
||||||
|
"FOO", "bar",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectOpts: eboptions.Options{
|
||||||
|
CacheRepo: "localhost:5000/cache",
|
||||||
|
GitURL: "git@git.local/devcontainer.git",
|
||||||
|
RemoteRepoBuildMode: true,
|
||||||
|
CoderAgentToken: "token",
|
||||||
|
CoderAgentURL: "http://coder",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "extra_env override warnings",
|
||||||
|
data: CachedImageResourceModel{
|
||||||
|
BuilderImage: basetypes.NewStringValue("envbuilder:latest"),
|
||||||
|
CacheRepo: basetypes.NewStringValue("localhost:5000/cache"),
|
||||||
|
GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"),
|
||||||
|
BaseImageCacheDir: basetypes.NewStringValue("/tmp/cache"),
|
||||||
|
BuildContextPath: basetypes.NewStringValue("."),
|
||||||
|
CacheTTLDays: basetypes.NewInt64Value(7),
|
||||||
|
DevcontainerDir: basetypes.NewStringValue(".devcontainer"),
|
||||||
|
DevcontainerJSONPath: basetypes.NewStringValue(".devcontainer/devcontainer.json"),
|
||||||
|
DockerfilePath: basetypes.NewStringValue("Dockerfile"),
|
||||||
|
DockerConfigBase64: basetypes.NewStringValue("some base64"),
|
||||||
|
ExitOnBuildFailure: basetypes.NewBoolValue(true),
|
||||||
|
// ExtraEnv: map[string]basetypes.Value{},
|
||||||
|
FallbackImage: basetypes.NewStringValue("fallback"),
|
||||||
|
GitCloneDepth: basetypes.NewInt64Value(1),
|
||||||
|
GitCloneSingleBranch: basetypes.NewBoolValue(true),
|
||||||
|
GitHTTPProxyURL: basetypes.NewStringValue("http://proxy"),
|
||||||
|
GitPassword: basetypes.NewStringValue("password"),
|
||||||
|
GitSSHPrivateKeyPath: basetypes.NewStringValue("/tmp/id_rsa"),
|
||||||
|
GitUsername: basetypes.NewStringValue("user"),
|
||||||
|
IgnorePaths: listValue("ignore", "paths"),
|
||||||
|
Insecure: basetypes.NewBoolValue(true),
|
||||||
|
RemoteRepoBuildMode: basetypes.NewBoolValue(false),
|
||||||
|
SSLCertBase64: basetypes.NewStringValue("cert"),
|
||||||
|
Verbose: basetypes.NewBoolValue(true),
|
||||||
|
WorkspaceFolder: basetypes.NewStringValue("workspace"),
|
||||||
|
ExtraEnv: extraEnvMap(t,
|
||||||
|
"ENVBUILDER_CACHE_REPO", "override",
|
||||||
|
"ENVBUILDER_GIT_URL", "override",
|
||||||
|
"ENVBUILDER_BASE_IMAGE_CACHE_DIR", "override",
|
||||||
|
"ENVBUILDER_BUILD_CONTEXT_PATH", "override",
|
||||||
|
"ENVBUILDER_CACHE_TTL_DAYS", "8",
|
||||||
|
"ENVBUILDER_DEVCONTAINER_DIR", "override",
|
||||||
|
"ENVBUILDER_DEVCONTAINER_JSON_PATH", "override",
|
||||||
|
"ENVBUILDER_DOCKERFILE_PATH", "override",
|
||||||
|
"ENVBUILDER_DOCKER_CONFIG_BASE64", "override",
|
||||||
|
"ENVBUILDER_EXIT_ON_BUILD_FAILURE", "false",
|
||||||
|
"ENVBUILDER_FALLBACK_IMAGE", "override",
|
||||||
|
"ENVBUILDER_GIT_CLONE_DEPTH", "2",
|
||||||
|
"ENVBUILDER_GIT_CLONE_SINGLE_BRANCH", "false",
|
||||||
|
"ENVBUILDER_GIT_HTTP_PROXY_URL", "override",
|
||||||
|
"ENVBUILDER_GIT_PASSWORD", "override",
|
||||||
|
"ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", "override",
|
||||||
|
"ENVBUILDER_GIT_USERNAME", "override",
|
||||||
|
"ENVBUILDER_IGNORE_PATHS", "override",
|
||||||
|
"ENVBUILDER_INSECURE", "false",
|
||||||
|
"ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true",
|
||||||
|
"ENVBUILDER_SSL_CERT_BASE64", "override",
|
||||||
|
"ENVBUILDER_VERBOSE", "false",
|
||||||
|
"ENVBUILDER_WORKSPACE_FOLDER", "override",
|
||||||
|
"FOO", "bar",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectOpts: eboptions.Options{
|
||||||
|
// not overridden
|
||||||
|
CacheRepo: "localhost:5000/cache",
|
||||||
|
GitURL: "git@git.local/devcontainer.git",
|
||||||
|
// overridden
|
||||||
|
BaseImageCacheDir: "override",
|
||||||
|
BuildContextPath: "override",
|
||||||
|
CacheTTLDays: 8,
|
||||||
|
DevcontainerDir: "override",
|
||||||
|
DevcontainerJSONPath: "override",
|
||||||
|
DockerfilePath: "override",
|
||||||
|
DockerConfigBase64: "override",
|
||||||
|
ExitOnBuildFailure: false,
|
||||||
|
FallbackImage: "override",
|
||||||
|
GitCloneDepth: 2,
|
||||||
|
GitCloneSingleBranch: false,
|
||||||
|
GitHTTPProxyURL: "override",
|
||||||
|
GitPassword: "override",
|
||||||
|
GitSSHPrivateKeyPath: "override",
|
||||||
|
GitUsername: "override",
|
||||||
|
IgnorePaths: []string{"override"},
|
||||||
|
Insecure: false,
|
||||||
|
RemoteRepoBuildMode: true,
|
||||||
|
SSLCertBase64: "override",
|
||||||
|
Verbose: false,
|
||||||
|
WorkspaceFolder: "override",
|
||||||
|
},
|
||||||
|
expectNumWarningDiags: 23,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "extra_env override errors",
|
||||||
|
data: CachedImageResourceModel{
|
||||||
|
BuilderImage: basetypes.NewStringValue("envbuilder:latest"),
|
||||||
|
CacheRepo: basetypes.NewStringValue("localhost:5000/cache"),
|
||||||
|
GitURL: basetypes.NewStringValue("git@git.local/devcontainer.git"),
|
||||||
|
ExtraEnv: extraEnvMap(t,
|
||||||
|
"ENVBUILDER_CACHE_TTL_DAYS", "not a number",
|
||||||
|
"ENVBUILDER_VERBOSE", "not a bool",
|
||||||
|
"FOO", "bar",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectOpts: eboptions.Options{
|
||||||
|
// not overridden
|
||||||
|
CacheRepo: "localhost:5000/cache",
|
||||||
|
GitURL: "git@git.local/devcontainer.git",
|
||||||
|
RemoteRepoBuildMode: true,
|
||||||
|
},
|
||||||
|
expectNumErrorDiags: 2,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
actual, diags := optionsFromDataModel(tc.data)
|
||||||
|
assert.Equal(t, tc.expectNumErrorDiags, diags.ErrorsCount())
|
||||||
|
assert.Equal(t, tc.expectNumWarningDiags, diags.WarningsCount())
|
||||||
|
assert.EqualValues(t, tc.expectOpts, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_computeEnvFromOptions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
opts eboptions.Options
|
||||||
|
extraEnv map[string]string
|
||||||
|
expectEnv map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
opts: eboptions.Options{},
|
||||||
|
expectEnv: map[string]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all options",
|
||||||
|
opts: eboptions.Options{
|
||||||
|
BaseImageCacheDir: "string",
|
||||||
|
BinaryPath: "string",
|
||||||
|
BuildContextPath: "string",
|
||||||
|
CacheRepo: "string",
|
||||||
|
CacheTTLDays: 1,
|
||||||
|
CoderAgentSubsystem: []string{"one", "two"},
|
||||||
|
CoderAgentToken: "string",
|
||||||
|
CoderAgentURL: "string",
|
||||||
|
DevcontainerDir: "string",
|
||||||
|
DevcontainerJSONPath: "string",
|
||||||
|
DockerConfigBase64: "string",
|
||||||
|
DockerfilePath: "string",
|
||||||
|
ExitOnBuildFailure: true,
|
||||||
|
ExportEnvFile: "string",
|
||||||
|
FallbackImage: "string",
|
||||||
|
ForceSafe: true,
|
||||||
|
GetCachedImage: true,
|
||||||
|
GitCloneDepth: 1,
|
||||||
|
GitCloneSingleBranch: true,
|
||||||
|
GitHTTPProxyURL: "string",
|
||||||
|
GitPassword: "string",
|
||||||
|
GitSSHPrivateKeyPath: "string",
|
||||||
|
GitURL: "string",
|
||||||
|
GitUsername: "string",
|
||||||
|
IgnorePaths: []string{"one", "two"},
|
||||||
|
InitArgs: "string",
|
||||||
|
InitCommand: "string",
|
||||||
|
InitScript: "string",
|
||||||
|
Insecure: true,
|
||||||
|
LayerCacheDir: "string",
|
||||||
|
PostStartScriptPath: "string",
|
||||||
|
PushImage: true,
|
||||||
|
RemoteRepoBuildMode: true,
|
||||||
|
RemoteRepoDir: "string",
|
||||||
|
SetupScript: "string",
|
||||||
|
SkipRebuild: true,
|
||||||
|
SSLCertBase64: "string",
|
||||||
|
Verbose: true,
|
||||||
|
WorkspaceFolder: "string",
|
||||||
|
},
|
||||||
|
extraEnv: map[string]string{
|
||||||
|
"ENVBUILDER_SOMETHING": "string", // should be ignored
|
||||||
|
"FOO": "bar", // should be included
|
||||||
|
},
|
||||||
|
expectEnv: map[string]string{
|
||||||
|
"CODER_AGENT_SUBSYSTEM": "one,two",
|
||||||
|
"CODER_AGENT_TOKEN": "string",
|
||||||
|
"CODER_AGENT_URL": "string",
|
||||||
|
"ENVBUILDER_BASE_IMAGE_CACHE_DIR": "string",
|
||||||
|
"ENVBUILDER_BINARY_PATH": "string",
|
||||||
|
"ENVBUILDER_BUILD_CONTEXT_PATH": "string",
|
||||||
|
"ENVBUILDER_CACHE_REPO": "string",
|
||||||
|
"ENVBUILDER_CACHE_TTL_DAYS": "1",
|
||||||
|
"ENVBUILDER_DEVCONTAINER_DIR": "string",
|
||||||
|
"ENVBUILDER_DEVCONTAINER_JSON_PATH": "string",
|
||||||
|
"ENVBUILDER_DOCKER_CONFIG_BASE64": "string",
|
||||||
|
"ENVBUILDER_DOCKERFILE_PATH": "string",
|
||||||
|
"ENVBUILDER_EXIT_ON_BUILD_FAILURE": "true",
|
||||||
|
"ENVBUILDER_EXPORT_ENV_FILE": "string",
|
||||||
|
"ENVBUILDER_FALLBACK_IMAGE": "string",
|
||||||
|
"ENVBUILDER_FORCE_SAFE": "true",
|
||||||
|
"ENVBUILDER_GET_CACHED_IMAGE": "true",
|
||||||
|
"ENVBUILDER_GIT_CLONE_DEPTH": "1",
|
||||||
|
"ENVBUILDER_GIT_CLONE_SINGLE_BRANCH": "true",
|
||||||
|
"ENVBUILDER_GIT_HTTP_PROXY_URL": "string",
|
||||||
|
"ENVBUILDER_GIT_PASSWORD": "string",
|
||||||
|
"ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH": "string",
|
||||||
|
"ENVBUILDER_GIT_URL": "string",
|
||||||
|
"ENVBUILDER_GIT_USERNAME": "string",
|
||||||
|
"ENVBUILDER_IGNORE_PATHS": "one,two",
|
||||||
|
"ENVBUILDER_INIT_ARGS": "string",
|
||||||
|
"ENVBUILDER_INIT_COMMAND": "string",
|
||||||
|
"ENVBUILDER_INIT_SCRIPT": "string",
|
||||||
|
"ENVBUILDER_INSECURE": "true",
|
||||||
|
"ENVBUILDER_LAYER_CACHE_DIR": "string",
|
||||||
|
"ENVBUILDER_POST_START_SCRIPT_PATH": "string",
|
||||||
|
"ENVBUILDER_PUSH_IMAGE": "true",
|
||||||
|
"ENVBUILDER_REMOTE_REPO_BUILD_MODE": "true",
|
||||||
|
"ENVBUILDER_REMOTE_REPO_DIR": "string",
|
||||||
|
"ENVBUILDER_SETUP_SCRIPT": "string",
|
||||||
|
"ENVBUILDER_SKIP_REBUILD": "true",
|
||||||
|
"ENVBUILDER_SSL_CERT_BASE64": "string",
|
||||||
|
"ENVBUILDER_VERBOSE": "true",
|
||||||
|
"ENVBUILDER_WORKSPACE_FOLDER": "string",
|
||||||
|
"FOO": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if tc.extraEnv == nil {
|
||||||
|
tc.extraEnv = map[string]string{}
|
||||||
|
}
|
||||||
|
actual := computeEnvFromOptions(tc.opts, tc.extraEnv)
|
||||||
|
assert.EqualValues(t, tc.expectEnv, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listValue(vs ...string) basetypes.ListValue {
|
||||||
|
vals := make([]attr.Value, len(vs))
|
||||||
|
for i, s := range vs {
|
||||||
|
vals[i] = basetypes.NewStringValue(s)
|
||||||
|
}
|
||||||
|
return basetypes.NewListValueMust(basetypes.StringType{}, vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extraEnvMap(t *testing.T, kvs ...string) basetypes.MapValue {
|
||||||
|
t.Helper()
|
||||||
|
if len(kvs)%2 != 0 {
|
||||||
|
t.Fatalf("extraEnvMap: expected even number of key-value pairs, got %d", len(kvs))
|
||||||
|
}
|
||||||
|
vals := make(map[string]attr.Value)
|
||||||
|
for i := 0; i < len(kvs); i += 2 {
|
||||||
|
vals[kvs[i]] = basetypes.NewStringValue(kvs[i+1])
|
||||||
|
}
|
||||||
|
return basetypes.NewMapValueMust(basetypes.StringType{}, vals)
|
||||||
|
}
|
|
@ -49,14 +49,14 @@ func (d *testDependencies) Config(t testing.TB) string {
|
||||||
resource "envbuilder_cached_image" "test" {
|
resource "envbuilder_cached_image" "test" {
|
||||||
builder_image = {{ quote .BuilderImage }}
|
builder_image = {{ quote .BuilderImage }}
|
||||||
cache_repo = {{ quote .CacheRepo }}
|
cache_repo = {{ quote .CacheRepo }}
|
||||||
|
git_url = {{ quote .Repo.URL }}
|
||||||
extra_env = {
|
extra_env = {
|
||||||
|
"ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH": {{ quote .Repo.Key }}
|
||||||
|
"ENVBUILDER_VERBOSE": true
|
||||||
{{ range $k, $v := .ExtraEnv }}
|
{{ range $k, $v := .ExtraEnv }}
|
||||||
{{ quote $k }}: {{ quote $v }}
|
{{ quote $k }}: {{ quote $v }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
}
|
}
|
||||||
git_url = {{ quote .Repo.URL }}
|
|
||||||
git_ssh_private_key_path = {{ quote .Repo.Key }}
|
|
||||||
verbose = true
|
|
||||||
}`
|
}`
|
||||||
|
|
||||||
fm := template.FuncMap{"quote": quote}
|
fm := template.FuncMap{"quote": quote}
|
||||||
|
@ -71,7 +71,7 @@ func quote(s string) string {
|
||||||
return fmt.Sprintf("%q", s)
|
return fmt.Sprintf("%q", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(ctx context.Context, t testing.TB, files map[string]string) testDependencies {
|
func setup(ctx context.Context, t testing.TB, extraEnv, files map[string]string) testDependencies {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
envbuilderImage := getEnvOrDefault("ENVBUILDER_IMAGE", "localhost:5000/envbuilder")
|
envbuilderImage := getEnvOrDefault("ENVBUILDER_IMAGE", "localhost:5000/envbuilder")
|
||||||
|
@ -89,7 +89,7 @@ func setup(ctx context.Context, t testing.TB, files map[string]string) testDepen
|
||||||
return testDependencies{
|
return testDependencies{
|
||||||
BuilderImage: envbuilderImageRef,
|
BuilderImage: envbuilderImageRef,
|
||||||
CacheRepo: reg + "/test",
|
CacheRepo: reg + "/test",
|
||||||
ExtraEnv: make(map[string]string),
|
ExtraEnv: extraEnv,
|
||||||
Repo: gitRepo,
|
Repo: gitRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,18 +106,38 @@ func seedCache(ctx context.Context, t testing.TB, deps testDependencies) {
|
||||||
|
|
||||||
ensureImage(ctx, t, cli, deps.BuilderImage)
|
ensureImage(ctx, t, cli, deps.BuilderImage)
|
||||||
|
|
||||||
|
// Set up env for envbuilder
|
||||||
|
seedEnv := map[string]string{
|
||||||
|
"ENVBUILDER_CACHE_REPO": deps.CacheRepo,
|
||||||
|
"ENVBUILDER_EXIT_ON_BUILD_FAILURE": "true",
|
||||||
|
"ENVBUILDER_INIT_SCRIPT": "exit",
|
||||||
|
"ENVBUILDER_PUSH_IMAGE": "true",
|
||||||
|
"ENVBUILDER_VERBOSE": "true",
|
||||||
|
"ENVBUILDER_GIT_URL": deps.Repo.URL,
|
||||||
|
"ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH": "/id_ed25519",
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range deps.ExtraEnv {
|
||||||
|
if !strings.HasPrefix(k, "ENVBUILDER_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seedEnv[k]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seedEnv[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
seedDockerEnv := make([]string, 0)
|
||||||
|
for k, v := range seedEnv {
|
||||||
|
seedDockerEnv = append(seedDockerEnv, k+"="+v)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("running envbuilder to seed cache with args: %v", seedDockerEnv)
|
||||||
|
|
||||||
// Run envbuilder using this dir as a local layer cache
|
// Run envbuilder using this dir as a local layer cache
|
||||||
ctr, err := cli.ContainerCreate(ctx, &container.Config{
|
ctr, err := cli.ContainerCreate(ctx, &container.Config{
|
||||||
Image: deps.BuilderImage,
|
Image: deps.BuilderImage,
|
||||||
Env: []string{
|
Env: seedDockerEnv,
|
||||||
"ENVBUILDER_CACHE_REPO=" + deps.CacheRepo,
|
|
||||||
"ENVBUILDER_EXIT_ON_BUILD_FAILURE=true",
|
|
||||||
"ENVBUILDER_INIT_SCRIPT=exit",
|
|
||||||
"ENVBUILDER_PUSH_IMAGE=true",
|
|
||||||
"ENVBUILDER_VERBOSE=true",
|
|
||||||
"ENVBUILDER_GIT_URL=" + deps.Repo.URL,
|
|
||||||
"ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH=/id_ed25519",
|
|
||||||
},
|
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
testContainerLabel: "true",
|
testContainerLabel: "true",
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue