Skip to content
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

Call local modules by default #1918

Merged
merged 2 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "terraform/test-fixtures/v0.15.0_module/.terraform/modules/consul"]
path = terraform/test-fixtures/v0.15.0_module/.terraform/modules/consul
url = https://github.com/hashicorp/terraform-aws-consul
[submodule "integrationtest/inspection/with_module_init/.terraform/modules/remote"]
path = integrationtest/inspection/with_module_init/.terraform/modules/remote
url = https://github.com/terraform-aws-modules/terraform-aws-ec2-instance
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ Application Options:
--enable-plugin=PLUGIN_NAME Enable plugins from the command line
--var-file=FILE Terraform variable file name
--var='foo=bar' Set a Terraform variable
--module Enable module inspection
--no-module Disable module inspection
--call-module-type=[all|local|none] Types of module to call (default: local)
--chdir=DIR Switch to a different working directory before executing the command
--recursive Run command in each directory recursively
--filter=FILE Filter issues by file names or globs
Expand Down
2 changes: 1 addition & 1 deletion cmd/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ By setting TFLINT_LOG=trace, you can confirm the changes made by the autofix and
}

func (cli *CLI) setupRunners(opts Options, dir string) ([]*tflint.Runner, error) {
configs, diags := cli.loader.LoadConfig(dir, cli.config.Module)
configs, diags := cli.loader.LoadConfig(dir, cli.config.CallModuleType)
if diags.HasErrors() {
return []*tflint.Runner{}, fmt.Errorf("Failed to load configurations; %w", diags)
}
Expand Down
33 changes: 23 additions & 10 deletions cmd/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"log"
"strings"

"github.com/terraform-linters/tflint/terraform"
"github.com/terraform-linters/tflint/tflint"
)

