Skip to content

Commit 858144f

Browse files
committed
Enable deep checking
1 parent c3bb8c9 commit 858144f

File tree

7 files changed

+316
-11
lines changed

7 files changed

+316
-11
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ require (
77
github.com/hashicorp/hcl/v2 v2.9.0
88
github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.4
99
github.com/terraform-linters/tflint-plugin-sdk v0.8.1
10+
google.golang.org/api v0.40.0
1011
)

go.sum

Lines changed: 75 additions & 11 deletions
Large diffs are not rendered by default.

google/client.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package google
2+
3+
import (
4+
"context"
5+
6+
"google.golang.org/api/serviceusage/v1"
7+
)
8+
9+
// Client is a wrapper of the Google API client
10+
type Client struct {
11+
ServiceUsage *serviceusage.Service
12+
}
13+
14+
// NewClient returns a new Client
15+
func NewClient() (*Client, error) {
16+
serviceusageClient, err := serviceusage.NewService(context.TODO())
17+
if err != nil {
18+
return nil, err
19+
}
20+
21+
return &Client{
22+
ServiceUsage: serviceusageClient,
23+
}, nil
24+
}

google/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package google
2+
3+
import "github.com/hashicorp/hcl/v2"
4+
5+
// Config is the configuration for the ruleset.
6+
type Config struct {
7+
DeepCheck bool `hcl:"deep_check,optional"`
8+
9+
Remain hcl.Body `hcl:",remain"`
10+
}

google/provider.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package google
2+
3+
import (
4+
"os"
5+
6+
"github.com/hashicorp/hcl/v2"
7+
"github.com/terraform-linters/tflint-plugin-sdk/terraform/configs"
8+
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
9+
)
10+
11+
// GoogleProviderBlockSchema is a schema of `google` provider block
12+
var GoogleProviderBlockSchema = &hcl.BodySchema{
13+
Attributes: []hcl.AttributeSchema{
14+
{Name: "project"},
15+
},
16+
}
17+
18+
// ProviderData represents a provider block with an eval context (runner)
19+
type ProviderData struct {
20+
provider *configs.Provider
21+
runner tflint.Runner
22+
attributes hcl.Attributes
23+
blocks hcl.Blocks
24+
}
25+
26+
// GetProject retrieves project_id from the "provider" block in the Terraform configuration and environment variables
27+
func GetProject(runner tflint.Runner) (string, error) {
28+
provider, err := runner.RootProvider("google")
29+
if err != nil || provider == nil {
30+
return "", err
31+
}
32+
33+
d, err := newProviderData(provider, runner)
34+
if err != nil {
35+
return "", err
36+
}
37+
38+
project, exists, err := d.Get("project")
39+
if exists {
40+
return project, err
41+
}
42+
43+
envs := []string{"GOOGLE_PROJECT", "GOOGLE_CLOUD_PROJECT", "GCLOUD_PROJECT", "CLOUDSDK_CORE_PROJECT"}
44+
for _, env := range envs {
45+
if project := os.Getenv(env); project != "" {
46+
return project, nil
47+
}
48+
}
49+
50+
return "", nil
51+
}
52+
53+
func newProviderData(provider *configs.Provider, runner tflint.Runner) (*ProviderData, error) {
54+
providerData := &ProviderData{
55+
provider: provider,
56+
runner: runner,
57+
attributes: map[string]*hcl.Attribute{},
58+
blocks: []*hcl.Block{},
59+
}
60+
61+
if provider != nil {
62+
content, _, diags := provider.Config.PartialContent(GoogleProviderBlockSchema)
63+
if diags.HasErrors() {
64+
return nil, diags
65+
}
66+
67+
providerData.attributes = content.Attributes
68+
providerData.blocks = content.Blocks
69+
}
70+
71+
return providerData, nil
72+
}
73+
74+
// Get returns a value corresponding to the given key
75+
// It should be noted that the value is evaluated if it is evaluable
76+
// The second return value is a flag that determines whether a value exists
77+
// We assume the provider has only simple attributes, so it just returns string
78+
func (d *ProviderData) Get(key string) (string, bool, error) {
79+
attribute, exists := d.attributes[key]
80+
if !exists {
81+
return "", false, nil
82+
}
83+
84+
var val string
85+
err := d.runner.EvaluateExprOnRootCtx(attribute.Expr, &val, nil)
86+
87+
err = d.runner.EnsureNoError(err, func() error { return nil })
88+
if err != nil {
89+
return "", true, err
90+
}
91+
return val, true, nil
92+
}

google/ruleset.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package google
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/hashicorp/hcl/v2/gohcl"
7+
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
8+
)
9+
10+
// RuleSet is the custom ruleset for the Google provider plugin.
11+
type RuleSet struct {
12+
tflint.BuiltinRuleSet
13+
APIRules []tflint.Rule
14+
config *Config
15+
}
16+
17+
// RuleNames is a list of rule names provided by the plugin.
18+
func (r *RuleSet) RuleNames() []string {
19+
names := []string{}
20+
for _, rule := range r.Rules {
21+
names = append(names, rule.Name())
22+
}
23+
for _, rule := range r.APIRules {
24+
names = append(names, rule.Name())
25+
}
26+
return names
27+
}
28+
29+
// ApplyConfig reflects the plugin configuration to the ruleset.
30+
func (r *RuleSet) ApplyConfig(config *tflint.Config) error {
31+
r.ApplyCommonConfig(config)
32+
33+
// Apply "plugin" block config
34+
cfg := Config{}
35+
diags := gohcl.DecodeBody(config.Body, nil, &cfg)
36+
if diags.HasErrors() {
37+
return diags
38+
}
39+
r.config = &cfg
40+
41+
// Apply config for API rules
42+
for _, rule := range r.APIRules {
43+
enabled := rule.Enabled()
44+
if cfg := config.Rules[rule.Name()]; cfg != nil {
45+
enabled = cfg.Enabled
46+
} else if config.DisabledByDefault {
47+
enabled = false
48+
}
49+
50+
if cfg.DeepCheck && enabled {
51+
r.EnabledRules = append(r.EnabledRules, rule)
52+
}
53+
}
54+
55+
return nil
56+
}
57+
58+
// Check runs inspections for each rule with the custom Google runner.
59+
func (r *RuleSet) Check(rr tflint.Runner) error {
60+
runner, err := NewRunner(rr, r.config)
61+
if err != nil {
62+
return err
63+
}
64+
65+
for _, rule := range r.EnabledRules {
66+
if err := rule.Check(runner); err != nil {
67+
return fmt.Errorf("Failed to check `%s` rule: %s", rule.Name(), err)
68+
}
69+
}
70+
return nil
71+
}

google/runner.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package google
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
7+
)
8+
9+
// Runner is a wrapper of RPC client for inserting custom actions for Google provider.
10+
type Runner struct {
11+
tflint.Runner
12+
Client *Client
13+
Project string
14+
}
15+
16+
// NewRunner returns a custom Google runner.
17+
func NewRunner(runner tflint.Runner, config *Config) (*Runner, error) {
18+
var client *Client
19+
var project string
20+
var err error
21+
if config.DeepCheck {
22+
client, err = NewClient()
23+
if err != nil {
24+
return nil, err
25+
}
26+
27+
project, err = GetProject(runner)
28+
if err != nil {
29+
return nil, err
30+
}
31+
}
32+
33+
return &Runner{
34+
Runner: runner,
35+
Client: client,
36+
Project: project,
37+
}, nil
38+
}
39+
40+
// ParentProject returns a part of the API path about the parent project
41+
func (r *Runner) ParentProject() string {
42+
return fmt.Sprintf("projects/%s", r.Project)
43+
}

0 commit comments

Comments
 (0)