terraform-linters / tflint

A Pluggable Terraform Linter
Mozilla Public License 2.0
4.96k stars 357 forks source link

Config() rpc call failing when terraform variable has default value but no type defined #1300

Closed Vince-Chenal closed 2 years ago

Vince-Chenal commented 2 years ago

I was writing a custom tflint ruleset and found this problem.

My usecase is to be able to verify that any aws provider has default_tags block defined.

I can use RootProvider() function to get a provider by name like this:

runner.RootProvider("aws")
runner.RootProvider("aws.<some_alias>")

But I don't know the aliases in advance, so I need a way to list providers first.

I found this issue in which I saw that there was no official way for listing providers and that the only way was calling the Config() function and then search for .Module.ProviderConfigs inside.

Trying to do so, I got a lot of errors like this one:

$ tflint --only=myrule
Failed to check ruleset. An error occurred:

Error: Failed to check `myrule` rule: variables.tf:1,1-16: cannot unmarshal variable default value; msgpack: invalid code=a6 decoding array length

After investigations I found that the problem was that Config() function is unable to encode variables that have a default value but no type defined.

I think having a RootProviders() function listing all providers as suggested here would be the best option.

In the meantime I'll stick with Config() function and catch the error.

Steps to reproduce

tflint custom rule

...
func (r *MyCustomRule) Name() string {
    return "myrule"
}
...
func (r *MyCustomRule) Check(runner tflint.Runner) error {
    conf, err := runner.Config()
    if err != nil {
        return err
    }
    _ = conf

    return nil
}
...

Terraform

Working:

variable "test" {
    default = 123
    type    = number
}
$ tflint --only=myrule

Not Working:

variable "test" {
    default = 123
}
$ tflint --only=myrule
Failed to check ruleset. An error occurred:

Error: Failed to check `myrule` rule: variables.tf:1,1-16: cannot unmarshal variable default value; msgpack: invalid code=a6 decoding array length

Version

$ tflint -v
TFLint version 0.34.1
+ ruleset.aws (0.11.0)
+ ruleset.myrule (0.0.1)

$ terraform -v
Terraform v1.1.4
wata727 commented 2 years ago

Hi @Vince-Chenal, thank you for opening this issue.

I'm currently working on a major update to the plugin API, and perhaps the new API will be able to meet this requirement. For example, you can get a list of providers in the root module like this:

providers, _ := runner.GetModuleContent(&hclext.BodySchema{
    Blocks: []hclext.BlockSchema{
        {
            Type: "provider",
            LabelNames: []string{"name"},
            Body: &hclext.BodySchema{
                Attributes: []hclext.AttributeSchema{{Name: "default_tags"}},
            },
        },
    },
}, nil)

for _, provider := range providers.Blocks {
    provider.Body.Attributes["default_tags"].Expr // => An expression of `default_tags`
}

Please see here for details. You can try out the new API by building these.

If you're in a hurry, it's a bug that causes an error in the current Config(), and we can fix it. I'm happy to review it.

Vince-Chenal commented 2 years ago

Hey @wata727, Thanks for your answer. I did not have the time to dig into the actual issue in Config() yet so I don't know if it's easily fixable or not. Good to hear that the tool is alive and evolving :+1:

Vince-Chenal commented 2 years ago

Hey @wata727,

I've been trying to adapt my custom plugin to the new sdk version with success so far. But I just found something which I wanted to ask you about.

At some point I need to retrieve all the aws providers so I wrote the following code as you suggested:

providers, err := runner.GetModuleContent(&hclext.BodySchema{
    Blocks: []hclext.BlockSchema{
        {
            Type:       "provider",
            LabelNames: []string{"name"},
            Body: &hclext.BodySchema{
                Attributes: []hclext.AttributeSchema{{Name: "default_tags"}},
            },
        },
    },
}, nil)
if err != nil {
    return err
}

I thought the schema would act as some kind of filter (retrieving only the providers that match this schema) but in the result I get all the providers and not only the ones that match the schema. It's not a problem since I can re-filter everything myself but I was wondering if I was doing something weird.

Am I missing something?

wata727 commented 2 years ago

This is intended behavior. Top-level schemas only match provider blocks, but nested schemas are always optional. So, it also matches provider blocks that do not have default_tags. However, these blocks do not have any attributes. This is so that you can get a provider that does not have a specific attribute.

The original issue has been fixed in v0.35, so close this issue. If you have any other questions about the SDK's usage, open a new issue in https://github.com/terraform-linters/tflint-plugin-sdk.