Expand All @@ -21,8 +22,9 @@ type Options struct {
EnablePlugins []string `long:"enable-plugin" description:"Enable plugins from the command line" value-name:"PLUGIN_NAME"`
Varfiles []string `long:"var-file" description:"Terraform variable file name" value-name:"FILE"`
Variables []string `long:"var" description:"Set a Terraform variable" value-name:"'foo=bar'"`
Module *bool `long:"module" description:"Enable module inspection"`
NoModule *bool `long:"no-module" description:"Disable module inspection"`
Module *bool `long:"module" description:"Enable module inspection" hidden:"true"`
NoModule *bool `long:"no-module" description:"Disable module inspection" hidden:"true"`
CallModuleType *string `long:"call-module-type" description:"Types of module to call (default: local)" choice:"all" choice:"local" choice:"none"`
Chdir string `long:"chdir" description:"Switch to a different working directory before executing the command" value-name:"DIR"`
Recursive bool `long:"recursive" description:"Run command in each directory recursively"`
Filter []string `long:"filter" description:"Filter issues by file names or globs" value-name:"FILE"`
Expand Down Expand Up @@ -52,14 +54,25 @@ func (opts *Options) toConfig() *tflint.Config {
opts.Variables = []string{}
}

var module, moduleSet bool
callModuleType := terraform.CallLocalModule
callModuleTypeSet := false
// --call-module-type takes precedence over --module/--no-module. This is for backward compatibility.
if opts.Module != nil {
module = *opts.Module
moduleSet = true
callModuleType = terraform.CallAllModule
callModuleTypeSet = true
}
if opts.NoModule != nil {
module = !*opts.NoModule
moduleSet = true
callModuleType = terraform.CallNoModule
callModuleTypeSet = true
}
if opts.CallModuleType != nil {
var err error
callModuleType, err = terraform.AsCallModuleType(*opts.CallModuleType)
if err != nil {
// This should never happen because the option is already validated by go-flags
panic(err)
}
callModuleTypeSet = true
}

var force, forceSet bool
Expand All @@ -69,7 +82,7 @@ func (opts *Options) toConfig() *tflint.Config {
}

log.Printf("[DEBUG] CLI Options")
log.Printf("[DEBUG] Module: %t", module)
log.Printf("[DEBUG] CallModuleType: %s", callModuleType)
log.Printf("[DEBUG] Force: %t", force)
log.Printf("[DEBUG] Format: %s", opts.Format)
log.Printf("[DEBUG] Varfiles: %s", strings.Join(opts.Varfiles, ", "))
Expand Down Expand Up @@ -113,8 +126,8 @@ func (opts *Options) toConfig() *tflint.Config {
}

return &tflint.Config{
Module: module,
ModuleSet: moduleSet,
CallModuleType: callModuleType,
CallModuleTypeSet: callModuleTypeSet,

Force: force,
ForceSet: forceSet,
Expand Down
61 changes: 46 additions & 15 deletions cmd/option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
flags "github.com/jessevdk/go-flags"
"github.com/terraform-linters/tflint/terraform"
"github.com/terraform-linters/tflint/tflint"
)

Expand All @@ -21,12 +22,27 @@ func Test_toConfig(t *testing.T) {
Command: "./tflint",
Expected: tflint.EmptyConfig(),
},
{
Name: "--call-module-type",
Command: "./tflint --call-module-type all",
Expected: &tflint.Config{
CallModuleType: terraform.CallAllModule,
CallModuleTypeSet: true,
Force: false,
IgnoreModules: map[string]bool{},
Varfiles: []string{},
Variables: []string{},
DisabledByDefault: false,
Rules: map[string]*tflint.RuleConfig{},
Plugins: map[string]*tflint.PluginConfig{},
},
},
{
Name: "--module",
Command: "./tflint --module",
Expected: &tflint.Config{
Module: true,
ModuleSet: true,
CallModuleType: terraform.CallAllModule,
CallModuleTypeSet: true,
Force: false,
IgnoreModules: map[string]bool{},
Varfiles: []string{},
Expand All @@ -40,8 +56,23 @@ func Test_toConfig(t *testing.T) {
Name: "--no-module",
Command: "./tflint --no-module",
Expected: &tflint.Config{
Module: false,
ModuleSet: true,
CallModuleType: terraform.CallNoModule,
CallModuleTypeSet: true,
Force: false,
IgnoreModules: map[string]bool{},
Varfiles: []string{},
Variables: []string{},
DisabledByDefault: false,
Rules: map[string]*tflint.RuleConfig{},
Plugins: map[string]*tflint.PluginConfig{},
},
},
{
Name: "--module and --call-module-type",
Command: "./tflint --module --call-module-type none",
Expected: &tflint.Config{
CallModuleType: terraform.CallNoModule,
CallModuleTypeSet: true,
Force: false,
IgnoreModules: map[string]bool{},
Varfiles: []string{},
Expand All @@ -55,7 +86,7 @@ func Test_toConfig(t *testing.T) {
Name: "--force",
Command: "./tflint --force",
Expected: &tflint.Config{
Module: false,
CallModuleType: terraform.CallLocalModule,
Force: true,
ForceSet: true,
IgnoreModules: map[string]bool{},
Expand All @@ -70,7 +101,7 @@ func Test_toConfig(t *testing.T) {
Name: "--ignore-module",
Command: "./tflint --ignore-module module1,module2",
Expected: &tflint.Config{
Module: false,
CallModuleType: terraform.CallLocalModule,
Force: false,
IgnoreModules: map[string]bool{"module1": true, "module2": true},
Varfiles: []string{},
Expand All @@ -84,7 +115,7 @@ func Test_toConfig(t *testing.T) {
Name: "multiple `--ignore-module`",
Command: "./tflint --ignore-module module1 --ignore-module module2",
Expected: &tflint.Config{
Module: false,
CallModuleType: terraform.CallLocalModule,
Force: false,
IgnoreModules: map[string]bool{"module1": true, "module2": true},
Varfiles: []string{},
Expand All @@ -98,7 +129,7 @@ func Test_toConfig(t *testing.T) {
Name: "--var-file",
Command: "./tflint --var-file example1.tfvars,example2.tfvars",
Expected: &tflint.Config{
Module: false,
CallModuleType: terraform.CallLocalModule,
Force: false,
IgnoreModules: map[string]bool{},
Varfiles: []string{"example1.tfvars", "example2.tfvars"},
Expand All @@ -112,7 +143,7 @@ func Test_toConfig(t *testing.T) {
Name: "multiple `--var-file`",
Command: "./tflint --var-file example1.tfvars --var-file example2.tfvars",
Expected: &tflint.Config{
Module: false,
CallModuleType: terraform.CallLocalModule,
Force: false,
IgnoreModules: map[string]bool{},
Varfiles: []string{"example1.tfvars", "example2.tfvars"},
Expand All @@ -126,7 +157,7 @@ func Test_toConfig(t *testing.T) {
Name: "--var",
Command: "./tflint --var foo=bar --var bar=baz",
Expected: &tflint.Config{
Module: false,
CallModuleType: terraform.CallLocalModule,
Force: false,
IgnoreModules: map[string]bool{},
Varfiles: []string{},
Expand All @@ -140,7 +171,7 @@ func Test_toConfig(t *testing.T) {
Name: "--enable-rule",
Command: "./tflint --enable-rule aws_instance_invalid_type --enable-rule aws_instance_previous_type",
Expected: &tflint.Config{
Module: false,
CallModuleType: terraform.CallLocalModule,
Force: false,
IgnoreModules: map[string]bool{},
Varfiles: []string{},
Expand All @@ -165,7 +196,7 @@ func Test_toConfig(t *testing.T) {
Name: "--disable-rule",
Command: "./tflint --disable-rule aws_instance_invalid_type --disable-rule aws_instance_previous_type",
Expected: &tflint.Config{
Module: false,
CallModuleType: terraform.CallLocalModule,
Force: false,
IgnoreModules: map[string]bool{},
Varfiles: []string{},
Expand All @@ -190,7 +221,7 @@ func Test_toConfig(t *testing.T) {
Name: "--only",
Command: "./tflint --only aws_instance_invalid_type",
Expected: &tflint.Config{
Module: false,
CallModuleType: terraform.CallLocalModule,
Force: false,
IgnoreModules: map[string]bool{},
Varfiles: []string{},
Expand All @@ -212,7 +243,7 @@ func Test_toConfig(t *testing.T) {
Name: "--enable-plugin",
Command: "./tflint --enable-plugin test --enable-plugin another-test",
Expected: &tflint.Config{
Module: false,
CallModuleType: terraform.CallLocalModule,
Force: false,
IgnoreModules: map[string]bool{},
Varfiles: []string{},
Expand All @@ -237,7 +268,7 @@ func Test_toConfig(t *testing.T) {
Name: "--format",
Command: "./tflint --format compact",
Expected: &tflint.Config{
Module: false,
CallModuleType: terraform.CallLocalModule,
Force: false,
IgnoreModules: map[string]bool{},
Varfiles: []string{},
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This guide describes the various features of TFLint for end users.
- [Configuring TFLint](config.md)
- [Configuring Plugins](plugins.md)
- [Switching working directory](working-directory.md)
- [Module Inspection](module-inspection.md)
- [Calling Modules](calling-modules.md)
- [Annotations](annotations.md)
- [Autofix](autofix.md)
- [Compatibility with Terraform](compatibility.md)
Expand Down
60 changes: 60 additions & 0 deletions docs/user-guide/calling-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Calling Modules

You can inspect not only the root module but also [module calls](https://developer.hashicorp.com/terraform/language/modules/syntax#calling-a-child-module). TFLint evaluates each call (i.e. `module` block) and emits any issues that result from the specified input variables.

```hcl
module "aws_instance" {
source = "./module"

ami = "ami-b73b63a0"
instance_type = "t1.2xlarge"
}
```

```console
$ tflint
1 issue(s) found:

Error: instance_type is not a valid value (aws_instance_invalid_type)

on template.tf line 5:
5: instance_type = "t1.2xlarge"

Callers:
template.tf:5,19-31
module/instance.tf:5,19-36

```

By default, TFLint only calls local modules whose the `source` is a relative path like `./*`. If you want to call remote modules (registry, git, etc.), you must run `terraform init` (or `terraform get`) before invoking TFLint so that modules are loaded into the `.terraform` directory. After that, invoke TFLint with `--call-module-type=all`.

```console
$ terraform init
$ tflint --call-module-type=all
```

The `--call-module-type` can also be set via configuration:

```hcl
config {
call_module_type = "all"
}
```

If you don't want to call any modules, pass `--call-module-type=none`:

```console
$ tflint --call-module-type=none
```

If you want to ignore inspection for a particular module, you can use `--ignore-module`:

```console
$ tflint --ignore-module=./module
```

## Caveats

* Issues _must_ be associated with a variable that was passed to the module. If an issue within a child module is detected in an expression that does not reference a variable (`var`), it will be discarded.
* Rules that evaluate syntax rather than content _should_ ignore child modules.
* If you want to evaluate all TFLint rules on non-root modules, inspect directly against the module directories. Note that there is a difference between calling a child module in an inspection and inspecting a child module as the root module.
4 changes: 3 additions & 1 deletion docs/user-guide/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ Similar to support for meta-arguments, some rules may process a dynamic block as

## Modules

Resources contained within modules are ignored by default, but when the [Module Inspection](./module-inspection.md) is enabled, the arguments of module calls are inspected.
TFLint doesn't automatically inspect the content of modules themselves. However, by default, it will analyze their content in order to raise any issues that arise from attributes in module calls.

```hcl
resource "aws_instance" "static" {
Expand All @@ -159,6 +159,8 @@ module "aws_instance" {
}
```

Remote modules can also be inspected. See [Calling Modules](./calling-modules.md) for details.

## Environment Variables

The following environment variables are supported:
Expand Down
26 changes: 21 additions & 5 deletions docs/user-guide/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ config {
format = "compact"
plugin_dir = "~/.tflint.d/plugins"

module = true
call_module_type = "local"
force = false
disabled_by_default = false

Expand Down Expand Up @@ -63,11 +63,27 @@ In recursive mode (`--recursive`), this field will be ignored in configuration f

Set the plugin directory. The default is `~/.tflint.d/plugins` (or `./.tflint.d/plugins`). See also [Configuring Plugins](plugins.md#advanced-usage)

### `module`
### `call_module_type`

CLI flag: `--call-module-type`

Select types of module to call. The following values are valid:

CLI flag: `--module`
- all
- local (default)
- none

Enable [Module Inspection](module-inspection.md).
If you select `all`, you can call all (local and remote) modules. See [Calling Modules](./calling-modules.md).

```hcl
config {
call_module_type = "all"
}
```

```console
$ tflint --call-module-type=all
```

### `force`

Expand Down Expand Up @@ -110,7 +126,7 @@ $ tflint --only aws_instance_invalid_type --only aws_instance_previous_type

CLI flag: `--ignore-module`

Skip inspections for module calls in [Module Inspection](module-inspection.md). Note that you need to specify module sources rather than module ids for backward compatibility.
Adding a module source to `ignore_module` will cause it to be ignored when [calling modules](./calling-modules.md). Note that you need to specify module sources rather than module ids for backward compatibility.

```hcl
config {
Expand Down
Loading
Loading