From 479403edb3fb8ad3b8003389fa302f7cc9f3d56c Mon Sep 17 00:00:00 2001 From: Matthew Duren Date: Fri, 13 May 2022 15:33:23 -0400 Subject: [PATCH 1/3] Add more file structure rules for locals and init --- main.go | 5 - rules/terraform_kb4_file_structure.go | 126 +++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index af0ea22..86ac8e8 100644 --- a/main.go +++ b/main.go @@ -13,11 +13,6 @@ import ( * For Modules * - Resources should be named `this` where possible. * - No providers in modules (this can be ignored on a module by module basis if needed) - * For all terraform - * - `terraform_remote_state {}` in _init.tf - * - `provider {}` in _init.tf - * - `terraform {}` in _init.tf - * - Do we want to restrict where locals can be put? */ //go:embed VERSION diff --git a/rules/terraform_kb4_file_structure.go b/rules/terraform_kb4_file_structure.go index 1e444ef..b509f78 100644 --- a/rules/terraform_kb4_file_structure.go +++ b/rules/terraform_kb4_file_structure.go @@ -9,7 +9,7 @@ import ( "github.com/terraform-linters/tflint-plugin-sdk/tflint" ) -var EXPECTED_FILES []string = []string{"_init.tf", "_variables.tf", "_outputs.tf"} +var EXPECTED_FILES []string = []string{"_init.tf", "_variables.tf", "_outputs.tf", "_locals.tf"} // TerraformKb4FileStructureRule checks whether modules adhere to Terraform's standard module structure type TerraformKb4FileStructureRule struct { @@ -48,6 +48,10 @@ func (r *TerraformKb4FileStructureRule) Check(runner tflint.Runner) error { r.checkFiles(runner) r.checkVariables(runner) r.checkOutputs(runner) + r.checkProviders(runner) + r.checkTerraformBlock(runner) + r.checkLocals(runner) + r.checkTerraformRemoteState(runner) return nil } @@ -118,15 +122,127 @@ func (r *TerraformKb4FileStructureRule) checkOutputs(runner tflint.Runner) error return err } - for _, variable := range content.Blocks { - if variable.DefRange.Filename != "_outputs.tf" { + for _, output := range content.Blocks { + if output.DefRange.Filename != "_outputs.tf" { runner.EmitIssue( r, - fmt.Sprintf("variable %q should be moved from %s to %s", variable.Labels[0], variable.DefRange.Filename, "_outputs.tf"), - variable.DefRange, + fmt.Sprintf("output %q should be moved from %s to %s", output.Labels[0], output.DefRange.Filename, "_outputs.tf"), + output.DefRange, ) } } return nil } + +func (r *TerraformKb4FileStructureRule) checkProviders(runner tflint.Runner) error { + + content, err := runner.GetModuleContent(&hclext.BodySchema{ + Blocks: []hclext.BlockSchema{ + { + Type: "provider", + LabelNames: []string{"name"}, + }, + }, + }, nil) + + if err != nil { + return err + } + + for _, provider := range content.Blocks { + if provider.DefRange.Filename != "_init.tf" { + runner.EmitIssue( + r, + fmt.Sprintf("provider %q should be moved from %s to %s", provider.Labels[0], provider.DefRange.Filename, "_init.tf"), + provider.DefRange, + ) + } + } + + return nil +} + +func (r *TerraformKb4FileStructureRule) checkTerraformBlock(runner tflint.Runner) error { + + content, err := runner.GetModuleContent(&hclext.BodySchema{ + Blocks: []hclext.BlockSchema{ + { + Type: "terraform", + }, + }, + }, nil) + + if err != nil { + return err + } + + for _, terraformBlock := range content.Blocks { + if terraformBlock.DefRange.Filename != "_init.tf" { + runner.EmitIssue( + r, + fmt.Sprintf("terraform block %q should be moved from %s to %s", terraformBlock.Labels[0], terraformBlock.DefRange.Filename, "_init.tf"), + terraformBlock.DefRange, + ) + } + } + + return nil +} + +func (r *TerraformKb4FileStructureRule) checkLocals(runner tflint.Runner) error { + + content, err := runner.GetModuleContent(&hclext.BodySchema{ + Blocks: []hclext.BlockSchema{ + { + Type: "locals", + }, + }, + }, nil) + + if err != nil { + return err + } + + for _, locals := range content.Blocks { + if locals.DefRange.Filename != "_init.tf" { + runner.EmitIssue( + r, + fmt.Sprintf("locals block %q should be moved from %s to %s", locals.Labels[0], locals.DefRange.Filename, "_init.tf"), + locals.DefRange, + ) + } + } + + return nil +} + +func (r *TerraformKb4FileStructureRule) checkTerraformRemoteState(runner tflint.Runner) error { + + content, err := runner.GetModuleContent(&hclext.BodySchema{ + Blocks: []hclext.BlockSchema{ + { + Type: "data", + LabelNames: []string{"name"}, + }, + }, + }, nil) + + if err != nil { + return err + } + + for _, data := range content.Blocks { + if data.DefRange.Filename != "_init.tf" { + if data.Type == "terraform_remote_state" { + runner.EmitIssue( + r, + fmt.Sprintf("data terraform_remote_state %q should be moved from %s to %s", data.Name, data.DefRange.Filename, "_init.tf"), + data.DefRange, + ) + } + } + } + + return nil +} From fddecd87ed6212eeec72d89ee3f830bbf206203f Mon Sep 17 00:00:00 2001 From: Matthew Duren Date: Mon, 16 May 2022 16:08:00 -0400 Subject: [PATCH 2/3] Add todo --- main.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main.go b/main.go index 86ac8e8..81e8570 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,12 @@ import ( * For Modules * - Resources should be named `this` where possible. * - No providers in modules (this can be ignored on a module by module basis if needed) + * For all terraform + * - The following checks need tests: + * - checkProviders + * - checkTerraformBlock + * - checkLocals + * - checkTerraformRemoteState */ //go:embed VERSION From 551eb06db0a34fe07c19a839960a7223a31771a9 Mon Sep 17 00:00:00 2001 From: Matthew Duren Date: Mon, 16 May 2022 16:10:21 -0400 Subject: [PATCH 3/3] Add tests --- rules/terraform_kb4_file_structure_test.go | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/rules/terraform_kb4_file_structure_test.go b/rules/terraform_kb4_file_structure_test.go index a53fe18..668ca44 100644 --- a/rules/terraform_kb4_file_structure_test.go +++ b/rules/terraform_kb4_file_structure_test.go @@ -42,6 +42,14 @@ func Test_TerraformKb4ModuleStructureRule(t *testing.T) { Start: hcl.InitialPos, }, }, + { + Rule: NewTerraformKb4FileStructureRule(), + Message: "Module should include a _locals.tf file.", + Range: hcl.Range{ + Filename: "_locals.tf", + Start: hcl.InitialPos, + }, + }, }, }, { @@ -50,6 +58,7 @@ func Test_TerraformKb4ModuleStructureRule(t *testing.T) { "_init.tf": `terraform {}`, "_variables.tf": `variable "v" {}`, // "_outputs.tf": `variable "v" {}`, + "_locals.tf": `locals {}`, }, Expected: helper.Issues{ { @@ -68,6 +77,7 @@ func Test_TerraformKb4ModuleStructureRule(t *testing.T) { "_init.tf": `terraform {}`, // "_variables.tf": `variable "some_variable" {}`, "_outputs.tf": `output "some_output" {}`, + "_locals.tf": `locals {}`, }, Expected: helper.Issues{ { @@ -86,6 +96,7 @@ func Test_TerraformKb4ModuleStructureRule(t *testing.T) { // "_init.tf": `terraform {}`, "_variables.tf": `variable "some_variable" {}`, "_outputs.tf": `output "some_output" {}`, + "_locals.tf": `locals {}`, }, Expected: helper.Issues{ { @@ -98,12 +109,32 @@ func Test_TerraformKb4ModuleStructureRule(t *testing.T) { }, }, }, + { + Name: "missing locals file", + Content: map[string]string{ + "_init.tf": `terraform {}`, + "_variables.tf": `variable "some_variable" {}`, + "_outputs.tf": `output "some_output" {}`, + // "_locals.tf": `locals {}`, + }, + Expected: helper.Issues{ + { + Rule: NewTerraformKb4FileStructureRule(), + Message: "Module should include a _locals.tf file.", + Range: hcl.Range{ + Filename: filepath.Join("_locals.tf"), + Start: hcl.InitialPos, + }, + }, + }, + }, { Name: "no missing files", Content: map[string]string{ "_init.tf": `terraform {}`, "_variables.tf": `variable "some_variable" {}`, "_outputs.tf": `output "some_output" {}`, + "_locals.tf": `locals {}`, }, Expected: helper.Issues{}, }, @@ -113,6 +144,7 @@ func Test_TerraformKb4ModuleStructureRule(t *testing.T) { "_init.tf": `variable "misplace_variable" {}`, "_variables.tf": `variable "some_variable" {}`, "_outputs.tf": `output "some_output" {}`, + "_locals.tf": `locals {}`, }, Expected: helper.Issues{ {