github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/tflint/interface.go (about)

     1  package tflint
     2  
     3  import (
     4  	"github.com/hashicorp/hcl/v2"
     5  	"github.com/terraform-linters/tflint-plugin-sdk/hclext"
     6  	"github.com/terraform-linters/tflint-plugin-sdk/terraform/addrs"
     7  	"github.com/zclconf/go-cty/cty"
     8  )
     9  
    10  // RuleSet is a list of rules that a plugin should provide.
    11  // Normally, plugins can use BuiltinRuleSet directly,
    12  // but you can also use custom rulesets that satisfy this interface.
    13  // The actual implementation can be found in plugin/host2plugin.GRPCServer.
    14  type RuleSet interface {
    15  	// RuleSetName is the name of the ruleset. This method is not expected to be overridden.
    16  	RuleSetName() string
    17  
    18  	// RuleSetVersion is the version of the plugin. This method is not expected to be overridden.
    19  	RuleSetVersion() string
    20  
    21  	// RuleNames is a list of rule names provided by the plugin. This method is not expected to be overridden.
    22  	RuleNames() []string
    23  
    24  	// VersionConstraint declares the version of TFLint the plugin will work with. Default is no constraint.
    25  	VersionConstraint() string
    26  
    27  	// ConfigSchema returns the ruleset plugin config schema.
    28  	// If you return a schema, TFLint will extract the config from .tflint.hcl based on that schema
    29  	// and pass it to ApplyConfig. This schema should be a schema inside of "plugin" block.
    30  	// If you don't need a config that controls the entire plugin, you don't need to override this method.
    31  	//
    32  	// It is recommended to use hclext.ImpliedBodySchema to generate the schema from the structure:
    33  	//
    34  	// ```
    35  	// type myPluginConfig struct {
    36  	//   Style       string `hclext:"style"`
    37  	//   Description string `hclext:"description,optional"`
    38  	//   Detail      Detail `hclext:"detail,block"`
    39  	// }
    40  	//
    41  	// config := &myPluginConfig{}
    42  	// hclext.ImpliedBodySchema(config)
    43  	// ```
    44  	ConfigSchema() *hclext.BodySchema
    45  
    46  	// ApplyGlobalConfig applies the common config to the ruleset.
    47  	// This is not supposed to be overridden from custom rulesets.
    48  	// Override the ApplyConfig if you want to apply the plugin's custom configuration.
    49  	ApplyGlobalConfig(*Config) error
    50  
    51  	// ApplyConfig applies the configuration to the ruleset.
    52  	// Custom rulesets can override this method to reflect the plugin's custom configuration.
    53  	//
    54  	// You can reflect the body in the structure by using hclext.DecodeBody:
    55  	//
    56  	// ```
    57  	// type myPluginConfig struct {
    58  	//   Style       string `hclext:"style"`
    59  	//   Description string `hclext:"description,optional"`
    60  	//   Detail      Detail `hclext:"detail,block"`
    61  	// }
    62  	//
    63  	// config := &myPluginConfig{}
    64  	// hclext.DecodeBody(body, nil, config)
    65  	// ```
    66  	ApplyConfig(*hclext.BodyContent) error
    67  
    68  	// NewRunner returns a new runner based on the original runner.
    69  	// Custom rulesets can override this method to inject a custom runner.
    70  	NewRunner(Runner) (Runner, error)
    71  
    72  	// BuiltinImpl returns the receiver itself as BuiltinRuleSet.
    73  	// This is not supposed to be overridden from custom rulesets.
    74  	BuiltinImpl() *BuiltinRuleSet
    75  
    76  	// All Ruleset must embed the builtin ruleset.
    77  	mustEmbedBuiltinRuleSet()
    78  }
    79  
    80  // Runner acts as a client for each plugin to query the host process about the Terraform configurations.
    81  // The actual implementation can be found in plugin/plugin2host.GRPCClient.
    82  type Runner interface {
    83  	// GetOriginalwd returns the original working directory.
    84  	// Normally this is equal to os.Getwd(), but differs if --chdir or --recursive is used.
    85  	// If you need the absolute path of the file, joining with the original working directory is appropriate.
    86  	GetOriginalwd() (string, error)
    87  
    88  	// GetModulePath returns the current module path address.
    89  	GetModulePath() (addrs.Module, error)
    90  
    91  	// GetResourceContent retrieves the content of resources based on the passed schema.
    92  	// The schema allows you to specify attributes and blocks that describe the structure needed for the inspection:
    93  	//
    94  	// ```
    95  	// runner.GetResourceContent("aws_instance", &hclext.BodySchema{
    96  	//   Attributes: []hclext.AttributeSchema{{Name: "instance_type"}},
    97  	//   Blocks: []hclext.BlockSchema{
    98  	//     {
    99  	//       Type: "ebs_block_device",
   100  	//       Body: &hclext.BodySchema{Attributes: []hclext.AttributeSchema{{Name: "volume_size"}}},
   101  	//     },
   102  	//   },
   103  	// }, nil)
   104  	// ```
   105  	GetResourceContent(resourceName string, schema *hclext.BodySchema, option *GetModuleContentOption) (*hclext.BodyContent, error)
   106  
   107  	// GetProviderContent retrieves the content of providers based on the passed schema.
   108  	// This method is GetResourceContent for providers.
   109  	GetProviderContent(providerName string, schema *hclext.BodySchema, option *GetModuleContentOption) (*hclext.BodyContent, error)
   110  
   111  	// GetModuleContent retrieves the content of the module based on the passed schema.
   112  	// GetResourceContent/GetProviderContent are syntactic sugar for GetModuleContent, which you can use to access other structures.
   113  	GetModuleContent(schema *hclext.BodySchema, option *GetModuleContentOption) (*hclext.BodyContent, error)
   114  
   115  	// GetFile returns the hcl.File object.
   116  	// This is low level API for accessing information such as comments and syntax.
   117  	// When accessing resources, expressions, etc, it is recommended to use high-level APIs.
   118  	GetFile(filename string) (*hcl.File, error)
   119  
   120  	// GetFiles returns a map[string]hcl.File object, where the key is the file name.
   121  	// This is low level API for accessing information such as comments and syntax.
   122  	GetFiles() (map[string]*hcl.File, error)
   123  
   124  	// WalkExpressions traverses expressions in all files by the passed walker.
   125  	// The walker can be passed any structure that satisfies the `tflint.ExprWalker`
   126  	// interface, or a `tflint.ExprWalkFunc`. Example of passing function:
   127  	//
   128  	// ```
   129  	// runner.WalkExpressions(tflint.ExprWalkFunc(func (expr hcl.Expression) hcl.Diagnostics {
   130  	//   // Write code here
   131  	// }))
   132  	// ```
   133  	//
   134  	// If you pass ExprWalkFunc, the function will be called for every expression.
   135  	// Note that it behaves differently in native HCL syntax and JSON syntax.
   136  	//
   137  	// In the HCL syntax, `var.foo` and `var.bar` in `[var.foo, var.bar]` are
   138  	// also passed to the walker. In other words, it traverses expressions recursively.
   139  	// To avoid redundant checks, the walker should check the kind of expression.
   140  	//
   141  	// In the JSON syntax, only an expression of an attribute seen from the top
   142  	// level of the file is passed. In other words, it doesn't traverse expressions
   143  	// recursively. This is a limitation of JSON syntax.
   144  	WalkExpressions(walker ExprWalker) hcl.Diagnostics
   145  
   146  	// DecodeRuleConfig fetches the rule's configuration and reflects the result in the 2nd argument.
   147  	// The argument is expected to be a pointer to a structure tagged with hclext:
   148  	//
   149  	// ```
   150  	// type myRuleConfig struct {
   151  	//   Style       string `hclext:"style"`
   152  	//   Description string `hclext:"description,optional"`
   153  	//   Detail      Detail `hclext:"detail,block"`
   154  	// }
   155  	//
   156  	// config := &myRuleConfig{}
   157  	// runner.DecodeRuleConfig("my_rule", config)
   158  	// ```
   159  	//
   160  	// See the hclext.DecodeBody documentation and examples for more details.
   161  	DecodeRuleConfig(ruleName string, ret interface{}) error
   162  
   163  	// EvaluateExpr evaluates an expression and assigns its value to a Go value target,
   164  	// which must be a pointer or a function. Any other type of target will trigger a panic.
   165  	//
   166  	// For pointers, if the expression value cannot be assigned to the target, an error is returned.
   167  	// Some examples of this include unknown values (like variables without defaults or
   168  	// aws_instance.foo.arn), null values, and sensitive values (for variables with sensitive = true).
   169  	//
   170  	// These errors be handled with errors.Is():
   171  	//
   172  	// ```
   173  	// var val string
   174  	// err := runner.EvaluateExpr(expr, &val, nil)
   175  	// if err != nil {
   176  	//   if errors.Is(err, tflint.ErrUnknownValue) {
   177  	//     // Ignore unknown values
   178  	//	   return nil
   179  	//   }
   180  	//   if errors.Is(err, tflint.ErrNullValue) {
   181  	//     // Ignore null values because null means that the value is not set
   182  	//	   return nil
   183  	//   }
   184  	//   if errors.Is(err, tflint.ErrSensitive) {
   185  	//     // Ignore sensitive values
   186  	//     return nil
   187  	//   }
   188  	//   return err
   189  	// }
   190  	// ```
   191  	//
   192  	// However, if the target is cty.Value, these errors will not be returned.
   193  	//
   194  	// Here are the types that can be passed as the target: string, int, bool, []string,
   195  	// []int, []bool, map[string]string, map[string]int, map[string]bool, and cty.Value.
   196  	// Passing any other type will result in a panic, but you can make an exception by
   197  	// passing wantType as an option.
   198  	//
   199  	// ```
   200  	// type complexVal struct {
   201  	//   Key     string `cty:"key"`
   202  	//   Enabled bool   `cty:"enabled"`
   203  	// }
   204  	//
   205  	// wantType := cty.List(cty.Object(map[string]cty.Type{
   206  	//   "key":     cty.String,
   207  	//   "enabled": cty.Bool,
   208  	// }))
   209  	//
   210  	// var complexVals []complexVal
   211  	// runner.EvaluateExpr(expr, &compleVals, &tflint.EvaluateExprOption{WantType: &wantType})
   212  	// ```
   213  	//
   214  	// For functions (callbacks), the assigned value is used as an argument to execute
   215  	// the function. If a value cannot be assigned to the argument type, the execution
   216  	// is skipped instead of returning an error. This is useful when it's always acceptable
   217  	// to ignore exceptional values.
   218  	//
   219  	// Here's an example of how you can pass a function to EvaluateExpr:
   220  	//
   221  	// ```
   222  	// runner.EvaluateExpr(expr, func (val string) error {
   223  	//   // Test value
   224  	// }, nil)
   225  	// ```
   226  	EvaluateExpr(expr hcl.Expression, target interface{}, option *EvaluateExprOption) error
   227  
   228  	// EmitIssue sends an issue to TFLint. You need to pass the message of the issue and the range.
   229  	EmitIssue(rule Rule, message string, issueRange hcl.Range) error
   230  
   231  	// EmitIssueWithFix is similar to EmitIssue, but it also supports autofix.
   232  	// If you pass a function that rewrites the source code to the last argument,
   233  	// TFLint will apply the fix when the --fix option is specified.
   234  	//
   235  	// The function is passed a tflint.Fixer that can be used to rewrite the source code.
   236  	// See the tflint.Fixer interface for more details.
   237  	//
   238  	// Issues emitted using this function are automatically marked as fixable.
   239  	// However, if you don't want to support autofix only under certain conditions (e.g. JSON syntax),
   240  	// you can return tflint.ErrFixNotSupported from the fix function.
   241  	// In this case, the issue will not be marked as fixable and the fix will not be applied.
   242  	//
   243  	// As a best practice for autofix, we recommend minimizing the amount of code changed at once.
   244  	// If fixes for the same range conflict within the same rule, Fixer will return an error.
   245  	EmitIssueWithFix(rule Rule, message string, issueRange hcl.Range, fixFunc func(f Fixer) error) error
   246  
   247  	// EnsureNoError is a helper for error handling. Depending on the type of error generated by EvaluateExpr,
   248  	// determine whether to exit, skip, or continue. If it is continued, the passed function will be executed.
   249  	//
   250  	// Deprecated: Use EvaluateExpr with a function callback. e.g. EvaluateExpr(expr, func (val T) error {}, ...)
   251  	EnsureNoError(error, func() error) error
   252  }
   253  
   254  // Fixer is a tool to rewrite HCL source code.
   255  // The actual implementation is in the internal.Fixer.
   256  type Fixer interface {
   257  	// ReplaceText rewrites the given range of source code to a new text.
   258  	// If the range is overlapped with a previous rewrite range, it returns an error.
   259  	//
   260  	// Either string or tflint.TextNode is valid as an argument.
   261  	// TextNode can be obtained with fixer.TextAt(range).
   262  	// If the argument is a TextNode, and the range is contained in the replacement range,
   263  	// this function automatically minimizes the replacement range as much as possible.
   264  	//
   265  	// For example, if the source code is "(foo)", ReplaceText(range, "[foo]")
   266  	// rewrites the whole "(foo)". But ReplaceText(range, "[", TextAt(fooRange), "]")
   267  	// rewrites only "(" and ")". This is useful to avoid unintended conflicts.
   268  	ReplaceText(hcl.Range, ...any) error
   269  
   270  	// InsertTextBefore inserts the given text before the given range.
   271  	InsertTextBefore(hcl.Range, string) error
   272  
   273  	// InsertTextAfter inserts the given text after the given range.
   274  	InsertTextAfter(hcl.Range, string) error
   275  
   276  	// Remove removes the given range of source code.
   277  	Remove(hcl.Range) error
   278  
   279  	// RemoveAttribute removes the given attribute from the source code.
   280  	// The difference from Remove is that it removes the attribute
   281  	// and the associated newlines, indentations, and comments.
   282  	// This only works for HCL native syntax. JSON syntax is not supported
   283  	// and returns tflint.ErrFixNotSupported.
   284  	RemoveAttribute(*hcl.Attribute) error
   285  
   286  	// RemoveBlock removes the given block from the source code.
   287  	// The difference from Remove is that it removes the block
   288  	// and the associated newlines, indentations, and comments.
   289  	// This only works for HCL native syntax. JSON syntax is not supported
   290  	// and returns tflint.ErrFixNotSupported.
   291  	RemoveBlock(*hcl.Block) error
   292  
   293  	// RemoveExtBlock removes the given block from the source code.
   294  	// This is similar to RemoveBlock, but it works for hclext.Block.
   295  	RemoveExtBlock(*hclext.Block) error
   296  
   297  	// TextAt returns a text node at the given range.
   298  	// This is expected to be passed as an argument to ReplaceText.
   299  	// Note this doesn't take into account the changes made by the fixer in a rule.
   300  	TextAt(hcl.Range) TextNode
   301  
   302  	// ValueText returns a text representation of the given cty.Value.
   303  	// Values are always converted to a single line. For more pretty-printing,
   304  	// implement your own conversion function.
   305  	//
   306  	// This function is inspired by hclwrite.TokensForValue.
   307  	// https://github.com/hashicorp/hcl/blob/v2.16.2/hclwrite/generate.go#L26
   308  	ValueText(cty.Value) string
   309  
   310  	// RangeTo returns a range from the given start position to the given text.
   311  	// Note that it doesn't check if the text is actually in the range.
   312  	RangeTo(to string, filename string, start hcl.Pos) hcl.Range
   313  }
   314  
   315  // Rule is the interface that the plugin's rules should satisfy.
   316  type Rule interface {
   317  	// Name will be displayed with a message of an issue and will be the identifier used to control
   318  	// the behavior of this rule in the configuration file etc.
   319  	// Therefore, it is expected that this will not duplicate the rule names provided by other plugins.
   320  	Name() string
   321  
   322  	// Enabled indicates whether the rule is enabled by default.
   323  	Enabled() bool
   324  
   325  	// Severity indicates the severity of the rule.
   326  	Severity() Severity
   327  
   328  	// Link allows you to add a reference link to the rule.
   329  	Link() string
   330  
   331  	// Metadata allows you to set any metadata to the rule.
   332  	// This value is never referenced by the SDK and can be used for your custom ruleset.
   333  	Metadata() interface{}
   334  
   335  	// Check is the entrypoint of the rule. You can fetch Terraform configurations and send issues via Runner.
   336  	Check(Runner) error
   337  
   338  	// All rules must embed the default rule.
   339  	mustEmbedDefaultRule()
   340  }