diff --git a/Makefile b/Makefile
index 538ad8841..612df6ce9 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,7 @@ build:
install:
go install
-e2e: prepare
+e2e: prepare install
go test -timeout 5m ./integrationtest/inspection ./integrationtest/langserver ./integrationtest/init ./integrationtest/cli
lint:
diff --git a/README.md b/README.md
index ecb9bda1f..28b4ff5ef 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ A Pluggable [Terraform](https://www.terraform.io/) Linter
TFLint is a framework and each feature is provided by plugins, the key features are as follows:
-- Find possible errors (like illegal instance types) for Major Cloud providers (AWS/Azure/GCP).
+- Find possible errors (like invalid instance types) for Major Cloud providers (AWS/Azure/GCP).
- Warn about deprecated syntax, unused declarations.
- Enforce best practices, naming conventions.
@@ -74,14 +74,25 @@ If you want to run on GitHub Actions, [setup-tflint](https://github.com/terrafor
## Getting Started
-If you are using an AWS/Azure/GCP provider, it is a good idea to install the plugin and try it according to each usage:
+First, enable rules for [Terraform Language](https://www.terraform.io/language) (e.g. warn about deprecated syntax, unused declarations). [TFLint Ruleset for Terraform Language](https://github.com/terraform-linters/tflint-ruleset-terraform) is bundled with TFLint, so you can use it without installing it separately.
+
+The bundled plugin enables the "recommended" preset by default, but you can disable the plugin or use a different preset. Declare the plugin block in `.tflint.hcl` like this:
+
+```hcl
+plugin "terraform" {
+ enabled = true
+ preset = "recommended"
+}
+```
+
+See the [tflint-ruleset-terraform documentation](https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/configuration.md) for more information.
+
+Next, If you are using an AWS/Azure/GCP provider, it is a good idea to install the plugin and try it according to each usage:
- [Amazon Web Services](https://github.com/terraform-linters/tflint-ruleset-aws)
- [Microsoft Azure](https://github.com/terraform-linters/tflint-ruleset-azurerm)
- [Google Cloud Platform](https://github.com/terraform-linters/tflint-ruleset-google)
-Rules for the Terraform Language is built into the TFLint binary, so you don't need to install any plugins. Please see [Rules](docs/rules) for a list of available rules.
-
If you want to extend TFLint with other plugins, you can declare the plugins in the config file and easily install them with `tflint --init`.
```hcl
diff --git a/cmd/bundled_plugin.go b/cmd/bundled_plugin.go
new file mode 100644
index 000000000..f35ff3537
--- /dev/null
+++ b/cmd/bundled_plugin.go
@@ -0,0 +1,24 @@
+package cmd
+
+import (
+ "fmt"
+
+ "github.com/terraform-linters/tflint-plugin-sdk/plugin"
+ "github.com/terraform-linters/tflint-plugin-sdk/tflint"
+ "github.com/terraform-linters/tflint-ruleset-terraform/project"
+ "github.com/terraform-linters/tflint-ruleset-terraform/rules"
+ "github.com/terraform-linters/tflint-ruleset-terraform/terraform"
+)
+
+func (cli *CLI) actAsBundledPlugin() int {
+ plugin.Serve(&plugin.ServeOpts{
+ RuleSet: &terraform.RuleSet{
+ BuiltinRuleSet: tflint.BuiltinRuleSet{
+ Name: "terraform",
+ Version: fmt.Sprintf("%s-bundled", project.Version),
+ },
+ PresetRules: rules.PresetRules,
+ },
+ })
+ return ExitCodeOK
+}
diff --git a/cmd/cli.go b/cmd/cli.go
index 647b64299..8a1f79756 100644
--- a/cmd/cli.go
+++ b/cmd/cli.go
@@ -97,6 +97,8 @@ func (cli *CLI) Run(args []string) int {
return cli.init(opts)
case opts.Langserver:
return cli.startLanguageServer(opts.Config, opts.toConfig())
+ case opts.ActAsBundledPlugin:
+ return cli.actAsBundledPlugin()
default:
return cli.inspect(opts, dir, filterFiles)
}
diff --git a/cmd/inspect.go b/cmd/inspect.go
index 4a1b39e6f..59fa381a5 100644
--- a/cmd/inspect.go
+++ b/cmd/inspect.go
@@ -7,7 +7,6 @@ import (
"github.com/spf13/afero"
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
"github.com/terraform-linters/tflint/plugin"
- "github.com/terraform-linters/tflint/rules"
"github.com/terraform-linters/tflint/tflint"
)
@@ -51,7 +50,7 @@ func (cli *CLI) inspect(opts Options, dir string, filterFiles []string) int {
}
defer rulesetPlugin.Clean()
- rulesets := []tflint.RuleSet{&rules.RuleSet{}}
+ rulesets := []tflint.RuleSet{}
config := cfg.ToPluginConfig()
for name, ruleset := range rulesetPlugin.RuleSets {
if err := ruleset.ApplyGlobalConfig(config); err != nil {
@@ -86,16 +85,6 @@ func (cli *CLI) inspect(opts Options, dir string, filterFiles []string) int {
}
// Run inspection
- for _, rule := range rules.NewRules(cfg) {
- for _, runner := range runners {
- err := rule.Check(runner)
- if err != nil {
- cli.formatter.Print(tflint.Issues{}, fmt.Errorf("Failed to check `%s` rule; %w", rule.Name(), err), cli.loader.Sources())
- return ExitCodeError
- }
- }
- }
-
for _, ruleset := range rulesetPlugin.RuleSets {
for _, runner := range runners {
err = ruleset.Check(plugin.NewGRPCServer(runner, rootRunner, cli.loader.Files()))
diff --git a/cmd/option.go b/cmd/option.go
index 0d8803d6c..83ab5f289 100644
--- a/cmd/option.go
+++ b/cmd/option.go
@@ -9,23 +9,24 @@ import (
// Options is an option specified by arguments.
type Options struct {
- Version bool `short:"v" long:"version" description:"Print TFLint version"`
- Init bool `long:"init" description:"Install plugins"`
- Langserver bool `long:"langserver" description:"Start language server"`
- Format string `short:"f" long:"format" description:"Output format" choice:"default" choice:"json" choice:"checkstyle" choice:"junit" choice:"compact" choice:"sarif"`
- Config string `short:"c" long:"config" description:"Config file name" value-name:"FILE" default:".tflint.hcl"`
- IgnoreModules []string `long:"ignore-module" description:"Ignore module sources" value-name:"SOURCE"`
- EnableRules []string `long:"enable-rule" description:"Enable rules from the command line" value-name:"RULE_NAME"`
- DisableRules []string `long:"disable-rule" description:"Disable rules from the command line" value-name:"RULE_NAME"`
- Only []string `long:"only" description:"Enable only this rule, disabling all other defaults. Can be specified multiple times" value-name:"RULE_NAME"`
- 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:"Inspect modules"`
- Force bool `long:"force" description:"Return zero exit status even if issues found"`
- Color bool `long:"color" description:"Enable colorized output"`
- NoColor bool `long:"no-color" description:"Disable colorized output"`
- LogLevel string `long:"loglevel" description:"Change the loglevel" choice:"trace" choice:"debug" choice:"info" choice:"warn" choice:"error"`
+ Version bool `short:"v" long:"version" description:"Print TFLint version"`
+ Init bool `long:"init" description:"Install plugins"`
+ Langserver bool `long:"langserver" description:"Start language server"`
+ Format string `short:"f" long:"format" description:"Output format" choice:"default" choice:"json" choice:"checkstyle" choice:"junit" choice:"compact" choice:"sarif"`
+ Config string `short:"c" long:"config" description:"Config file name" value-name:"FILE" default:".tflint.hcl"`
+ IgnoreModules []string `long:"ignore-module" description:"Ignore module sources" value-name:"SOURCE"`
+ EnableRules []string `long:"enable-rule" description:"Enable rules from the command line" value-name:"RULE_NAME"`
+ DisableRules []string `long:"disable-rule" description:"Disable rules from the command line" value-name:"RULE_NAME"`
+ Only []string `long:"only" description:"Enable only this rule, disabling all other defaults. Can be specified multiple times" value-name:"RULE_NAME"`
+ 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:"Inspect modules"`
+ Force bool `long:"force" description:"Return zero exit status even if issues found"`
+ Color bool `long:"color" description:"Enable colorized output"`
+ NoColor bool `long:"no-color" description:"Disable colorized output"`
+ LogLevel string `long:"loglevel" description:"Change the loglevel" choice:"trace" choice:"debug" choice:"info" choice:"warn" choice:"error"`
+ ActAsBundledPlugin bool `long:"act-as-bundled-plugin" hidden:"true"`
}
func (opts *Options) toConfig() *tflint.Config {
diff --git a/docs/README.md b/docs/README.md
index 1e8c32ba6..f4fdc4601 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,5 +1,4 @@
# TFLint Documentation
- [User Guide](user-guide)
-- [Rules](rules)
- [Developer Guide](developer-guide)
diff --git a/docs/rules/README.md b/docs/rules/README.md
deleted file mode 100644
index 19906a02f..000000000
--- a/docs/rules/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Rules
-
-Rules are usually provided by ruleset plugins, but the rules for the Terraform Language are built into the TFLint binary. Terraform language rules implement recommendations from the [Terraform language documentation](https://www.terraform.io/language). If you want to enforce additional usage and style conventions in your configuration, you can author your own ruleset plugin.
-
-Below is a list of available rules.
-
-|Rule|Description|Enabled|
-| --- | --- | --- |
-|[terraform_comment_syntax](terraform_comment_syntax.md)|Disallow `//` comments in favor of `#`||
-|[terraform_deprecated_index](terraform_deprecated_index.md)|Disallow legacy dot index syntax||
-|[terraform_deprecated_interpolation](terraform_deprecated_interpolation.md)|Disallow deprecated (0.11-style) interpolation|✔|
-|[terraform_documented_outputs](terraform_documented_outputs.md)|Disallow `output` declarations without description||
-|[terraform_documented_variables](terraform_documented_variables.md)|Disallow `variable` declarations without description||
-|[terraform_empty_list_equality](terraform_empty_list_equality.md)|Disallow comparisons with `[]` when checking if a collection is empty||
-|[terraform_module_pinned_source](terraform_module_pinned_source.md)|Disallow specifying a git or mercurial repository as a module source without pinning to a version|✔|
-|[terraform_module_version](terraform_module_version.md)|Checks that Terraform modules sourced from a registry specify a version|✔|
-|[terraform_naming_convention](terraform_naming_convention.md)|Enforces naming conventions for resources, data sources, etc||
-|[terraform_required_providers](terraform_required_providers.md)|Require that all providers have version constraints through required_providers||
-|[terraform_required_version](terraform_required_version.md)|Disallow `terraform` declarations without require_version||
-|[terraform_standard_module_structure](terraform_standard_module_structure.md)|Ensure that a module complies with the Terraform Standard Module Structure||
-|[terraform_typed_variables](terraform_typed_variables.md)|Disallow `variable` declarations without type||
-|[terraform_unused_declarations](terraform_unused_declarations.md)|Disallow variables, data sources, and locals that are declared but never used||
-|[terraform_unused_required_providers](terraform_unused_required_providers.md)|Check that all `required_providers` are used in the module||
-|[terraform_workspace_remote](terraform_workspace_remote.md)|`terraform.workspace` should not be used with a "remote" backend with remote execution|✔|
diff --git a/docs/rules/terraform_comment_syntax.md b/docs/rules/terraform_comment_syntax.md
deleted file mode 100644
index 50d0042a4..000000000
--- a/docs/rules/terraform_comment_syntax.md
+++ /dev/null
@@ -1,32 +0,0 @@
-# terraform_comment_syntax
-
-Disallow `//` comments in favor of `#`.
-
-## Example
-
-```hcl
-# Good
-// Bad
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: Single line comments should begin with # (terraform_comment_syntax)
-
- on main.tf line 2:
- 2: // Bad
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.16.0/docs/rules/terraform_typed_variables.md
-```
-
-## Why
-
-The Terraform language supports two different syntaxes for single-line comments: `#` and `//`. However, `#` is the default comment style and should be used in most cases.
-
-* [Configuration Syntax: Comments](https://www.terraform.io/docs/configuration/syntax.html#comments)
-
-## How To Fix
-
-Replace the leading double-slash (`//`) in your comment with the number sign (`#`).
diff --git a/docs/rules/terraform_deprecated_index.md b/docs/rules/terraform_deprecated_index.md
deleted file mode 100644
index 35e2b79eb..000000000
--- a/docs/rules/terraform_deprecated_index.md
+++ /dev/null
@@ -1,32 +0,0 @@
-# terraform_deprecated_index
-
-Disallow legacy dot index syntax.
-
-## Example
-
-```hcl
-locals {
- list = ["a", "b", "c"]
- value = list.0
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: List items should be accessed using square brackets (terraform_deprecated_index)
-
- on example.tf line 3:
- 3: value = list.0
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.16.1/docs/rules/terraform_deprecated_index.md
-```
-
-## Why
-
-Terraform v0.12 supports traditional square brackets for accessing list items by index. However, for backward compatability with v0.11, Terraform continues to support accessing list items with the dot syntax normally used for attributes. While Terraform does not print warnings for this syntax, it is no longer documented and its use is discouraged.
-
-## How To Fix
-
-Switch to the square bracket syntax when accessing items in list, including resources that use `count`.
diff --git a/docs/rules/terraform_deprecated_interpolation.md b/docs/rules/terraform_deprecated_interpolation.md
deleted file mode 100644
index 192515e4e..000000000
--- a/docs/rules/terraform_deprecated_interpolation.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# terraform_deprecated_interpolation
-
-Disallow deprecated (0.11-style) interpolation
-
-## Example
-
-```hcl
-resource "aws_instance" "deprecated" {
- instance_type = "${var.type}"
-}
-
-resource "aws_instance" "new" {
- instance_type = var.type
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: Interpolation-only expressions are deprecated in Terraform v0.12.14 (terraform_deprecated_interpolation)
-
- on example.tf line 2:
- 2: instance_type = "${var.type}"
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.14.0/docs/rules/terraform_deprecated_interpolation.md
-
-```
-
-## Why
-
-Terraform v0.12 introduces a new interpolation syntax, but continues to support the old 0.11-style interpolation syntax for compatibility.
-
-Terraform will currently print diagnostic warnings when deprecated interpolations are used. Consistent with its deprecation policy, they will raise errors in the next major release (v0.13). TFLint emits an issue instead of a warning with the same logic.
-
-## How To Fix
-
-Switch to the new interpolation syntax. See the release notes for Terraform 0.12.14 for details: https://github.com/hashicorp/terraform/releases/tag/v0.12.14
\ No newline at end of file
diff --git a/docs/rules/terraform_documented_outputs.md b/docs/rules/terraform_documented_outputs.md
deleted file mode 100644
index 87d262c8e..000000000
--- a/docs/rules/terraform_documented_outputs.md
+++ /dev/null
@@ -1,49 +0,0 @@
-# terraform_documented_outputs
-
-Disallow `output` declarations without description.
-
-## Example
-
-```hcl
-output "no_description" {
- value = "value"
-}
-
-output "empty_description" {
- value = "value"
- description = ""
-}
-
-output "description" {
- value = "value"
- description = "This is description"
-}
-```
-
-```
-$ tflint
-2 issue(s) found:
-
-Notice: `no_description` output has no description (terraform_documented_outputs)
-
- on template.tf line 1:
- 1: output "no_description" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.11.0/docs/rules/terraform_documented_outputs.md
-
-Notice: `empty_description` output has no description (terraform_documented_outputs)
-
- on template.tf line 5:
- 5: output "empty_description" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.11.0/docs/rules/terraform_documented_outputs.md
-
-```
-
-## Why
-
-Since `description` is optional value, it is not always necessary to write it. But this rule is useful if you want to force the writing of description. Especially it is useful when combined with [terraform-docs](https://github.com/segmentio/terraform-docs).
-
-## How To Fix
-
-Write a description other than an empty string.
diff --git a/docs/rules/terraform_documented_variables.md b/docs/rules/terraform_documented_variables.md
deleted file mode 100644
index a4229cd16..000000000
--- a/docs/rules/terraform_documented_variables.md
+++ /dev/null
@@ -1,49 +0,0 @@
-# terraform_documented_variables
-
-Disallow `variable` declarations without description.
-
-## Example
-
-```hcl
-variable "no_description" {
- default = "value"
-}
-
-variable "empty_description" {
- default = "value"
- description = ""
-}
-
-variable "description" {
- default = "value"
- description = "This is description"
-}
-```
-
-```
-$ tflint
-2 issue(s) found:
-
-Notice: `no_description` variable has no description (terraform_documented_variables)
-
- on template.tf line 1:
- 1: variable "no_description" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.11.0/docs/rules/terraform_documented_variables.md
-
-Notice: `empty_description` variable has no description (terraform_documented_variables)
-
- on template.tf line 5:
- 5: variable "empty_description" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.11.0/docs/rules/terraform_documented_variables.md
-
-```
-
-## Why
-
-Since `description` is optional value, it is not always necessary to write it. But this rule is useful if you want to force the writing of description. Especially it is useful when combined with [terraform-docs](https://github.com/segmentio/terraform-docs).
-
-## How To Fix
-
-Write a description other than an empty string.
diff --git a/docs/rules/terraform_empty_list_equality.md b/docs/rules/terraform_empty_list_equality.md
deleted file mode 100644
index 68b00d6f8..000000000
--- a/docs/rules/terraform_empty_list_equality.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# terraform_empty_list_equality
-
-Disallow comparisons with `[]` when checking if a collection is empty.
-
-## Example
-
-```hcl
-variable "my_list" {
- type = list(string)
-}
-resource "aws_db_instance" "mysql" {
- count = var.my_list == [] ? 0 : 1
- instance_class = "m4.2xlarge"
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: Comparing a collection with an empty list is invalid. To detect an empty collection, check its length. (terraform_empty_list_equality)
-
- on test.tf line 5:
- 5: count = var.my_list == [] ? 0 : 1
-
-Reference: https://github.com/terraform-linters/tflint/blob/master/docs/rules/terraform_empty_list_equality.md
-
-```
-
-## Why
-
-The `==` operator can only return true when the two operands have identical types, and the type of `[]` alone (without any further type conversions) is an empty tuple rather than a list of objects, strings, numbers or any other type. Therefore, a comparison with a single `[]` with the goal of checking if a collection is empty, will always return false.
-
-## How To Fix
-
-Check if a collection is empty by checking its length instead. For example: `length(var.my_list) == 0`.
diff --git a/docs/rules/terraform_module_pinned_source.md b/docs/rules/terraform_module_pinned_source.md
deleted file mode 100644
index 7123b373f..000000000
--- a/docs/rules/terraform_module_pinned_source.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# terraform_module_pinned_source
-
-Disallow specifying a git or mercurial repository as a module source without pinning to a version.
-
-## Configuration
-
-Name | Default | Value
---- | --- | ---
-enabled | true | Boolean
-style | `flexible` | `flexible`, `semver`
-default_branches | `["master", "main", "default", "develop"]` |
-
-```hcl
-rule "terraform_module_pinned_source" {
- enabled = true
- style = "flexible"
- default_branches = ["dev"]
-}
-```
-
-Configured `default_branches` will be appended to the defaults rather than overriding them.
-
-## Example
-
-### style = "flexible"
-
-In the "flexible" style, all sources must be pinned to non-default version.
-
-```hcl
-module "unpinned" {
- source = "git://hashicorp.com/consul.git"
-}
-
-module "default_git" {
- source = "git://hashicorp.com/consul.git?ref=master"
-}
-
-module "default_mercurial" {
- source = "hg::http://hashicorp.com/consul.hg?rev=default"
-}
-
-module "pinned_git" {
- source = "git://hashicorp.com/consul.git?ref=feature"
-}
-```
-
-```
-$ tflint
-3 issue(s) found:
-
-Warning: Module source "git://hashicorp.com/consul.git" is not pinned (terraform_module_pinned_source)
-
- on template.tf line 2:
- 2: source = "git://hashicorp.com/consul.git"
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.15.0/docs/rules/terraform_module_pinned_source.md
-
-Warning: Module source "git://hashicorp.com/consul.git?ref=master" uses a default branch as ref (master) (terraform_module_pinned_source)
-
- on template.tf line 6:
- 6: source = "git://hashicorp.com/consul.git?ref=master"
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.15.0/docs/rules/terraform_module_pinned_source.md
-
-Warning: Module source "hg::http://hashicorp.com/consul.hg?rev=default" uses a default branch as rev (default) (terraform_module_pinned_source)
-
- on template.tf line 10:
- 10: source = "hg::http://hashicorp.com/consul.hg?rev=default"
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.15.0/docs/rules/terraform_module_pinned_source.md
-
-```
-
-### style = "semver"
-
-In the "semver" style, all sources must be pinned to semantic version reference. This is stricter than the "flexible" style.
-
-```hcl
-module "unpinned" {
- source = "git://hashicorp.com/consul.git"
-}
-
-module "pinned_to_branch" {
- source = "git://hashicorp.com/consul.git?ref=feature"
-}
-
-module "pinned_to_version" {
- source = "git://hashicorp.com/consul.git?ref=v1.2.0"
-}
-```
-
-```
-$ tflint
-2 issue(s) found:
-
-Warning: Module source "git://hashicorp.com/consul.git" is not pinned (terraform_module_pinned_source)
-
- on template.tf line 2:
- 2: source = "git://hashicorp.com/consul.git"
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.15.0/docs/rules/terraform_module_pinned_source.md
-
-Warning: Module source "git://hashicorp.com/consul.git?ref=feature" uses a ref which is not a semantic version string (terraform_module_pinned_source)
-
- on template.tf line 6:
- 6: source = "git://hashicorp.com/consul.git?ref=feature"
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.15.0/docs/rules/terraform_module_pinned_source.md
-
-```
-
-## Why
-
-Terraform allows you to source modules from source control repositories. If you do not pin the revision to use, the dependency you require may introduce unexpected breaking changes. To prevent this, always specify an explicit version to check out.
-
-Pinning to a mutable reference, such as a branch, still allows for unintended breaking changes. Semver style can help avoid this.
-
-## How To Fix
-
-Specify a version pin. For git repositories, it should not be "master". For Mercurial repositories, it should not be "default".
-
-In the "semver" style: specify a semantic version pin of the form `vX.Y.Z`. The leading `v` is optional.
diff --git a/docs/rules/terraform_module_version.md b/docs/rules/terraform_module_version.md
deleted file mode 100644
index 18b6cafc7..000000000
--- a/docs/rules/terraform_module_version.md
+++ /dev/null
@@ -1,97 +0,0 @@
-# terraform_module_version
-
-Ensure that all modules sourced from a [Terraform Registry](https://www.terraform.io/docs/language/modules/sources.html#terraform-registry) specify a `version`.
-
-## Configuration
-
-Name | Description | Default | Type
---- | --- | --- | ---
-exact | Require an exact version | false | Boolean
-
-```hcl
-rule "terraform_module_version" {
- enabled = true
- exact = false # default
-}
-```
-
-## Example
-
-```tf
-module "exact" {
- source = "terraform-aws-modules/vpc/aws"
- version = "1.0.0"
-}
-
-module "range" {
- source = "terraform-aws-modules/vpc/aws"
- version = ">= 1.0.0"
-}
-
-module "latest" {
- source = "terraform-aws-modules/vpc/aws"
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: module "latest" should specify a version (terraform_module_version)
-
- on main.tf line 11:
- 11: module "latest" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/master/docs/rules/terraform_module_version.md
-```
-
-### Exact
-
-```hcl
-rule "terraform_module_version" {
- enabled = true
- exact = true
-}
-```
-
-```tf
-module "exact" {
- source = "terraform-aws-modules/vpc/aws"
- version = "1.0.0"
-}
-
-module "range" {
- source = "terraform-aws-modules/vpc/aws"
- version = ">= 1.0.0"
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: module "range" should specify an exact version, but a range was found (terraform_module_version)
-
- on main.tf line 8:
- 8: version = ">= 1.0.0"
-
-Reference: https://github.com/terraform-linters/tflint/blob/master/docs/rules/terraform_module_version.md
-```
-
-## Why
-
-Terraform's [module version documentation](https://www.terraform.io/docs/language/modules/syntax.html#version) states:
-
-> When using modules installed from a module registry, we recommend explicitly constraining the acceptable version numbers to avoid unexpected or unwanted changes.
-
-When no `version` is specified, Terraform will download the latest version available on the registry. Using a new major version of a module could cause the destruction of existing resources, or the creation of new resources that are not backwards compatible. Generally you should at least constrain modules to a specific major version.
-
-### Exact Versions
-
-Depending on your workflow, you may want to enforce that modules specify an _exact_ version by settings `exact = true` for this rule. This will disallow any module that includes multiple comma-separated version constraints, or any [constraint operator](https://www.terraform.io/docs/language/expressions/version-constraints.html#version-constraint-syntax) other than `=`. Exact versions are often used with automated dependency managers like [Dependabot](https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/about-dependabot-version-updates) and [Renovate](https://docs.renovatebot.com), which will automatically propose a pull request to update the module when a new version is released.
-
-Keep in mind that the module may include further child modules, which have their own version constraints. TFLint _does not_ check version constraints set in child modules. **Enabling this rule cannot guarantee that `terraform init` will be deterministic**. Use [Terraform dependency lock files](https://www.terraform.io/docs/language/dependency-lock.html) to ensure that Terraform will always use the same version of all modules (and providers) until you explicitly update them.
-
-## How To Fix
-
-Specify a `version`. If `exact = true`, this must be an exact version.
diff --git a/docs/rules/terraform_naming_convention.md b/docs/rules/terraform_naming_convention.md
deleted file mode 100644
index cc9e8ff73..000000000
--- a/docs/rules/terraform_naming_convention.md
+++ /dev/null
@@ -1,285 +0,0 @@
-# terraform_naming_convention
-
-Enforces naming conventions for the following blocks:
-
-* Resources
-* Input variables
-* Output values
-* Local values
-* Modules
-* Data sources
-
-## Configuration
-
-Name | Default | Value
---- | --- | ---
-enabled | `false` | Boolean
-format | `snake_case` | `snake_case`, `mixed_snake_case`, `none` or a custom format defined using the `custom_formats` attribute
-custom | `""` | String representation of a golang regular expression that the block name must match
-custom_formats | `{}` | Definition of custom formats that can be used in the `format` attribute
-data | | Block settings to override naming convention for data sources
-locals | | Block settings to override naming convention for local values
-module | | Block settings to override naming convention for modules
-output | | Block settings to override naming convention for output values
-resource | | Block settings to override naming convention for resources
-variable | | Block settings to override naming convention for input variables
-
-
-#### `format`
-
-The `format` option defines the allowed formats for the block label.
-This option accepts one of the following values:
-
-* `snake_case` - standard snake_case format - all characters must be lower-case, and underscores are allowed.
-* `mixed_snake_case` - modified snake_case format - characters may be upper or lower case, and underscores are allowed.
-* `none` - signifies "this block shall not have its format checked". This can be useful if you want to enforce no particular format for a block.
-
-#### `custom`
-
-The `custom` option defines a custom regex that the identifier must match. This option allows you to have a bit more finer-grained control over identifiers, letting you force certain patterns and substrings.
-
-#### `custom_formats`
-
-The `custom_formats` attribute defines additional formats that can be used in the `format` option. Like `custom`, it allows you to define a custom regular expression that the identifier must match, but it also lets you supply a description that will be shown when the check fails. Also, it allows you to reuse a custom regex.
-
-This attribute is a map, where the keys are the identifiers of the custom formats, and the values are objects with a `regex` and a `description` key.
-
-## Examples
-
-### Default - enforce snake_case for all blocks
-
-#### Rule configuration
-
-```hcl
-rule "terraform_naming_convention" {
- enabled = true
-}
-```
-
-#### Sample terraform source file
-
-```hcl
-data "aws_eip" "camelCase" {
-}
-
-data "aws_eip" "valid_name" {
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Notice: data name `camelCase` must match the following format: snake_case (terraform_naming_convention)
-
- on template.tf line 1:
- 1: data "aws_eip" "camelCase" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.15.3/docs/rules/terraform_naming_convention.md
-
-```
-
-
-### Custom naming expression for all blocks
-
-#### Rule configuration
-
-```hcl
-rule "terraform_naming_convention" {
- enabled = true
-
- custom = "^[a-zA-Z]+([_-][a-zA-Z]+)*$"
-}
-```
-
-#### Sample terraform source file
-
-```hcl
-resource "aws_eip" "Invalid_Name_With_Number123" {
-}
-
-resource "aws_eip" "Name-With_Dash" {
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Notice: resource name `Invalid_Name_With_Number123` must match the following RegExp: ^[a-zA-Z]+([_-][a-zA-Z]+)*$ (terraform_naming_convention)
-
- on template.tf line 1:
- 1: resource "aws_eip" "Invalid_Name_With_Number123" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.15.3/docs/rules/terraform_naming_convention.md
-
-```
-
-
-### Custom format for all blocks
-
-#### Rule configuration
-
-```hcl
-rule "terraform_naming_convention" {
- enabled = true
- format = "custom_format"
-
- custom_formats = {
- custom_format = {
- description = "Custom Format"
- regex = "^[a-zA-Z]+([_-][a-zA-Z]+)*$"
- }
- }
-}
-```
-
-#### Sample terraform source file
-
-```hcl
-resource "aws_eip" "Invalid_Name_With_Number123" {
-}
-
-resource "aws_eip" "Name-With_Dash" {
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Notice: resource name `Invalid_Name_With_Number123` must match the following format: Custom Format (terraform_naming_convention)
-
- on template.tf line 1:
- 1: resource "aws_eip" "Invalid_Name_With_Number123" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.15.3/docs/rules/terraform_naming_convention.md
-
-```
-
-
-### Override default setting for specific block type
-
-#### Rule configuration
-
-```hcl
-rule "terraform_naming_convention" {
- enabled = true
-
- module {
- custom = "^[a-zA-Z]+(_[a-zA-Z]+)*$"
- }
-}
-```
-
-#### Sample terraform source file
-
-```hcl
-// data name enforced with default snake_case
-data "aws_eip" "eip_1a" {
-}
-
-module "valid_module" {
- source = ""
-}
-
-module "invalid_module_with_number_1a" {
- source = ""
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Notice: module name `invalid_module_with_number_1a` must match the following RegExp: ^[a-zA-Z]+(_[a-zA-Z]+)*$ (terraform_naming_convention)
-
- on template.tf line 9:
- 9: module "invalid_module_with_number_1a" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.15.3/docs/rules/terraform_naming_convention.md
-
-```
-
-### Disable for specific block type
-
-#### Rule configuration
-
-```hcl
-rule "terraform_naming_convention" {
- enabled = true
-
- module {
- format = "none"
- }
-}
-```
-
-#### Sample terraform source file
-
-```hcl
-// data name enforced with default snake_case
-data "aws_eip" "eip_1a" {
-}
-
-// module names will not be enforced
-module "Valid_Name-Not-Enforced" {
- source = ""
-}
-```
-
-
-### Disable for all blocks but enforce a specific block type
-
-#### Rule configuration
-
-```hcl
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- local {
- format = "snake_case"
- }
-}
-```
-
-#### Sample terraform source file
-
-```hcl
-// Data block name not enforced
-data "aws_eip" "EIP_1a" {
-}
-
-// Resource block name not enforced
-resource "aws_eip" "EIP_1b" {
-}
-
-// local variable names enforced
-locals {
- valid_name = "valid"
- invalid-name = "dashes are not allowed with snake_case"
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Notice: local value name `invalid-name` must match the following format: snake_case (terraform_naming_convention)
-
- on template.tf line 12:
- 12: invalid-name = "dashes are not allowed with snake_case"
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.15.3/docs/rules/terraform_naming_convention.md
-
-```
-
-## Why
-
-Naming conventions are optional, so it is not necessary to follow this.
-But this rule is useful if you want to force the following naming conventions in line with the [Terraform Plugin Naming Best Practices](https://www.terraform.io/docs/extend/best-practices/naming.html).
-
-## How To Fix
-
-Update the block label according to the format or custom regular expression.
diff --git a/docs/rules/terraform_required_providers.md b/docs/rules/terraform_required_providers.md
deleted file mode 100644
index 2be4813fe..000000000
--- a/docs/rules/terraform_required_providers.md
+++ /dev/null
@@ -1,74 +0,0 @@
-# terraform_required_providers
-
-Require that all providers have version constraints through `required_providers`.
-
-## Configuration
-
-```hcl
-rule "terraform_required_providers" {
- enabled = true
-}
-```
-
-## Examples
-
-```hcl
-provider "template" {}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: Missing version constraint for provider "template" in "required_providers" (terraform_required_providers)
-
- on main.tf line 1:
- 1: provider "template" {}
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.18.0/docs/rules/terraform_required_providers.md
-```
-
-
-
-```hcl
-provider "template" {
- version = "2"
-}
-```
-
-```
-$ tflint
-2 issue(s) found:
-
-Warning: provider.template: version constraint should be specified via "required_providers" (terraform_required_providers)
-
- on main.tf line 1:
- 1: provider "template" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.18.0/docs/rules/terraform_required_providers.md
-
-Warning: Missing version constraint for provider "template" in "required_providers" (terraform_required_providers)
-
- on main.tf line 1:
- 1: provider "template" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.18.0/docs/rules/terraform_required_providers.md
-```
-
-## Why
-
-Providers are plugins released on a separate rhythm from Terraform itself, and so they have their own version numbers. For production use, you should constrain the acceptable provider versions via configuration, to ensure that new versions with breaking changes will not be automatically installed by `terraform init` in future.
-
-## How To Fix
-
-Add the [`required_providers`](https://www.terraform.io/docs/configuration/terraform.html#specifying-required-provider-versions) block to the `terraform` configuration block and include current versions for all providers. For example:
-
-```tf
-terraform {
- required_providers {
- template = "~> 2.0"
- }
-}
-```
-
-Provider version constraints can be specified using a [version argument within a provider block](https://www.terraform.io/docs/configuration/providers.html#provider-versions) for backwards compatability. This approach is now discouraged, particularly for child modules.
diff --git a/docs/rules/terraform_required_version.md b/docs/rules/terraform_required_version.md
deleted file mode 100644
index 357cdfec7..000000000
--- a/docs/rules/terraform_required_version.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# terraform_required_version
-
-Disallow `terraform` declarations without `required_version`.
-
-## Configuration
-
-```hcl
-rule "terraform_required_version" {
- enabled = true
-}
-```
-
-## Example
-
-```hcl
-terraform {
- required_version = ">= 1.0"
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: terraform "required_version" attribute is required
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.11.0/docs/rules/terraform_required_version.md
-```
-
-## Why
-The `required_version` setting can be used to constrain which versions of the Terraform CLI can be used with your configuration.
-If the running version of Terraform doesn't match the constraints specified, Terraform will produce an error and exit without
-taking any further actions.
-
-## How To Fix
-
-Add the `required_version` attribute to the terraform configuration block.
diff --git a/docs/rules/terraform_standard_module_structure.md b/docs/rules/terraform_standard_module_structure.md
deleted file mode 100644
index 143aef725..000000000
--- a/docs/rules/terraform_standard_module_structure.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# terraform_standard_module_structure
-
-Ensure that a module complies with the Terraform [Standard Module Structure](https://www.terraform.io/docs/modules/index.html#standard-module-structure)
-
-## Example
-
-_main.tf_
-```hcl
-variable "v" {}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: variable "v" should be moved from main.tf to variables.tf (terraform_standard_module_structure)
-
- on main.tf line 1:
- 1: variable "v" {}
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.16.0/docs/rules/terraform_standard_module_structure.md
-```
-
-## Why
-
-Terraform's documentation outlines a [Standard Module Structure](https://www.terraform.io/docs/modules/structure.html). A minimal module should have a `main.tf`, `variables.tf`, and `outputs.tf` file. Variable and output blocks should be included in the corresponding file.
-
-## How To Fix
-
-* Move blocks to their conventional files as needed
-* Create empty files even if no `variable` or `output` blocks are defined
diff --git a/docs/rules/terraform_typed_variables.md b/docs/rules/terraform_typed_variables.md
deleted file mode 100644
index 14a2f63c8..000000000
--- a/docs/rules/terraform_typed_variables.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# terraform_typed_variables
-
-Disallow `variable` declarations without type.
-
-## Example
-
-```hcl
-variable "no_type" {
- default = "value"
-}
-
-variable "enabled" {
- default = false
- description = "This is description"
- type = bool
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: `no_type` variable has no type (terraform_typed_variables)
-
- on template.tf line 1:
- 1: variable "no_type" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.11.0/docs/rules/terraform_typed_variables.md
-
-```
-
-## Why
-
-Since `type` is optional value, it is not always necessary to declare it. But this rule is useful if you want to force declaration of a type.
-
-## How To Fix
-Add a type to the variable. See https://www.terraform.io/docs/configuration/variables.html#type-constraints for more details about types
diff --git a/docs/rules/terraform_unused_declarations.md b/docs/rules/terraform_unused_declarations.md
deleted file mode 100644
index 9112eac59..000000000
--- a/docs/rules/terraform_unused_declarations.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# terraform_unused_declarations
-
-Disallow variables, data sources, and locals that are declared but never used.
-
-## Example
-
-```hcl
-variable "not_used" {}
-
-variable "used" {}
-output "out" {
- value = var.used
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: variable "not_used" is declared but not used (terraform_unused_declarations)
-
- on config.tf line 1:
- 1: variable "not_used" {
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.15.5/docs/rules/terraform_unused_declarations.md
-
-```
-
-## Why
-
-Terraform will ignore variables and locals that are not used. It will refresh declared data sources regardless of usage. However, unreferenced variables likely indicate either a bug (and should be referenced) or removed code (and should be removed).
-
-## How To Fix
-
-Remove the declaration. For `variable` and `data`, remove the entire block. For a `local` value, remove the attribute from the `locals` block.
-
-While data sources should generally not have side effects, take greater care when removing them. For example, removing `data "http"` will cause Terraform to no longer perform an HTTP `GET` request during each plan. If a data source is being used for side effects, add an annotation to ignore it:
-
-```tf
-# tflint-ignore: terraform_unused_declarations
-data "http" "example" {
- url = "https://checkpoint-api.hashicorp.com/v1/check/terraform"
-}
-```
\ No newline at end of file
diff --git a/docs/rules/terraform_unused_required_providers.md b/docs/rules/terraform_unused_required_providers.md
deleted file mode 100644
index 8d6921032..000000000
--- a/docs/rules/terraform_unused_required_providers.md
+++ /dev/null
@@ -1,64 +0,0 @@
-# terraform_unused_required_providers
-
-Check that all `required_providers` are used in the module.
-
-## Configuration
-
-```hcl
-rule "terraform_unused_required_providers" {
- enabled = true
-}
-```
-
-## Examples
-
-```hcl
-terraform {
- required_providers {
- null = {
- source = "hashicorp/null"
- }
- }
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: provider 'null' is declared in required_providers but not used by the module (terraform_unused_required_providers)
-
- on main.tf line 3:
- 3: null = {
- 4: source = "hashicorp/null"
- 5: }
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.22.0/docs/rules/terraform_unused_required_providers.md
-```
-
-## Why
-
-The `required_providers` block should specify providers used directly by the given Terraform module. Terraform will download all specified providers during `terraform init`. If all resources for a given provider are removed but the `required_providers` entry remains, Terraform will continue to download the provider.
-
-In general, each module should specify its own provider requirements for each provider it uses. Terraform will traverse the module graph and find a suitable version for all providers, or error if modules require conflicting versions.
-
-## How To Fix
-
-If the provider is no longer used, remove it from the `required_providers` block.
-
-If the provider is used in one or more child modules but not directly in the module where TFLint was invoked, cut and paste the provider requirement into those modules.
-
-If the provider is used in one or more child modules and you'd prefer to define a single requirement, you can ignore the warning:
-
-```tf
-terraform {
- required_providers {
- # tflint-ignore: terraform_unused_required_providers
- null = {
- source = "hashicorp/null"
- }
- }
-}
-```
-
-This will affect your ability to run `terraform` directly in the child module, especially if you use providers outside the default `hashicorp` namespace or specify a `version` for required providers ([recommended](./terraform_required_providers.md)).
diff --git a/docs/rules/terraform_workspace_remote.md b/docs/rules/terraform_workspace_remote.md
deleted file mode 100644
index 7dc9aaa02..000000000
--- a/docs/rules/terraform_workspace_remote.md
+++ /dev/null
@@ -1,62 +0,0 @@
-# terraform_workspace_remote
-
-`terraform.workspace` should not be used with a "remote" backend with remote execution.
-
-If remote operations are [disabled](https://www.terraform.io/docs/cloud/run/index.html#disabling-remote-operations) for your workspace, you can safely disable this rule:
-
-```hcl
-rule "terraform_workspace_remote" {
- enabled = false
-}
-```
-
-## Example
-
-```hcl
-terraform {
- backend "remote" {
- # ...
- }
-}
-
-resource "aws_instance" "a" {
- tags = {
- workspace = terraform.workspace
- }
-}
-```
-
-```
-$ tflint
-1 issue(s) found:
-
-Warning: terraform.workspace should not be used with a 'remote' backend (terraform_workspace_remote)
-
- on example.tf line 8:
- 8: tags = {
- 9: workspace = terraform.workspace
- 10: }
-
-Reference: https://github.com/terraform-linters/tflint/blob/v0.15.5/docs/rules/terraform_workspace_remote.md
-```
-
-## Why
-
-Terraform configuration may include the name of the [current workspace](https://www.terraform.io/docs/state/workspaces.html#current-workspace-interpolation) using the `${terraform.workspace}` interpolation sequence. However, when Terraform Cloud workspaces are executing Terraform runs remotely, the Terraform CLI always uses the `default` workspace.
-
-The [remote](https://www.terraform.io/docs/backends/types/remote.html) backend is used with Terraform Cloud workspaces. Even if you set a `prefix` in the `workspaces` block, this value will be ignored during remote runs.
-
-For more information, see the [`remote` backend workspaces documentation](https://www.terraform.io/docs/backends/types/remote.html#workspaces).
-
-## How To Fix
-
-Consider adding a variable to your configuration and setting it in each cloud workspace:
-
-```tf
-variable "workspace" {
- type = string
- description = "The workspace name"
-}
-```
-
-You can also name the variable based on what the workspace suffix represents in your configuration (e.g. environment).
diff --git a/docs/user-guide/plugins.md b/docs/user-guide/plugins.md
index 506aac1da..46672a841 100644
--- a/docs/user-guide/plugins.md
+++ b/docs/user-guide/plugins.md
@@ -83,3 +83,32 @@ plugin "foo" {
```
When the plugin is enabled, TFLint invokes the `tflint-ruleset-[name]` (`tflint-ruleset-[name].exe` on Windows) binary in the plugin directory (For instance, `~/.tflint.d/plugins/tflint-ruleset-[name]`). So you should move the binary into the directory in advance.
+
+## Bundled plugin
+
+[TFLint Ruleset for Terraform Language](https://github.com/terraform-linters/tflint-ruleset-terraform) is built directly into TFLint binary. This is called a bundled plugin. Unlike other plugins, bundled plugins can be used without installation.
+
+A bundled plugin is enabled by default without a plugin block declaration. The default config is below:
+
+```hcl
+plugin "terraform" {
+ enabled = true
+ preset = "recommended"
+}
+```
+
+You can also change the behavior of the bundled plugin by explicitly declaring a plugin block.
+
+If you want to use a different version of tflint-ruleset-terraform instead of the bundled plugin, you can install it with `tflint --init` by specifying the `version` and `source`. In this case the bundled plugin will not be automatically enabled.
+
+```hcl
+plugin "terraform" {
+ enabled = true
+ preset = "recommended"
+
+ version = "0.1.0"
+ source = "github.com/terraform-linters/tflint-ruleset-terraform"
+}
+```
+
+If you have tflint-ruleset-terraform manually installed, the bundled plugin will not be automatically enabled. In this case the manually installed version takes precedence.
diff --git a/go.mod b/go.mod
index e9ebe1847..790fb80a9 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,6 @@ module github.com/terraform-linters/tflint
go 1.19
require (
- github.com/Masterminds/semver/v3 v3.1.1
github.com/agext/levenshtein v1.2.3
github.com/apparentlymart/go-cidr v1.1.0
github.com/bmatcuk/doublestar v1.1.5
@@ -12,13 +11,11 @@ require (
github.com/google/go-cmp v0.5.8
github.com/google/go-github/v35 v35.3.0
github.com/google/uuid v1.3.0
- github.com/hashicorp/go-getter v1.6.2
github.com/hashicorp/go-plugin v1.4.5
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hcl/v2 v2.13.0
github.com/hashicorp/logutils v1.0.0
- github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c
github.com/jessevdk/go-flags v1.5.0
github.com/jstemmer/go-junit-report v1.0.0
github.com/mattn/go-colorable v0.1.13
@@ -27,7 +24,8 @@ require (
github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d
github.com/sourcegraph/jsonrpc2 v0.1.0
github.com/spf13/afero v1.9.2
- github.com/terraform-linters/tflint-plugin-sdk v0.11.1-0.20220812135228-391859ca83f5
+ github.com/terraform-linters/tflint-plugin-sdk v0.11.1-0.20220821130728-3b6ea1aa3821
+ github.com/terraform-linters/tflint-ruleset-terraform v0.0.0-20220828152837-bc67b803df5b
github.com/xeipuuv/gojsonschema v1.2.0
github.com/zclconf/go-cty v1.11.0
github.com/zclconf/go-cty-yaml v1.0.2
@@ -41,6 +39,7 @@ require (
require (
cloud.google.com/go v0.75.0 // indirect
cloud.google.com/go/storage v1.14.0 // indirect
+ github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/aws/aws-sdk-go v1.42.43 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
@@ -49,8 +48,10 @@ require (
github.com/google/go-querystring v1.0.0 // indirect
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hashicorp/go-getter v1.6.2 // indirect
github.com/hashicorp/go-hclog v1.2.2 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
+ github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c // indirect
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
diff --git a/go.sum b/go.sum
index 649d05493..b9639e676 100644
--- a/go.sum
+++ b/go.sum
@@ -269,8 +269,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
-github.com/terraform-linters/tflint-plugin-sdk v0.11.1-0.20220812135228-391859ca83f5 h1:U90aIT3pRv9zDCf1aIWclXyRnjIILe6BvrDDywrGBMo=
-github.com/terraform-linters/tflint-plugin-sdk v0.11.1-0.20220812135228-391859ca83f5/go.mod h1:g5BTK4enaWI/EPr2qWfRDJU/Qqnu84Y33JTETyVxxMA=
+github.com/terraform-linters/tflint-plugin-sdk v0.11.1-0.20220821130728-3b6ea1aa3821 h1:EcgqevoO56J8zEbiEKVesICh4CaPY6+/JWbtxLnuUFw=
+github.com/terraform-linters/tflint-plugin-sdk v0.11.1-0.20220821130728-3b6ea1aa3821/go.mod h1:E+gbw/AE/SDuvqh2RgVykKuQLleDd/iYP55DE1lLKac=
+github.com/terraform-linters/tflint-ruleset-terraform v0.0.0-20220828152837-bc67b803df5b h1:5D4knL9Th0BZecqRuMvEJZqvZCEqZcGXRT/ZqtGW6tM=
+github.com/terraform-linters/tflint-ruleset-terraform v0.0.0-20220828152837-bc67b803df5b/go.mod h1:ZM2YzKRBSFXClWekQD8OTF7XhTEjllMA6ikeq76JTuQ=
github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
diff --git a/integrationtest/bundled/basic/main.tf b/integrationtest/bundled/basic/main.tf
new file mode 100644
index 000000000..486ce412d
--- /dev/null
+++ b/integrationtest/bundled/basic/main.tf
@@ -0,0 +1,10 @@
+variable "instance_type" {}
+variable "unused" {
+ type = string
+}
+
+resource "aws_instance" "main" {
+ count = [] == [] ? 1 : 0
+
+ instance_type = "${var.instance_type}"
+}
diff --git a/integrationtest/bundled/basic/result.json b/integrationtest/bundled/basic/result.json
new file mode 100644
index 000000000..cba9858ee
--- /dev/null
+++ b/integrationtest/bundled/basic/result.json
@@ -0,0 +1,125 @@
+{
+ "issues": [
+ {
+ "rule": {
+ "name": "terraform_required_version",
+ "severity": "warning",
+ "link": "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_required_version.md"
+ },
+ "message": "terraform \"required_version\" attribute is required",
+ "range": {
+ "filename": "",
+ "start": {
+ "line": 0,
+ "column": 0
+ },
+ "end": {
+ "line": 0,
+ "column": 0
+ }
+ },
+ "callers": []
+ },
+ {
+ "rule": {
+ "name": "terraform_typed_variables",
+ "severity": "warning",
+ "link": "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_typed_variables.md"
+ },
+ "message": "`instance_type` variable has no type",
+ "range": {
+ "filename": "main.tf",
+ "start": {
+ "line": 1,
+ "column": 1
+ },
+ "end": {
+ "line": 1,
+ "column": 25
+ }
+ },
+ "callers": []
+ },
+ {
+ "rule": {
+ "name": "terraform_unused_declarations",
+ "severity": "warning",
+ "link": "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_unused_declarations.md"
+ },
+ "message": "variable \"unused\" is declared but not used",
+ "range": {
+ "filename": "main.tf",
+ "start": {
+ "line": 2,
+ "column": 1
+ },
+ "end": {
+ "line": 2,
+ "column": 18
+ }
+ },
+ "callers": []
+ },
+ {
+ "rule": {
+ "name": "terraform_required_providers",
+ "severity": "warning",
+ "link": "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_required_providers.md"
+ },
+ "message": "Missing version constraint for provider \"aws\" in \"required_providers\"",
+ "range": {
+ "filename": "main.tf",
+ "start": {
+ "line": 6,
+ "column": 1
+ },
+ "end": {
+ "line": 6,
+ "column": 31
+ }
+ },
+ "callers": []
+ },
+ {
+ "rule": {
+ "name": "terraform_empty_list_equality",
+ "severity": "warning",
+ "link": "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_empty_list_equality.md"
+ },
+ "message": "Comparing a collection with an empty list is invalid. To detect an empty collection, check its length.",
+ "range": {
+ "filename": "main.tf",
+ "start": {
+ "line": 7,
+ "column": 11
+ },
+ "end": {
+ "line": 7,
+ "column": 19
+ }
+ },
+ "callers": []
+ },
+ {
+ "rule": {
+ "name": "terraform_deprecated_interpolation",
+ "severity": "warning",
+ "link": "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_deprecated_interpolation.md"
+ },
+ "message": "Interpolation-only expressions are deprecated in Terraform v0.12.14",
+ "range": {
+ "filename": "main.tf",
+ "start": {
+ "line": 9,
+ "column": 19
+ },
+ "end": {
+ "line": 9,
+ "column": 41
+ }
+ },
+ "callers": []
+ }
+ ],
+ "errors": []
+}
diff --git a/integrationtest/bundled/bundled_test.go b/integrationtest/bundled/bundled_test.go
new file mode 100644
index 000000000..58c901617
--- /dev/null
+++ b/integrationtest/bundled/bundled_test.go
@@ -0,0 +1,111 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+ "github.com/terraform-linters/tflint/formatter"
+)
+
+func TestIntegration(t *testing.T) {
+ tests := []struct {
+ name string
+ command string
+ dir string
+ }{
+ {
+ name: "empty module",
+ command: "tflint --format json --force",
+ dir: "empty",
+ },
+ {
+ name: "basic",
+ command: "tflint --format json --force",
+ dir: "basic",
+ },
+ {
+ name: "disable bundled plugin",
+ command: "tflint --format json --force",
+ dir: "disable",
+ },
+ {
+ name: "with config",
+ command: "tflint --format json --force",
+ dir: "with_config",
+ },
+ }
+
+ dir, _ := os.Getwd()
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ testDir := filepath.Join(dir, test.dir)
+
+ t.Cleanup(func() {
+ if err := os.Chdir(dir); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ if err := os.Chdir(testDir); err != nil {
+ t.Fatal(err)
+ }
+
+ args := strings.Split(test.command, " ")
+ var cmd *exec.Cmd
+ if runtime.GOOS == "windows" {
+ cmd = exec.Command("tflint.exe", args[1:]...)
+ } else {
+ cmd = exec.Command("tflint", args[1:]...)
+ }
+ outStream, errStream := new(bytes.Buffer), new(bytes.Buffer)
+ cmd.Stdout = outStream
+ cmd.Stderr = errStream
+
+ if err := cmd.Run(); err != nil {
+ t.Fatalf("Failed to exec command: %s", err)
+ }
+
+ var b []byte
+ var err error
+ if runtime.GOOS == "windows" && IsWindowsResultExist() {
+ b, err = os.ReadFile(filepath.Join(testDir, "result_windows.json"))
+ } else {
+ b, err = os.ReadFile(filepath.Join(testDir, "result.json"))
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var expected *formatter.JSONOutput
+ if err := json.Unmarshal(b, &expected); err != nil {
+ t.Fatal(err)
+ }
+
+ var got *formatter.JSONOutput
+ if err := json.Unmarshal(outStream.Bytes(), &got); err != nil {
+ t.Fatal(err)
+ }
+
+ opts := []cmp.Option{
+ cmpopts.IgnoreFields(formatter.JSONRule{}, "Link"),
+ }
+ if diff := cmp.Diff(got, expected, opts...); diff != "" {
+ t.Error(diff)
+ }
+ })
+ }
+}
+
+func IsWindowsResultExist() bool {
+ _, err := os.Stat("result_windows.json")
+ return !os.IsNotExist(err)
+}
diff --git a/integrationtest/bundled/disable/.tflint.hcl b/integrationtest/bundled/disable/.tflint.hcl
new file mode 100644
index 000000000..f66ea16c4
--- /dev/null
+++ b/integrationtest/bundled/disable/.tflint.hcl
@@ -0,0 +1,3 @@
+plugin "terraform" {
+ enabled = false
+}
diff --git a/integrationtest/bundled/disable/main.tf b/integrationtest/bundled/disable/main.tf
new file mode 100644
index 000000000..0fdb1f8a1
--- /dev/null
+++ b/integrationtest/bundled/disable/main.tf
@@ -0,0 +1,4 @@
+variable "instance_type" {
+ type = string
+ default = "t2.micro"
+}
diff --git a/integrationtest/bundled/disable/result.json b/integrationtest/bundled/disable/result.json
new file mode 100644
index 000000000..d59359714
--- /dev/null
+++ b/integrationtest/bundled/disable/result.json
@@ -0,0 +1 @@
+{"issues":[],"errors":[]}
diff --git a/integrationtest/bundled/empty/result.json b/integrationtest/bundled/empty/result.json
new file mode 100644
index 000000000..d59359714
--- /dev/null
+++ b/integrationtest/bundled/empty/result.json
@@ -0,0 +1 @@
+{"issues":[],"errors":[]}
diff --git a/integrationtest/bundled/with_config/.tflint.hcl b/integrationtest/bundled/with_config/.tflint.hcl
new file mode 100644
index 000000000..392f64269
--- /dev/null
+++ b/integrationtest/bundled/with_config/.tflint.hcl
@@ -0,0 +1,9 @@
+plugin "terraform" {
+ enabled = true
+
+ preset = "all"
+}
+
+rule "terraform_standard_module_structure" {
+ enabled = false
+}
diff --git a/integrationtest/bundled/with_config/main.tf b/integrationtest/bundled/with_config/main.tf
new file mode 100644
index 000000000..0fdb1f8a1
--- /dev/null
+++ b/integrationtest/bundled/with_config/main.tf
@@ -0,0 +1,4 @@
+variable "instance_type" {
+ type = string
+ default = "t2.micro"
+}
diff --git a/integrationtest/bundled/with_config/result.json b/integrationtest/bundled/with_config/result.json
new file mode 100644
index 000000000..39935ee9f
--- /dev/null
+++ b/integrationtest/bundled/with_config/result.json
@@ -0,0 +1,65 @@
+{
+ "issues": [
+ {
+ "rule": {
+ "name": "terraform_required_version",
+ "severity": "warning",
+ "link": "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_required_version.md"
+ },
+ "message": "terraform \"required_version\" attribute is required",
+ "range": {
+ "filename": "",
+ "start": {
+ "line": 0,
+ "column": 0
+ },
+ "end": {
+ "line": 0,
+ "column": 0
+ }
+ },
+ "callers": []
+ },
+ {
+ "rule": {
+ "name": "terraform_documented_variables",
+ "severity": "info",
+ "link": "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_documented_variables.md"
+ },
+ "message": "`instance_type` variable has no description",
+ "range": {
+ "filename": "main.tf",
+ "start": {
+ "line": 1,
+ "column": 1
+ },
+ "end": {
+ "line": 1,
+ "column": 25
+ }
+ },
+ "callers": []
+ },
+ {
+ "rule": {
+ "name": "terraform_unused_declarations",
+ "severity": "warning",
+ "link": "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_unused_declarations.md"
+ },
+ "message": "variable \"instance_type\" is declared but not used",
+ "range": {
+ "filename": "main.tf",
+ "start": {
+ "line": 1,
+ "column": 1
+ },
+ "end": {
+ "line": 1,
+ "column": 25
+ }
+ },
+ "callers": []
+ }
+ ],
+ "errors": []
+}
diff --git a/integrationtest/cli/cli_test.go b/integrationtest/cli/cli_test.go
index 8edf154e8..93e1a74f7 100644
--- a/integrationtest/cli/cli_test.go
+++ b/integrationtest/cli/cli_test.go
@@ -14,6 +14,12 @@ import (
)
func TestIntegration(t *testing.T) {
+ // Disable the bundled plugin because the `os.Executable()` is go(1) in the tests
+ tflint.DisableBundledPlugin = true
+ defer func() {
+ tflint.DisableBundledPlugin = false
+ }()
+
tests := []struct {
name string
command string
diff --git a/integrationtest/init/init_test.go b/integrationtest/init/init_test.go
index 2e3403d5e..56e33b14e 100644
--- a/integrationtest/init/init_test.go
+++ b/integrationtest/init/init_test.go
@@ -8,9 +8,16 @@ import (
"testing"
"github.com/terraform-linters/tflint/cmd"
+ "github.com/terraform-linters/tflint/tflint"
)
func TestIntegration(t *testing.T) {
+ // Disable the bundled plugin because the `os.Executable()` is go(1) in the tests
+ tflint.DisableBundledPlugin = true
+ defer func() {
+ tflint.DisableBundledPlugin = false
+ }()
+
current, _ := os.Getwd()
dir := filepath.Join(current, "basic")
diff --git a/integrationtest/inspection/inspection_test.go b/integrationtest/inspection/inspection_test.go
index 7cf350b60..923938da1 100644
--- a/integrationtest/inspection/inspection_test.go
+++ b/integrationtest/inspection/inspection_test.go
@@ -15,6 +15,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/terraform-linters/tflint/cmd"
"github.com/terraform-linters/tflint/formatter"
+ "github.com/terraform-linters/tflint/tflint"
)
func TestMain(m *testing.M) {
@@ -174,6 +175,12 @@ func TestIntegration(t *testing.T) {
},
}
+ // Disable the bundled plugin because the `os.Executable()` is go(1) in the tests
+ tflint.DisableBundledPlugin = true
+ defer func() {
+ tflint.DisableBundledPlugin = false
+ }()
+
dir, _ := os.Getwd()
for _, tc := range cases {
t.Run(tc.Name, func(t *testing.T) {
diff --git a/integrationtest/langserver/langserver_test.go b/integrationtest/langserver/langserver_test.go
index 8a59acd05..62b0fdcb8 100644
--- a/integrationtest/langserver/langserver_test.go
+++ b/integrationtest/langserver/langserver_test.go
@@ -26,6 +26,12 @@ type jsonrpcMessage struct {
}
func TestMain(m *testing.M) {
+ // Disable the bundled plugin because the `os.Executable()` is go(1) in the tests
+ tflint.DisableBundledPlugin = true
+ defer func() {
+ tflint.DisableBundledPlugin = false
+ }()
+
filter := &logutils.LevelFilter{
Levels: []logutils.LogLevel{"TRACE", "DEBUG", "INFO", "WARN", "ERROR"},
MinLevel: logutils.LogLevel(""),
diff --git a/langserver/handler.go b/langserver/handler.go
index 04452053e..1a4900dd0 100644
--- a/langserver/handler.go
+++ b/langserver/handler.go
@@ -17,7 +17,6 @@ import (
"github.com/spf13/afero"
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
"github.com/terraform-linters/tflint/plugin"
- "github.com/terraform-linters/tflint/rules"
"github.com/terraform-linters/tflint/tflint"
)
@@ -39,7 +38,7 @@ func NewHandler(configPath string, cliConfig *tflint.Config) (jsonrpc2.Handler,
return nil, nil, err
}
- rulesets := []tflint.RuleSet{&rules.RuleSet{}}
+ rulesets := []tflint.RuleSet{}
for _, ruleset := range rulsetPlugin.RuleSets {
rulesets = append(rulesets, ruleset)
}
@@ -52,7 +51,6 @@ func NewHandler(configPath string, cliConfig *tflint.Config) (jsonrpc2.Handler,
cliConfig: cliConfig,
config: cfg,
fs: afero.NewCopyOnWriteFs(afero.NewOsFs(), afero.NewMemMapFs()),
- rules: rules.NewRules(cfg),
plugin: rulsetPlugin,
diagsPaths: []string{},
}).handle), rulsetPlugin, nil
@@ -64,7 +62,6 @@ type handler struct {
config *tflint.Config
fs afero.Fs
rootDir string
- rules []rules.Rule
plugin *plugin.Plugin
shutdown bool
diagsPaths []string
@@ -165,15 +162,6 @@ func (h *handler) inspect() (map[string][]lsp.Diagnostic, error) {
}
runners = append(runners, runner)
- for _, rule := range h.rules {
- for _, runner := range runners {
- err := rule.Check(runner)
- if err != nil {
- return ret, fmt.Errorf("Failed to check `%s` rule: %w", rule.Name(), err)
- }
- }
- }
-
config := h.config.ToPluginConfig()
for name, ruleset := range h.plugin.RuleSets {
if err := ruleset.ApplyGlobalConfig(config); err != nil {
diff --git a/langserver/workspace_did_change_watched_files.go b/langserver/workspace_did_change_watched_files.go
index fab5ce61f..ab97b0062 100644
--- a/langserver/workspace_did_change_watched_files.go
+++ b/langserver/workspace_did_change_watched_files.go
@@ -8,7 +8,6 @@ import (
lsp "github.com/sourcegraph/go-lsp"
"github.com/sourcegraph/jsonrpc2"
"github.com/spf13/afero"
- "github.com/terraform-linters/tflint/rules"
"github.com/terraform-linters/tflint/tflint"
)
@@ -23,7 +22,6 @@ func (h *handler) workspaceDidChangeWatchedFiles(ctx context.Context, conn *json
}
newConfig.Merge(h.cliConfig)
h.config = newConfig
- h.rules = rules.NewRules(h.config)
h.fs = afero.NewCopyOnWriteFs(afero.NewOsFs(), afero.NewMemMapFs())
diff --git a/plugin/discovery.go b/plugin/discovery.go
index dd2b19cbb..3f247c3f5 100644
--- a/plugin/discovery.go
+++ b/plugin/discovery.go
@@ -18,6 +18,8 @@ import (
// Discovery searches and launches plugins according the passed configuration.
// If the plugin is not enabled, skip without starting.
+// The Terraform Language plugin is treated specially. Plugins for which no version
+// is specified will launch the bundled plugin instead of returning an error.
func Discovery(config *tflint.Config) (*Plugin, error) {
clients := map[string]*plugin.Client{}
rulesets := map[string]*host2plugin.GRPCClient{}
@@ -27,14 +29,23 @@ func Discovery(config *tflint.Config) (*Plugin, error) {
pluginPath, err := FindPluginPath(installCfg)
var cmd *exec.Cmd
if os.IsNotExist(err) {
- if installCfg.ManuallyInstalled() {
- pluginDir, err := getPluginDir(config)
+ if pluginCfg.Name == "terraform" && installCfg.ManuallyInstalled() {
+ log.Print("[INFO] Plugin `terraform` is not installed, but the bundled plugin is available.")
+ self, err := os.Executable()
if err != nil {
return nil, err
}
- return nil, fmt.Errorf("Plugin `%s` not found in %s", pluginCfg.Name, pluginDir)
+ cmd = exec.Command(self, "--act-as-bundled-plugin")
+ } else {
+ if installCfg.ManuallyInstalled() {
+ pluginDir, err := getPluginDir(config)
+ if err != nil {
+ return nil, err
+ }
+ return nil, fmt.Errorf("Plugin `%s` not found in %s", pluginCfg.Name, pluginDir)
+ }
+ return nil, fmt.Errorf("Plugin `%s` not found. Did you run `tflint --init`?", pluginCfg.Name)
}
- return nil, fmt.Errorf("Plugin `%s` not found. Did you run `tflint --init`?", pluginCfg.Name)
} else {
cmd = exec.Command(pluginPath)
}
diff --git a/plugin/discovery_test.go b/plugin/discovery_test.go
index 763c1f63a..cec80019b 100644
--- a/plugin/discovery_test.go
+++ b/plugin/discovery_test.go
@@ -33,7 +33,9 @@ func Test_Discovery(t *testing.T) {
},
},
})
- defer plugin.Clean()
+ if plugin != nil {
+ defer plugin.Clean()
+ }
if err != nil {
t.Fatalf("Unexpected error occurred %s", err)
@@ -74,7 +76,9 @@ func Test_Discovery_local(t *testing.T) {
},
},
})
- defer plugin.Clean()
+ if plugin != nil {
+ defer plugin.Clean()
+ }
if err != nil {
t.Fatalf("Unexpected error occurred %s", err)
@@ -108,7 +112,9 @@ func Test_Discovery_envVar(t *testing.T) {
},
},
})
- defer plugin.Clean()
+ if plugin != nil {
+ defer plugin.Clean()
+ }
if err != nil {
t.Fatalf("Unexpected error occurred %s", err)
@@ -140,7 +146,9 @@ func Test_Discovery_pluginDirConfig(t *testing.T) {
},
},
})
- defer plugin.Clean()
+ if plugin != nil {
+ defer plugin.Clean()
+ }
if err != nil {
t.Fatalf("Unexpected error occurred %s", err)
@@ -267,6 +275,46 @@ func Test_Discovery_notFoundForAutoInstallation(t *testing.T) {
}
}
+func Test_Discovery_bundledPluginWithVersion(t *testing.T) {
+ cwd, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err = os.Chdir(cwd); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ err = os.Chdir(filepath.Join(cwd, "test-fixtures", "no_plugins"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ original := PluginRoot
+ PluginRoot = filepath.Join(cwd, "test-fixtures", "no_plugins")
+ defer func() { PluginRoot = original }()
+
+ _, err = Discovery(&tflint.Config{
+ Plugins: map[string]*tflint.PluginConfig{
+ "terraform": {
+ Name: "terraform",
+ Enabled: true,
+ Source: "github.com/terraform-linters/tflint-ruleset-terraform",
+ Version: "0.1.0",
+ },
+ },
+ })
+
+ if err == nil {
+ t.Fatal("An error should have occurred, but it did not occur")
+ }
+ expected := "Plugin `terraform` not found. Did you run `tflint --init`?"
+ if err.Error() != expected {
+ t.Fatalf("Error message not matched: want=%s, got=%s", expected, err.Error())
+ }
+}
+
func Test_FindPluginPath(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
diff --git a/rules/provider.go b/rules/provider.go
deleted file mode 100644
index a205c7962..000000000
--- a/rules/provider.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package rules
-
-import (
- "fmt"
- "log"
-
- "github.com/terraform-linters/tflint/rules/terraformrules"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// Rule is an implementation that receives a Runner and inspects for resources and modules.
-type Rule interface {
- Name() string
- Enabled() bool
- Check(runner *tflint.Runner) error
-}
-
-// DefaultRules is rules by default
-var DefaultRules = []Rule{
- terraformrules.NewTerraformDeprecatedIndexRule(),
- terraformrules.NewTerraformDeprecatedInterpolationRule(),
- terraformrules.NewTerraformDocumentedOutputsRule(),
- terraformrules.NewTerraformDocumentedVariablesRule(),
- terraformrules.NewTerraformEmptyListEqualityRule(),
- terraformrules.NewTerraformModulePinnedSourceRule(),
- terraformrules.NewTerraformModuleVersionRule(),
- terraformrules.NewTerraformNamingConventionRule(),
- terraformrules.NewTerraformStandardModuleStructureRule(),
- terraformrules.NewTerraformTypedVariablesRule(),
- terraformrules.NewTerraformRequiredVersionRule(),
- terraformrules.NewTerraformRequiredProvidersRule(),
- terraformrules.NewTerraformWorkspaceRemoteRule(),
- terraformrules.NewTerraformUnusedDeclarationsRule(),
- terraformrules.NewTerraformUnusedRequiredProvidersRule(),
- terraformrules.NewTerraformCommentSyntaxRule(),
-}
-
-// CheckRuleNames returns map of rules indexed by name
-func CheckRuleNames(ruleNames []string) error {
- log.Print("[INFO] Checking rules")
-
- rulesMap := map[string]Rule{}
- for _, rule := range DefaultRules {
- rulesMap[rule.Name()] = rule
- }
-
- totalEnabled := 0
- for _, rule := range rulesMap {
- if rule.Enabled() {
- totalEnabled++
- }
- }
- log.Printf("[INFO] %d (%d) rules total", len(rulesMap), totalEnabled)
- for _, rule := range ruleNames {
- if _, ok := rulesMap[rule]; !ok {
- return fmt.Errorf("Rule not found: %s", rule)
- }
- }
- return nil
-}
-
-// NewRules returns rules according to configuration
-func NewRules(c *tflint.Config) []Rule {
- log.Print("[INFO] Prepare rules")
-
- ret := []Rule{}
-
- if c.DisabledByDefault {
- log.Printf("[DEBUG] Only mode is enabled. Ignoring default rules")
- }
-
- for _, rule := range DefaultRules {
- enabled := rule.Enabled()
- if r := c.Rules[rule.Name()]; r != nil {
- if r.Enabled {
- log.Printf("[DEBUG] `%s` is enabled", rule.Name())
- } else {
- log.Printf("[DEBUG] `%s` is disabled", rule.Name())
- }
- enabled = r.Enabled
- } else if c.DisabledByDefault {
- enabled = false
- }
-
- if enabled {
- ret = append(ret, rule)
- }
- }
- if c.DisabledByDefault && len(ret) == 0 {
- log.Printf("[WARN] Only mode is enabled and no rules were provided")
- }
- log.Printf("[INFO] %d default rules enabled", len(ret))
- return ret
-}
diff --git a/rules/provider_test.go b/rules/provider_test.go
deleted file mode 100644
index b49e407e2..000000000
--- a/rules/provider_test.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package rules
-
-import (
- "errors"
- "reflect"
- "testing"
-
- "github.com/terraform-linters/tflint/rules/terraformrules"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_CheckRuleNames(t *testing.T) {
- cases := []struct {
- Name string
- Rules []string
- Expected error
- }{
- {
- Name: "no error",
- Rules: []string{"terraform_deprecated_interpolation"},
- Expected: nil,
- },
- {
- Name: "invalid rule name",
- Rules: []string{
- "terraform_deprecated_interpolation",
- "invalid_not_exist",
- },
- Expected: errors.New("Rule not found: invalid_not_exist"),
- },
- }
-
- for _, tc := range cases {
- err := CheckRuleNames(tc.Rules)
- if !reflect.DeepEqual(tc.Expected, err) {
- t.Fatalf("Failed `%s` test: expected `%#v`, but got `%#v`", tc.Name, tc.Expected, err)
- }
- }
-}
-
-func Test_NewRules(t *testing.T) {
- // Mock rules in test
- DefaultRules = []Rule{
- terraformrules.NewTerraformDeprecatedInterpolationRule(),
- terraformrules.NewTerraformNamingConventionRule(),
- }
-
- cases := []struct {
- Name string
- Config *tflint.Config
- Expected []Rule
- }{
- {
- Name: "default",
- Config: tflint.EmptyConfig(),
- Expected: []Rule{
- terraformrules.NewTerraformDeprecatedInterpolationRule(),
- },
- },
- {
- Name: "enabled = false",
- Config: &tflint.Config{
- Rules: map[string]*tflint.RuleConfig{
- "terraform_deprecated_interpolation": {
- Enabled: false,
- },
- },
- },
- Expected: []Rule{},
- },
- {
- Name: "enabled = true",
- Config: &tflint.Config{
- Rules: map[string]*tflint.RuleConfig{
- "terraform_naming_convention": {
- Enabled: true,
- },
- },
- },
- Expected: []Rule{
- terraformrules.NewTerraformDeprecatedInterpolationRule(),
- terraformrules.NewTerraformNamingConventionRule(),
- },
- },
- {
- Name: "disabled_by_default = true",
- Config: &tflint.Config{
- DisabledByDefault: true,
- Rules: map[string]*tflint.RuleConfig{
- "terraform_naming_convention": {
- Enabled: true,
- },
- },
- },
- Expected: []Rule{
- terraformrules.NewTerraformNamingConventionRule(),
- },
- },
- }
-
- for _, tc := range cases {
- ret := NewRules(tc.Config)
- if !reflect.DeepEqual(tc.Expected, ret) {
- t.Fatalf("Failed `%s` test: expected rules are `%#v`, but got `%#v`", tc.Name, tc.Expected, ret)
- }
- }
-}
diff --git a/rules/ruleset.go b/rules/ruleset.go
deleted file mode 100644
index c4b430b20..000000000
--- a/rules/ruleset.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package rules
-
-import "github.com/terraform-linters/tflint/tflint"
-
-// RuleSet is a pseudo RuleSet to handle core rules like plugin
-type RuleSet struct{}
-
-// RuleSetName is the name of the rule set.
-func (r *RuleSet) RuleSetName() (string, error) {
- return "Core", nil
-}
-
-// RuleSetVersion is the version of the plugin.
-func (r *RuleSet) RuleSetVersion() (string, error) {
- return tflint.Version, nil
-}
-
-// RuleNames is a list of rule names provided by the plugin.
-func (r *RuleSet) RuleNames() ([]string, error) {
- names := []string{}
- for _, rule := range DefaultRules {
- names = append(names, rule.Name())
- }
- return names, nil
-}
diff --git a/rules/terraformrules/terraform_comment_syntax.go b/rules/terraformrules/terraform_comment_syntax.go
deleted file mode 100644
index 2ed8fa887..000000000
--- a/rules/terraformrules/terraform_comment_syntax.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package terraformrules
-
-import (
- "log"
- "strings"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/hashicorp/hcl/v2/hclsyntax"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// TerraformCommentSyntaxRule checks whether comments use the preferred syntax
-type TerraformCommentSyntaxRule struct{}
-
-// NewTerraformCommentSyntaxRule returns a new rule
-func NewTerraformCommentSyntaxRule() *TerraformCommentSyntaxRule {
- return &TerraformCommentSyntaxRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformCommentSyntaxRule) Name() string {
- return "terraform_comment_syntax"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformCommentSyntaxRule) Enabled() bool {
- return false
-}
-
-// Severity returns the rule severity
-func (r *TerraformCommentSyntaxRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformCommentSyntaxRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check checks whether single line comments is used
-func (r *TerraformCommentSyntaxRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- for name, file := range runner.Files() {
- if err := r.checkComments(runner, name, file); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (r *TerraformCommentSyntaxRule) checkComments(runner *tflint.Runner, filename string, file *hcl.File) error {
- if strings.HasSuffix(filename, ".json") {
- return nil
- }
-
- tokens, diags := hclsyntax.LexConfig(file.Bytes, filename, hcl.InitialPos)
- if diags.HasErrors() {
- return diags
- }
-
- for _, token := range tokens {
- if token.Type != hclsyntax.TokenComment {
- continue
- }
-
- if strings.HasPrefix(string(token.Bytes), "//") {
- runner.EmitIssue(
- r,
- "Single line comments should begin with #",
- token.Range,
- )
- }
- }
-
- return nil
-}
diff --git a/rules/terraformrules/terraform_comment_syntax_test.go b/rules/terraformrules/terraform_comment_syntax_test.go
deleted file mode 100644
index d97899984..000000000
--- a/rules/terraformrules/terraform_comment_syntax_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package terraformrules
-
-import (
- "testing"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformCommentSyntaxRule(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- JSON bool
- Expected tflint.Issues
- }{
- {
- Name: "hash comment",
- Content: `# foo`,
- Expected: tflint.Issues{},
- },
- {
- Name: "multi-line comment",
- Content: `
-/*
- This comment spans multiple lines
-*/
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "double-slash comment",
- Content: `// foo`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformCommentSyntaxRule(),
- Message: "Single line comments should begin with #",
- Range: hcl.Range{
- Filename: "variables.tf",
- Start: hcl.Pos{
- Line: 1,
- Column: 1,
- },
- End: hcl.Pos{
- Line: 1,
- Column: 7,
- },
- },
- },
- },
- },
- {
- Name: "end-of-line hash comment",
- Content: `
-variable "foo" {
- type = string # a string
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "JSON",
- Content: `{"variable": {"foo": {"type": "string"}}}`,
- JSON: true,
- Expected: tflint.Issues{},
- },
- }
-
- rule := NewTerraformCommentSyntaxRule()
-
- for _, tc := range cases {
- filename := "variables.tf"
- if tc.JSON {
- filename += ".json"
- }
-
- runner := tflint.TestRunner(t, map[string]string{filename: tc.Content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
diff --git a/rules/terraformrules/terraform_deprecated_index.go b/rules/terraformrules/terraform_deprecated_index.go
deleted file mode 100644
index 65b238008..000000000
--- a/rules/terraformrules/terraform_deprecated_index.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package terraformrules
-
-import (
- "log"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/hashicorp/hcl/v2/hclsyntax"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// TerraformDeprecatedIndexRule warns about usage of the legacy dot syntax for indexes (foo.0)
-type TerraformDeprecatedIndexRule struct{}
-
-// NewTerraformDeprecatedIndexRule return a new rule
-func NewTerraformDeprecatedIndexRule() *TerraformDeprecatedIndexRule {
- return &TerraformDeprecatedIndexRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformDeprecatedIndexRule) Name() string {
- return "terraform_deprecated_index"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformDeprecatedIndexRule) Enabled() bool {
- return false
-}
-
-// Severity returns the rule severity
-func (r *TerraformDeprecatedIndexRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformDeprecatedIndexRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check walks all expressions and emit issues if deprecated index syntax is found
-func (r *TerraformDeprecatedIndexRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- return runner.WalkExpressions(func(expr hcl.Expression) error {
- for _, variable := range expr.Variables() {
- for _, traversal := range variable.SimpleSplit().Rel {
- if traversal, ok := traversal.(hcl.TraverseIndex); ok {
- filename := traversal.SrcRange.Filename
- bytes := traversal.SrcRange.SliceBytes(runner.File(filename).Bytes)
-
- tokens, diags := hclsyntax.LexExpression(bytes, filename, traversal.SrcRange.Start)
- if diags.HasErrors() {
- return diags
- }
-
- if tokens[0].Type == hclsyntax.TokenDot {
- runner.EmitIssue(
- r,
- "List items should be accessed using square brackets",
- expr.Range(),
- )
- }
- }
- }
- }
-
- return nil
- })
-}
diff --git a/rules/terraformrules/terraform_deprecated_index_test.go b/rules/terraformrules/terraform_deprecated_index_test.go
deleted file mode 100644
index a7b478dde..000000000
--- a/rules/terraformrules/terraform_deprecated_index_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package terraformrules
-
-import (
- "testing"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformDeprecatedIndexRule(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- Expected tflint.Issues
- }{
- {
- Name: "deprecated dot index style",
- Content: `
-locals {
- list = ["a"]
- value = list.0
-}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformDeprecatedIndexRule(),
- Message: "List items should be accessed using square brackets",
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{
- Line: 4,
- Column: 11,
- },
- End: hcl.Pos{
- Line: 4,
- Column: 17,
- },
- },
- },
- },
- },
- {
- Name: "attribute access",
- Content: `
-locals {
- map = {a = "b"}
- value = map.a
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "fractional number",
- Content: `
-locals {
- value = 1.5
-}
-`,
- Expected: tflint.Issues{},
- },
- }
-
- rule := NewTerraformDeprecatedIndexRule()
-
- for _, tc := range cases {
- runner := tflint.TestRunner(t, map[string]string{"config.tf": tc.Content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
diff --git a/rules/terraformrules/terraform_deprecated_interpolation.go b/rules/terraformrules/terraform_deprecated_interpolation.go
deleted file mode 100644
index b7269b176..000000000
--- a/rules/terraformrules/terraform_deprecated_interpolation.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package terraformrules
-
-import (
- "log"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/hashicorp/hcl/v2/hclsyntax"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// TerraformDeprecatedInterpolationRule warns of deprecated interpolation in Terraform v0.11 or earlier.
-type TerraformDeprecatedInterpolationRule struct{}
-
-// NewTerraformDeprecatedInterpolationRule return a new rule
-func NewTerraformDeprecatedInterpolationRule() *TerraformDeprecatedInterpolationRule {
- return &TerraformDeprecatedInterpolationRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformDeprecatedInterpolationRule) Name() string {
- return "terraform_deprecated_interpolation"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformDeprecatedInterpolationRule) Enabled() bool {
- return true
-}
-
-// Severity returns the rule severity
-func (r *TerraformDeprecatedInterpolationRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformDeprecatedInterpolationRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check emits issues on the deprecated interpolation syntax.
-// This logic is equivalent to the warning logic implemented in Terraform.
-// See https://github.com/hashicorp/terraform/pull/23348
-func (r *TerraformDeprecatedInterpolationRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- return runner.WalkExpressions(func(expr hcl.Expression) error {
- r.checkForDeprecatedInterpolationsInExpr(runner, expr)
- return nil
- })
-}
-
-func (r *TerraformDeprecatedInterpolationRule) checkForDeprecatedInterpolationsInExpr(runner *tflint.Runner, expr hcl.Expression) {
- if _, ok := expr.(*hclsyntax.TemplateWrapExpr); !ok {
- return
- }
-
- runner.EmitIssue(
- r,
- "Interpolation-only expressions are deprecated in Terraform v0.12.14",
- expr.Range(),
- )
-}
diff --git a/rules/terraformrules/terraform_deprecated_interpolation_test.go b/rules/terraformrules/terraform_deprecated_interpolation_test.go
deleted file mode 100644
index 6579caa8c..000000000
--- a/rules/terraformrules/terraform_deprecated_interpolation_test.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package terraformrules
-
-import (
- "testing"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformDeprecatedInterpolationRule(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- Expected tflint.Issues
- }{
- {
- Name: "deprecated single interpolation",
- Content: `
-resource "null_resource" "a" {
- triggers = "${var.triggers}"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformDeprecatedInterpolationRule(),
- Message: "Interpolation-only expressions are deprecated in Terraform v0.12.14",
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 3, Column: 13},
- End: hcl.Pos{Line: 3, Column: 30},
- },
- },
- },
- },
- {
- Name: "deprecated single interpolation in provider block",
- Content: `
-provider "null" {
- foo = "${var.triggers["foo"]}"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformDeprecatedInterpolationRule(),
- Message: "Interpolation-only expressions are deprecated in Terraform v0.12.14",
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 3, Column: 8},
- End: hcl.Pos{Line: 3, Column: 32},
- },
- },
- },
- },
- {
- Name: "deprecated single interpolation in locals block",
- Content: `
-locals {
- foo = "${var.triggers["foo"]}"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformDeprecatedInterpolationRule(),
- Message: "Interpolation-only expressions are deprecated in Terraform v0.12.14",
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 3, Column: 8},
- End: hcl.Pos{Line: 3, Column: 32},
- },
- },
- },
- },
- {
- Name: "deprecated single interpolation in nested block",
- Content: `
-resource "null_resource" "a" {
- provisioner "local-exec" {
- single = "${var.triggers["greeting"]}"
- }
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformDeprecatedInterpolationRule(),
- Message: "Interpolation-only expressions are deprecated in Terraform v0.12.14",
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 4, Column: 12},
- End: hcl.Pos{Line: 4, Column: 41},
- },
- },
- },
- },
- {
- Name: "interpolation as template",
- Content: `
-resource "null_resource" "a" {
- triggers = "${var.triggers} "
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "interpolation in array",
- Content: `
-resource "null_resource" "a" {
- triggers = ["${var.triggers}"]
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformDeprecatedInterpolationRule(),
- Message: "Interpolation-only expressions are deprecated in Terraform v0.12.14",
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 3, Column: 14},
- End: hcl.Pos{Line: 3, Column: 31},
- },
- },
- },
- },
- {
- Name: "new interpolation syntax",
- Content: `
-resource "null_resource" "a" {
- triggers = var.triggers
-}`,
- Expected: tflint.Issues{},
- },
- }
-
- rule := NewTerraformDeprecatedInterpolationRule()
-
- for _, tc := range cases {
- t.Run(tc.Name, func(t *testing.T) {
- runner := tflint.TestRunner(t, map[string]string{"config.tf": tc.Content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- })
- }
-}
diff --git a/rules/terraformrules/terraform_documented_outputs.go b/rules/terraformrules/terraform_documented_outputs.go
deleted file mode 100644
index c5a42fb4e..000000000
--- a/rules/terraformrules/terraform_documented_outputs.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "log"
-
- "github.com/hashicorp/hcl/v2/gohcl"
- "github.com/terraform-linters/tflint-plugin-sdk/hclext"
- sdk "github.com/terraform-linters/tflint-plugin-sdk/tflint"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// TerraformDocumentedOutputsRule checks whether outputs have descriptions
-type TerraformDocumentedOutputsRule struct{}
-
-// NewTerraformDocumentedOutputsRule returns a new rule
-func NewTerraformDocumentedOutputsRule() *TerraformDocumentedOutputsRule {
- return &TerraformDocumentedOutputsRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformDocumentedOutputsRule) Name() string {
- return "terraform_documented_outputs"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformDocumentedOutputsRule) Enabled() bool {
- return false
-}
-
-// Severity returns the rule severity
-func (r *TerraformDocumentedOutputsRule) Severity() tflint.Severity {
- return tflint.NOTICE
-}
-
-// Link returns the rule reference link
-func (r *TerraformDocumentedOutputsRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check checks whether outputs have descriptions
-func (r *TerraformDocumentedOutputsRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- body, diags := runner.GetModuleContent(&hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "output",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{
- Attributes: []hclext.AttributeSchema{{Name: "description"}},
- },
- },
- },
- }, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return diags
- }
-
- for _, output := range body.Blocks {
- attr, exists := output.Body.Attributes["description"]
- if !exists {
- runner.EmitIssue(
- r,
- fmt.Sprintf("`%s` output has no description", output.Labels[0]),
- output.DefRange,
- )
- continue
- }
-
- var description string
- diags = gohcl.DecodeExpression(attr.Expr, nil, &description)
- if diags.HasErrors() {
- return diags
- }
-
- if description == "" {
- runner.EmitIssue(
- r,
- fmt.Sprintf("`%s` output has no description", output.Labels[0]),
- output.DefRange,
- )
- }
- }
-
- return nil
-}
diff --git a/rules/terraformrules/terraform_documented_outputs_test.go b/rules/terraformrules/terraform_documented_outputs_test.go
deleted file mode 100644
index fd90ec1d0..000000000
--- a/rules/terraformrules/terraform_documented_outputs_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package terraformrules
-
-import (
- "testing"
-
- hcl "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformDocumentedOutputsRule(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- Expected tflint.Issues
- }{
- {
- Name: "no description",
- Content: `
-output "endpoint" {
- value = aws_alb.main.dns_name
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformDocumentedOutputsRule(),
- Message: "`endpoint` output has no description",
- Range: hcl.Range{
- Filename: "outputs.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 18},
- },
- },
- },
- },
- {
- Name: "empty description",
- Content: `
-output "endpoint" {
- value = aws_alb.main.dns_name
- description = ""
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformDocumentedOutputsRule(),
- Message: "`endpoint` output has no description",
- Range: hcl.Range{
- Filename: "outputs.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 18},
- },
- },
- },
- },
- {
- Name: "with description",
- Content: `
-output "endpoint" {
- value = aws_alb.main.dns_name
- description = "DNS Endpoint"
-}`,
- Expected: tflint.Issues{},
- },
- }
-
- rule := NewTerraformDocumentedOutputsRule()
-
- for _, tc := range cases {
- runner := tflint.TestRunner(t, map[string]string{"outputs.tf": tc.Content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
diff --git a/rules/terraformrules/terraform_documented_variables.go b/rules/terraformrules/terraform_documented_variables.go
deleted file mode 100644
index 62e8a1c2c..000000000
--- a/rules/terraformrules/terraform_documented_variables.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "log"
-
- "github.com/hashicorp/hcl/v2/gohcl"
- "github.com/terraform-linters/tflint-plugin-sdk/hclext"
- sdk "github.com/terraform-linters/tflint-plugin-sdk/tflint"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// TerraformDocumentedVariablesRule checks whether variables have descriptions
-type TerraformDocumentedVariablesRule struct{}
-
-// NewTerraformDocumentedVariablesRule returns a new rule
-func NewTerraformDocumentedVariablesRule() *TerraformDocumentedVariablesRule {
- return &TerraformDocumentedVariablesRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformDocumentedVariablesRule) Name() string {
- return "terraform_documented_variables"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformDocumentedVariablesRule) Enabled() bool {
- return false
-}
-
-// Severity returns the rule severity
-func (r *TerraformDocumentedVariablesRule) Severity() tflint.Severity {
- return tflint.NOTICE
-}
-
-// Link returns the rule reference link
-func (r *TerraformDocumentedVariablesRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check checks whether variables have descriptions
-func (r *TerraformDocumentedVariablesRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- body, diags := runner.GetModuleContent(&hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "variable",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{
- Attributes: []hclext.AttributeSchema{{Name: "description"}},
- },
- },
- },
- }, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return diags
- }
-
- for _, variable := range body.Blocks {
- attr, exists := variable.Body.Attributes["description"]
- if !exists {
- runner.EmitIssue(
- r,
- fmt.Sprintf("`%s` variable has no description", variable.Labels[0]),
- variable.DefRange,
- )
- continue
- }
-
- var description string
- diags = gohcl.DecodeExpression(attr.Expr, nil, &description)
- if diags.HasErrors() {
- return diags
- }
-
- if description == "" {
- runner.EmitIssue(
- r,
- fmt.Sprintf("`%s` variable has no description", variable.Labels[0]),
- variable.DefRange,
- )
- }
- }
-
- return nil
-}
diff --git a/rules/terraformrules/terraform_documented_variables_test.go b/rules/terraformrules/terraform_documented_variables_test.go
deleted file mode 100644
index 97533bc51..000000000
--- a/rules/terraformrules/terraform_documented_variables_test.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package terraformrules
-
-import (
- "testing"
-
- hcl "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformDocumentedVariablesRule(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- Expected tflint.Issues
- }{
- {
- Name: "no description",
- Content: `
-variable "no_description" {
- default = "default"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformDocumentedVariablesRule(),
- Message: "`no_description` variable has no description",
- Range: hcl.Range{
- Filename: "variables.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 26},
- },
- },
- },
- },
- {
- Name: "empty description",
- Content: `
-variable "empty_description" {
- description = ""
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformDocumentedVariablesRule(),
- Message: "`empty_description` variable has no description",
- Range: hcl.Range{
- Filename: "variables.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 29},
- },
- },
- },
- },
- {
- Name: "with description",
- Content: `
-variable "with_description" {
- description = "This is description"
-}`,
- Expected: tflint.Issues{},
- },
- }
-
- rule := NewTerraformDocumentedVariablesRule()
-
- for _, tc := range cases {
- runner := tflint.TestRunner(t, map[string]string{"variables.tf": tc.Content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
diff --git a/rules/terraformrules/terraform_empty_list_equality.go b/rules/terraformrules/terraform_empty_list_equality.go
deleted file mode 100644
index 83bbfd7a8..000000000
--- a/rules/terraformrules/terraform_empty_list_equality.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package terraformrules
-
-import (
- "log"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/hashicorp/hcl/v2/hclsyntax"
- "github.com/terraform-linters/tflint/tflint"
- "github.com/zclconf/go-cty/cty"
-)
-
-// TerraformEmptyListEqualityRule checks whether is there a comparison with an empty list
-type TerraformEmptyListEqualityRule struct{}
-
-// NewTerraformCommentSyntaxRule returns a new rule
-func NewTerraformEmptyListEqualityRule() *TerraformEmptyListEqualityRule {
- return &TerraformEmptyListEqualityRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformEmptyListEqualityRule) Name() string {
- return "terraform_empty_list_equality"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformEmptyListEqualityRule) Enabled() bool {
- return true
-}
-
-// Severity returns the rule severity
-func (r *TerraformEmptyListEqualityRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformEmptyListEqualityRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check checks whether the list is being compared with static empty list
-func (r *TerraformEmptyListEqualityRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- if err := r.checkEmptyList(runner); err != nil {
- return err
- }
-
- return nil
-}
-
-// checkEmptyList visits all blocks that can contain expressions and checks for comparisons with static empty list
-func (r *TerraformEmptyListEqualityRule) checkEmptyList(runner *tflint.Runner) error {
- return runner.WalkExpressions(func(expr hcl.Expression) error {
- if binaryOpExpr, ok := expr.(*hclsyntax.BinaryOpExpr); ok && binaryOpExpr.Op.Type == cty.Bool {
- if tupleConsExpr, ok := binaryOpExpr.LHS.(*hclsyntax.TupleConsExpr); ok && len(tupleConsExpr.Exprs) == 0 {
- r.emitIssue(binaryOpExpr.Range(), runner)
- } else if tupleConsExpr, ok := binaryOpExpr.RHS.(*hclsyntax.TupleConsExpr); ok && len(tupleConsExpr.Exprs) == 0 {
- r.emitIssue(binaryOpExpr.Range(), runner)
- }
- }
- return nil
- })
-}
-
-// emitIssue emits issue for comparison with static empty list
-func (r *TerraformEmptyListEqualityRule) emitIssue(exprRange hcl.Range, runner *tflint.Runner) {
- runner.EmitIssue(
- r,
- "Comparing a collection with an empty list is invalid. To detect an empty collection, check its length.",
- exprRange,
- )
-}
diff --git a/rules/terraformrules/terraform_empty_list_equality_test.go b/rules/terraformrules/terraform_empty_list_equality_test.go
deleted file mode 100644
index 484fa4209..000000000
--- a/rules/terraformrules/terraform_empty_list_equality_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package terraformrules
-
-import (
- "testing"
-
- hcl "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformEmptyListEqualityRule(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- Expected tflint.Issues
- }{
- {
- Name: "comparing with [] is not recommended",
- Content: `
-resource "aws_db_instance" "mysql" {
- count = [] == [] ? 0 : 1
- instance_class = "m4.2xlarge"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformEmptyListEqualityRule(),
- Message: "Comparing a collection with an empty list is invalid. To detect an empty collection, check its length.",
- Range: hcl.Range{
- Filename: "resource.tf",
- Start: hcl.Pos{Line: 3, Column: 10},
- End: hcl.Pos{Line: 3, Column: 18},
- },
- },
- },
- },
- {
- Name: "multiple comparisons with [] are not recommended",
- Content: `
-resource "aws_db_instance" "mysql" {
- count = [] == [] || [] == [] ? 1 : 0
- instance_class = "m4.2xlarge"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformEmptyListEqualityRule(),
- Message: "Comparing a collection with an empty list is invalid. To detect an empty collection, check its length.",
- Range: hcl.Range{
- Filename: "resource.tf",
- Start: hcl.Pos{Line: 3, Column: 10},
- End: hcl.Pos{Line: 3, Column: 18},
- },
- },
- {
- Rule: NewTerraformEmptyListEqualityRule(),
- Message: "Comparing a collection with an empty list is invalid. To detect an empty collection, check its length.",
- Range: hcl.Range{
- Filename: "resource.tf",
- Start: hcl.Pos{Line: 3, Column: 22},
- End: hcl.Pos{Line: 3, Column: 30},
- },
- },
- },
- },
- {
- Name: "comparing with [] inside parenthesis is not recommended",
- Content: `
-resource "aws_db_instance" "mysql" {
- count = ([] == []) ? 1 : 0
- instance_class = "m4.2xlarge"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformEmptyListEqualityRule(),
- Message: "Comparing a collection with an empty list is invalid. To detect an empty collection, check its length.",
- Range: hcl.Range{
- Filename: "resource.tf",
- Start: hcl.Pos{Line: 3, Column: 11},
- End: hcl.Pos{Line: 3, Column: 19},
- },
- },
- },
- },
- {
- Name: "negatively comparing with [] is not recommended",
- Content: `
-variable "my_list" {
- type = list(string)
-}
-resource "aws_db_instance" "mysql" {
- count = var.my_list != [] ? 1 : 0
- instance_class = "m4.2xlarge"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformEmptyListEqualityRule(),
- Message: "Comparing a collection with an empty list is invalid. To detect an empty collection, check its length.",
- Range: hcl.Range{
- Filename: "resource.tf",
- Start: hcl.Pos{Line: 6, Column: 10},
- End: hcl.Pos{Line: 6, Column: 27},
- },
- },
- },
- },
- {
- Name: "checking if length is 0 is recommended",
- Content: `
-variable "my_list" {
- type = list(string)
-}
-resource "aws_db_instance" "mysql" {
- count = length(var.my_list) == 0 ? 1 : 0
- instance_class = "m4.2xlarge"
-}`,
- Expected: tflint.Issues{},
- },
- }
-
- rule := NewTerraformEmptyListEqualityRule()
-
- for _, tc := range cases {
- runner := tflint.TestRunner(t, map[string]string{"resource.tf": tc.Content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
diff --git a/rules/terraformrules/terraform_module_pinned_source.go b/rules/terraformrules/terraform_module_pinned_source.go
deleted file mode 100644
index 5da25de69..000000000
--- a/rules/terraformrules/terraform_module_pinned_source.go
+++ /dev/null
@@ -1,185 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "log"
- "net/url"
- "path/filepath"
- "strings"
-
- "github.com/Masterminds/semver/v3"
- "github.com/hashicorp/go-getter"
- sdk "github.com/terraform-linters/tflint-plugin-sdk/tflint"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// TerraformModulePinnedSourceRule checks unpinned or default version module source
-type TerraformModulePinnedSourceRule struct {
- attributeName string
-}
-
-type terraformModulePinnedSourceRuleConfig struct {
- Style string `hcl:"style,optional"`
- DefaultBranches []string `hcl:"default_branches,optional"`
-}
-
-// NewTerraformModulePinnedSourceRule returns new rule with default attributes
-func NewTerraformModulePinnedSourceRule() *TerraformModulePinnedSourceRule {
- return &TerraformModulePinnedSourceRule{
- attributeName: "source",
- }
-}
-
-// Name returns the rule name
-func (r *TerraformModulePinnedSourceRule) Name() string {
- return "terraform_module_pinned_source"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformModulePinnedSourceRule) Enabled() bool {
- return true
-}
-
-// Severity returns the rule severity
-func (r *TerraformModulePinnedSourceRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformModulePinnedSourceRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check checks if module source version is pinned
-// Note that this rule is valid only for Git or Mercurial source
-func (r *TerraformModulePinnedSourceRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- config := terraformModulePinnedSourceRuleConfig{Style: "flexible"}
- config.DefaultBranches = append(config.DefaultBranches, "master", "main", "default", "develop")
- if err := runner.DecodeRuleConfig(r.Name(), &config); err != nil {
- return err
- }
-
- body, diags := runner.GetModuleContent(moduleCallSchema, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return diags
- }
-
- for _, block := range body.Blocks {
- module, diags := decodeModuleCall(block)
- if diags.HasErrors() {
- return diags
- }
-
- if err := r.checkModule(runner, module, config); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (r *TerraformModulePinnedSourceRule) checkModule(runner *tflint.Runner, module *moduleCall, config terraformModulePinnedSourceRuleConfig) error {
- log.Printf("[DEBUG] Walk `%s` attribute", module.name+".source")
-
- source, err := getter.Detect(module.source, filepath.Dir(module.defRange.Filename), []getter.Detector{
- // https://github.com/hashicorp/terraform/blob/51b0aee36cc2145f45f5b04051a01eb6eb7be8bf/internal/getmodules/getter.go#L30-L52
- new(getter.GitHubDetector),
- new(getter.GitDetector),
- new(getter.BitBucketDetector),
- new(getter.GCSDetector),
- new(getter.S3Detector),
- new(getter.FileDetector),
- })
- if err != nil {
- return err
- }
-
- u, err := url.Parse(source)
- if err != nil {
- return err
- }
-
- switch u.Scheme {
- case "git", "hg":
- default:
- return nil
- }
-
- if u.Opaque != "" {
- // for git:: or hg:: pseudo-URLs, Opaque is :https, but query will still be parsed
- query := u.RawQuery
- u, err = url.Parse(strings.TrimPrefix(u.Opaque, ":"))
- if err != nil {
- return err
- }
-
- u.RawQuery = query
- }
-
- if u.Hostname() == "" {
- runner.EmitIssue(
- r,
- fmt.Sprintf("Module source %q is not a valid URL", module.source),
- module.sourceAttr.Expr.Range(),
- )
-
- return nil
- }
-
- query := u.Query()
-
- if ref := query.Get("ref"); ref != "" {
- return r.checkRevision(runner, module, config, "ref", ref)
- }
-
- if rev := query.Get("rev"); rev != "" {
- return r.checkRevision(runner, module, config, "rev", rev)
- }
-
- runner.EmitIssue(
- r,
- fmt.Sprintf(`Module source "%s" is not pinned`, module.source),
- module.sourceAttr.Expr.Range(),
- )
-
- return nil
-}
-
-func (r *TerraformModulePinnedSourceRule) checkRevision(runner *tflint.Runner, module *moduleCall, config terraformModulePinnedSourceRuleConfig, key string, value string) error {
- switch config.Style {
- // The "flexible" style requires a revision that is not a default branch
- case "flexible":
- for _, branch := range config.DefaultBranches {
- if value == branch {
- runner.EmitIssue(
- r,
- fmt.Sprintf("Module source \"%s\" uses a default branch as %s (%s)", module.source, key, branch),
- module.sourceAttr.Expr.Range(),
- )
-
- return nil
- }
- }
- // The "semver" style requires a revision that is a semantic version
- case "semver":
- _, err := semver.NewVersion(value)
- if err != nil {
- runner.EmitIssue(
- r,
- fmt.Sprintf("Module source \"%s\" uses a %s which is not a semantic version string", module.source, key),
- module.sourceAttr.Expr.Range(),
- )
- }
- default:
- return fmt.Errorf("`%s` is invalid style", config.Style)
- }
-
- return nil
-}
diff --git a/rules/terraformrules/terraform_module_pinned_source_test.go b/rules/terraformrules/terraform_module_pinned_source_test.go
deleted file mode 100644
index cb9d9afd3..000000000
--- a/rules/terraformrules/terraform_module_pinned_source_test.go
+++ /dev/null
@@ -1,546 +0,0 @@
-package terraformrules
-
-import (
- "os"
- "testing"
-
- hcl "github.com/hashicorp/hcl/v2"
- "github.com/spf13/afero"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformModulePinnedSource(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: "local module",
- Content: `
-module "unpinned" {
- source = "./local"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "git module is not pinned",
- Content: `
-module "unpinned" {
- source = "git://hashicorp.com/consul.git"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"git://hashicorp.com/consul.git\" is not pinned",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 44},
- },
- },
- },
- },
- {
- Name: "git module reference is default",
- Content: `
-module "default_git" {
- source = "git://hashicorp.com/consul.git?ref=master"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"git://hashicorp.com/consul.git?ref=master\" uses a default branch as ref (master)",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 55},
- },
- },
- },
- },
- {
- Name: "git module reference is pinned",
- Content: `
-module "pinned_git" {
- source = "git://hashicorp.com/consul.git?ref=pinned"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "invalid URL",
- Content: `
-module "invalid" {
- source = "git://#{}.com"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: `Module source "git://#{}.com" is not a valid URL`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 27},
- },
- },
- },
- },
- {
- Name: "git module reference is pinned, but style is semver",
- Content: `
-module "pinned_git" {
- source = "git://hashicorp.com/consul.git?ref=pinned"
-}`,
- Config: `
-rule "terraform_module_pinned_source" {
- enabled = true
- style = "semver"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"git://hashicorp.com/consul.git?ref=pinned\" uses a ref which is not a semantic version string",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 55},
- },
- },
- },
- },
- {
- Name: "git module reference is pinned to semver",
- Content: `
-module "pinned_git" {
- source = "git://hashicorp.com/consul.git?ref=v1.2.3"
-}`,
- Config: `
-rule "terraform_module_pinned_source" {
- enabled = true
- style = "semver"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "git module reference is pinned to semver (no leading v)",
- Content: `
-module "pinned_git" {
- source = "git://hashicorp.com/consul.git?ref=1.2.3"
-}`,
- Config: `
-rule "terraform_module_pinned_source" {
- enabled = true
- style = "semver"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "github module is not pinned",
- Content: `
-module "unpinned" {
- source = "github.com/hashicorp/consul"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"github.com/hashicorp/consul\" is not pinned",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 41},
- },
- },
- },
- },
- {
- Name: "github ssh module is not pinned",
- Content: `
-module "unpinned" {
- source = "git@github.com:hashicorp/consul.git"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"git@github.com:hashicorp/consul.git\" is not pinned",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 49},
- },
- },
- },
- },
- {
- Name: "github module reference is default",
- Content: `
-module "default_git" {
- source = "github.com/hashicorp/consul.git?ref=master"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"github.com/hashicorp/consul.git?ref=master\" uses a default branch as ref (master)",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 56},
- },
- },
- },
- },
- {
- Name: "github module reference is pinned",
- Content: `
-module "pinned_git" {
- source = "github.com/hashicorp/consul.git?ref=pinned"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "github ssh module is pinned",
- Content: `
-module "unpinned" {
- source = "git@github.com:hashicorp/consul.git?ref=pinned"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "github module reference is pinned, but style is semver",
- Content: `
-module "pinned_git" {
- source = "github.com/hashicorp/consul.git?ref=pinned"
-}`,
- Config: `
-rule "terraform_module_pinned_source" {
- enabled = true
- style = "semver"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"github.com/hashicorp/consul.git?ref=pinned\" uses a ref which is not a semantic version string",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 56},
- },
- },
- },
- },
- {
- Name: "github module reference is pinned to semver",
- Content: `
-module "pinned_git" {
- source = "github.com/hashicorp/consul.git?ref=v1.2.3"
-}`,
- Config: `
-rule "terraform_module_pinned_source" {
- enabled = true
- style = "semver"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "bitbucket module is not pinned",
- Content: `
-module "unpinned" {
- source = "bitbucket.org/hashicorp/tf-test-git"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"bitbucket.org/hashicorp/tf-test-git\" is not pinned",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 49},
- },
- },
- },
- },
- {
- Name: "bitbucket git module reference is default",
- Content: `
-module "default_git" {
- source = "bitbucket.org/hashicorp/tf-test-git.git?ref=master"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"bitbucket.org/hashicorp/tf-test-git.git?ref=master\" uses a default branch as ref (master)",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 64},
- },
- },
- },
- },
- {
- Name: "bitbucket git module reference is pinned",
- Content: `
-module "pinned_git" {
- source = "bitbucket.org/hashicorp/tf-test-git.git?ref=pinned"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "bitbucket git module reference is pinned, but style is semver",
- Content: `
-module "pinned_git" {
- source = "bitbucket.org/hashicorp/tf-test-git.git?ref=pinned"
-}`,
- Config: `
-rule "terraform_module_pinned_source" {
- enabled = true
- style = "semver"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"bitbucket.org/hashicorp/tf-test-git.git?ref=pinned\" uses a ref which is not a semantic version string",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 64},
- },
- },
- },
- },
- {
- Name: "bitbucket git module reference is pinned to semver",
- Content: `
-module "pinned_git" {
- source = "bitbucket.org/hashicorp/tf-test-git.git?ref=v1.2.3"
-}`,
- Config: `
-rule "terraform_module_pinned_source" {
- enabled = true
- style = "semver"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "generic git (git::https) module reference is not pinned",
- Content: `
-module "unpinned_generic_git_https" {
- source = "git::https://hashicorp.com/consul.git"
-}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"git::https://hashicorp.com/consul.git\" is not pinned",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 51},
- },
- },
- },
- },
- {
- Name: "generic git (git::ssh) module reference is not pinned",
- Content: `
-module "unpinned_generic_git_ssh" {
- source = "git::ssh://git@github.com/owner/repo.git"
-}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"git::ssh://git@github.com/owner/repo.git\" is not pinned",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 54},
- },
- },
- },
- },
- {
- Name: "generic git (git::https) module reference is default",
- Content: `
-module "default_generic_git_https" {
- source = "git::https://hashicorp.com/consul.git?ref=master"
-}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"git::https://hashicorp.com/consul.git?ref=master\" uses a default branch as ref (master)",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 62},
- },
- },
- },
- },
- {
- Name: "generic git (git::ssh) module reference is default",
- Content: `
-module "default_generic_git_ssh" {
- source = "git::ssh://git@github.com/owner/repo.git?ref=master"
-}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"git::ssh://git@github.com/owner/repo.git?ref=master\" uses a default branch as ref (master)",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 65},
- },
- },
- },
- },
- {
- Name: "generic git (git::https) module reference is pinned",
- Content: `
-module "pinned_generic_git_https" {
- source = "git::https://hashicorp.com/consul.git?ref=pinned"
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "generic git (git::ssh) module reference is pinned",
- Content: `
-module "pinned_generic_git_ssh" {
- source = "git::ssh://git@github.com/owner/repo.git?ref=pinned"
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "github module reference is unpinned via custom branches",
- Content: `
-module "pinned_git" {
- source = "github.com/hashicorp/consul.git?ref=foo"
-}`,
- Config: `
-rule "terraform_module_pinned_source" {
- enabled = true
- default_branches = ["foo"]
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"github.com/hashicorp/consul.git?ref=foo\" uses a default branch as ref (foo)",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 53},
- },
- },
- },
- },
- {
- Name: "mercurial module is not pinned",
- Content: `
-module "default_mercurial" {
- source = "hg::http://hashicorp.com/consul.hg"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"hg::http://hashicorp.com/consul.hg\" is not pinned",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 48},
- },
- },
- },
- },
- {
- Name: "mercurial module reference is default",
- Content: `
-module "default_mercurial" {
- source = "hg::http://hashicorp.com/consul.hg?rev=default"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"hg::http://hashicorp.com/consul.hg?rev=default\" uses a default branch as rev (default)",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 60},
- },
- },
- },
- },
- {
- Name: "mercurial module reference is pinned",
- Content: `
-module "pinned_mercurial" {
- source = "hg::http://hashicorp.com/consul.hg?rev=pinned"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "git module is not pinned with default config",
- Content: `
-module "unpinned" {
- source = "git://hashicorp.com/consul.git"
-}`,
- Config: `
-rule "terraform_module_pinned_source" {
- enabled = true
- style = "flexible"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModulePinnedSourceRule(),
- Message: "Module source \"git://hashicorp.com/consul.git\" is not pinned",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 3, Column: 12},
- End: hcl.Pos{Line: 3, Column: 44},
- },
- },
- },
- },
- }
-
- rule := NewTerraformModulePinnedSourceRule()
-
- for _, tc := range cases {
- tc := tc
- t.Run(tc.Name, func(t *testing.T) {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"module.tf": tc.Content}, loadConfigfromTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- })
- }
-}
-
-// TODO: Replace with TestRunner
-func loadConfigfromTempFile(t *testing.T, content string) *tflint.Config {
- if content == "" {
- return tflint.EmptyConfig()
- }
-
- tmpfile, err := os.CreateTemp("", "terraform_module_pinned_source")
- if err != nil {
- t.Fatal(err)
- }
- defer os.Remove(tmpfile.Name())
-
- if _, err := tmpfile.Write([]byte(content)); err != nil {
- t.Fatal(err)
- }
- config, err := tflint.LoadConfig(afero.Afero{Fs: afero.NewOsFs()}, tmpfile.Name())
- if err != nil {
- t.Fatal(err)
- }
- return config
-}
diff --git a/rules/terraformrules/terraform_module_version.go b/rules/terraformrules/terraform_module_version.go
deleted file mode 100644
index 763fbcb53..000000000
--- a/rules/terraformrules/terraform_module_version.go
+++ /dev/null
@@ -1,132 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "log"
- "regexp"
-
- tfaddr "github.com/hashicorp/terraform-registry-address"
- sdk "github.com/terraform-linters/tflint-plugin-sdk/tflint"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// SemVer regexp with optional leading =
-// https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
-var exactVersionRegexp = regexp.MustCompile(`^=?\s*` + `(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)
-
-// TerraformModuleVersionRule checks that Terraform modules sourced from a registry specify a version
-type TerraformModuleVersionRule struct{}
-
-// TerraformModuleVersionRuleConfig is the config structure for the TerraformModuleVersionRule rule
-type TerraformModuleVersionRuleConfig struct {
- Exact bool `hcl:"exact,optional"`
-}
-
-// NewTerraformModuleVersionRule returns a new rule
-func NewTerraformModuleVersionRule() *TerraformModuleVersionRule {
- return &TerraformModuleVersionRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformModuleVersionRule) Name() string {
- return "terraform_module_version"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformModuleVersionRule) Enabled() bool {
- return true
-}
-
-// Severity returns the rule severity
-func (r *TerraformModuleVersionRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformModuleVersionRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check checks whether module source attributes resolve to a Terraform registry
-// If they do, it checks a version (or range) is set
-func (r *TerraformModuleVersionRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- config := TerraformModuleVersionRuleConfig{}
- if err := runner.DecodeRuleConfig(r.Name(), &config); err != nil {
- return err
- }
-
- body, diags := runner.GetModuleContent(moduleCallSchema, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return diags
- }
-
- for _, block := range body.Blocks {
- module, diags := decodeModuleCall(block)
- if diags.HasErrors() {
- return diags
- }
-
- if err := r.checkModule(runner, module, config); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (r *TerraformModuleVersionRule) checkModule(runner *tflint.Runner, module *moduleCall, config TerraformModuleVersionRuleConfig) error {
- log.Printf("[DEBUG] Walk `%s` attribute", module.name+".source")
-
- _, err := tfaddr.ParseModuleSource(module.source)
- if err != nil {
- // If parsing fails, the source does not expect to specify a version,
- // such as local or remote. So instead of returning an error,
- // it returns nil to stop the check.
- return nil
- }
-
- return r.checkVersion(runner, module, config)
-}
-
-func (r *TerraformModuleVersionRule) checkVersion(runner *tflint.Runner, module *moduleCall, config TerraformModuleVersionRuleConfig) error {
- if module.version == nil {
- runner.EmitIssue(
- r,
- fmt.Sprintf("module %q should specify a version", module.name),
- module.defRange,
- )
-
- return nil
- }
-
- if !config.Exact {
- return nil
- }
-
- if len(module.version) > 1 {
- runner.EmitIssue(
- r,
- fmt.Sprintf("module %q should specify an exact version, but multiple constraints were found", module.name),
- module.versionAttr.Range,
- )
-
- return nil
- }
-
- if !exactVersionRegexp.MatchString(module.version[0].String()) {
- runner.EmitIssue(
- r,
- fmt.Sprintf("module %q should specify an exact version, but a range was found", module.name),
- module.versionAttr.Range,
- )
- }
-
- return nil
-}
diff --git a/rules/terraformrules/terraform_module_version_test.go b/rules/terraformrules/terraform_module_version_test.go
deleted file mode 100644
index 2828b9854..000000000
--- a/rules/terraformrules/terraform_module_version_test.go
+++ /dev/null
@@ -1,271 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "os"
- "testing"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/spf13/afero"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func TestTerraformModuleVersion_Registry(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: "version",
- Content: `
-module "m" {
- source = "ns/name/provider"
- version = "1.0.0"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "multiple digits",
- Content: `
-module "m" {
- source = "ns/name/provider"
- version = "10.0.0"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "version equals",
- Content: `
-module "m" {
- source = "ns/name/provider"
- version = "= 1.0.0"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "prerelease",
- Content: `
-module "m" {
- source = "ns/name/provider"
- version = "2.0.0-pre"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "custom host",
- Content: `
-module "m" {
- source = "my.private.reigstry/ns/name/provider"
- version = "1.0.0"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "child module",
- Content: `
-module "m" {
- source = "ns/name/provider//modules/child"
- version = "1.0.0"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "range",
- Content: `
-module "m" {
- source = "ns/name/provider"
- version = "~> 1"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "multiple",
- Content: `
-module "m" {
- source = "ns/name/provider"
- version = "1, 2, 3"
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "missing",
- Content: `
-module "m" {
- source = "ns/name/provider"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModuleVersionRule(),
- Message: `module "m" should specify a version`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 11},
- },
- },
- },
- },
- {
- Name: "exact version valid",
- Content: `
-module "m" {
- source = "ns/name/provider"
- version = "1.0.0"
-}`,
- Config: testTerraformModuleVersionExactConfig,
- Expected: tflint.Issues{},
- },
- {
- Name: "exact version invalid: multiple constraints",
- Content: `
-module "m" {
- source = "ns/name/provider"
- version = "1.0.0, 1.0.1"
-}`,
- Config: testTerraformModuleVersionExactConfig,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModuleVersionRule(),
- Message: `module "m" should specify an exact version, but multiple constraints were found`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 4, Column: 2},
- End: hcl.Pos{Line: 4, Column: 26},
- },
- },
- },
- },
- {
- Name: "exact version invalid: range operator",
- Content: `
-module "m" {
- source = "ns/name/provider"
- version = "~> 1.0.0"
-}`,
- Config: testTerraformModuleVersionExactConfig,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModuleVersionRule(),
- Message: `module "m" should specify an exact version, but a range was found`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 4, Column: 2},
- End: hcl.Pos{Line: 4, Column: 22},
- },
- },
- },
- },
- {
- Name: "exact version invalid: partial version",
- Content: `
-module "m" {
- source = "ns/name/provider"
- version = "1.0"
-}`,
- Config: testTerraformModuleVersionExactConfig,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformModuleVersionRule(),
- Message: `module "m" should specify an exact version, but a range was found`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{Line: 4, Column: 2},
- End: hcl.Pos{Line: 4, Column: 17},
- },
- },
- },
- },
- }
-
- rule := NewTerraformModuleVersionRule()
-
- for _, tc := range cases {
- tc := tc
- t.Run(tc.Name, func(t *testing.T) {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"module.tf": tc.Content}, loadConfigfromModuleVersionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- })
- }
-}
-
-func TestTerraformModuleVersion_NonRegistry(t *testing.T) {
- cases := []struct {
- Name string
- Source string
- }{
- {
- Name: "local",
- Source: "./local/dir",
- },
- {
- Name: "github",
- Source: "github.com/hashicorp/example",
- },
- {
- Name: "git",
- Source: "git::https://example.com/vpc.git",
- },
- {
- Name: "https",
- Source: "https://example.com/vpc-module.zip",
- },
- }
-
- rule := NewTerraformModuleVersionRule()
-
- for _, tc := range cases {
- tc := tc
- t.Run(tc.Name, func(t *testing.T) {
- content := fmt.Sprintf(testTerraformModuleVersionNonRegistrySource, tc.Source)
- runner := tflint.TestRunner(t, map[string]string{"module.tf": content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tflint.Issues{}, runner.Issues)
- })
- }
-}
-
-// TODO: Replace with TestRunner
-func loadConfigfromModuleVersionTempFile(t *testing.T, content string) *tflint.Config {
- if content == "" {
- return tflint.EmptyConfig()
- }
-
- tmpfile, err := os.CreateTemp("", "terraform_module_version")
- if err != nil {
- t.Fatal(err)
- }
- defer os.Remove(tmpfile.Name())
-
- if _, err := tmpfile.Write([]byte(content)); err != nil {
- t.Fatal(err)
- }
- config, err := tflint.LoadConfig(afero.Afero{Fs: afero.NewOsFs()}, tmpfile.Name())
- if err != nil {
- t.Fatal(err)
- }
- return config
-}
-
-const testTerraformModuleVersionExactConfig = `
-rule "terraform_module_version" {
- enabled = true
- exact = true
-}
-`
-
-const testTerraformModuleVersionNonRegistrySource = `
-module "m" {
- source = "%s"
-}
-`
diff --git a/rules/terraformrules/terraform_naming_convention.go b/rules/terraformrules/terraform_naming_convention.go
deleted file mode 100644
index eff0bf73d..000000000
--- a/rules/terraformrules/terraform_naming_convention.go
+++ /dev/null
@@ -1,272 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "log"
- "regexp"
- "strings"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint-plugin-sdk/hclext"
- sdk "github.com/terraform-linters/tflint-plugin-sdk/tflint"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// TerraformNamingConventionRule checks whether blocks follow naming convention
-type TerraformNamingConventionRule struct{}
-
-type terraformNamingConventionRuleConfig struct {
- Format string `hcl:"format,optional"`
- Custom string `hcl:"custom,optional"`
-
- CustomFormats map[string]*CustomFormatConfig `hcl:"custom_formats,optional"`
-
- Data *BlockFormatConfig `hcl:"data,block"`
- Locals *BlockFormatConfig `hcl:"locals,block"`
- Module *BlockFormatConfig `hcl:"module,block"`
- Output *BlockFormatConfig `hcl:"output,block"`
- Resource *BlockFormatConfig `hcl:"resource,block"`
- Variable *BlockFormatConfig `hcl:"variable,block"`
-}
-
-// CustomFormatConfig defines a custom format that can be used instead of the predefined formats
-type CustomFormatConfig struct {
- Regexp string `cty:"regex"`
- Description string `cty:"description"`
-}
-
-// BlockFormatConfig defines the pre-defined format or custom regular expression to use
-type BlockFormatConfig struct {
- Format string `hcl:"format,optional"`
- Custom string `hcl:"custom,optional"`
-}
-
-// NameValidator contains the regular expression to validate block name, if it was a named format, and the format name/regular expression string
-type NameValidator struct {
- Format string
- IsNamedFormat bool
- Regexp *regexp.Regexp
-}
-
-// NewTerraformNamingConventionRule returns new rule with default attributes
-func NewTerraformNamingConventionRule() *TerraformNamingConventionRule {
- return &TerraformNamingConventionRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformNamingConventionRule) Name() string {
- return "terraform_naming_convention"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformNamingConventionRule) Enabled() bool {
- return false
-}
-
-// Severity returns the rule severity
-func (r *TerraformNamingConventionRule) Severity() tflint.Severity {
- return tflint.NOTICE
-}
-
-// Link returns the rule reference link
-func (r *TerraformNamingConventionRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check checks whether blocks follow naming convention
-func (r *TerraformNamingConventionRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- config := terraformNamingConventionRuleConfig{}
- config.Format = "snake_case"
- if err := runner.DecodeRuleConfig(r.Name(), &config); err != nil {
- return err
- }
-
- defaultNameValidator, err := config.getNameValidator()
- if err != nil {
- return fmt.Errorf("Invalid default configuration: %v", err)
- }
-
- var nameValidator *NameValidator
-
- body, diags := runner.GetModuleContent(&hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "data",
- LabelNames: []string{"type", "name"},
- Body: &hclext.BodySchema{},
- },
- {
- Type: "module",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{},
- },
- {
- Type: "output",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{},
- },
- {
- Type: "resource",
- LabelNames: []string{"type", "name"},
- Body: &hclext.BodySchema{},
- },
- {
- Type: "variable",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{},
- },
- },
- }, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return diags
- }
- blocks := body.Blocks.ByType()
-
- // data
- dataBlockName := "data"
- nameValidator, err = config.Data.getNameValidator(defaultNameValidator, &config, dataBlockName)
- if err != nil {
- return err
- }
- for _, block := range blocks[dataBlockName] {
- nameValidator.checkBlock(runner, r, dataBlockName, block.Labels[1], &block.DefRange)
- }
-
- // modules
- moduleBlockName := "module"
- nameValidator, err = config.Module.getNameValidator(defaultNameValidator, &config, moduleBlockName)
- if err != nil {
- return err
- }
- for _, block := range blocks[moduleBlockName] {
- nameValidator.checkBlock(runner, r, moduleBlockName, block.Labels[0], &block.DefRange)
- }
-
- // outputs
- outputBlockName := "output"
- nameValidator, err = config.Output.getNameValidator(defaultNameValidator, &config, outputBlockName)
- if err != nil {
- return err
- }
- for _, block := range blocks[outputBlockName] {
- nameValidator.checkBlock(runner, r, outputBlockName, block.Labels[0], &block.DefRange)
- }
-
- // resources
- resourceBlockName := "resource"
- nameValidator, err = config.Resource.getNameValidator(defaultNameValidator, &config, resourceBlockName)
- if err != nil {
- return err
- }
- for _, block := range blocks[resourceBlockName] {
- nameValidator.checkBlock(runner, r, resourceBlockName, block.Labels[1], &block.DefRange)
- }
-
- // variables
- variableBlockName := "variable"
- nameValidator, err = config.Variable.getNameValidator(defaultNameValidator, &config, variableBlockName)
- if err != nil {
- return err
- }
- for _, block := range blocks[variableBlockName] {
- nameValidator.checkBlock(runner, r, variableBlockName, block.Labels[0], &block.DefRange)
- }
-
- // locals
- localBlockName := "local value"
- nameValidator, err = config.Locals.getNameValidator(defaultNameValidator, &config, localBlockName)
- if err != nil {
- return err
- }
- locals, diags := getLocals(runner)
- if diags.HasErrors() {
- return diags
- }
- for name, local := range locals {
- nameValidator.checkBlock(runner, r, localBlockName, name, &local.defRange)
- }
-
- return nil
-}
-
-func (validator *NameValidator) checkBlock(runner *tflint.Runner, r *TerraformNamingConventionRule, blockTypeName string, blockName string, blockDeclRange *hcl.Range) {
- if validator != nil && !validator.Regexp.MatchString(blockName) {
- var formatType string
- if validator.IsNamedFormat {
- formatType = "format"
- } else {
- formatType = "RegExp"
- }
-
- runner.EmitIssue(
- r,
- fmt.Sprintf("%s name `%s` must match the following %s: %s", blockTypeName, blockName, formatType, validator.Format),
- *blockDeclRange,
- )
- }
-}
-
-func (blockFormatConfig *BlockFormatConfig) getNameValidator(defaultValidator *NameValidator, config *terraformNamingConventionRuleConfig, blockName string) (*NameValidator, error) {
- validator := defaultValidator
- if blockFormatConfig != nil {
- nameValidator, err := getNameValidator(blockFormatConfig.Custom, blockFormatConfig.Format, config)
- if err != nil {
- return nil, fmt.Errorf("Invalid %s configuration: %v", blockName, err)
- }
-
- validator = nameValidator
- }
- return validator, nil
-}
-
-func (config *terraformNamingConventionRuleConfig) getNameValidator() (*NameValidator, error) {
- return getNameValidator(config.Custom, config.Format, config)
-}
-
-var predefinedFormats = map[string]*regexp.Regexp{
- "snake_case": regexp.MustCompile("^[a-z][a-z0-9]*(_[a-z0-9]+)*$"),
- "mixed_snake_case": regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"),
-}
-
-func getNameValidator(custom string, format string, config *terraformNamingConventionRuleConfig) (*NameValidator, error) {
- // Prefer custom format if specified
- if custom != "" {
- return getCustomNameValidator(false, custom, custom)
- } else if format != "none" {
- customFormats := config.CustomFormats
- customFormatConfig, exists := customFormats[format]
- if exists {
- return getCustomNameValidator(true, customFormatConfig.Description, customFormatConfig.Regexp)
- }
-
- regex, exists := predefinedFormats[strings.ToLower(format)]
- if exists {
- nameValidator := &NameValidator{
- IsNamedFormat: true,
- Format: format,
- Regexp: regex,
- }
- return nameValidator, nil
- }
- return nil, fmt.Errorf("`%s` is unsupported format", format)
- }
-
- return nil, nil
-}
-
-func getCustomNameValidator(isNamed bool, format, expression string) (*NameValidator, error) {
- regex, err := regexp.Compile(expression)
- nameValidator := &NameValidator{
- IsNamedFormat: isNamed,
- Format: format,
- Regexp: regex,
- }
- return nameValidator, err
-}
diff --git a/rules/terraformrules/terraform_naming_convention_test.go b/rules/terraformrules/terraform_naming_convention_test.go
deleted file mode 100644
index 384e53215..000000000
--- a/rules/terraformrules/terraform_naming_convention_test.go
+++ /dev/null
@@ -1,2916 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "os"
- "testing"
-
- hcl "github.com/hashicorp/hcl/v2"
- "github.com/spf13/afero"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// Data blocks
-func Test_TerraformNamingConventionRule_Data_DefaultEmpty(t *testing.T) {
- testDataSnakeCase(t, "default config", "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_DefaultFormat(t *testing.T) {
- testDataMixedSnakeCase(t, `default config (format="mixed_snake_case")`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_DefaultCustom(t *testing.T) {
- testDataSnakeCase(t, `default config (custom="^[a-z_]+$")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_DefaultDisabled(t *testing.T) {
- testDataDisabled(t, `default config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_DefaultFormat_OverrideFormat(t *testing.T) {
- testDataSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-
- data {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_DefaultFormat_OverrideCustom(t *testing.T) {
- testDataSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-
- data {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_DefaultCustom_OverrideFormat(t *testing.T) {
- testDataSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^ignored$"
-
- data {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_DefaultCustom_OverrideCustom(t *testing.T) {
- testDataSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^ignored$"
-
- data {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_DefaultDisabled_OverrideFormat(t *testing.T) {
- testDataSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- data {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_DefaultDisabled_OverrideCustom(t *testing.T) {
- testDataSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- data {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_DefaultEmpty_OverrideDisabled(t *testing.T) {
- testDataDisabled(t, `overridden config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
-
- data {
- format = "none"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_DefaultFormat_OverrideDisabled(t *testing.T) {
- testDataDisabled(t, `overridden config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "snake_case"
-
- data {
- format = "none"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_CustomFormats(t *testing.T) {
- testDataSnakeCase(t, `default config (custom_format="custom_snake_case")`, "format: Custom Snake Case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "custom_snake_case"
-
- custom_formats = {
- custom_snake_case = {
- description = "Custom Snake Case"
- regex = "^[a-z][a-z0-9]*(_[a-z0-9]+)*$"
- }
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Data_CustomFormats_OverridePredefined(t *testing.T) {
- testDataSnakeCase(t, `default config (custom_format="snake_case")`, "format: Custom Snake Case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "snake_case"
-
- custom_formats = {
- snake_case = {
- description = "Custom Snake Case"
- regex = "^[a-z][a-z0-9]*(_[a-z0-9]+)*$"
- }
- }
-}`)
-}
-
-func testDataSnakeCase(t *testing.T, testType string, formatName string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("data: %s - Invalid snake_case with dash", testType),
- Content: `
-data "aws_eip" "dash-name" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("data name `dash-name` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 27},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("data: %s - Invalid snake_case with camelCase", testType),
- Content: `
-data "aws_eip" "camelCased" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("data name `camelCased` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 28},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("data: %s - Invalid snake_case with double underscore", testType),
- Content: `
-data "aws_eip" "foo__bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("data name `foo__bar` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 26},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("data: %s - Invalid snake_case with underscore tail", testType),
- Content: `
-data "aws_eip" "foo_bar_" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("data name `foo_bar_` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 26},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("data: %s - Invalid snake_case with Mixed_Snake_Case", testType),
- Content: `
-data "aws_eip" "Foo_Bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("data name `Foo_Bar` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 25},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("data: %s - Invalid snake_case with count = 0", testType),
- Content: `
-data "aws_eip" "camelCased" {
- count = 0
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("data name `camelCased` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 28},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("data: %s - Valid snake_case", testType),
- Content: `
-data "aws_eip" "foo_bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("data: %s - Valid single word", testType),
- Content: `
-data "aws_eip" "foo" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-func testDataMixedSnakeCase(t *testing.T, testType string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("data: %s - Invalid mixed_snake_case with dash", testType),
- Content: `
-data "aws_eip" "dash-name" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "data name `dash-name` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 27},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("data: %s - Invalid mixed_snake_case with double underscore", testType),
- Content: `
-data "aws_eip" "Foo__Bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "data name `Foo__Bar` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 26},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("data: %s - Invalid mixed_snake_case with underscore tail", testType),
- Content: `
-data "aws_eip" "Foo_Bar_" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "data name `Foo_Bar_` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 26},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("data: %s - Valid snake_case", testType),
- Content: `
-data "aws_eip" "foo_bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("data: %s - Valid single word", testType),
- Content: `
-data "aws_eip" "foo" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("data: %s - Valid Mixed_Snake_Case", testType),
- Content: `
-data "aws_eip" "Foo_Bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("data: %s - Valid single word with upper characters", testType),
- Content: `
-data "aws_eip" "foo" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("data: %s - Valid PascalCase", testType),
- Content: `
-data "aws_eip" "PascalCase" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("data: %s - Valid camelCase", testType),
- Content: `
-data "aws_eip" "camelCase" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-func testDataDisabled(t *testing.T, testType string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("data: %s - Valid mixed_snake_case with dash", testType),
- Content: `
-data "aws_eip" "dash-name" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("data: %s - Valid snake_case", testType),
- Content: `
-data "aws_eip" "foo_bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("data: %s - Valid single word", testType),
- Content: `
-data "aws_eip" "foo" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("data: %s - Valid Mixed_Snake_Case", testType),
- Content: `
-data "aws_eip" "Foo_Bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("data: %s - Valid single word upper characters", testType),
- Content: `
-data "aws_eip" "Foo" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("data: %s - Valid PascalCase", testType),
- Content: `
-data "aws_eip" "PascalCase" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("data: %s - Valid camelCase", testType),
- Content: `
-data "aws_eip" "camelCase" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-// Local values
-func Test_TerraformNamingConventionRule_Locals_DefaultEmpty(t *testing.T) {
- testLocalsSnakeCase(t, "default config", "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Locals_DefaultFormat(t *testing.T) {
- testLocalsMixedSnakeCase(t, `default config (format="mixed_snake_case")`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Locals_DefaultCustom(t *testing.T) {
- testLocalsSnakeCase(t, `default config (custom="^[a-z_]+$")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Locals_DefaultDisabled(t *testing.T) {
- testLocalsDisabled(t, `default config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Locals_DefaultFormat_OverrideFormat(t *testing.T) {
- testLocalsSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-
- locals {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Locals_DefaultFormat_OverrideCustom(t *testing.T) {
- testLocalsSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-
- locals {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Locals_DefaultCustom_OverrideFormat(t *testing.T) {
- testLocalsSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^ignored$"
-
- locals {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Locals_DefaultCustom_OverrideCustom(t *testing.T) {
- testLocalsSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^ignored$"
-
- locals {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Locals_DefaultDisabled_OverrideFormat(t *testing.T) {
- testLocalsSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- locals {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Locals_DefaultDisabled_OverrideCustom(t *testing.T) {
- testLocalsSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- locals {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Locals_DefaultEmpty_OverrideDisabled(t *testing.T) {
- testLocalsDisabled(t, `overridden config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
-
- locals {
- format = "none"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Locals_DefaultFormat_OverrideDisabled(t *testing.T) {
- testLocalsDisabled(t, `overridden config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "snake_case"
-
- locals {
- format = "none"
- }
-}`)
-}
-
-func testLocalsSnakeCase(t *testing.T, testType string, formatName string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("locals: %s - Invalid snake_case with dash", testType),
- Content: `
-locals {
- dash-name = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("local value name `dash-name` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 3, Column: 3},
- End: hcl.Pos{Line: 3, Column: 24},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("locals: %s - Invalid snake_case with camelCase", testType),
- Content: `
-locals {
- camelCased = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("local value name `camelCased` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 3, Column: 3},
- End: hcl.Pos{Line: 3, Column: 25},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("locals: %s - Invalid snake_case with double underscore", testType),
- Content: `
-locals {
- foo__bar = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("local value name `foo__bar` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 3, Column: 3},
- End: hcl.Pos{Line: 3, Column: 23},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("locals: %s - Invalid snake_case with underscore tail", testType),
- Content: `
-locals {
- foo_bar_ = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("local value name `foo_bar_` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 3, Column: 3},
- End: hcl.Pos{Line: 3, Column: 23},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("locals: %s - Invalid snake_case with Mixed_Snake_Case", testType),
- Content: `
-locals {
- Foo_Bar = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("local value name `Foo_Bar` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 3, Column: 3},
- End: hcl.Pos{Line: 3, Column: 22},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid snake_case", testType),
- Content: `
-locals {
- foo_bar = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid single word", testType),
- Content: `
-locals {
- foo = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-func testLocalsMixedSnakeCase(t *testing.T, testType string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("locals: %s - Invalid mixed_snake_case with dash", testType),
- Content: `
-locals {
- dash-name = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "local value name `dash-name` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 3, Column: 3},
- End: hcl.Pos{Line: 3, Column: 24},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("locals: %s - Invalid mixed_snake_case with double underscore", testType),
- Content: `
-locals {
- Foo__Bar = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "local value name `Foo__Bar` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 3, Column: 3},
- End: hcl.Pos{Line: 3, Column: 23},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("locals: %s - Invalid mixed_snake_case with underscore tail", testType),
- Content: `
-locals {
- Foo_Bar_ = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "local value name `Foo_Bar_` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 3, Column: 3},
- End: hcl.Pos{Line: 3, Column: 23},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid snake_case", testType),
- Content: `
-locals {
- foo_bar = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid single word", testType),
- Content: `
-locals {
- foo = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid Mixed_Snake_Case", testType),
- Content: `
-locals {
- Foo_Bar = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid single word with upper characters", testType),
- Content: `
-locals {
- Foo = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid PascalCase", testType),
- Content: `
-locals {
- PascalCase = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid camelCase", testType),
- Content: `
-locals {
- camelCase = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-func testLocalsDisabled(t *testing.T, testType string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("locals: %s - Valid mixed_snake_case with dash", testType),
- Content: `
-locals {
- dash-name = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid snake_case", testType),
- Content: `
-locals {
- foo_bar = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid single word", testType),
- Content: `
-locals {
- foo = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid Mixed_Snake_Case", testType),
- Content: `
-locals {
- Foo_Bar = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid single word with upper characters", testType),
- Content: `
-locals {
- Foo = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid PascalCase", testType),
- Content: `
-locals {
- PascalCase = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("locals: %s - Valid camelCase", testType),
- Content: `
-locals {
- camelCase = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-// Module blocks
-func Test_TerraformNamingConventionRule_Module_DefaultEmpty(t *testing.T) {
- testModuleSnakeCase(t, "default config", "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Module_DefaultFormat(t *testing.T) {
- testModuleMixedSnakeCase(t, `default config (format="mixed_snake_case")`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Module_DefaultCustom(t *testing.T) {
- testModuleSnakeCase(t, `default config (custom="^[a-z_]+$")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Module_DefaultDisabled(t *testing.T) {
- testModuleDisabled(t, `default config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Module_DefaultFormat_OverrideFormat(t *testing.T) {
- testModuleSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-
- module {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Module_DefaultFormat_OverrideCustom(t *testing.T) {
- testModuleSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-
- module {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Module_DefaultCustom_OverrideFormat(t *testing.T) {
- testModuleSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^ignored$"
-
- module {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Module_DefaultCustom_OverrideCustom(t *testing.T) {
- testModuleSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^ignored$"
-
- module {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Module_DefaultDisabled_OverrideFormat(t *testing.T) {
- testModuleSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- module {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Module_DefaultDisabled_OverrideCustom(t *testing.T) {
- testModuleSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- module {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Module_DefaultEmpty_OverrideDisabled(t *testing.T) {
- testModuleDisabled(t, `overridden config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
-
- module {
- format = "none"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Module_DefaultFormat_OverrideDisabled(t *testing.T) {
- testModuleDisabled(t, `overridden config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "snake_case"
-
- module {
- format = "none"
- }
-}`)
-}
-
-func testModuleSnakeCase(t *testing.T, testType string, formatName string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("module: %s - Invalid snake_case with dash", testType),
- Content: `
-module "dash-name" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("module name `dash-name` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 19},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("module: %s - Invalid snake_case with camelCase", testType),
- Content: `
-module "camelCased" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("module name `camelCased` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 20},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("module: %s - Invalid snake_case with double underscore", testType),
- Content: `
-module "foo__bar" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("module name `foo__bar` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 18},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("module: %s - Invalid snake_case with underscore tail", testType),
- Content: `
-module "foo_bar_" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("module name `foo_bar_` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 18},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("module: %s - Invalid snake_case with Mixed_Snake_Case", testType),
- Content: `
-module "Foo_Bar" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("module name `Foo_Bar` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 17},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("module: %s - Valid snake_case", testType),
- Content: `
-module "foo_bar" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("module: %s - Valid single word", testType),
- Content: `
-module "foo" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-func testModuleMixedSnakeCase(t *testing.T, testType string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("module: %s - Invalid mixed_snake_case with dash", testType),
- Content: `
-module "dash-name" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "module name `dash-name` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 19},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("module: %s - Invalid mixed_snake_case with double underscore", testType),
- Content: `
-module "Foo__Bar" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "module name `Foo__Bar` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 18},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("module: %s - Invalid mixed_snake_case with underscore tail", testType),
- Content: `
-module "Foo_Bar_" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "module name `Foo_Bar_` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 18},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("module: %s - Valid snake_case", testType),
- Content: `
-module "foo_bar" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("module: %s - Valid single word", testType),
- Content: `
-module "foo" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("module: %s - Valid Mixed_Snake_Case", testType),
- Content: `
-module "Foo_Bar" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("module: %s - Valid single word with upper characters", testType),
- Content: `
-module "foo" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("module: %s - Valid PascalCase", testType),
- Content: `
-module "PascalCase" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("module: %s - Valid camelCase", testType),
- Content: `
-module "camelCase" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-func testModuleDisabled(t *testing.T, testType string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("module: %s - Valid mixed_snake_case with dash", testType),
- Content: `
-module "dash-name" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("module: %s - Valid snake_case", testType),
- Content: `
-module "foo_bar" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("module: %s - Valid single word", testType),
- Content: `
-module "foo" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("module: %s - Valid Mixed_Snake_Case", testType),
- Content: `
-module "Foo_Bar" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("module: %s - Valid single word upper characters", testType),
- Content: `
-module "Foo" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("module: %s - Valid PascalCase", testType),
- Content: `
-module "PascalCase" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("module: %s - Valid camelCase", testType),
- Content: `
-module "camelCase" {
- source = "./module"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-// Output blocks
-func Test_TerraformNamingConventionRule_Output_DefaultEmpty(t *testing.T) {
- testOutputSnakeCase(t, "default config", "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Output_DefaultFormat(t *testing.T) {
- testOutputMixedSnakeCase(t, `default config (format="mixed_snake_case")`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Output_DefaultCustom(t *testing.T) {
- testOutputSnakeCase(t, `default config (custom="^[a-z_]+$")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Output_DefaultDisabled(t *testing.T) {
- testOutputDisabled(t, `default config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Output_DefaultFormat_OverrideFormat(t *testing.T) {
- testOutputSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-
- output {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Output_DefaultFormat_OverrideCustom(t *testing.T) {
- testOutputSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-
- output {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Output_DefaultCustom_OverrideFormat(t *testing.T) {
- testOutputSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^ignored$"
-
- output {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Output_DefaultCustom_OverrideCustom(t *testing.T) {
- testOutputSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^ignored$"
-
- output {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Output_DefaultDisabled_OverrideFormat(t *testing.T) {
- testOutputSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- output {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Output_DefaultDisabled_OverrideCustom(t *testing.T) {
- testOutputSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- output {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Output_DefaultEmpty_OverrideDisabled(t *testing.T) {
- testOutputDisabled(t, `overridden config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
-
- output {
- format = "none"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Output_DefaultFormat_OverrideDisabled(t *testing.T) {
- testOutputDisabled(t, `overridden config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "snake_case"
-
- output {
- format = "none"
- }
-}`)
-}
-
-func testOutputSnakeCase(t *testing.T, testType string, formatName string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("output: %s - Invalid snake_case with dash", testType),
- Content: `
-output "dash-name" {
- value = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("output name `dash-name` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 19},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("output: %s - Invalid snake_case with camelCase", testType),
- Content: `
-output "camelCased" {
- value = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("output name `camelCased` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 20},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("output: %s - Invalid snake_case with double underscore", testType),
- Content: `
-output "foo__bar" {
- value = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("output name `foo__bar` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 18},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("output: %s - Invalid snake_case with underscore tail", testType),
- Content: `
-output "foo_bar_" {
- value = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("output name `foo_bar_` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 18},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("output: %s - Invalid snake_case with Mixed_Snake_Case", testType),
- Content: `
-output "Foo_Bar" {
- value = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("output name `Foo_Bar` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 17},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("output: %s - Valid snake_case", testType),
- Content: `
-output "foo_bar" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("output: %s - Valid single word", testType),
- Content: `
-output "foo" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-func testOutputMixedSnakeCase(t *testing.T, testType string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("output: %s - Invalid mixed_snake_case with dash", testType),
- Content: `
-output "dash-name" {
- value = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "output name `dash-name` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 19},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("output: %s - Invalid mixed_snake_case with double underscore", testType),
- Content: `
-output "Foo__Bar" {
- value = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "output name `Foo__Bar` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 18},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("output: %s - Invalid mixed_snake_case with underscore tail", testType),
- Content: `
-output "Foo_Bar_" {
- value = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "output name `Foo_Bar_` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 18},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("output: %s - Valid snake_case", testType),
- Content: `
-output "foo_bar" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("output: %s - Valid single word", testType),
- Content: `
-output "foo" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("output: %s - Valid Mixed_Snake_Case", testType),
- Content: `
-output "Foo_Bar" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("output: %s - Valid single word with upper characters", testType),
- Content: `
-output "foo" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("output: %s - Valid PascalCase", testType),
- Content: `
-output "PascalCase" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("output: %s - Valid camelCase", testType),
- Content: `
-output "camelCase" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-func testOutputDisabled(t *testing.T, testType string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("output: %s - Valid mixed_snake_case with dash", testType),
- Content: `
-output "dash-name" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("output: %s - Valid snake_case", testType),
- Content: `
-output "foo_bar" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("output: %s - Valid single word", testType),
- Content: `
-output "foo" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("output: %s - Valid Mixed_Snake_Case", testType),
- Content: `
-output "Foo_Bar" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("output: %s - Valid single word upper characters", testType),
- Content: `
-output "Foo" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("output: %s - Valid PascalCase", testType),
- Content: `
-output "PascalCase" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("output: %s - Valid camelCase", testType),
- Content: `
-output "camelCase" {
- value = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-// Resource blocks
-func Test_TerraformNamingConventionRule_Resource_DefaultEmpty(t *testing.T) {
- testResourceSnakeCase(t, "default config", "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Resource_DefaultFormat(t *testing.T) {
- testResourceMixedSnakeCase(t, `default config (format="mixed_snake_case")`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Resource_DefaultCustom(t *testing.T) {
- testResourceSnakeCase(t, `default config (custom="^[a-z_]+$")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Resource_DefaultDisabled(t *testing.T) {
- testResourceDisabled(t, `default config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Resource_DefaultFormat_OverrideFormat(t *testing.T) {
- testResourceSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-
- resource {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Resource_DefaultFormat_OverrideCustom(t *testing.T) {
- testResourceSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-
- resource {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Resource_DefaultCustom_OverrideFormat(t *testing.T) {
- testResourceSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^ignored$"
-
- resource {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Resource_DefaultCustom_OverrideCustom(t *testing.T) {
- testResourceSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^ignored$"
-
- resource {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Resource_DefaultDisabled_OverrideFormat(t *testing.T) {
- testResourceSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- resource {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Resource_DefaultDisabled_OverrideCustom(t *testing.T) {
- testResourceSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- resource {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Resource_DefaultEmpty_OverrideDisabled(t *testing.T) {
- testResourceDisabled(t, `overridden config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
-
- resource {
- format = "none"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Resource_DefaultFormat_OverrideDisabled(t *testing.T) {
- testResourceDisabled(t, `overridden config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "snake_case"
-
- resource {
- format = "none"
- }
-}`)
-}
-
-func testResourceSnakeCase(t *testing.T, testType string, formatName string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("resource: %s - Invalid snake_case with dash", testType),
- Content: `
-resource "aws_eip" "dash-name" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("resource name `dash-name` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 31},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("resource: %s - Invalid snake_case with camelCase", testType),
- Content: `
-resource "aws_eip" "camelCased" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("resource name `camelCased` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 32},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("resource: %s - Invalid snake_case with double underscore", testType),
- Content: `
-resource "aws_eip" "foo__bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("resource name `foo__bar` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 30},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("resource: %s - Invalid snake_case with underscore tail", testType),
- Content: `
-resource "aws_eip" "foo_bar_" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("resource name `foo_bar_` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 30},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("resource: %s - Invalid snake_case with Mixed_Snake_Case", testType),
- Content: `
-resource "aws_eip" "Foo_Bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("resource name `Foo_Bar` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 29},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid snake_case", testType),
- Content: `
-resource "aws_eip" "foo_bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid single word", testType),
- Content: `
-resource "aws_eip" "foo" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-func testResourceMixedSnakeCase(t *testing.T, testType string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("resource: %s - Invalid mixed_snake_case with dash", testType),
- Content: `
-resource "aws_eip" "dash-name" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "resource name `dash-name` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 31},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("resource: %s - Invalid mixed_snake_case with double underscore", testType),
- Content: `
-resource "aws_eip" "Foo__Bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "resource name `Foo__Bar` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 30},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("resource: %s - Invalid mixed_snake_case with underscore tail", testType),
- Content: `
-resource "aws_eip" "Foo_Bar_" {
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "resource name `Foo_Bar_` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 30},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid snake_case", testType),
- Content: `
-resource "aws_eip" "foo_bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid single word", testType),
- Content: `
-resource "aws_eip" "foo" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid Mixed_Snake_Case", testType),
- Content: `
-resource "aws_eip" "Foo_Bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid single word with upper characters", testType),
- Content: `
-resource "aws_eip" "foo" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid PascalCase", testType),
- Content: `
-resource "aws_eip" "PascalCase" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid camelCase", testType),
- Content: `
-resource "aws_eip" "camelCase" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-func testResourceDisabled(t *testing.T, testType string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("resource: %s - Valid mixed_snake_case with dash", testType),
- Content: `
-resource "aws_eip" "dash-name" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid snake_case", testType),
- Content: `
-resource "aws_eip" "foo_bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid single word", testType),
- Content: `
-resource "aws_eip" "foo" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid Mixed_Snake_Case", testType),
- Content: `
-resource "aws_eip" "Foo_Bar" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid single word upper characters", testType),
- Content: `
-resource "aws_eip" "Foo" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid PascalCase", testType),
- Content: `
-resource "aws_eip" "PascalCase" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("resource: %s - Valid camelCase", testType),
- Content: `
-resource "aws_eip" "camelCase" {
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-// Variable blocks
-func Test_TerraformNamingConventionRule_Variable_DefaultEmpty(t *testing.T) {
- testVariableSnakeCase(t, "default config", "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Variable_DefaultFormat(t *testing.T) {
- testVariableMixedSnakeCase(t, `default config (format="mixed_snake_case")`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Variable_DefaultCustom(t *testing.T) {
- testVariableSnakeCase(t, `default config (custom="^[a-z_]+$")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Variable_DefaultDisabled(t *testing.T) {
- testVariableDisabled(t, `default config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Variable_DefaultFormat_OverrideFormat(t *testing.T) {
- testVariableSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-
- variable {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Variable_DefaultFormat_OverrideCustom(t *testing.T) {
- testVariableSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "mixed_snake_case"
-
- variable {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Variable_DefaultCustom_OverrideFormat(t *testing.T) {
- testVariableSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^ignored$"
-
- variable {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Variable_DefaultCustom_OverrideCustom(t *testing.T) {
- testVariableSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- custom = "^ignored$"
-
- variable {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Variable_DefaultDisabled_OverrideFormat(t *testing.T) {
- testVariableSnakeCase(t, `overridden config (format="snake_case")`, "format: snake_case", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- variable {
- format = "snake_case"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Variable_DefaultDisabled_OverrideCustom(t *testing.T) {
- testVariableSnakeCase(t, `overridden config (format="snake_case")`, "RegExp: ^[a-z][a-z]*(_[a-z]+)*$", `
-rule "terraform_naming_convention" {
- enabled = true
- format = "none"
-
- variable {
- custom = "^[a-z][a-z]*(_[a-z]+)*$"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Variable_DefaultEmpty_OverrideDisabled(t *testing.T) {
- testVariableDisabled(t, `overridden config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
-
- variable {
- format = "none"
- }
-}`)
-}
-
-func Test_TerraformNamingConventionRule_Variable_DefaultFormat_OverrideDisabled(t *testing.T) {
- testVariableDisabled(t, `overridden config (format=null)`, `
-rule "terraform_naming_convention" {
- enabled = true
- format = "snake_case"
-
- variable {
- format = "none"
- }
-}`)
-}
-
-func testVariableSnakeCase(t *testing.T, testType string, formatName string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("variable: %s - Invalid snake_case with dash", testType),
- Content: `
-variable "dash-name" {
- description = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("variable name `dash-name` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 21},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("variable: %s - Invalid snake_case with camelCase", testType),
- Content: `
-variable "camelCased" {
- description = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("variable name `camelCased` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 22},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("variable: %s - Invalid snake_case with double underscore", testType),
- Content: `
-variable "foo__bar" {
- description = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("variable name `foo__bar` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 20},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("variable: %s - Invalid snake_case with underscore tail", testType),
- Content: `
-variable "foo_bar_" {
- description = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("variable name `foo_bar_` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 20},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("variable: %s - Invalid snake_case with Mixed_Snake_Case", testType),
- Content: `
-variable "Foo_Bar" {
- description = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: fmt.Sprintf("variable name `Foo_Bar` must match the following %s", formatName),
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 19},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid snake_case", testType),
- Content: `
-variable "foo_bar" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid single word", testType),
- Content: `
-variable "foo" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-func testVariableMixedSnakeCase(t *testing.T, testType string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("variable: %s - Invalid mixed_snake_case with dash", testType),
- Content: `
-variable "dash-name" {
- description = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "variable name `dash-name` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 21},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("variable: %s - Invalid mixed_snake_case with double underscore", testType),
- Content: `
-variable "Foo__Bar" {
- description = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "variable name `Foo__Bar` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 20},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("variable: %s - Invalid mixed_snake_case with underscore tail", testType),
- Content: `
-variable "Foo_Bar_" {
- description = "invalid"
-}`,
- Config: config,
- Expected: tflint.Issues{
- {
- Rule: rule,
- Message: "variable name `Foo_Bar_` must match the following format: mixed_snake_case",
- Range: hcl.Range{
- Filename: "tests.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 20},
- },
- },
- },
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid snake_case", testType),
- Content: `
-variable "foo_bar" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid single word", testType),
- Content: `
-variable "foo" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid Mixed_Snake_Case", testType),
- Content: `
-variable "Foo_Bar" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid single word with upper characters", testType),
- Content: `
-variable "foo" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid PascalCase", testType),
- Content: `
-variable "PascalCase" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid camelCase", testType),
- Content: `
-variable "camelCase" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-func testVariableDisabled(t *testing.T, testType string, config string) {
- rule := NewTerraformNamingConventionRule()
-
- cases := []struct {
- Name string
- Content string
- Config string
- Expected tflint.Issues
- }{
- {
- Name: fmt.Sprintf("variable: %s - Valid mixed_snake_case with dash", testType),
- Content: `
-variable "dash-name" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid snake_case", testType),
- Content: `
-variable "foo_bar" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid single word", testType),
- Content: `
-variable "foo" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid Mixed_Snake_Case", testType),
- Content: `
-variable "Foo_Bar" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid single word upper characters", testType),
- Content: `
-variable "Foo" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid PascalCase", testType),
- Content: `
-variable "PascalCase" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- {
- Name: fmt.Sprintf("variable: %s - Valid camelCase", testType),
- Content: `
-variable "camelCase" {
- description = "valid"
-}`,
- Config: config,
- Expected: tflint.Issues{},
- },
- }
-
- for _, tc := range cases {
- runner := tflint.TestRunnerWithConfig(t, map[string]string{"tests.tf": tc.Content}, loadConfigFromNamingConventionTempFile(t, tc.Config))
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
-
-// TODO: Replace with TestRunner
-func loadConfigFromNamingConventionTempFile(t *testing.T, content string) *tflint.Config {
- if content == "" {
- return tflint.EmptyConfig()
- }
-
- tmpfile, err := os.CreateTemp("", "terraform_naming_convention")
- if err != nil {
- t.Fatal(err)
- }
- defer os.Remove(tmpfile.Name())
-
- if _, err := tmpfile.Write([]byte(content)); err != nil {
- t.Fatal(err)
- }
- config, err := tflint.LoadConfig(afero.Afero{Fs: afero.NewOsFs()}, tmpfile.Name())
- if err != nil {
- t.Fatal(err)
- }
- return config
-}
diff --git a/rules/terraformrules/terraform_required_providers.go b/rules/terraformrules/terraform_required_providers.go
deleted file mode 100644
index 279973bc4..000000000
--- a/rules/terraformrules/terraform_required_providers.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "log"
-
- "github.com/hashicorp/hcl/v2"
- tfaddr "github.com/hashicorp/terraform-registry-address"
- "github.com/terraform-linters/tflint-plugin-sdk/hclext"
- sdk "github.com/terraform-linters/tflint-plugin-sdk/tflint"
- "github.com/terraform-linters/tflint/tflint"
- "github.com/zclconf/go-cty/cty"
-)
-
-// TerraformRequiredProvidersRule checks whether Terraform sets version constraints for all configured providers
-type TerraformRequiredProvidersRule struct{}
-
-// NewTerraformRequiredProvidersRule returns new rule with default attributes
-func NewTerraformRequiredProvidersRule() *TerraformRequiredProvidersRule {
- return &TerraformRequiredProvidersRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformRequiredProvidersRule) Name() string {
- return "terraform_required_providers"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformRequiredProvidersRule) Enabled() bool {
- return false
-}
-
-// Severity returns the rule severity
-func (r *TerraformRequiredProvidersRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformRequiredProvidersRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check Checks whether provider required version is set
-func (r *TerraformRequiredProvidersRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- body, diags := runner.GetModuleContent(&hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "provider",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{
- Attributes: []hclext.AttributeSchema{
- {Name: "version"},
- },
- },
- },
- },
- }, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return diags
- }
-
- for _, provider := range body.Blocks {
- if _, exists := provider.Body.Attributes["version"]; exists {
- runner.EmitIssue(
- r,
- `provider version constraint should be specified via "required_providers"`,
- provider.DefRange,
- )
- }
- }
-
- providerRefs, diags := getProviderRefs(runner)
- if diags.HasErrors() {
- return diags
- }
-
- requiredProvidersSchema := []hclext.AttributeSchema{}
- for name := range providerRefs {
- requiredProvidersSchema = append(requiredProvidersSchema, hclext.AttributeSchema{Name: name})
- }
-
- body, diags = runner.GetModuleContent(&hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "terraform",
- Body: &hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "required_providers",
- Body: &hclext.BodySchema{
- Attributes: requiredProvidersSchema,
- },
- },
- },
- },
- },
- },
- }, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return diags
- }
-
- requiredProviders := hclext.Attributes{}
- for _, terraform := range body.Blocks {
- for _, requiredProvidersBlock := range terraform.Body.Blocks {
- requiredProviders = requiredProvidersBlock.Body.Attributes
- }
- }
-
- for name, ref := range providerRefs {
- if name == "terraform" {
- // "terraform" provider is a builtin provider
- // @see https://github.com/hashicorp/terraform/blob/v1.2.5/internal/addrs/provider.go#L106-L112
- continue
- }
-
- provider, exists := requiredProviders[name]
- if !exists {
- runner.EmitIssue(r, fmt.Sprintf(`Missing version constraint for provider "%s" in "required_providers"`, name), ref.defRange)
- continue
- }
-
- val, diags := provider.Expr.Value(&hcl.EvalContext{
- Variables: map[string]cty.Value{
- // configuration_aliases can declare additional provider instances
- // required provider "foo" could have: configuration_aliases = [foo.a, foo.b]
- // @see https://www.terraform.io/language/modules/develop/providers#provider-aliases-within-modules
- name: cty.DynamicVal,
- },
- })
- if diags.HasErrors() {
- return diags
- }
- // Look for a single static string, in case we have the legacy version-only
- // format in the configuration.
- if val.Type() == cty.String {
- continue
- }
-
- vm := val.AsValueMap()
- if _, exists := vm["version"]; !exists {
- if source, exists := vm["source"]; exists {
- p, err := tfaddr.ParseProviderSource(source.AsString())
- if err != nil {
- return err
- }
-
- if p.IsBuiltIn() {
- continue
- }
- }
- runner.EmitIssue(r, fmt.Sprintf(`Missing version constraint for provider "%s" in "required_providers"`, name), ref.defRange)
- }
- }
-
- return nil
-}
diff --git a/rules/terraformrules/terraform_required_providers_test.go b/rules/terraformrules/terraform_required_providers_test.go
deleted file mode 100644
index d1655f809..000000000
--- a/rules/terraformrules/terraform_required_providers_test.go
+++ /dev/null
@@ -1,366 +0,0 @@
-package terraformrules
-
-import (
- "testing"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformRequiredProvidersRule(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- Expected tflint.Issues
- }{
- {
- Name: "no version",
- Content: `
-provider "template" {}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformRequiredProvidersRule(),
- Message: `Missing version constraint for provider "template" in "required_providers"`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{
- Line: 2,
- Column: 1,
- },
- End: hcl.Pos{
- Line: 2,
- Column: 20,
- },
- },
- },
- },
- },
- {
- Name: "implicit provider - resource",
- Content: `
-resource "random_string" "foo" {
- length = 16
-}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformRequiredProvidersRule(),
- Message: `Missing version constraint for provider "random" in "required_providers"`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{
- Line: 2,
- Column: 1,
- },
- End: hcl.Pos{
- Line: 2,
- Column: 31,
- },
- },
- },
- },
- },
- {
- Name: "implicit provider - data source",
- Content: `
-data "template_file" "foo" {
- template = ""
-}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformRequiredProvidersRule(),
- Message: `Missing version constraint for provider "template" in "required_providers"`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{
- Line: 2,
- Column: 1,
- },
- End: hcl.Pos{
- Line: 2,
- Column: 27,
- },
- },
- },
- },
- },
- {
- Name: "required_providers object",
- Content: `
-terraform {
- required_providers {
- template = {
- source = "hashicorp/template"
- version = "~> 2"
- }
- }
-}
-
-provider "template" {}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "required_providers string",
- Content: `
-terraform {
- required_providers {
- template = "~> 2"
- }
-}
-
-provider "template" {}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "required_providers object missing version",
- Content: `
-terraform {
- required_providers {
- template = {
- source = "hashicorp/template"
- }
- }
-}
-
-provider "template" {}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformRequiredProvidersRule(),
- Message: `Missing version constraint for provider "template" in "required_providers"`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{
- Line: 10,
- Column: 1,
- },
- End: hcl.Pos{
- Line: 10,
- Column: 20,
- },
- },
- },
- },
- },
- {
- Name: "single provider with alias",
- Content: `
-provider "template" {
- alias = "b"
-}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformRequiredProvidersRule(),
- Message: `Missing version constraint for provider "template" in "required_providers"`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{
- Line: 2,
- Column: 1,
- },
- End: hcl.Pos{
- Line: 2,
- Column: 20,
- },
- },
- },
- },
- },
- {
- Name: "version set",
- Content: `
-terraform {
- required_providers {
- template = {
- source = "hashicorp/template"
- version = "~> 2"
- }
- }
-}
-
-provider "template" {
- version = "~> 2"
-}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformRequiredProvidersRule(),
- Message: `provider version constraint should be specified via "required_providers"`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{
- Line: 11,
- Column: 1,
- },
- End: hcl.Pos{
- Line: 11,
- Column: 20,
- },
- },
- },
- },
- },
- {
- Name: "version set with configuration_aliases",
- Content: `
-terraform {
- required_providers {
- template = {
- source = "hashicorp/template"
- version = "~> 2"
-
- configuration_aliases = [template.alias]
- }
- }
-}
-
-data "template_file" "foo" {
- provider = template.alias
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "version set with alias",
- Content: `
-terraform {
- required_providers {
- template = {
- source = "hashicorp/template"
- version = "~> 2"
- }
- }
-}
-
-provider "template" {
- alias = "foo"
- version = "~> 2"
-}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformRequiredProvidersRule(),
- Message: `provider version constraint should be specified via "required_providers"`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{
- Line: 11,
- Column: 1,
- },
- End: hcl.Pos{
- Line: 11,
- Column: 20,
- },
- },
- },
- },
- },
- {
- Name: "terraform provider",
- Content: `
-data "terraform_remote_state" "foo" {}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "builtin provider",
- Content: `
-terraform {
- required_providers {
- test = {
- source = "terraform.io/builtin/test"
- }
- }
-}
-
-resource "test_assertions" "foo" {}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "resource provider ref",
- Content: `
-terraform {
- required_providers {
- google = {
- version = "~> 4.27.0"
- }
- }
-}
-
-resource "google_compute_instance" "foo" {
- provider = google-beta
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformRequiredProvidersRule(),
- Message: `Missing version constraint for provider "google-beta" in "required_providers"`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{
- Line: 10,
- Column: 1,
- },
- End: hcl.Pos{
- Line: 10,
- Column: 41,
- },
- },
- },
- },
- },
- {
- Name: "resource provider ref as string",
- Content: `
-terraform {
- required_providers {
- google = {
- version = "~> 4.27.0"
- }
- }
-}
-
-resource "google_compute_instance" "foo" {
- provider = "google-beta"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformRequiredProvidersRule(),
- Message: `Missing version constraint for provider "google-beta" in "required_providers"`,
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{
- Line: 10,
- Column: 1,
- },
- End: hcl.Pos{
- Line: 10,
- Column: 41,
- },
- },
- },
- },
- },
- }
-
- rule := NewTerraformRequiredProvidersRule()
-
- for _, tc := range cases {
- tc := tc
-
- t.Run(tc.Name, func(t *testing.T) {
- runner := tflint.TestRunner(t, map[string]string{"module.tf": tc.Content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- })
- }
-}
diff --git a/rules/terraformrules/terraform_required_version.go b/rules/terraformrules/terraform_required_version.go
deleted file mode 100644
index f2e150a85..000000000
--- a/rules/terraformrules/terraform_required_version.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package terraformrules
-
-import (
- "log"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint-plugin-sdk/hclext"
- sdk "github.com/terraform-linters/tflint-plugin-sdk/tflint"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// TerraformRequiredVersionRule checks whether a terraform version has required_version attribute
-type TerraformRequiredVersionRule struct{}
-
-// NewTerraformRequiredVersionRule returns new rule with default attributes
-func NewTerraformRequiredVersionRule() *TerraformRequiredVersionRule {
- return &TerraformRequiredVersionRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformRequiredVersionRule) Name() string {
- return "terraform_required_version"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformRequiredVersionRule) Enabled() bool {
- return false
-}
-
-// Severity returns the rule severity
-func (r *TerraformRequiredVersionRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformRequiredVersionRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check Checks whether required_version is set
-func (r *TerraformRequiredVersionRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- body, diags := runner.GetModuleContent(&hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "terraform",
- Body: &hclext.BodySchema{
- Attributes: []hclext.AttributeSchema{{Name: "required_version"}},
- },
- },
- },
- }, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return diags
- }
-
- var exists bool
-
- for _, block := range body.Blocks {
- _, ok := block.Body.Attributes["required_version"]
- exists = exists || ok
- }
-
- if !exists {
- runner.EmitIssue(
- r,
- `terraform "required_version" attribute is required`,
- hcl.Range{},
- )
- }
-
- return nil
-}
diff --git a/rules/terraformrules/terraform_required_version_test.go b/rules/terraformrules/terraform_required_version_test.go
deleted file mode 100644
index 129ae83e5..000000000
--- a/rules/terraformrules/terraform_required_version_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package terraformrules
-
-import (
- "testing"
-
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformRequiredVersionRule(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- Expected tflint.Issues
- }{
- {
- Name: "unset",
- Content: `
-terraform {}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformRequiredVersionRule(),
- Message: "terraform \"required_version\" attribute is required",
- },
- },
- },
- {
- Name: "set",
- Content: `
-terraform {
- required_version = "~> 0.12"
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "multiple blocks",
- Content: `
-terraform {
- cloud {
- workspaces {
- name = "foo"
- }
- }
-}
-
-terraform {
- required_version = "~> 0.12"
-}
-`,
- Expected: tflint.Issues{},
- },
- }
-
- rule := NewTerraformRequiredVersionRule()
-
- for _, tc := range cases {
- tc := tc
-
- t.Run(tc.Name, func(t *testing.T) {
- runner := tflint.TestRunner(t, map[string]string{"module.tf": tc.Content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatal(err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- })
- }
-}
diff --git a/rules/terraformrules/terraform_standard_module_structure.go b/rules/terraformrules/terraform_standard_module_structure.go
deleted file mode 100644
index 44bffe8d0..000000000
--- a/rules/terraformrules/terraform_standard_module_structure.go
+++ /dev/null
@@ -1,180 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "log"
- "path/filepath"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint-plugin-sdk/hclext"
- sdk "github.com/terraform-linters/tflint-plugin-sdk/tflint"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-const (
- filenameMain = "main.tf"
- filenameVariables = "variables.tf"
- filenameOutputs = "outputs.tf"
-)
-
-// TerraformStandardModuleStructureRule checks whether modules adhere to Terraform's standard module structure
-type TerraformStandardModuleStructureRule struct{}
-
-// NewTerraformStandardModuleStructureRule returns a new rule
-func NewTerraformStandardModuleStructureRule() *TerraformStandardModuleStructureRule {
- return &TerraformStandardModuleStructureRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformStandardModuleStructureRule) Name() string {
- return "terraform_standard_module_structure"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformStandardModuleStructureRule) Enabled() bool {
- return false
-}
-
-// Severity returns the rule severity
-func (r *TerraformStandardModuleStructureRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformStandardModuleStructureRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check emits errors for any missing files and any block types that are included in the wrong file
-func (r *TerraformStandardModuleStructureRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- body, diags := runner.GetModuleContent(&hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "variable",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{},
- },
- {
- Type: "output",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{},
- },
- },
- }, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return diags
- }
-
- blocks := body.Blocks.ByType()
-
- r.checkFiles(runner, body.Blocks)
- r.checkVariables(runner, blocks["variable"])
- r.checkOutputs(runner, blocks["output"])
-
- return nil
-}
-
-func (r *TerraformStandardModuleStructureRule) checkFiles(runner *tflint.Runner, blocks hclext.Blocks) {
- if r.onlyJSON(runner) {
- return
- }
-
- f := runner.Files()
- var dir string
- files := make(map[string]*hcl.File, len(f))
- for name, file := range f {
- dir = filepath.Dir(name)
- files[filepath.Base(name)] = file
- }
-
- log.Printf("[DEBUG] %d files found: %v", len(files), files)
-
- if files[filenameMain] == nil {
- runner.EmitIssue(
- r,
- fmt.Sprintf("Module should include a %s file as the primary entrypoint", filenameMain),
- hcl.Range{
- Filename: filepath.Join(dir, filenameMain),
- Start: hcl.InitialPos,
- },
- )
- }
-
- if files[filenameVariables] == nil && len(blocks.ByType()["variable"]) == 0 {
- runner.EmitIssue(
- r,
- fmt.Sprintf("Module should include an empty %s file", filenameVariables),
- hcl.Range{
- Filename: filepath.Join(dir, filenameVariables),
- Start: hcl.InitialPos,
- },
- )
- }
-
- if files[filenameOutputs] == nil && len(blocks.ByType()["output"]) == 0 {
- runner.EmitIssue(
- r,
- fmt.Sprintf("Module should include an empty %s file", filenameOutputs),
- hcl.Range{
- Filename: filepath.Join(dir, filenameOutputs),
- Start: hcl.InitialPos,
- },
- )
- }
-}
-
-func (r *TerraformStandardModuleStructureRule) checkVariables(runner *tflint.Runner, variables hclext.Blocks) {
- for _, variable := range variables {
- if filename := variable.DefRange.Filename; r.shouldMove(filename, filenameVariables) {
- runner.EmitIssue(
- r,
- fmt.Sprintf("variable %q should be moved from %s to %s", variable.Labels[0], filename, filenameVariables),
- variable.DefRange,
- )
- }
- }
-}
-
-func (r *TerraformStandardModuleStructureRule) checkOutputs(runner *tflint.Runner, outputs hclext.Blocks) {
- for _, output := range outputs {
- if filename := output.DefRange.Filename; r.shouldMove(filename, filenameOutputs) {
- runner.EmitIssue(
- r,
- fmt.Sprintf("output %q should be moved from %s to %s", output.Labels[0], filename, filenameOutputs),
- output.DefRange,
- )
- }
- }
-}
-
-func (r *TerraformStandardModuleStructureRule) onlyJSON(runner *tflint.Runner) bool {
- files := runner.Files()
-
- if len(files) == 0 {
- return false
- }
-
- for filename := range files {
- if filepath.Ext(filename) != ".json" {
- return false
- }
- }
-
- return true
-}
-
-func (r *TerraformStandardModuleStructureRule) shouldMove(path string, expected string) bool {
- // json files are likely generated and conventional filenames do not apply
- if filepath.Ext(path) == ".json" {
- return false
- }
-
- return filepath.Base(path) != expected
-}
diff --git a/rules/terraformrules/terraform_standard_module_structure_test.go b/rules/terraformrules/terraform_standard_module_structure_test.go
deleted file mode 100644
index 06ffb38c8..000000000
--- a/rules/terraformrules/terraform_standard_module_structure_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-package terraformrules
-
-import (
- "path/filepath"
- "testing"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformStandardModuleStructureRule(t *testing.T) {
- cases := []struct {
- Name string
- Content map[string]string
- Expected tflint.Issues
- }{
- {
- Name: "empty module",
- Content: map[string]string{},
- Expected: tflint.Issues{
- {
- Rule: NewTerraformStandardModuleStructureRule(),
- Message: "Module should include a main.tf file as the primary entrypoint",
- Range: hcl.Range{
- Filename: "main.tf",
- Start: hcl.InitialPos,
- },
- },
- {
- Rule: NewTerraformStandardModuleStructureRule(),
- Message: "Module should include an empty variables.tf file",
- Range: hcl.Range{
- Filename: "variables.tf",
- Start: hcl.InitialPos,
- },
- },
- {
- Rule: NewTerraformStandardModuleStructureRule(),
- Message: "Module should include an empty outputs.tf file",
- Range: hcl.Range{
- Filename: "outputs.tf",
- Start: hcl.InitialPos,
- },
- },
- },
- },
- {
- Name: "directory in path",
- Content: map[string]string{
- "foo/main.tf": "",
- "foo/variables.tf": `
-variable "v" {}
- `,
- },
- Expected: tflint.Issues{
- {
- Rule: NewTerraformStandardModuleStructureRule(),
- Message: "Module should include an empty outputs.tf file",
- Range: hcl.Range{
- Filename: filepath.Join("foo", "outputs.tf"),
- Start: hcl.InitialPos,
- },
- },
- },
- },
- {
- Name: "move variable",
- Content: map[string]string{
- "main.tf": `
-variable "v" {}
-`,
- "variables.tf": "",
- "outputs.tf": "",
- },
- Expected: tflint.Issues{
- {
- Rule: NewTerraformStandardModuleStructureRule(),
- Message: `variable "v" should be moved from main.tf to variables.tf`,
- Range: hcl.Range{
- Filename: "main.tf",
- Start: hcl.Pos{
- Line: 2,
- Column: 1,
- },
- End: hcl.Pos{
- Line: 2,
- Column: 13,
- },
- },
- },
- },
- },
- {
- Name: "move output",
- Content: map[string]string{
- "main.tf": `
-output "o" { value = null }
-`,
- "variables.tf": "",
- "outputs.tf": "",
- },
- Expected: tflint.Issues{
- {
- Rule: NewTerraformStandardModuleStructureRule(),
- Message: `output "o" should be moved from main.tf to outputs.tf`,
- Range: hcl.Range{
- Filename: "main.tf",
- Start: hcl.Pos{
- Line: 2,
- Column: 1,
- },
- End: hcl.Pos{
- Line: 2,
- Column: 11,
- },
- },
- },
- },
- },
- {
- Name: "json only",
- Content: map[string]string{
- "main.tf.json": "{}",
- },
- Expected: tflint.Issues{},
- },
- {
- Name: "json variable",
- Content: map[string]string{
- "main.tf.json": `{"variable": {"v": {}}}`,
- },
- Expected: tflint.Issues{},
- },
- {
- Name: "json output",
- Content: map[string]string{
- "main.tf.json": `{"output": {"o": {"value": null}}}`,
- },
- Expected: tflint.Issues{},
- },
- }
-
- rule := NewTerraformStandardModuleStructureRule()
-
- for _, tc := range cases {
- tc := tc
- t.Run(tc.Name, func(t *testing.T) {
- runner := tflint.TestRunnerWithConfig(t, tc.Content, &tflint.Config{
- Module: true,
- })
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- })
- }
-}
diff --git a/rules/terraformrules/terraform_typed_variables.go b/rules/terraformrules/terraform_typed_variables.go
deleted file mode 100644
index 940c8eb42..000000000
--- a/rules/terraformrules/terraform_typed_variables.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "log"
-
- "github.com/terraform-linters/tflint-plugin-sdk/hclext"
- sdk "github.com/terraform-linters/tflint-plugin-sdk/tflint"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// TerraformTypedVariablesRule checks whether variables have a type declared
-type TerraformTypedVariablesRule struct{}
-
-// NewTerraformTypedVariablesRule returns a new rule
-func NewTerraformTypedVariablesRule() *TerraformTypedVariablesRule {
- return &TerraformTypedVariablesRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformTypedVariablesRule) Name() string {
- return "terraform_typed_variables"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformTypedVariablesRule) Enabled() bool {
- return false
-}
-
-// Severity returns the rule severity
-func (r *TerraformTypedVariablesRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformTypedVariablesRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check checks whether variables have type
-func (r *TerraformTypedVariablesRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- body, diags := runner.GetModuleContent(&hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "variable",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{
- Attributes: []hclext.AttributeSchema{{Name: "type"}},
- },
- },
- },
- }, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return diags
- }
-
- for _, variable := range body.Blocks {
- if _, exists := variable.Body.Attributes["type"]; !exists {
- runner.EmitIssue(
- r,
- fmt.Sprintf("`%v` variable has no type", variable.Labels[0]),
- variable.DefRange,
- )
- }
- }
-
- return nil
-}
diff --git a/rules/terraformrules/terraform_typed_variables_test.go b/rules/terraformrules/terraform_typed_variables_test.go
deleted file mode 100644
index 28c1fea88..000000000
--- a/rules/terraformrules/terraform_typed_variables_test.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package terraformrules
-
-import (
- "testing"
-
- hcl "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformTypedVariablesRule(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- JSON bool
- Expected tflint.Issues
- }{
- {
- Name: "no type",
- Content: `
-variable "no_type" {
- default = "default"
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformTypedVariablesRule(),
- Message: "`no_type` variable has no type",
- Range: hcl.Range{
- Filename: "variables.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 19},
- },
- },
- },
- },
- {
- Name: "complex type",
- Content: `
-variable "no_type2" {
- type = list(object({
- internal = number
- external = number
- protocol = string
- }))
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "with type",
- Content: `
-variable "with_type" {
- type = string
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "type any",
- Content: `
-variable "any" {
- type = any
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "json with type",
- JSON: true,
- Content: `
-{
- "variable": {
- "with_type": {
- "type": "string"
- }
- }
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "json no type",
- JSON: true,
- Content: `
-{
- "variable": {
- "no_type": {
- "default": "foo"
- }
- }
-}
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformTypedVariablesRule(),
- Message: "`no_type` variable has no type",
- Range: hcl.Range{
- Filename: "variables.tf.json",
- Start: hcl.Pos{Line: 4, Column: 16},
- End: hcl.Pos{Line: 4, Column: 17},
- },
- },
- },
- },
- }
-
- rule := NewTerraformTypedVariablesRule()
-
- for _, tc := range cases {
- filename := "variables.tf"
- if tc.JSON {
- filename += ".json"
- }
-
- runner := tflint.TestRunner(t, map[string]string{filename: tc.Content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- }
-}
diff --git a/rules/terraformrules/terraform_unused_declaration.go b/rules/terraformrules/terraform_unused_declaration.go
deleted file mode 100644
index 1d6d0fc40..000000000
--- a/rules/terraformrules/terraform_unused_declaration.go
+++ /dev/null
@@ -1,147 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "log"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint-plugin-sdk/hclext"
- sdk "github.com/terraform-linters/tflint-plugin-sdk/tflint"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// TerraformUnusedDeclarationsRule checks whether variables, data sources, or locals are declared but unused
-type TerraformUnusedDeclarationsRule struct{}
-
-type declarations struct {
- Variables map[string]*hclext.Block
- DataResources map[string]*hclext.Block
- Locals map[string]*local
-}
-
-// NewTerraformUnusedDeclarationsRule returns a new rule
-func NewTerraformUnusedDeclarationsRule() *TerraformUnusedDeclarationsRule {
- return &TerraformUnusedDeclarationsRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformUnusedDeclarationsRule) Name() string {
- return "terraform_unused_declarations"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformUnusedDeclarationsRule) Enabled() bool {
- return false
-}
-
-// Severity returns the rule severity
-func (r *TerraformUnusedDeclarationsRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformUnusedDeclarationsRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check emits issues for any variables, locals, and data sources that are declared but not used
-func (r *TerraformUnusedDeclarationsRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- decl, err := r.declarations(runner)
- if err != nil {
- return err
- }
- err = runner.WalkExpressions(func(expr hcl.Expression) error {
- return r.checkForRefsInExpr(expr, decl)
- })
- if err != nil {
- return err
- }
-
- for _, variable := range decl.Variables {
- runner.EmitIssue(
- r,
- fmt.Sprintf(`variable "%s" is declared but not used`, variable.Labels[0]),
- variable.DefRange,
- )
- }
- for _, data := range decl.DataResources {
- runner.EmitIssue(
- r,
- fmt.Sprintf(`data "%s" "%s" is declared but not used`, data.Labels[0], data.Labels[1]),
- data.DefRange,
- )
- }
- for _, local := range decl.Locals {
- runner.EmitIssue(
- r,
- fmt.Sprintf(`local.%s is declared but not used`, local.name),
- local.defRange,
- )
- }
-
- return nil
-}
-
-func (r *TerraformUnusedDeclarationsRule) declarations(runner *tflint.Runner) (*declarations, error) {
- decl := &declarations{
- Variables: map[string]*hclext.Block{},
- DataResources: map[string]*hclext.Block{},
- Locals: map[string]*local{},
- }
-
- body, diags := runner.GetModuleContent(&hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "variable",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{},
- },
- {
- Type: "data",
- LabelNames: []string{"type", "name"},
- Body: &hclext.BodySchema{},
- },
- },
- }, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return decl, diags
- }
-
- for _, block := range body.Blocks {
- if block.Type == "variable" {
- decl.Variables[block.Labels[0]] = block
- } else {
- decl.DataResources[fmt.Sprintf("data.%s.%s", block.Labels[0], block.Labels[1])] = block
- }
- }
-
- locals, diags := getLocals(runner)
- if diags.HasErrors() {
- return decl, diags
- }
- decl.Locals = locals
-
- return decl, nil
-}
-
-func (r *TerraformUnusedDeclarationsRule) checkForRefsInExpr(expr hcl.Expression, decl *declarations) error {
- for _, ref := range referencesInExpr(expr) {
- switch sub := ref.subject.(type) {
- case inputVariableReference:
- delete(decl.Variables, sub.name)
- case localValueReference:
- delete(decl.Locals, sub.name)
- case dataResourceReference:
- delete(decl.DataResources, sub.String())
- }
- }
-
- return nil
-}
diff --git a/rules/terraformrules/terraform_unused_declarations_test.go b/rules/terraformrules/terraform_unused_declarations_test.go
deleted file mode 100644
index 288fb1bc2..000000000
--- a/rules/terraformrules/terraform_unused_declarations_test.go
+++ /dev/null
@@ -1,213 +0,0 @@
-package terraformrules
-
-import (
- "testing"
-
- hcl "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformUnusedDeclarationsRule(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- JSON bool
- Expected tflint.Issues
- }{
- {
- Name: "unused variable",
- Content: `
-variable "not_used" {}
-
-variable "used" {}
-output "u" { value = var.used }
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformUnusedDeclarationsRule(),
- Message: `variable "not_used" is declared but not used`,
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 20},
- },
- },
- },
- },
- {
- Name: "unused data source",
- Content: `
-data "null_data_source" "not_used" {}
-
-data "null_data_source" "used" {}
-output "u" { value = data.null_data_source.used }
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformUnusedDeclarationsRule(),
- Message: `data "null_data_source" "not_used" is declared but not used`,
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 2, Column: 1},
- End: hcl.Pos{Line: 2, Column: 35},
- },
- },
- },
- },
- {
- Name: "unused local source",
- Content: `
-locals {
- not_used = ""
- used = ""
-}
-
-output "u" { value = local.used }
-`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformUnusedDeclarationsRule(),
- Message: `local.not_used is declared but not used`,
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 3, Column: 2},
- End: hcl.Pos{Line: 3, Column: 15},
- },
- },
- },
- },
- {
- Name: "variable used in resource",
- Content: `
-variable "used" {}
-resource "null_resource" "n" {
- triggers = {
- u = var.used
- }
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "variable used in module",
- Content: `
-variable "used" {}
-module "m" {
- source = "./module"
- u = var.used
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "variable used in module",
- Content: `
-variable "used" {}
-module "m" {
- source = "./module"
- u = var.used
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "local used in module",
- Content: `
-locals { used = "used" }
-module "m" {
- source = "./module"
- u = local.used
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "variable used in provider",
- Content: `
-variable "aws_region" {}
-provider "aws" {
- region = var.aws_region
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "meta-arguments",
- Content: `
-variable "used" {}
-resource "null_resource" "n" {
- triggers = {
- u = var.used
- }
-
- lifecycle {
- ignore_changes = [triggers]
- }
-
- providers = {
- null = null
- }
-
- depends_on = [aws_instance.foo]
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "additional traversal",
- Content: `
-variable "v" {
- type = object({ foo = string })
-}
-output "v" {
- value = var.v.foo
-}
-
-data "terraform_remote_state" "d" {}
-output "d" {
- value = data.terraform_remote_state.d.outputs.foo
-}
-`,
- Expected: tflint.Issues{},
- },
- {
- Name: "json",
- JSON: true,
- Content: `
-{
- "resource": {
- "foo": {
- "bar": {
- "nested": [{
- "${var.again}": []
- }]
- }
- }
- },
- "variable": {
- "again": {}
- }
-}`,
- Expected: tflint.Issues{},
- },
- }
-
- rule := NewTerraformUnusedDeclarationsRule()
-
- for _, tc := range cases {
- filename := "config.tf"
- if tc.JSON {
- filename += ".json"
- }
-
- t.Run(tc.Name, func(t *testing.T) {
- runner := tflint.TestRunner(t, map[string]string{filename: tc.Content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- })
- }
-}
diff --git a/rules/terraformrules/terraform_unused_required_providers.go b/rules/terraformrules/terraform_unused_required_providers.go
deleted file mode 100644
index 2d523e6fa..000000000
--- a/rules/terraformrules/terraform_unused_required_providers.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "log"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// TerraformUnusedRequiredProvidersRule checks whether required providers are used in the module
-type TerraformUnusedRequiredProvidersRule struct{}
-
-// NewTerraformUnusedRequiredProvidersRule returns new rule with default attributes
-func NewTerraformUnusedRequiredProvidersRule() *TerraformUnusedRequiredProvidersRule {
- return &TerraformUnusedRequiredProvidersRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformUnusedRequiredProvidersRule) Name() string {
- return "terraform_unused_required_providers"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformUnusedRequiredProvidersRule) Enabled() bool {
- return false
-}
-
-// Severity returns the rule severity
-func (r *TerraformUnusedRequiredProvidersRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformUnusedRequiredProvidersRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check checks whether required providers are used
-func (r *TerraformUnusedRequiredProvidersRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- providerRefs, diags := getProviderRefs(runner)
- if diags.HasErrors() {
- return diags
- }
-
- requiredProviders := hcl.Attributes{}
- for _, file := range runner.Files() {
- content, _, schemaDiags := file.Body.PartialContent(&hcl.BodySchema{
- Blocks: []hcl.BlockHeaderSchema{{Type: "terraform"}},
- })
- diags = diags.Extend(schemaDiags)
- if diags.HasErrors() {
- continue
- }
-
- for _, block := range content.Blocks {
- content, _, schemaDiags = block.Body.PartialContent(&hcl.BodySchema{
- Blocks: []hcl.BlockHeaderSchema{{Type: "required_providers"}},
- })
- diags = diags.Extend(schemaDiags)
- if diags.HasErrors() {
- continue
- }
-
- for _, block := range content.Blocks {
- var attrDiags hcl.Diagnostics
- requiredProviders, attrDiags = block.Body.JustAttributes()
- diags = diags.Extend(attrDiags)
- if diags.HasErrors() {
- continue
- }
- }
- }
- }
- if diags.HasErrors() {
- return diags
- }
-
- for _, required := range requiredProviders {
- if _, exists := providerRefs[required.Name]; !exists {
- runner.EmitIssue(
- r,
- fmt.Sprintf("provider '%s' is declared in required_providers but not used by the module", required.Name),
- required.Range,
- )
- }
- }
-
- return nil
-}
diff --git a/rules/terraformrules/terraform_unused_required_providers_test.go b/rules/terraformrules/terraform_unused_required_providers_test.go
deleted file mode 100644
index 8d266b509..000000000
--- a/rules/terraformrules/terraform_unused_required_providers_test.go
+++ /dev/null
@@ -1,278 +0,0 @@
-package terraformrules
-
-import (
- "testing"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformUnusedRequiredProvidersRule(t *testing.T) {
- cases := []struct {
- Name string
- Content string
- Expected tflint.Issues
- }{
- {
- Name: "empty",
- Content: "",
- Expected: tflint.Issues{},
- },
- {
- Name: "used - resource",
- Content: `
- terraform {
- required_providers {
- null = {
- source = "hashicorp/null"
- }
- }
- }
-
- resource "null_resource" "foo" {}
- `,
- Expected: tflint.Issues{},
- },
- {
- Name: "used - data source",
- Content: `
- terraform {
- required_providers {
- null = {
- source = "hashicorp/null"
- }
- }
- }
-
- resource "null_data_source" "foo" {}
- `,
- Expected: tflint.Issues{},
- },
- {
- Name: "used - resource provider override",
- Content: `
- terraform {
- required_providers {
- custom-null = {
- source = "custom/null"
- }
- }
- }
-
- resource "null_resource" "foo" {
- provider = custom-null
- }
- `,
- Expected: tflint.Issues{},
- },
- {
- Name: "used - data source provider override",
- Content: `
- terraform {
- required_providers {
- custom-null = {
- source = "custom/null"
- }
- }
- }
-
- resource "null_data_source" "foo" {
- provider = custom-null
- }
- `,
- Expected: tflint.Issues{},
- },
- {
- Name: "used - module provider override",
- Content: `
- terraform {
- required_providers {
- custom-null = {
- source = "custom/null"
- }
- }
- }
-
- module "m" {
- source = "./m"
-
- providers = {
- null = custom-null
- }
- }
- `,
- Expected: tflint.Issues{},
- },
- {
- Name: "used - module provider override with alias",
- Content: `
- terraform {
- required_providers {
- null = {
- source = "hashicorp/null"
- configuration_aliases = [null.a]
- }
- }
- }
-
- module "m" {
- source = "./m"
-
- providers = {
- null = null.a
- }
- }
- `,
- Expected: tflint.Issues{},
- },
- {
- Name: "used - provider",
- Content: `
- terraform {
- required_providers {
- null = {
- source = "hashicorp/null"
- }
- }
- }
-
- provider "null" {}
- `,
- Expected: tflint.Issues{},
- },
- {
- Name: "unused",
- Content: `
- terraform {
- required_providers {
- null = {
- source = "hashicorp/null"
- }
- }
- }
- `,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformUnusedRequiredProvidersRule(),
- Message: "provider 'null' is declared in required_providers but not used by the module",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{
- Line: 4,
- Column: 7,
- },
- End: hcl.Pos{
- Line: 6,
- Column: 8,
- },
- },
- },
- },
- },
- {
- Name: "unused - override",
- Content: `
- terraform {
- required_providers {
- null = {
- source = "hashicorp/null"
- }
-
- custom-null = {
- source = "custom/null"
- }
- }
- }
-
- resource "null_resource" "foo" {
- provider = custom-null
- }
- `,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformUnusedRequiredProvidersRule(),
- Message: "provider 'null' is declared in required_providers but not used by the module",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{
- Line: 4,
- Column: 7,
- },
- End: hcl.Pos{
- Line: 6,
- Column: 8,
- },
- },
- },
- },
- },
- {
- Name: "unused - module",
- Content: `
- terraform {
- required_providers {
- null = {
- source = "hashicorp/null"
- }
- }
- }
-
- module "m" {
- source = "./m"
- }
- `,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformUnusedRequiredProvidersRule(),
- Message: "provider 'null' is declared in required_providers but not used by the module",
- Range: hcl.Range{
- Filename: "module.tf",
- Start: hcl.Pos{
- Line: 4,
- Column: 7,
- },
- End: hcl.Pos{
- Line: 6,
- Column: 8,
- },
- },
- },
- },
- },
- {
- Name: "used - unevaluated resource",
- Content: `
- terraform {
- required_providers {
- null = {
- source = "hashicorp/null"
- }
- }
- }
-
- variable "foo" {}
-
- resource "null_resource" "foo" {
- count = var.foo
- }
- `,
- Expected: tflint.Issues{},
- },
- }
-
- rule := NewTerraformUnusedRequiredProvidersRule()
-
- for _, tc := range cases {
- tc := tc
-
- t.Run(tc.Name, func(t *testing.T) {
- runner := tflint.TestRunner(t, map[string]string{"module.tf": tc.Content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- })
- }
-}
diff --git a/rules/terraformrules/terraform_workspace_remote.go b/rules/terraformrules/terraform_workspace_remote.go
deleted file mode 100644
index 3e87e985e..000000000
--- a/rules/terraformrules/terraform_workspace_remote.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package terraformrules
-
-import (
- "log"
-
- "github.com/hashicorp/hcl/v2"
- "github.com/hashicorp/hcl/v2/hclsyntax"
- "github.com/hashicorp/hcl/v2/json"
- "github.com/terraform-linters/tflint-plugin-sdk/hclext"
- sdk "github.com/terraform-linters/tflint-plugin-sdk/tflint"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-// TerraformWorkspaceRemoteRule warns of the use of terraform.workspace with a remote backend
-type TerraformWorkspaceRemoteRule struct{}
-
-// NewTerraformWorkspaceRemoteRule return a new rule
-func NewTerraformWorkspaceRemoteRule() *TerraformWorkspaceRemoteRule {
- return &TerraformWorkspaceRemoteRule{}
-}
-
-// Name returns the rule name
-func (r *TerraformWorkspaceRemoteRule) Name() string {
- return "terraform_workspace_remote"
-}
-
-// Enabled returns whether the rule is enabled by default
-func (r *TerraformWorkspaceRemoteRule) Enabled() bool {
- return true
-}
-
-// Severity returns the rule severity
-func (r *TerraformWorkspaceRemoteRule) Severity() tflint.Severity {
- return tflint.WARNING
-}
-
-// Link returns the rule reference link
-func (r *TerraformWorkspaceRemoteRule) Link() string {
- return tflint.ReferenceLink(r.Name())
-}
-
-// Check checks for a "remote" backend and if found emits issues for
-// each use of terraform.workspace in an expression.
-func (r *TerraformWorkspaceRemoteRule) Check(runner *tflint.Runner) error {
- if !runner.TFConfig.Path.IsRoot() {
- // This rule does not evaluate child modules.
- return nil
- }
-
- log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath())
-
- body, diags := runner.GetModuleContent(&hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "terraform",
- Body: &hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "backend",
- LabelNames: []string{"type"},
- Body: &hclext.BodySchema{},
- },
- },
- },
- },
- },
- }, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return diags
- }
-
- var remoteBackend bool
- for _, terraform := range body.Blocks {
- for _, backend := range terraform.Body.Blocks {
- if backend.Labels[0] == "remote" {
- remoteBackend = true
- }
- }
- }
- if !remoteBackend {
- return nil
- }
-
- return runner.WalkExpressions(func(expr hcl.Expression) error {
- return r.checkForTerraformWorkspaceInExpr(runner, expr)
- })
-}
-
-func (r *TerraformWorkspaceRemoteRule) checkForTerraformWorkspaceInExpr(runner *tflint.Runner, expr hcl.Expression) error {
- _, isScopeTraversalExpr := expr.(*hclsyntax.ScopeTraversalExpr)
- if !isScopeTraversalExpr && !json.IsJSONExpression(expr) {
- return nil
- }
-
- for _, ref := range referencesInExpr(expr) {
- switch sub := ref.subject.(type) {
- case terraformReference:
- if sub.name == "workspace" {
- runner.EmitIssue(
- r,
- "terraform.workspace should not be used with a 'remote' backend",
- expr.Range(),
- )
- return nil
- }
- }
- }
-
- return nil
-}
diff --git a/rules/terraformrules/terraform_workspace_remote_test.go b/rules/terraformrules/terraform_workspace_remote_test.go
deleted file mode 100644
index c13b05c08..000000000
--- a/rules/terraformrules/terraform_workspace_remote_test.go
+++ /dev/null
@@ -1,276 +0,0 @@
-package terraformrules
-
-import (
- "testing"
-
- hcl "github.com/hashicorp/hcl/v2"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-func Test_TerraformWorkspaceRemoteRule(t *testing.T) {
- cases := []struct {
- Name string
- JSON bool
- Content string
- Expected tflint.Issues
- }{
- {
- Name: "terraform.workspace in resource with remote backend",
- Content: `
-terraform {
- backend "remote" {}
-}
-resource "null_resource" "a" {
- triggers = {
- w = terraform.workspace
- }
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformWorkspaceRemoteRule(),
- Message: "terraform.workspace should not be used with a 'remote' backend",
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 7, Column: 7},
- End: hcl.Pos{Line: 7, Column: 26},
- },
- },
- },
- },
- {
- Name: "terraform.workspace with no backend",
- Content: `
-resource "null_resource" "a" {
- triggers = {
- w = terraform.workspace
- }
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "terraform.workspace with non-remote backend",
- Content: `
-terraform {
- backend "local" {}
-}
-
-resource "null_resource" "a" {
- triggers = {
- w = terraform.workspace
- }
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "terraform.workspace in data source with remote backend",
- Content: `
-terraform {
- backend "remote" {}
-}
-data "null_data_source" "a" {
- inputs = {
- w = terraform.workspace
- }
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformWorkspaceRemoteRule(),
- Message: "terraform.workspace should not be used with a 'remote' backend",
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 7, Column: 7},
- End: hcl.Pos{Line: 7, Column: 26},
- },
- },
- },
- },
- {
- Name: "terraform.workspace in module call with remote backend",
- Content: `
-terraform {
- backend "remote" {}
-}
-module "a" {
- source = "./module"
- w = terraform.workspace
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformWorkspaceRemoteRule(),
- Message: "terraform.workspace should not be used with a 'remote' backend",
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 7, Column: 6},
- End: hcl.Pos{Line: 7, Column: 25},
- },
- },
- },
- },
- {
- Name: "terraform.workspace in provider config with remote backend",
- Content: `
-terraform {
- backend "remote" {}
-}
-provider "aws" {
- assume_role {
- role_arn = terraform.workspace
- }
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformWorkspaceRemoteRule(),
- Message: "terraform.workspace should not be used with a 'remote' backend",
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 7, Column: 14},
- End: hcl.Pos{Line: 7, Column: 33},
- },
- },
- },
- },
- {
- Name: "terraform.workspace in locals with remote backend",
- Content: `
-terraform {
- backend "remote" {}
-}
-locals {
- w = terraform.workspace
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformWorkspaceRemoteRule(),
- Message: "terraform.workspace should not be used with a 'remote' backend",
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 6, Column: 6},
- End: hcl.Pos{Line: 6, Column: 25},
- },
- },
- },
- },
- {
- Name: "terraform.workspace in output with remote backend",
- Content: `
-terraform {
- backend "remote" {}
-}
-output "o" {
- value = terraform.workspace
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformWorkspaceRemoteRule(),
- Message: "terraform.workspace should not be used with a 'remote' backend",
- Range: hcl.Range{
- Filename: "config.tf",
- Start: hcl.Pos{Line: 6, Column: 10},
- End: hcl.Pos{Line: 6, Column: 29},
- },
- },
- },
- },
- {
- Name: "nonmatching expressions with remote backend",
- Content: `
-terraform {
- backend "remote" {}
-}
-locals {
- a = "terraform.workspace"
- b = path.module
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "meta-arguments",
- Content: `
-terraform {
- backend "remote" {}
-}
-resource "aws_instance" "foo" {
- instance_type = "t3.nano"
-
- lifecycle {
- ignore_changes = [instance_type]
- }
-
- providers = {
- aws = aws
- }
-
- depends_on = [aws_instance.bar]
-}`,
- Expected: tflint.Issues{},
- },
- {
- Name: "terraform.workspace in JSON syntax",
- JSON: true,
- Content: `
-{
- "terraform": {
- "backend": {
- "remote": {}
- }
- },
- "resource": {
- "null_resource": {
- "a": {
- "triggers": {
- "w": "${terraform.workspace}"
- }
- }
- }
- }
-}`,
- Expected: tflint.Issues{
- {
- Rule: NewTerraformWorkspaceRemoteRule(),
- Message: "terraform.workspace should not be used with a 'remote' backend",
- Range: hcl.Range{
- Filename: "config.tf.json",
- Start: hcl.Pos{Line: 8, Column: 15},
- End: hcl.Pos{Line: 16, Column: 4},
- },
- },
- },
- },
- {
- Name: "with ignore_changes",
- Content: `
-terraform {
- backend "remote" {}
-}
-
-resource "kubernetes_secret" "my_secret" {
- data = {}
-
- lifecycle {
- ignore_changes = [
- data
- ]
- }
-}`,
- Expected: tflint.Issues{},
- },
- }
-
- rule := NewTerraformWorkspaceRemoteRule()
-
- for _, tc := range cases {
- t.Run(tc.Name, func(t *testing.T) {
- filename := "config.tf"
- if tc.JSON {
- filename = "config.tf.json"
- }
- runner := tflint.TestRunner(t, map[string]string{filename: tc.Content})
-
- if err := rule.Check(runner); err != nil {
- t.Fatalf("Unexpected error occurred: %s", err)
- }
-
- tflint.AssertIssues(t, tc.Expected, runner.Issues)
- })
- }
-}
diff --git a/rules/terraformrules/terraformrules_test.go b/rules/terraformrules/terraformrules_test.go
deleted file mode 100644
index e487feb4b..000000000
--- a/rules/terraformrules/terraformrules_test.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package terraformrules
-
-import (
- "io"
- "log"
- "os"
- "testing"
-)
-
-func TestMain(m *testing.M) {
- log.SetOutput(io.Discard)
- os.Exit(m.Run())
-}
diff --git a/rules/terraformrules/utils.go b/rules/terraformrules/utils.go
deleted file mode 100644
index af9ba06ae..000000000
--- a/rules/terraformrules/utils.go
+++ /dev/null
@@ -1,396 +0,0 @@
-package terraformrules
-
-import (
- "fmt"
- "strings"
-
- "github.com/hashicorp/go-version"
- "github.com/hashicorp/hcl/v2"
- "github.com/hashicorp/hcl/v2/gohcl"
- "github.com/hashicorp/hcl/v2/hclsyntax"
- "github.com/terraform-linters/tflint-plugin-sdk/hclext"
- sdk "github.com/terraform-linters/tflint-plugin-sdk/tflint"
- "github.com/terraform-linters/tflint/tflint"
-)
-
-type moduleCall struct {
- name string
- defRange hcl.Range
- source string
- sourceAttr *hclext.Attribute
- version version.Constraints
- versionAttr *hclext.Attribute
-}
-
-func decodeModuleCall(block *hclext.Block) (*moduleCall, hcl.Diagnostics) {
- module := &moduleCall{
- name: block.Labels[0],
- defRange: block.DefRange,
- }
- diags := hcl.Diagnostics{}
-
- if source, exists := block.Body.Attributes["source"]; exists {
- module.sourceAttr = source
- sourceDiags := gohcl.DecodeExpression(source.Expr, nil, &module.source)
- diags = diags.Extend(sourceDiags)
- }
-
- if versionAttr, exists := block.Body.Attributes["version"]; exists {
- module.versionAttr = versionAttr
-
- var versionVal string
- versionDiags := gohcl.DecodeExpression(versionAttr.Expr, nil, &versionVal)
- diags = diags.Extend(versionDiags)
- if diags.HasErrors() {
- return module, diags
- }
-
- constraints, err := version.NewConstraint(versionVal)
- if err != nil {
- diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Invalid version constraint",
- Detail: "This string does not use correct version constraint syntax.",
- Subject: versionAttr.Expr.Range().Ptr(),
- })
- }
- module.version = constraints
- }
-
- return module, diags
-}
-
-var moduleCallSchema = &hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "module",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{
- Attributes: []hclext.AttributeSchema{
- {Name: "source"},
- {Name: "version"},
- },
- },
- },
- },
-}
-
-type local struct {
- name string
- defRange hcl.Range
-}
-
-func getLocals(runner *tflint.Runner) (map[string]*local, hcl.Diagnostics) {
- locals := map[string]*local{}
- diags := hcl.Diagnostics{}
-
- for _, file := range runner.Files() {
- content, _, schemaDiags := file.Body.PartialContent(&hcl.BodySchema{
- Blocks: []hcl.BlockHeaderSchema{{Type: "locals"}},
- })
- diags = diags.Extend(schemaDiags)
- if diags.HasErrors() {
- continue
- }
-
- for _, block := range content.Blocks {
- attrs, localsDiags := block.Body.JustAttributes()
- diags = diags.Extend(localsDiags)
- if diags.HasErrors() {
- continue
- }
-
- for name, attr := range attrs {
- locals[name] = &local{
- name: attr.Name,
- defRange: attr.Range,
- }
- }
- }
- }
-
- return locals, diags
-}
-
-type providerRef struct {
- name string
- defRange hcl.Range
-}
-
-func getProviderRefs(runner *tflint.Runner) (map[string]*providerRef, hcl.Diagnostics) {
- providerRefs := map[string]*providerRef{}
-
- body, diags := runner.GetModuleContent(&hclext.BodySchema{
- Blocks: []hclext.BlockSchema{
- {
- Type: "resource",
- LabelNames: []string{"type", "name"},
- Body: &hclext.BodySchema{
- Attributes: []hclext.AttributeSchema{
- {Name: "provider"},
- },
- },
- },
- {
- Type: "data",
- LabelNames: []string{"type", "name"},
- Body: &hclext.BodySchema{
- Attributes: []hclext.AttributeSchema{
- {Name: "provider"},
- },
- },
- },
- {
- Type: "provider",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{},
- },
- {
- Type: "module",
- LabelNames: []string{"name"},
- Body: &hclext.BodySchema{
- Attributes: []hclext.AttributeSchema{
- {Name: "providers"},
- },
- },
- },
- },
- }, sdk.GetModuleContentOption{IncludeNotCreated: true})
- if diags.HasErrors() {
- return providerRefs, diags
- }
-
- for _, block := range body.Blocks {
- switch block.Type {
- case "resource":
- fallthrough
- case "data":
- if attr, exists := block.Body.Attributes["provider"]; exists {
- ref, decodeDiags := decodeProviderRef(attr.Expr, block.DefRange)
- diags = diags.Extend(decodeDiags)
- if diags.HasErrors() {
- continue
- }
- providerRefs[ref.name] = ref
- } else {
- providerName := block.Labels[0]
- if under := strings.Index(providerName, "_"); under != -1 {
- providerName = providerName[:under]
- }
- providerRefs[providerName] = &providerRef{
- name: providerName,
- defRange: block.DefRange,
- }
- }
- case "provider":
- providerRefs[block.Labels[0]] = &providerRef{
- name: block.Labels[0],
- defRange: block.DefRange,
- }
- case "module":
- if attr, exists := block.Body.Attributes["providers"]; exists {
- pairs, mapDiags := hcl.ExprMap(attr.Expr)
- diags = diags.Extend(mapDiags)
- if diags.HasErrors() {
- continue
- }
-
- for _, pair := range pairs {
- ref, decodeDiags := decodeProviderRef(pair.Value, block.DefRange)
- diags = diags.Extend(decodeDiags)
- if diags.HasErrors() {
- continue
- }
- providerRefs[ref.name] = ref
- }
- }
- }
- }
-
- return providerRefs, nil
-}
-
-func decodeProviderRef(expr hcl.Expression, defRange hcl.Range) (*providerRef, hcl.Diagnostics) {
- expr, diags := shimTraversalInString(expr)
- if diags.HasErrors() {
- return nil, diags
- }
-
- traversal, diags := hcl.AbsTraversalForExpr(expr)
- if diags.HasErrors() {
- return nil, diags
- }
-
- return &providerRef{
- name: traversal.RootName(),
- defRange: defRange,
- }, nil
-}
-
-type reference struct {
- subject referencable
-}
-
-type referencable interface {
- referenceableSigil()
-}
-
-type inputVariableReference struct {
- referencable
- name string
-}
-
-type localValueReference struct {
- referencable
- name string
-}
-
-type terraformReference struct {
- referencable
- name string
-}
-
-type dataResourceReference struct {
- referencable
- typeName string
- name string
-}
-
-func (d *dataResourceReference) String() string {
- return fmt.Sprintf("data.%s.%s", d.typeName, d.name)
-}
-
-// referencesInExpr extracts and returns references from hcl.Expression.
-// This is roughly equivalent to ReferencesInExpr in Terraform interlan API,
-// but simply ignores invalid references without returing diagnostics.
-// This is because it is not the responsibility of this method to detect invalid references.
-// Invalid references should be caught by terraform validate.
-//
-// @see https://github.com/hashicorp/terraform/blob/v1.2.5/internal/lang/references.go#L75
-func referencesInExpr(expr hcl.Expression) []*reference {
- references := []*reference{}
-
- for _, traversal := range expr.Variables() {
- name := traversal.RootName()
-
- switch name {
- case "var":
- name, diags := parseSingleAttrRef(traversal)
- if diags.HasErrors() {
- continue
- }
- references = append(references, &reference{subject: inputVariableReference{name: name}})
- case "local":
- name, diags := parseSingleAttrRef(traversal)
- if diags.HasErrors() {
- continue
- }
- references = append(references, &reference{subject: localValueReference{name: name}})
- case "terraform":
- name, diags := parseSingleAttrRef(traversal)
- if diags.HasErrors() {
- continue
- }
- references = append(references, &reference{subject: terraformReference{name: name}})
- case "data":
- if len(traversal) < 3 {
- // The "data" object must be followed by two attribute names: the data source type and the resource name.
- continue
- }
-
- var typeName string
- switch tt := traversal[1].(type) {
- case hcl.TraverseRoot:
- typeName = tt.Name
- case hcl.TraverseAttr:
- typeName = tt.Name
- default:
- // The "data" object does not support this operation.
- continue
- }
-
- attrTrav, ok := traversal[2].(hcl.TraverseAttr)
- if !ok {
- // A reference to a data source must be followed by at least one attribute access, specifying the resource name.
- continue
- }
-
- references = append(references, &reference{subject: dataResourceReference{typeName: typeName, name: attrTrav.Name}})
- }
- }
-
- return references
-}
-
-// @see https://github.com/hashicorp/terraform/blob/v1.2.5/internal/addrs/parse_ref.go#L392
-func parseSingleAttrRef(traversal hcl.Traversal) (string, hcl.Diagnostics) {
- var diags hcl.Diagnostics
-
- root := traversal.RootName()
- rootRange := traversal[0].SourceRange()
-
- if len(traversal) < 2 {
- diags = diags.Append(&hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Invalid reference",
- Detail: fmt.Sprintf("The %q object cannot be accessed directly. Instead, access one of its attributes.", root),
- Subject: &rootRange,
- })
- return "", diags
- }
- if attrTrav, ok := traversal[1].(hcl.TraverseAttr); ok {
- return attrTrav.Name, diags
- }
- diags = diags.Append(&hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Invalid reference",
- Detail: fmt.Sprintf("The %q object does not support this operation.", root),
- Subject: traversal[1].SourceRange().Ptr(),
- })
- return "", diags
-}
-
-// @see https://github.com/hashicorp/terraform/blob/v1.2.5/internal/configs/compat_shim.go#L34
-func shimTraversalInString(expr hcl.Expression) (hcl.Expression, hcl.Diagnostics) {
- // ObjectConsKeyExpr is a special wrapper type used for keys on object
- // constructors to deal with the fact that naked identifiers are normally
- // handled as "bareword" strings rather than as variable references. Since
- // we know we're interpreting as a traversal anyway (and thus it won't
- // matter whether it's a string or an identifier) we can safely just unwrap
- // here and then process whatever we find inside as normal.
- if ocke, ok := expr.(*hclsyntax.ObjectConsKeyExpr); ok {
- expr = ocke.Wrapped
- }
-
- if _, ok := expr.(*hclsyntax.TemplateExpr); !ok {
- return expr, nil
- }
-
- strVal, diags := expr.Value(nil)
- if diags.HasErrors() || strVal.IsNull() || !strVal.IsKnown() {
- // Since we're not even able to attempt a shim here, we'll discard
- // the diagnostics we saw so far and let the caller's own error
- // handling take care of reporting the invalid expression.
- return expr, nil
- }
-
- // The position handling here isn't _quite_ right because it won't
- // take into account any escape sequences in the literal string, but
- // it should be close enough for any error reporting to make sense.
- srcRange := expr.Range()
- startPos := srcRange.Start // copy
- startPos.Column++ // skip initial quote
- startPos.Byte++ // skip initial quote
-
- traversal, tDiags := hclsyntax.ParseTraversalAbs(
- []byte(strVal.AsString()),
- srcRange.Filename,
- startPos,
- )
- diags = append(diags, tDiags...)
-
- return &hclsyntax.ScopeTraversalExpr{
- Traversal: traversal,
- SrcRange: srcRange,
- }, diags
-}
diff --git a/tflint/config.go b/tflint/config.go
index 32382e149..dc0f64524 100644
--- a/tflint/config.go
+++ b/tflint/config.go
@@ -8,6 +8,7 @@ import (
hcl "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclparse"
+ "github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/mitchellh/go-homedir"
"github.com/spf13/afero"
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
@@ -108,9 +109,18 @@ func EmptyConfig() *Config {
}
}
-// LoadConfig reads TFLint config from a file.
-// If ./.tflint.hcl does not exist, load ~/.tflint.hcl.
-// This fallback does not fire when explicitly reading a file other than .tflint.hcl.
+// LoadConfig loads TFLint config file.
+// The priority of the configuration files is as follows:
+//
+// 1. current directory (./.tflint.hcl)
+// 2. home directory (~/.tflint.hcl)
+//
+// If neither file exists, an empty config is returned.
+// You can also load any file name. However, there is no fallback
+// to the home directory in this case.
+//
+// It also automatically enables bundled plugin if the "terraform"
+// plugin block is not explicitly declared.
func LoadConfig(fs afero.Afero, file string) (*Config, error) {
log.Printf("[INFO] Load config: %s", file)
if f, err := fs.Open(file); err == nil {
@@ -118,7 +128,7 @@ func LoadConfig(fs afero.Afero, file string) (*Config, error) {
if err != nil {
return nil, err
}
- return cfg, nil
+ return cfg.enableBundledPlugin(), nil
} else if file != defaultConfigFile {
return nil, fmt.Errorf("failed to load file: %w", err)
} else {
@@ -136,12 +146,12 @@ func LoadConfig(fs afero.Afero, file string) (*Config, error) {
if err != nil {
return nil, err
}
- return cfg, nil
+ return cfg.enableBundledPlugin(), nil
}
log.Printf("[INFO] file not found")
log.Print("[INFO] Use default config")
- return EmptyConfig(), nil
+ return EmptyConfig().enableBundledPlugin(), nil
}
func loadConfig(file afero.File) (*Config, error) {
@@ -263,10 +273,50 @@ func loadConfig(file afero.File) (*Config, error) {
return config, nil
}
+// Enable the "recommended" preset if the bundled plugin is automatically enabled.
+var bundledPluginConfigFilename = "__bundled_plugin_config.hcl"
+var bundledPluginConfigContent = `
+preset = "recommended"
+`
+
+// DisbaleBundledPlugin is a flag to temporarily disable the bundled plugin for integration tests.
+var DisableBundledPlugin = false
+
+// Terraform Language plugin is automatically enabled if the plugin isn't explicitly declared.
+func (c *Config) enableBundledPlugin() *Config {
+ if DisableBundledPlugin {
+ return c
+ }
+
+ f, diags := hclsyntax.ParseConfig([]byte(bundledPluginConfigContent), bundledPluginConfigFilename, hcl.InitialPos)
+ if diags.HasErrors() {
+ panic(diags)
+ }
+
+ if _, exists := c.Plugins["terraform"]; !exists {
+ log.Print("[INFO] The `terraform` plugin block is not found. Enable the plugin `terraform` automatically")
+
+ c.Plugins["terraform"] = &PluginConfig{
+ Name: "terraform",
+ Enabled: true,
+ Body: f.Body,
+ }
+ }
+ return c
+}
+
// Sources returns parsed config file sources.
-// Normally, there is only one file, but it is represented by map to retain the file name.
+// To support bundle plugin config, this function returns c.sources
+// with a merge of the pseudo config file.
func (c *Config) Sources() map[string][]byte {
- return c.sources
+ ret := map[string][]byte{
+ bundledPluginConfigFilename: []byte(bundledPluginConfigContent),
+ }
+
+ for name, content := range c.sources {
+ ret[name] = content
+ }
+ return ret
}
// Merge merges the two configs and applies to itself.
@@ -341,8 +391,8 @@ func (c *PluginConfig) Content(schema *hclext.BodySchema) (*hclext.BodyContent,
return hclext.Content(c.Body, schema)
}
-// RuleSet is an interface to handle plugin's RuleSet and core RuleSet both
-// In the future, when all RuleSets are cut out into plugins, it will no longer be needed.
+// RuleSet is an interface to handle plugin's RuleSet.
+// The real impl is github.com/terraform-linters/tflint-plugin-sdk/plugin/host2plugin.GRPCClient.
type RuleSet interface {
RuleSetName() (string, error)
RuleSetVersion() (string, error)
diff --git a/tflint/config_test.go b/tflint/config_test.go
index 49cb93c48..1f1b71409 100644
--- a/tflint/config_test.go
+++ b/tflint/config_test.go
@@ -110,6 +110,10 @@ plugin "baz" {
Name: "baz",
Enabled: true,
},
+ "terraform": {
+ Name: "terraform",
+ Enabled: true,
+ },
},
},
errCheck: neverHappend,
@@ -118,7 +122,7 @@ plugin "baz" {
name: "empty file",
file: "empty.hcl",
files: map[string]string{"empty.hcl": ""},
- want: EmptyConfig(),
+ want: EmptyConfig().enableBundledPlugin(),
errCheck: neverHappend,
},
{
@@ -139,14 +143,45 @@ config {
Variables: []string{},
DisabledByDefault: true,
Rules: map[string]*RuleConfig{},
- Plugins: map[string]*PluginConfig{},
+ Plugins: map[string]*PluginConfig{
+ "terraform": {
+ Name: "terraform",
+ Enabled: true,
+ },
+ },
},
errCheck: neverHappend,
},
{
name: "no config",
file: ".tflint.hcl",
- want: EmptyConfig(),
+ want: EmptyConfig().enableBundledPlugin(),
+ errCheck: neverHappend,
+ },
+ {
+ name: "terraform plugin",
+ file: "config.hcl",
+ files: map[string]string{
+ "config.hcl": `
+plugin "terraform" {
+ enabled = false
+}`,
+ },
+ want: &Config{
+ Module: false,
+ Force: false,
+ IgnoreModules: map[string]bool{},
+ Varfiles: []string{},
+ Variables: []string{},
+ DisabledByDefault: false,
+ Rules: map[string]*RuleConfig{},
+ Plugins: map[string]*PluginConfig{
+ "terraform": {
+ Name: "terraform",
+ Enabled: false,
+ },
+ },
+ },
errCheck: neverHappend,
},
{