Skip to content

fix(internal/provider): correctly override from extra_env #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 4, 2024

Conversation

johnstcn
Copy link
Member

@johnstcn johnstcn commented Sep 3, 2024

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.

Before, we were setting environment variables directly from the data source model but not applying them properly to envbuilder.Options
The new overall flow is:

  • Data source model is converted to an envbuilder options struct
  • extra_env is applied to envbuilder options struct
  • Computed env is generated from envbuilder options struct

@johnstcn johnstcn self-assigned this Sep 3, 2024
Comment on lines +91 to +98
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",
},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case should assert the correct behaviour of the provider.

Comment on lines +509 to +510
if sa, ok := opt.Value.(*serpent.StringArray); ok {
val = strings.Join(sa.GetSlice(), ",")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is due to the incorrect String() method being called or what; when I stepped through in a debugger I could see the method of a serpent.StringArray being called. Although I do note that the method in question has a value receiver instead of a pointer receiver.

Comment on lines +516 to +519
case "", "false", "0":
// Skip zero values.
continue
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK all of our boolean-ish options are false by default.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit flimsy IMHO; could we be more explicit about the types we want to skip, or use reflect to determine if a zero value is set?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reflect would definitely be an option here; I chose to just hard-code these for simplicity.

Comment on lines +454 to +458
// 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("")
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm explicitly choosing to overwrite instead of append here as I believe the latter will be suprising and unexpected.

Copy link

@dannykopping dannykopping Sep 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a chance this could get set to empty but then not replaced?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If somehow ENVBUILDER_IGNORE_PATHS ended up in extraEnv but not in optsMap, which would likely result in a nil pointer error on line 457. I could only see that happening if we removed that option.

This whole options situation is horrible and I agree we need a better way to do it. For now, I'm just focusing on getting it working.

Copy link
Member

@mtojek mtojek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overall change LGTM. My main concern is keeping different ENV vars hardcoded instead of a dedicated property/schema file. If these are magic strings, maybe we should create a dedicated file to keep track of them?

Nothing blocking. Thanks for working on this!

return diags
}

func computeEnvFromOptions(opts eboptions.Options, extraEnv map[string]string) map[string]string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Maybe we should move computeEnvFromOptions to a helper function or lib as cached_image_resource.go is getting larger.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will address in a follow-up.

Comment on lines +311 to +312
overrides["ENVBUILDER_BASE_IMAGE_CACHE_DIR"] = struct{}{}
opts.BaseImageCacheDir = data.BaseImageCacheDir.ValueString()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I understand there is no simple way to replace this wall of condition-override-value 's with any dynamic logic?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One option I've been considering is using struct tags to match up the fields between these two. A 'dirty hack' could be using json.Marshal / json.Unmarshal to convert between the two. Some of the subtleties of the Terraform data model might be lost though (e.g. "unknown" versus "zero value").

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a chance of losing the information, then let's leave it as is, although I admit it is tempting to make it more dynamic.

},
{
name: "all options without extra_env",
data: CachedImageResourceModel{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would it help if we have some YAML files with test input data? I mean, less Go code...

Copy link
Member Author

@johnstcn johnstcn Sep 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of using x/tools/txtar to store this.
Danny used it for the provisionerd timings tests (example).

switch key {

// These options may not be overridden.
case "ENVBUILDER_CACHE_REPO", "ENVBUILDER_GIT_URL":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would love to see these keys defined in some schema instead of hardcoded

Copy link

@dannykopping dannykopping left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines +454 to +458
// 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("")
}
Copy link

@dannykopping dannykopping Sep 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a chance this could get set to empty but then not replaced?

Comment on lines +516 to +519
case "", "false", "0":
// Skip zero values.
continue
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit flimsy IMHO; could we be more explicit about the types we want to skip, or use reflect to determine if a zero value is set?

// 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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: could we preallocate here based on the length of m?

@johnstcn johnstcn merged commit 23f2cf5 into main Sep 4, 2024
14 checks passed
@johnstcn johnstcn deleted the cj/extra-env-opts branch September 4, 2024 10:19
johnstcn added a commit that referenced this pull request Sep 4, 2024
Addresses some non-blocking comments from #44:

- Extracts some of the functions in cached_image_resource.go to separate internal packages tfutil and imgutil.
- Some other functions are extracted to helpers.go.
- Extracts non-overridable flags to a package-level variable.
- Pre-allocates some slices where possible.
- Removes some unused code and renames some existing code for readability
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants