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 CFT test lint subcommand, implement blueprint connection source version lint rule for POC #2631

Merged
merged 27 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9a3304a
Create connection version reference rules
Sep 26, 2024
ce34df9
add cft test lint subcommand
Sep 30, 2024
1282e54
implement metadata lint interfaces
Oct 2, 2024
5176497
Merge branch 'master' into 367401040-lint-ADC
Oct 3, 2024
03b5427
remove global referenced http get function
Oct 4, 2024
60bf72a
created metadata lint interface
Oct 8, 2024
372938b
Merge branch 'master' into 367401040-lint-ADC
Oct 8, 2024
c2f09b1
Add blueprint rules to the metadata lint
Oct 8, 2024
4ae359e
Add yaml file parser to metadata lint runner
Oct 8, 2024
b79b0eb
integrate lint runner with cft test subcommand
Oct 9, 2024
7f7c415
Update the connection version and source static rules
Oct 10, 2024
17933dd
Remove source rule, fix build
Oct 10, 2024
f36691e
Added blueprint connection version rule test
Oct 10, 2024
8ae4309
Added more version lint test cases
Oct 10, 2024
73fb5e5
Add checks to the version lint rule before access object
Oct 10, 2024
c64f486
Merge branch 'master' into 367401040-lint-ADC
Oct 16, 2024
ece7838
Rename to lint stuff, drop metadata in filenames.
Oct 17, 2024
1aa8481
Omit link() function from the lint interface
Oct 17, 2024
34b5806
Update cmd description
Oct 17, 2024
6453a53
Update cmd description
Oct 17, 2024
bafc8d1
Add test case without equal sign
Oct 17, 2024
8f9bcf9
Update blueprint version rule name
Oct 17, 2024
74d665b
Make lint stuffs unexported
Oct 17, 2024
7e27f2d
reuse UnmarshalMetadata in bpmetadata to parse in metadata.yaml
Oct 17, 2024
b11729d
Merge branch 'master' into 367401040-lint-ADC
q2w Oct 18, 2024
ed72e9f
rename rule to more accurate, add const metadataFile
Oct 18, 2024
ee66793
Merge branch 'master' into 367401040-lint-ADC
q2w Oct 21, 2024
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
46 changes: 46 additions & 0 deletions cli/bptest/blueprint_connection_source_version_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package bptest

import (
"fmt"
"github.com/hashicorp/go-version"
)

type BlueprintConnectionSourceVersionRule struct{}

func (r *BlueprintConnectionSourceVersionRule) name() string {
return "blueprint_connection_source_version_rule"
}

func (r *BlueprintConnectionSourceVersionRule) enabled() bool {
return true
}

func (r *BlueprintConnectionSourceVersionRule) check(ctx lintContext) error {
// Check if Spec or Interfaces is nil to avoid null pointer dereference
if ctx.metadata == nil || ctx.metadata.Spec == nil || ctx.metadata.Spec.Interfaces == nil {
fmt.Println("metadata, spec, or interfaces are nil")
return nil
}

for _, variable := range ctx.metadata.Spec.Interfaces.Variables {
if variable == nil {
continue // Skip if variable is nil
}

for _, conn := range variable.Connections {
if conn == nil || conn.Source == nil {
continue // Skip if connection or source is nil
}

if conn.Source.Version != "" {
_, err := version.NewConstraint(conn.Source.Version)
if err != nil {
return fmt.Errorf("invalid version: %w", err)
}
return nil
}
}
}

return nil
}
86 changes: 86 additions & 0 deletions cli/bptest/blueprint_connection_source_version_rule_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package bptest

import (
"testing"

"github.com/GoogleCloudPlatform/cloud-foundation-toolkit/cli/bpmetadata"
"github.com/stretchr/testify/assert"
)

func TestBlueprintConnectionSourceVersionRule(t *testing.T) {
tests := []struct {
name string
version string
expectErr bool
errorMessage string
}{
{
name: "Valid version - no equal sign expect version",
version: "1.2.3",
expectErr: false,
},
{
name: "Valid version - expect version",
version: "=1.2.3",
expectErr: false,
},
{
name: "Valid version - pessimistic constraint",
version: "~> 6.0",
expectErr: false,
},
{
name: "Valid version - minimal version",
version: ">= 0.13.7",
expectErr: false,
},
{
name: "Valid version - range interval",
version: ">= 0.13.7, < 2.0.0",
expectErr: false,
},
{
name: "Invalid version - random string",
version: "invalid_version",
expectErr: true,
errorMessage: "invalid_version",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
metadata := &bpmetadata.BlueprintMetadata{
Spec: &bpmetadata.BlueprintMetadataSpec{
Interfaces: &bpmetadata.BlueprintInterface{
Variables: []*bpmetadata.BlueprintVariable{
{
Connections: []*bpmetadata.BlueprintConnection{
{
Source: &bpmetadata.ConnectionSource{
Source: "example/source",
Version: tt.version,
},
},
},
},
},
},
},
}

ctx := lintContext{
metadata: metadata,
}

rule := &BlueprintConnectionSourceVersionRule{}
err := rule.check(ctx)

if tt.expectErr {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.errorMessage)
} else {
assert.NoError(t, err)
}
})
}
}
12 changes: 12 additions & 0 deletions cli/bptest/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func init() {
Cmd.AddCommand(runCmd)
Cmd.AddCommand(convertCmd)
Cmd.AddCommand(initCmd)
Cmd.AddCommand(lintCmd)

Cmd.PersistentFlags().StringVar(&flags.testDir, "test-dir", "", "Path to directory containing integration tests (default is computed by scanning current working directory)")
runCmd.Flags().StringVar(&flags.testStage, "stage", "", "Test stage to execute (default is running all stages in order - init, plan, apply, verify, teardown)")
Expand Down Expand Up @@ -145,3 +146,14 @@ var initCmd = &cobra.Command{
return initTest(initTestName)
},
}

var lintCmd = &cobra.Command{
Use: "lint",
Short: "Lints blueprint",
Long: "Lints TF blueprint",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
RunLintCommand()
return nil
},
}
47 changes: 47 additions & 0 deletions cli/bptest/lint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package bptest

import (
"fmt"
"github.com/GoogleCloudPlatform/cloud-foundation-toolkit/cli/bpmetadata"
"os"
)

const metadataFile = "metadata.yaml"

// RunLintCommand is the entry function that will run the metadata.yml lint checks.
func RunLintCommand() {
dir, err := os.Getwd()
if err != nil {
fmt.Printf("Error getting current directory: %v\n", err)
os.Exit(1)
}

// Parse medata.yaml to proto
metadata, err := bpmetadata.UnmarshalMetadata(dir, "/"+metadataFile)
metadataFile := dir + "/" + metadataFile

if err != nil {
fmt.Printf("Error parsing metadata file: %v\n", err)
os.Exit(1)
}

ctx := lintContext{
metadata: metadata,
filePath: metadataFile,
}

runner := &lintRunner{}
runner.RegisterRule(&BlueprintConnectionSourceVersionRule{})

// Run lint checks
errs := runner.Run(ctx)
if len(errs) > 0 {
fmt.Println("Linting failed with the following errors:")
for _, err := range errs {
fmt.Println("- ", err)
}
os.Exit(1)
} else {
fmt.Println("All lint checks passed!")
}
}
42 changes: 42 additions & 0 deletions cli/bptest/lint_interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package bptest

import (
"github.com/GoogleCloudPlatform/cloud-foundation-toolkit/cli/bpmetadata"
)

// lintRule defines the common interface for all metadata lint rules.
type lintRule interface {
name() string // Unique name of the rule
enabled() bool // Indicates if the rule is enabled by default
check(lintContext) error // Main entrypoint for rule validation
}

// LintContext holds the metadata and other contextual information for a rule.
type lintContext struct {
metadata *bpmetadata.BlueprintMetadata // Parsed metadata for the blueprint
filePath string // Path of the metadata file being checked
}

// LintRunner is responsible for running all registered lint rules.
type lintRunner struct {
rules []lintRule
}

// RegisterRule adds a new rule to the runner.
func (r *lintRunner) RegisterRule(rule lintRule) {
r.rules = append(r.rules, rule)
}

// Run runs all the registered rules on the provided context.
func (r *lintRunner) Run(ctx lintContext) []error {
var errs []error
for _, rule := range r.rules {
if rule.enabled() {
err := rule.check(ctx)
if err != nil {
errs = append(errs, err)
}
}
}
return errs
}
Loading