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
Add Warning for Deprecated Modules in Init
#34943
base: main
Are you sure you want to change the base?
Conversation
6b348b4
to
ae4207f
Compare
b9107c0
to
5471fdf
Compare
0e24d07
to
f27dad2
Compare
f27dad2
to
18df73f
Compare
a9d0a9c
to
1f19a2b
Compare
1f19a2b
to
b517651
Compare
c809c74
to
c8d198a
Compare
…e deprecation warnings
c8d198a
to
a79e38c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Things are looking great so far! Some items down below
var deprecationList []*ModuleDeprecationDiagnosticExtraDeprecationItem | ||
for _, modDeprecationInfo := range i.ModuleDeprecationInfos { | ||
if modDeprecationInfo != nil && modDeprecationInfo.RegistryDeprecation != nil { | ||
modDeprecation := fmt.Sprintf("\x1b[1mVersion %s of %s\x1b[0m", modDeprecationInfo.RegistryDeprecation.Version, modDeprecationInfo.SourceName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't tried this yet, but maybe we can leverage mitchellh/colorstring
to format the colors rather than hardcoding the ANSI escape codes.
modDeprecation := fmt.Sprintf("\x1b[1mVersion %s of %s\x1b[0m", modDeprecationInfo.RegistryDeprecation.Version, modDeprecationInfo.SourceName) | |
msg := colorize.Color("[reset][bold]Version %s of %s[reset]") | |
modDeprecation := fmt.Sprintf(msg, modDeprecationInfo.RegistryDeprecation.Version, modDeprecationInfo.SourceName) |
// as well as placed in the Diagnostic Extra for parsing for the SRO view in HCP Terraform | ||
func (i *WorkspaceDeprecationInfo) BuildDeprecationWarning() *hcl.Diagnostic { | ||
modDeprecationStrings := []string{} | ||
var deprecationList []*ModuleDeprecationDiagnosticExtraDeprecationItem |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(optional 💅) I'm wondering if it's beneficial to pre-allocate this slice to have a capacity of len(i.ModuleDeprecationInfos)
, since we know the underlying array size won't exceed that. So it becomes:
deprecationList := make([]*ModuleDeprecationDiagnosticExtraDeprecationItem, 0, len(i.ModuleDeprecationInfo))
This may prevent extra memory allocations on append()
for scenarios where we anticipate many deprecation diagnostics. Read about append() growth rates. See if you can also apply this to modDeprecationStrings
found := false | ||
for _, modProviderVersions := range resp.Modules { | ||
for _, modVersions := range modProviderVersions.Versions { | ||
vm, _ := version.NewVersion(modVersions.Version) | ||
if vm.Equal(record.Version) { | ||
moduleVersion = modVersions | ||
found = true | ||
break | ||
} | ||
} | ||
if found { | ||
break | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
found := false | |
for _, modProviderVersions := range resp.Modules { | |
for _, modVersions := range modProviderVersions.Versions { | |
vm, _ := version.NewVersion(modVersions.Version) | |
if vm.Equal(record.Version) { | |
moduleVersion = modVersions | |
found = true | |
break | |
} | |
} | |
if found { | |
break | |
} | |
} | |
found: | |
for _, modProviderVersions := range resp.Modules { | |
for _, modVersions := range modProviderVersions.Versions { | |
vm, _ := version.NewVersion(modVersions.Version) | |
if vm.Equal(record.Version) { | |
moduleVersion = modVersions | |
break found | |
} | |
} | |
} |
Use label breaks instead 👍
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
|
||
package configs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't forget to add tests for module_deprecations_test.go
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Several change requests inline; feel free to ask for more detail on anything.
I also have a couple big-picture questions for ya.
- Have y'all started work on the TFC version of this warning? And where will TFC be receiving the details from? I ask because,
terraform init
doesn't have a json log format, and we suppress its output entirely unless it errors out with a non-zero exit code (Cf.core/components/terraform/step_terraform_init.go
in tfc-agent), so TFC won't be able to learn about this from CLI. You likely already have plans to work around this, but I wanted to make sure I was raising the issue just in case - I think the current design of passing the deprecations info around as a return value from the module walk is viable, but for posterity's sake and to make sure you've considered the options — can you say a bit more about why this belongs in a separate return, rather than in a field on the
configs.Config
struct? Config is already a nested structure with child Configs for each module, so it sort of mirrors the separate structure you're building here. - We need tests for this behavior!! 😄
Also some follow-up smaller notes:
- I'm not sure how we plan to support this in the stacks runtime — I think it might require changes in go-slug/sourcebundle. Any other tf-core stacks peeps want to chime in on that?
@@ -111,7 +115,7 @@ func buildTestModules(root *Config, walker ModuleWalker) hcl.Diagnostics { | |||
CallRange: run.Module.DeclRange, | |||
} | |||
|
|||
cfg, modDiags := loadModule(root, &req, walker) | |||
cfg, modDiags, _ := loadModule(root, &req, walker) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right 🤔 I guess we have no way to know if the root module is deprecated, because we didn't fetch it from its source.
@@ -253,6 +263,8 @@ func rebaseChildModule(cfg *Config, root *Config) { | |||
cfg.Root = root | |||
} | |||
|
|||
// refactor this into it's own file, doesn't make sense to place this here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like detritus from a previous iteration; please remove.
for _, modProviderVersions := range resp.Modules { | ||
for _, modVersions := range modProviderVersions.Versions { | ||
vm, _ := version.NewVersion(modVersions.Version) | ||
if vm.Equal(record.Version) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, this freaked me out for a minute, but turns out that .Equal explicitly expects to maybe be called on a nil receiver. Ok!
@@ -149,6 +150,7 @@ func NewDiagnostic(diag tfdiags.Diagnostic, sources map[string][]byte) *Diagnost | |||
Summary: desc.Summary, | |||
Detail: desc.Detail, | |||
Address: desc.Address, | |||
Extra: diag.ExtraInfo(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚨 This makes me nervous!
As you saw in the tests that this broke, the existing ExtraInfo() types do not expect that they will be exposed to the outside world, and might create nonsense if they do get exposed. ("extra": true
? huh???)
I think you might want to
- Create a new tfdiags.PublicExtraInfo interface type (with one do-nothing function that types must implement).
- Grab the value of
diag.ExtraInfo()
and do a type-check on it, and conditionally add it to the output value only if it implements PublicExtraInfo. - Implement PublicExtraInfo for ModuleDeprecationDiagnosticExtra.
That way any existing extra types stay unexposed.
@@ -42,7 +42,6 @@ func (c *GetCommand) Run(args []string) int { | |||
} | |||
|
|||
path = c.normalizePath(path) | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like detritus from a previous iteration; plz remove.
var deprecationList []*ModuleDeprecationDiagnosticExtraDeprecationItem | ||
for _, modDeprecationInfo := range i.ModuleDeprecationInfos { | ||
if modDeprecationInfo != nil && modDeprecationInfo.RegistryDeprecation != nil { | ||
modDeprecation := fmt.Sprintf("\x1b[1mVersion %s of %s\x1b[0m", modDeprecationInfo.RegistryDeprecation.Version, modDeprecationInfo.SourceName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 Mmmmmm, this can't fly — unconditionally embedding ansi control codes into the text means this can never be a good citizen when -no-color
is specified.
On the CLI, diagnostics end up going through View.Diagnostics. So, you can use colorize
codes in the text that will get stripped out when it's in no-color mode. In this case, you'd want to replace the ansi codes with [bold]something[reset]
.
for _, deprecationString := range modDeprecationStrings { | ||
deprecationsMessage += deprecationString + "\n\n" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want strings.Join
here.
@@ -38,6 +38,7 @@ type Diagnostic struct { | |||
Address string `json:"address,omitempty"` | |||
Range *DiagnosticRange `json:"range,omitempty"` | |||
Snippet *DiagnosticSnippet `json:"snippet,omitempty"` | |||
Extra interface{} `json:"extra,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A change to this externally-facing json format will require an update to its docs page (https://developer.hashicorp.com/terraform/cli/commands/validate#json), and possibly a bump to its minor version number.
(But, I asked around about how seriously we take the minor version guarantees in these json formats, and the answer was inconclusive. We do at least want it documented, though.)
deprecationsMessage += deprecationString + "\n\n" | ||
} | ||
|
||
return &hcl.Diagnostic{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've got a modest architectural suggestion for you here — instead of re-using the hcl.Diagnostic type here, what do you think about making your own DeprecationsDiagnostic struct, shoving the flattened deprecationList (and a final boolean hasDeprecations result) into it, and implementing tfdiags.Diagnostic
on it?
I think it could yield some cleaner-feeling code, but it's not something I'd necessarily insist on.
Jira Ticket
Target Release
1.8.x
Draft CHANGELOG entry
ENHANCEMENTS
terraform init
: Displays a warning when deprecated modules are in use.Description
All module deprecation info is consolidated into and displayed as a single warning upon running
terraform init
. This includes both cases of where the module does, or does not, need installation. Furthermore, the warning can be raised for both modules that are directly used in configuration, as well as any external dependencies they depend on.Screenshot
Note that this screenshot includes both the root module as well as it's external dependencies