Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add location interface #18

Merged
merged 5 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
})
}
}