github.com/wata727/tflint@v0.12.2-0.20191013070026-96dd0d36f385/tflint/loader.go (about)

     1  package tflint
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"strings"
    12  
    13  	version "github.com/hashicorp/go-version"
    14  	hcl "github.com/hashicorp/hcl/v2"
    15  	"github.com/hashicorp/hcl/v2/hclsyntax"
    16  	"github.com/hashicorp/terraform/configs"
    17  	"github.com/hashicorp/terraform/terraform"
    18  	"github.com/spf13/afero"
    19  )
    20  
    21  //go:generate mockgen -source loader.go -destination loader_mock.go -package tflint -self_package github.com/wata727/tflint/tflint
    22  
    23  // AbstractLoader is a loader interface for mock
    24  type AbstractLoader interface {
    25  	LoadConfig(string) (*configs.Config, error)
    26  	LoadAnnotations(string) (map[string]Annotations, error)
    27  	LoadValuesFiles(...string) ([]terraform.InputValues, error)
    28  	Sources() map[string][]byte
    29  }
    30  
    31  // Loader is a wrapper of Terraform's configload.Loader
    32  type Loader struct {
    33  	parser               *configs.Parser
    34  	fs                   afero.Afero
    35  	currentDir           string
    36  	config               *Config
    37  	moduleSourceVersions map[string][]*version.Version
    38  	moduleManifest       map[string]*moduleManifest
    39  }
    40  
    41  type moduleManifest struct {
    42  	Key        string           `json:"Key"`
    43  	Source     string           `json:"Source"`
    44  	Version    *version.Version `json:"-"`
    45  	VersionStr string           `json:"Version,omitempty"`
    46  	Dir        string           `json:"Dir"`
    47  	Root       string           `json:"Root"`
    48  }
    49  
    50  type moduleManifestFile struct {
    51  	Modules []*moduleManifest `json:"Modules"`
    52  }
    53  
    54  // NewLoader returns a loader with module manifests
    55  func NewLoader(fs afero.Afero, cfg *Config) (*Loader, error) {
    56  	log.Print("[INFO] Initialize new loader")
    57  
    58  	l := &Loader{
    59  		parser:               configs.NewParser(fs),
    60  		fs:                   fs,
    61  		config:               cfg,
    62  		moduleSourceVersions: map[string][]*version.Version{},
    63  		moduleManifest:       map[string]*moduleManifest{},
    64  	}
    65  
    66  	if _, err := os.Stat(getTFModuleManifestPath()); !os.IsNotExist(err) {
    67  		log.Print("[INFO] Module manifest file found. Initializing...")
    68  		if err := l.initializeModuleManifest(); err != nil {
    69  			log.Printf("[ERROR] %s", err)
    70  			return nil, err
    71  		}
    72  	}
    73  
    74  	return l, nil
    75  }
    76  
    77  // LoadConfig loads Terraform's configurations
    78  // TODO: Can we use configload.LoadConfig instead?
    79  func (l *Loader) LoadConfig(dir string) (*configs.Config, error) {
    80  	l.currentDir = dir
    81  	log.Printf("[INFO] Load configurations under %s", dir)
    82  	rootMod, diags := l.parser.LoadConfigDir(dir)
    83  	if diags.HasErrors() {
    84  		log.Printf("[ERROR] %s", diags)
    85  		return nil, diags
    86  	}
    87  
    88  	if !l.config.Module {
    89  		log.Print("[INFO] Module inspection is disabled. Building a root module without children...")
    90  		cfg, diags := configs.BuildConfig(rootMod, l.ignoreModuleWalker())
    91  		if diags.HasErrors() {
    92  			return nil, diags
    93  		}
    94  		return cfg, nil
    95  	}
    96  	log.Print("[INFO] Module inspection is enabled. Building a root module with children...")
    97  
    98  	cfg, diags := configs.BuildConfig(rootMod, l.moduleWalker())
    99  	if !diags.HasErrors() {
   100  		return cfg, nil
   101  	}
   102  
   103  	log.Printf("[ERROR] Failed to load modules: %s", diags)
   104  	return nil, diags
   105  }
   106  
   107  // LoadAnnotations load TFLint annotation comments as HCL tokens.
   108  func (l *Loader) LoadAnnotations(dir string) (map[string]Annotations, error) {
   109  	primary, override, diags := l.parser.ConfigDirFiles(dir)
   110  	if diags != nil {
   111  		log.Printf("[ERROR] %s", diags)
   112  		return nil, diags
   113  	}
   114  	configFiles := append(primary, override...)
   115  
   116  	ret := map[string]Annotations{}
   117  
   118  	for _, configFile := range configFiles {
   119  		src, err := l.fs.ReadFile(configFile)
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  		tokens, diags := hclsyntax.LexConfig(src, configFile, hcl.Pos{Byte: 0, Line: 1, Column: 1})
   124  		if diags.HasErrors() {
   125  			return nil, diags
   126  		}
   127  		ret[configFile] = NewAnnotations(tokens)
   128  	}
   129  
   130  	return ret, nil
   131  }
   132  
   133  // LoadValuesFiles reads Terraform's values files and returns terraform.InputValues list in order of priority
   134  // Pass values ​​files specified from the CLI as the arguments in order of priority
   135  // This is the responsibility of the caller
   136  func (l *Loader) LoadValuesFiles(files ...string) ([]terraform.InputValues, error) {
   137  	log.Print("[INFO] Load values files")
   138  
   139  	values := []terraform.InputValues{}
   140  
   141  	for _, file := range files {
   142  		if _, err := os.Stat(file); os.IsNotExist(err) {
   143  			return values, fmt.Errorf("`%s` is not found", file)
   144  		}
   145  	}
   146  
   147  	autoLoadFiles, err := l.autoLoadValuesFiles()
   148  	if err != nil {
   149  		log.Printf("[ERROR] %s", err)
   150  		return nil, err
   151  	}
   152  	if _, err := os.Stat(defaultValuesFile); !os.IsNotExist(err) {
   153  		autoLoadFiles = append([]string{defaultValuesFile}, autoLoadFiles...)
   154  	}
   155  
   156  	for _, file := range autoLoadFiles {
   157  		vals, err := l.loadValuesFile(file, terraform.ValueFromAutoFile)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  		values = append(values, vals)
   162  	}
   163  	for _, file := range files {
   164  		vals, err := l.loadValuesFile(file, terraform.ValueFromNamedFile)
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  		values = append(values, vals)
   169  	}
   170  
   171  	return values, nil
   172  }
   173  
   174  // Sources returns the source code cache for the underlying parser of this loader
   175  func (l *Loader) Sources() map[string][]byte {
   176  	return l.parser.Sources()
   177  }
   178  
   179  // autoLoadValuesFiles returns all files which match *.auto.tfvars present in the current directory
   180  // The list is sorted alphabetically. This is equivalent to priority
   181  // Please note that terraform.tfvars is not included in this list
   182  func (l *Loader) autoLoadValuesFiles() ([]string, error) {
   183  	files, err := l.fs.ReadDir(".")
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  
   188  	ret := []string{}
   189  	for _, file := range files {
   190  		if file.IsDir() {
   191  			continue
   192  		}
   193  
   194  		if strings.HasSuffix(file.Name(), ".auto.tfvars") || strings.HasSuffix(file.Name(), ".auto.tfvars.json") {
   195  			ret = append(ret, file.Name())
   196  		}
   197  	}
   198  	sort.Strings(ret)
   199  
   200  	return ret, nil
   201  }
   202  
   203  func (l *Loader) loadValuesFile(file string, sourceType terraform.ValueSourceType) (terraform.InputValues, error) {
   204  	log.Printf("[INFO] Load `%s`", file)
   205  	vals, diags := l.parser.LoadValuesFile(file)
   206  	if diags.HasErrors() {
   207  		log.Printf("[ERROR] %s", diags)
   208  		if diags[0].Subject == nil {
   209  			// HACK: When Subject is nil, it outputs unintended message, so it replaces with actual file.
   210  			return nil, errors.New(strings.Replace(diags.Error(), "<nil>: ", fmt.Sprintf("%s: ", file), 1))
   211  		}
   212  		return nil, diags
   213  	}
   214  
   215  	ret := make(terraform.InputValues)
   216  	for k, v := range vals {
   217  		ret[k] = &terraform.InputValue{
   218  			Value:      v,
   219  			SourceType: sourceType,
   220  		}
   221  	}
   222  	return ret, nil
   223  }
   224  
   225  func (l *Loader) moduleWalker() configs.ModuleWalker {
   226  	return configs.ModuleWalkerFunc(func(req *configs.ModuleRequest) (*configs.Module, *version.Version, hcl.Diagnostics) {
   227  		key := req.Path.String()
   228  		record, ok := l.moduleManifest[key]
   229  		if !ok {
   230  			log.Printf("[DEBUG] Failed to search by `%s` key.", key)
   231  			return nil, nil, hcl.Diagnostics{
   232  				{
   233  					Severity: hcl.DiagError,
   234  					Summary:  fmt.Sprintf("`%s` module is not found. Did you run `terraform init`?", req.Name),
   235  					Subject:  &req.CallRange,
   236  				},
   237  			}
   238  		}
   239  
   240  		dir := filepath.Join(l.currentDir, record.Dir)
   241  		if record.Root != "" {
   242  			dir = filepath.Join(dir, record.Root)
   243  		}
   244  		log.Printf("[DEBUG] Trying to load the module: key=%s, version=%s, dir=%s", key, record.VersionStr, dir)
   245  
   246  		mod, diags := l.parser.LoadConfigDir(dir)
   247  		return mod, record.Version, diags
   248  	})
   249  }
   250  
   251  func (l *Loader) ignoreModuleWalker() configs.ModuleWalker {
   252  	return configs.ModuleWalkerFunc(func(req *configs.ModuleRequest) (*configs.Module, *version.Version, hcl.Diagnostics) {
   253  		return nil, nil, nil
   254  	})
   255  }
   256  
   257  func (l *Loader) initializeModuleManifest() error {
   258  	file, err := l.fs.ReadFile(getTFModuleManifestPath())
   259  	if err != nil {
   260  		return err
   261  	}
   262  	log.Printf("[DEBUG] Parsing the module manifest file: %s", file)
   263  
   264  	var manifestFile moduleManifestFile
   265  	err = json.Unmarshal(file, &manifestFile)
   266  	if err != nil {
   267  		return err
   268  	}
   269  
   270  	for _, m := range manifestFile.Modules {
   271  		if m.VersionStr != "" {
   272  			m.Version, err = version.NewVersion(m.VersionStr)
   273  			if err != nil {
   274  				return err
   275  			}
   276  			l.moduleSourceVersions[m.Source] = append(l.moduleSourceVersions[m.Source], m.Version)
   277  		}
   278  		l.moduleManifest[m.Key] = m
   279  	}
   280  
   281  	return nil
   282  }