github.com/wata727/tflint@v0.12.2-0.20191013070026-96dd0d36f385/tflint/config.go (about) 1 package tflint 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "os" 8 9 "github.com/hashicorp/hcl/v2/gohcl" 10 "github.com/hashicorp/hcl/v2/hclparse" 11 homedir "github.com/mitchellh/go-homedir" 12 "github.com/wata727/tflint/client" 13 ) 14 15 var defaultConfigFile = ".tflint.hcl" 16 var fallbackConfigFile = "~/.tflint.hcl" 17 18 type rawConfig struct { 19 Config *struct { 20 Module *bool `hcl:"module"` 21 DeepCheck *bool `hcl:"deep_check"` 22 Force *bool `hcl:"force"` 23 AwsCredentials *map[string]string `hcl:"aws_credentials"` 24 IgnoreModule *map[string]bool `hcl:"ignore_module"` 25 Varfile *[]string `hcl:"varfile"` 26 Variables *[]string `hcl:"variables"` 27 // Removed options 28 TerraformVersion *string `hcl:"terraform_version"` 29 IgnoreRule *map[string]bool `hcl:"ignore_rule"` 30 } `hcl:"config,block"` 31 Rules []RuleConfig `hcl:"rule,block"` 32 } 33 34 // Config describes the behavior of TFLint 35 type Config struct { 36 Module bool 37 DeepCheck bool 38 Force bool 39 AwsCredentials client.AwsCredentials 40 IgnoreModules map[string]bool 41 Varfiles []string 42 Variables []string 43 Rules map[string]*RuleConfig 44 } 45 46 // RuleConfig is a TFLint's rule config 47 type RuleConfig struct { 48 Name string `hcl:"name,label"` 49 Enabled bool `hcl:"enabled"` 50 } 51 52 // EmptyConfig returns default config 53 // It is mainly used for testing 54 func EmptyConfig() *Config { 55 return &Config{ 56 Module: false, 57 DeepCheck: false, 58 Force: false, 59 AwsCredentials: client.AwsCredentials{}, 60 IgnoreModules: map[string]bool{}, 61 Varfiles: []string{}, 62 Variables: []string{}, 63 Rules: map[string]*RuleConfig{}, 64 } 65 } 66 67 // LoadConfig loads TFLint config from file 68 // If failed to load the default config file, it tries to load config file under the home directory 69 // Therefore, if there is no default config file, it will not return an error 70 func LoadConfig(file string) (*Config, error) { 71 log.Printf("[INFO] Load config: %s", file) 72 if _, err := os.Stat(file); !os.IsNotExist(err) { 73 cfg, err := loadConfigFromFile(file) 74 if err != nil { 75 log.Printf("[ERROR] %s", err) 76 return nil, err 77 } 78 return cfg, nil 79 } else if file != defaultConfigFile { 80 log.Printf("[ERROR] %s", err) 81 return nil, fmt.Errorf("`%s` is not found", file) 82 } else { 83 log.Printf("[INFO] Default config file is not found. Ignored") 84 } 85 86 fallback, err := homedir.Expand(fallbackConfigFile) 87 if err != nil { 88 log.Printf("[ERROR] %s", err) 89 return nil, err 90 } 91 92 log.Printf("[INFO] Load fallback config: %s", fallback) 93 if _, err := os.Stat(fallback); !os.IsNotExist(err) { 94 cfg, err := loadConfigFromFile(fallback) 95 if err != nil { 96 return nil, err 97 } 98 return cfg, nil 99 } 100 log.Printf("[INFO] Fallback config file is not found. Ignored") 101 102 log.Print("[INFO] Use default config") 103 return EmptyConfig(), nil 104 } 105 106 // Merge returns a merged copy of the two configs 107 // Since the argument takes precedence, it can be used as overwriting of the config 108 func (c *Config) Merge(other *Config) *Config { 109 ret := c.copy() 110 111 if other.Module { 112 ret.Module = true 113 } 114 if other.DeepCheck { 115 ret.DeepCheck = true 116 } 117 if other.Force { 118 ret.Force = true 119 } 120 121 ret.AwsCredentials = ret.AwsCredentials.Merge(other.AwsCredentials) 122 ret.IgnoreModules = mergeBoolMap(ret.IgnoreModules, other.IgnoreModules) 123 ret.Varfiles = append(ret.Varfiles, other.Varfiles...) 124 ret.Variables = append(ret.Variables, other.Variables...) 125 126 ret.Rules = mergeRuleMap(ret.Rules, other.Rules) 127 128 return ret 129 } 130 131 func (c *Config) copy() *Config { 132 ignoreModules := make(map[string]bool) 133 for k, v := range c.IgnoreModules { 134 ignoreModules[k] = v 135 } 136 137 varfiles := make([]string, len(c.Varfiles)) 138 copy(varfiles, c.Varfiles) 139 140 variables := make([]string, len(c.Variables)) 141 copy(variables, c.Variables) 142 143 rules := map[string]*RuleConfig{} 144 for k, v := range c.Rules { 145 rules[k] = &RuleConfig{} 146 *rules[k] = *v 147 } 148 149 return &Config{ 150 Module: c.Module, 151 DeepCheck: c.DeepCheck, 152 Force: c.Force, 153 AwsCredentials: c.AwsCredentials, 154 IgnoreModules: ignoreModules, 155 Varfiles: varfiles, 156 Variables: variables, 157 Rules: rules, 158 } 159 } 160 161 func loadConfigFromFile(file string) (*Config, error) { 162 parser := hclparse.NewParser() 163 164 f, diags := parser.ParseHCLFile(file) 165 if diags.HasErrors() { 166 return nil, diags 167 } 168 169 var raw rawConfig 170 diags = gohcl.DecodeBody(f.Body, nil, &raw) 171 if diags.HasErrors() { 172 return nil, diags 173 } 174 175 if raw.Config != nil { 176 if raw.Config.TerraformVersion != nil { 177 return nil, errors.New("`terraform_version` was removed in v0.9.0 because the option is no longer used") 178 } 179 180 if raw.Config.IgnoreRule != nil { 181 return nil, errors.New("`ignore_rule` was removed in v0.12.0. Please define `rule` block with `enabled = false` instead") 182 } 183 } 184 185 cfg := raw.toConfig() 186 log.Printf("[DEBUG] Config loaded") 187 log.Printf("[DEBUG] Module: %t", cfg.Module) 188 log.Printf("[DEBUG] DeepCheck: %t", cfg.DeepCheck) 189 log.Printf("[DEBUG] Force: %t", cfg.Force) 190 log.Printf("[DEBUG] IgnoreModules: %#v", cfg.IgnoreModules) 191 log.Printf("[DEBUG] Varfiles: %#v", cfg.Varfiles) 192 log.Printf("[DEBUG] Variables: %#v", cfg.Variables) 193 log.Printf("[DEBUG] Rules: %#v", cfg.Rules) 194 195 return raw.toConfig(), nil 196 } 197 198 func mergeBoolMap(a, b map[string]bool) map[string]bool { 199 ret := map[string]bool{} 200 for k, v := range a { 201 ret[k] = v 202 } 203 for k, v := range b { 204 ret[k] = v 205 } 206 return ret 207 } 208 209 func mergeRuleMap(a, b map[string]*RuleConfig) map[string]*RuleConfig { 210 ret := map[string]*RuleConfig{} 211 for k, v := range a { 212 ret[k] = v 213 } 214 for k, v := range b { 215 ret[k] = v 216 } 217 return ret 218 } 219 220 func (raw *rawConfig) toConfig() *Config { 221 ret := EmptyConfig() 222 rc := raw.Config 223 224 if rc != nil { 225 if rc.Module != nil { 226 ret.Module = *rc.Module 227 } 228 if rc.DeepCheck != nil { 229 ret.DeepCheck = *rc.DeepCheck 230 } 231 if rc.Force != nil { 232 ret.Force = *rc.Force 233 } 234 if rc.AwsCredentials != nil { 235 credentials := *rc.AwsCredentials 236 ret.AwsCredentials.AccessKey = credentials["access_key"] 237 ret.AwsCredentials.SecretKey = credentials["secret_key"] 238 ret.AwsCredentials.Profile = credentials["profile"] 239 ret.AwsCredentials.CredsFile = credentials["shared_credentials_file"] 240 ret.AwsCredentials.Region = credentials["region"] 241 } 242 if rc.IgnoreModule != nil { 243 ret.IgnoreModules = *rc.IgnoreModule 244 } 245 if rc.Varfile != nil { 246 ret.Varfiles = *rc.Varfile 247 } 248 if rc.Variables != nil { 249 ret.Variables = *rc.Variables 250 } 251 } 252 253 for _, r := range raw.Rules { 254 var rule = r 255 ret.Rules[rule.Name] = &rule 256 } 257 258 return ret 259 }