github.com/jpreese/tflint@v0.19.2-0.20200908152133-b01686250fb6/rules/awsrules/models/generator/main.go (about)

     1  // +build generators
     2  
     3  package main
     4  
     5  import (
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"path/filepath"
    10  	"sort"
    11  
    12  	hcl "github.com/hashicorp/hcl/v2"
    13  	"github.com/hashicorp/hcl/v2/gohcl"
    14  	"github.com/hashicorp/hcl/v2/hclparse"
    15  	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    16  	"github.com/terraform-providers/terraform-provider-aws/aws"
    17  )
    18  
    19  type mappingFile struct {
    20  	Import   string    `hcl:"import"`
    21  	Mappings []mapping `hcl:"mapping,block"`
    22  	Tests    []test    `hcl:"test,block"`
    23  }
    24  
    25  type mapping struct {
    26  	Resource string         `hcl:"resource,label"`
    27  	Attrs    hcl.Attributes `hcl:",remain"`
    28  }
    29  
    30  type test struct {
    31  	Resource  string `hcl:"resource,label"`
    32  	Attribute string `hcl:"attribute,label"`
    33  	OK        string `hcl:"ok"`
    34  	NG        string `hcl:"ng"`
    35  }
    36  
    37  func main() {
    38  	files, err := filepath.Glob("./mappings/*.hcl")
    39  	if err != nil {
    40  		panic(err)
    41  	}
    42  
    43  	mappingFiles := []mappingFile{}
    44  	for _, file := range files {
    45  		parser := hclparse.NewParser()
    46  		f, diags := parser.ParseHCLFile(file)
    47  		if diags.HasErrors() {
    48  			panic(diags)
    49  		}
    50  
    51  		var mf mappingFile
    52  		diags = gohcl.DecodeBody(f.Body, nil, &mf)
    53  		if diags.HasErrors() {
    54  			panic(diags)
    55  		}
    56  		mappingFiles = append(mappingFiles, mf)
    57  	}
    58  
    59  	awsProvider := aws.Provider()
    60  
    61  	generatedRules := []string{}
    62  	for _, mappingFile := range mappingFiles {
    63  		raw, err := ioutil.ReadFile(mappingFile.Import)
    64  		if err != nil {
    65  			panic(err)
    66  		}
    67  
    68  		var api map[string]interface{}
    69  		err = json.Unmarshal(raw, &api)
    70  		if err != nil {
    71  			panic(err)
    72  		}
    73  		shapes := api["shapes"].(map[string]interface{})
    74  
    75  		for _, mapping := range mappingFile.Mappings {
    76  			for attribute, value := range mapping.Attrs {
    77  				shapeName := value.Expr.Variables()[0].RootName()
    78  				if shapeName == "any" {
    79  					continue
    80  				}
    81  				model := shapes[shapeName].(map[string]interface{})
    82  				schema := fetchSchema(mapping.Resource, attribute, model, awsProvider)
    83  				if validMapping(model) {
    84  					fmt.Printf("Generating rule for `%s.%s`\n", mapping.Resource, attribute)
    85  					generateRuleFile(mapping.Resource, attribute, model, schema)
    86  					for _, test := range mappingFile.Tests {
    87  						if mapping.Resource == test.Resource && attribute == test.Attribute {
    88  							generateRuleTestFile(mapping.Resource, attribute, model, test)
    89  						}
    90  					}
    91  					generatedRules = append(generatedRules, makeRuleName(mapping.Resource, attribute))
    92  				}
    93  			}
    94  		}
    95  	}
    96  
    97  	sort.Strings(generatedRules)
    98  	generateProviderFile(generatedRules)
    99  }
   100  
   101  func fetchSchema(resource, attribute string, model map[string]interface{}, provider *schema.Provider) *schema.Schema {
   102  	resourceSchema, ok := provider.ResourcesMap[resource]
   103  	if !ok {
   104  		panic(fmt.Sprintf("resource `%s` not found in the Terraform schema", resource))
   105  	}
   106  	attrSchema, ok := resourceSchema.Schema[attribute]
   107  	if !ok {
   108  		panic(fmt.Sprintf("`%s.%s` not found in the Terraform schema", resource, attribute))
   109  	}
   110  
   111  	switch model["type"].(string) {
   112  	case "string":
   113  		if attrSchema.Type != schema.TypeString {
   114  			panic(fmt.Sprintf("`%s.%s` is expected as string, but not", resource, attribute))
   115  		}
   116  	default:
   117  		// noop
   118  	}
   119  
   120  	return attrSchema
   121  }
   122  
   123  func validMapping(model map[string]interface{}) bool {
   124  	switch model["type"].(string) {
   125  	case "string":
   126  		if _, ok := model["max"]; ok {
   127  			return true
   128  		}
   129  		if min, ok := model["min"]; ok && int(min.(float64)) > 2 {
   130  			return true
   131  		}
   132  		if _, ok := model["pattern"]; ok {
   133  			return true
   134  		}
   135  		if _, ok := model["enum"]; ok {
   136  			return true
   137  		}
   138  		return false
   139  	default:
   140  		// Unsupported types
   141  		return false
   142  	}
   143  }