Skip to content

Commit

Permalink
feat: add location interface (#18)
Browse files Browse the repository at this point in the history
* feat: add location interface

* fix: forgot to add interface to Rules slice

* fix: remove unused func

* chore: update link

* refactor getAttr function, extract attribute non-existance check logic into new function attributeNotExist (#19)

---------

Co-authored-by: lonegunmanb <lonegunmanb@hotmail.com>
  • Loading branch information
matt-FFFFFF and lonegunmanb authored Apr 9, 2024
1 parent 9bbf595 commit 61f6cc4
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 4 deletions.
29 changes: 27 additions & 2 deletions interfaces/interface_variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,12 @@ func (vcr *InterfaceVarCheckRule) Check(r tflint.Runner) error {
}

typeAttr, c := CheckWithReturnValue(NewChecker(), getAttr(vcr, r, b, "type"))
defaultAttr, c := CheckWithReturnValue(c, getAttr(vcr, r, b, "default"))
var defaultAttr *hclext.Attribute
if vcr.Default.IsKnown() {
defaultAttr, c = CheckWithReturnValue(c, getAttr(vcr, r, b, "default"))
} else {
c = c.Check(attributeNotExist(vcr, r, b, "default"))
}
if c = c.Check(checkVarType(vcr, r, typeAttr)).
Check(checkDefaultValue(vcr, r, b, defaultAttr)).
Check(checkNullableValue(vcr, r, b)); c.err != nil {
Expand All @@ -118,7 +123,17 @@ func (vcr *InterfaceVarCheckRule) Check(r tflint.Runner) error {
return nil
}

// getTypeAttr returns a function that will return the type attribute from a given hcl block.
func attributeNotExist(vcr *InterfaceVarCheckRule, r tflint.Runner, b *hclext.Block, attrName string) func() (bool, error) {
return func() (bool, error) {
_, exist := b.Body.Attributes[attrName]
if exist {
return false, r.EmitIssue(vcr, fmt.Sprintf("`%s` %s should not be declared", b.Labels[0], attrName), b.DefRange)
}
return true, nil
}
}

// getAttr returns a function that will return the attribute from a given hcl block.
// It is designed to be used with the CheckWithReturnValue function.
func getAttr(rule tflint.Rule, r tflint.Runner, b *hclext.Block, attrName string) func() (*hclext.Attribute, bool, error) {
return func() (*hclext.Attribute, bool, error) {
Expand Down Expand Up @@ -187,6 +202,16 @@ func checkVarType(vcr *InterfaceVarCheckRule, r tflint.Runner, typeAttr *hclext.
func checkDefaultValue(vcr *InterfaceVarCheckRule, r tflint.Runner, b *hclext.Block, defaultAttr *hclext.Attribute) func() (bool, error) {
return func() (bool, error) {
// Check if the default value is correct.
if !vcr.Default.IsKnown() {
if defaultAttr != nil {
return true, r.EmitIssue(
vcr,
fmt.Sprintf("default value should not be set, see: %s", vcr.Link()),
defaultAttr.Range,
)
}
return true, nil
}
defaultVal, _ := defaultAttr.Expr.Value(nil)
if !check.EqualCtyValue(defaultVal, vcr.Default) {
return true, r.EmitIssue(
Expand Down
1 change: 1 addition & 0 deletions interfaces/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
var Rules = []tflint.Rule{
NewVarCheckRuleFromAvmInterface(CustomerManagedKey),
NewVarCheckRuleFromAvmInterface(DiagnosticSettings),
NewVarCheckRuleFromAvmInterface(Location),
NewVarCheckRuleFromAvmInterface(Lock),
NewVarCheckRuleFromAvmInterface(ManagedIdentities),
NewVarCheckRuleFromAvmInterface(RoleAssignments),
Expand Down
82 changes: 81 additions & 1 deletion interfaces/interfaces_simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package interfaces_test

import (
"fmt"
"github.com/stretchr/testify/assert"
"testing"

"github.com/Azure/tflint-ruleset-avm/interfaces"
"github.com/hashicorp/hcl/v2"
"github.com/matt-FFFFFF/tfvarcheck/varcheck"
"github.com/stretchr/testify/assert"
"github.com/terraform-linters/tflint-plugin-sdk/helper"
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
"github.com/zclconf/go-cty/cty"
Expand All @@ -30,6 +30,16 @@ var SimpleVar = interfaces.AvmInterface{
RuleSeverity: tflint.ERROR,
}

// SimpleVar represents a simple variable type with no default value used for testing.
var SimpleVarNoDefault = interfaces.AvmInterface{
VarCheck: varcheck.NewVarCheck(simpleType, cty.UnknownVal(cty.DynamicPseudoType), false),
RuleName: "simple",
VarTypeString: simpleVarTypeString,
RuleEnabled: true,
RuleLink: "https://simple",
RuleSeverity: tflint.ERROR,
}

func TestIncorrectInterfaceTypeStringShouldPanic(t *testing.T) {
defer func() {
err := recover()
Expand All @@ -40,6 +50,76 @@ func TestIncorrectInterfaceTypeStringShouldPanic(t *testing.T) {
t.Fatal("incorrect type should panic")
}

func TestSimpleInterfaceNoDefault(t *testing.T) {
cases := []struct {
Name string
Content string
JSON bool
Expected helper.Issues
}{
{
Name: "correct",
Content: toTerraformVarType(SimpleVarNoDefault),
Expected: helper.Issues{},
},

{
Name: "incorrect - has default value",
Content: `
variable "simple" {
type = object({
kind = string
name = optional(string, null)
})
default = {}
}`,
Expected: helper.Issues{
{
Rule: interfaces.NewVarCheckRuleFromAvmInterface(SimpleVarNoDefault),
Message: "`simple` default should not be declared",
},
},
},
{
Name: "incorrect - is nullable",
Content: `
variable "simple" {
type = object({
kind = string
name = optional(string, null)
})
}`,
Expected: helper.Issues{
{
Rule: interfaces.NewVarCheckRuleFromAvmInterface(SimpleVarNoDefault),
Message: "nullable should be set to false",
},
},
},
}

rule := interfaces.NewVarCheckRuleFromAvmInterface(SimpleVarNoDefault)

for _, tc := range cases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
filename := "variables.tf"
if tc.JSON {
filename += ".json"
}

runner := helper.TestRunner(t, map[string]string{filename: tc.Content})

if err := rule.Check(runner); err != nil {
t.Fatalf("Unexpected error occurred: %s", err)
}

helper.AssertIssuesWithoutRange(t, tc.Expected, runner.Issues)
})
}
}

func TestSimpleInterface(t *testing.T) {
cases := []struct {
Name string
Expand Down
4 changes: 3 additions & 1 deletion interfaces/interfaces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ func toTerraformVarType(i interfaces.AvmInterface) string {
SpacesBefore: 1,
},
})
varBody.SetAttributeValue("default", i.Default)
if i.Default.IsKnown() {
varBody.SetAttributeValue("default", i.Default)
}
if !i.Nullable {
varBody.SetAttributeValue("nullable", cty.False)
}
Expand Down
20 changes: 20 additions & 0 deletions interfaces/location.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package interfaces

import (
"github.com/matt-FFFFFF/tfvarcheck/varcheck"
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
"github.com/zclconf/go-cty/cty"
)

var LocationTypeString = `string`

var locationType = StringToTypeConstraintWithDefaults(LocationTypeString)

var Location = AvmInterface{
VarCheck: varcheck.NewVarCheck(locationType, cty.UnknownVal(cty.String), false),
RuleName: "location",
VarTypeString: LocationTypeString,
RuleEnabled: true,
RuleLink: "https://azure.github.io/Azure-Verified-Modules/specs/shared/#id-rmnfr2---category-inputs---parametervariable-naming",
RuleSeverity: tflint.ERROR,
}
45 changes: 45 additions & 0 deletions interfaces/location_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package interfaces_test

import (
"testing"

"github.com/Azure/tflint-ruleset-avm/interfaces"
"github.com/terraform-linters/tflint-plugin-sdk/helper"
)

// TestLockTerraformVar tests Lock interface.
func TestTerraformLocationInterface(t *testing.T) {
cases := []struct {
Name string
Content string
JSON bool
Expected helper.Issues
}{
{
Name: "correct",
Content: toTerraformVarType(interfaces.Location),
Expected: helper.Issues{},
},
}

rule := interfaces.NewVarCheckRuleFromAvmInterface(interfaces.Location)

for _, tc := range cases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
filename := "variables.tf"
if tc.JSON {
filename += ".json"
}

runner := helper.TestRunner(t, map[string]string{filename: tc.Content})

if err := rule.Check(runner); err != nil {
t.Fatalf("Unexpected error occurred: %s", err)
}

helper.AssertIssuesWithoutRange(t, tc.Expected, runner.Issues)
})
}
}

0 comments on commit 61f6cc4

Please sign in to comment.