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

     1  package tflint
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"strings"
     8  	"sync"
     9  
    10  	hcl "github.com/hashicorp/hcl/v2"
    11  	"github.com/hashicorp/terraform/addrs"
    12  	"github.com/hashicorp/terraform/configs"
    13  	"github.com/hashicorp/terraform/configs/configschema"
    14  	"github.com/hashicorp/terraform/lang"
    15  	"github.com/hashicorp/terraform/terraform"
    16  	"github.com/wata727/tflint/client"
    17  	"github.com/zclconf/go-cty/cty"
    18  	"github.com/zclconf/go-cty/cty/convert"
    19  	"github.com/zclconf/go-cty/cty/gocty"
    20  )
    21  
    22  // Runner checks templates according rules.
    23  // For variables interplation, it has Terraform eval context.
    24  // After checking, it accumulates results as issues.
    25  type Runner struct {
    26  	TFConfig  *configs.Config
    27  	Issues    Issues
    28  	AwsClient *client.AwsClient
    29  
    30  	ctx         terraform.BuiltinEvalContext
    31  	annotations map[string]Annotations
    32  	config      *Config
    33  	currentExpr hcl.Expression
    34  	modVars     map[string]*moduleVariable
    35  }
    36  
    37  // Rule is interface for building the issue
    38  type Rule interface {
    39  	Name() string
    40  	Severity() string
    41  	Link() string
    42  }
    43  
    44  // NewRunner returns new TFLint runner
    45  // It prepares built-in context (workpace metadata, variables) from
    46  // received `configs.Config` and `terraform.InputValues`
    47  func NewRunner(c *Config, ants map[string]Annotations, cfg *configs.Config, variables ...terraform.InputValues) (*Runner, error) {
    48  	path := "root"
    49  	if !cfg.Path.IsRoot() {
    50  		path = cfg.Path.String()
    51  	}
    52  	log.Printf("[INFO] Initialize new runner for %s", path)
    53  
    54  	runner := &Runner{
    55  		TFConfig:  cfg,
    56  		Issues:    Issues{},
    57  		AwsClient: &client.AwsClient{},
    58  
    59  		ctx: terraform.BuiltinEvalContext{
    60  			Evaluator: &terraform.Evaluator{
    61  				Meta: &terraform.ContextMeta{
    62  					Env: getTFWorkspace(),
    63  				},
    64  				Config:             cfg,
    65  				VariableValues:     prepareVariableValues(cfg.Module.Variables, variables...),
    66  				VariableValuesLock: &sync.Mutex{},
    67  			},
    68  		},
    69  		annotations: ants,
    70  		config:      c,
    71  	}
    72  
    73  	// Initialize client for the root runner
    74  	if c.DeepCheck && cfg.Path.IsRoot() {
    75  		// FIXME: Alias providers are not considered
    76  		providerConfig, err := NewProviderConfig(
    77  			cfg.Module.ProviderConfigs["aws"],
    78  			runner,
    79  			client.AwsProviderBlockSchema,
    80  		)
    81  		if err != nil {
    82  			return nil, err
    83  		}
    84  		creds, err := client.ConvertToCredentials(providerConfig)
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  
    89  		runner.AwsClient, err = client.NewAwsClient(c.AwsCredentials.Merge(creds))
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  	}
    94  
    95  	return runner, nil
    96  }
    97  
    98  // NewModuleRunners returns new TFLint runners for child modules
    99  // Recursively search modules and generate Runners
   100  // In order to propagate attributes of moduleCall as variables to the module,
   101  // evaluate the variables. If it cannot be evaluated, treat it as unknown
   102  func NewModuleRunners(parent *Runner) ([]*Runner, error) {
   103  	runners := []*Runner{}
   104  
   105  	for name, cfg := range parent.TFConfig.Children {
   106  		moduleCall, ok := parent.TFConfig.Module.ModuleCalls[name]
   107  		if !ok {
   108  			panic(fmt.Errorf("Expected module call `%s` is not found in `%s`", name, parent.TFConfig.Path.String()))
   109  		}
   110  		if parent.TFConfig.Path.IsRoot() && parent.config.IgnoreModules[moduleCall.SourceAddr] {
   111  			log.Printf("[INFO] Ignore `%s` module", moduleCall.Name)
   112  			continue
   113  		}
   114  
   115  		attributes, diags := moduleCall.Config.JustAttributes()
   116  		if diags.HasErrors() {
   117  			var causeErr error
   118  			if diags[0].Subject == nil {
   119  				// HACK: When Subject is nil, it outputs unintended message, so it replaces with actual file.
   120  				causeErr = errors.New(strings.Replace(diags.Error(), "<nil>: ", "", 1))
   121  			} else {
   122  				causeErr = diags
   123  			}
   124  			err := &Error{
   125  				Code:  UnexpectedAttributeError,
   126  				Level: ErrorLevel,
   127  				Message: fmt.Sprintf(
   128  					"Attribute of module not allowed was found in %s:%d",
   129  					moduleCall.DeclRange.Filename,
   130  					moduleCall.DeclRange.Start.Line,
   131  				),
   132  				Cause: causeErr,
   133  			}
   134  			log.Printf("[ERROR] %s", err)
   135  			return runners, err
   136  		}
   137  
   138  		modVars := map[string]*moduleVariable{}
   139  		for varName, rawVar := range cfg.Module.Variables {
   140  			if attribute, exists := attributes[varName]; exists {
   141  				evalauble, err := isEvaluableExpr(attribute.Expr)
   142  				if err != nil {
   143  					return runners, err
   144  				}
   145  
   146  				if evalauble {
   147  					val, diags := parent.ctx.EvaluateExpr(attribute.Expr, cty.DynamicPseudoType, nil)
   148  					if diags.HasErrors() {
   149  						err := &Error{
   150  							Code:  EvaluationError,
   151  							Level: ErrorLevel,
   152  							Message: fmt.Sprintf(
   153  								"Failed to eval an expression in %s:%d",
   154  								attribute.Expr.Range().Filename,
   155  								attribute.Expr.Range().Start.Line,
   156  							),
   157  							Cause: diags.Err(),
   158  						}
   159  						log.Printf("[ERROR] %s", err)
   160  						return runners, err
   161  					}
   162  					rawVar.Default = val
   163  				} else {
   164  					// If module attributes are not evaluable, it marks that value as unknown.
   165  					// Unknown values are ignored when evaluated inside the module.
   166  					log.Printf("[DEBUG] `%s` has been marked as unknown", varName)
   167  					rawVar.Default = cty.UnknownVal(cty.DynamicPseudoType)
   168  				}
   169  
   170  				if parent.TFConfig.Path.IsRoot() {
   171  					modVars[varName] = &moduleVariable{
   172  						Root:      true,
   173  						DeclRange: attribute.Expr.Range(),
   174  					}
   175  				} else {
   176  					parentVars := []*moduleVariable{}
   177  					for _, ref := range listVarRefs(attribute.Expr) {
   178  						if parentVar, exists := parent.modVars[ref.Name]; exists {
   179  							parentVars = append(parentVars, parentVar)
   180  						}
   181  					}
   182  					modVars[varName] = &moduleVariable{
   183  						Parents:   parentVars,
   184  						DeclRange: attribute.Expr.Range(),
   185  					}
   186  				}
   187  			}
   188  		}
   189  
   190  		runner, err := NewRunner(parent.config, parent.annotations, cfg)
   191  		if err != nil {
   192  			return runners, err
   193  		}
   194  		runner.modVars = modVars
   195  		// Inherit parent's AwsClient
   196  		runner.AwsClient = parent.AwsClient
   197  		runners = append(runners, runner)
   198  		moudleRunners, err := NewModuleRunners(runner)
   199  		if err != nil {
   200  			return runners, err
   201  		}
   202  		runners = append(runners, moudleRunners...)
   203  	}
   204  
   205  	return runners, nil
   206  }
   207  
   208  // EvaluateExpr is a wrapper of terraform.BultinEvalContext.EvaluateExpr and gocty.FromCtyValue
   209  // When it received slice as `ret`, it converts cty.Value to expected list type
   210  // because raw cty.Value has TupleType.
   211  func (r *Runner) EvaluateExpr(expr hcl.Expression, ret interface{}) error {
   212  	evaluable, err := isEvaluableExpr(expr)
   213  	if err != nil {
   214  		err := &Error{
   215  			Code:  EvaluationError,
   216  			Level: ErrorLevel,
   217  			Message: fmt.Sprintf(
   218  				"Failed to parse an expression in %s:%d",
   219  				expr.Range().Filename,
   220  				expr.Range().Start.Line,
   221  			),
   222  			Cause: err,
   223  		}
   224  		log.Printf("[ERROR] %s", err)
   225  		return err
   226  	}
   227  
   228  	if !evaluable {
   229  		err := &Error{
   230  			Code:  UnevaluableError,
   231  			Level: WarningLevel,
   232  			Message: fmt.Sprintf(
   233  				"Unevaluable expression found in %s:%d",
   234  				expr.Range().Filename,
   235  				expr.Range().Start.Line,
   236  			),
   237  		}
   238  		log.Printf("[WARN] %s; TFLint ignores an unevaluable expression.", err)
   239  		return err
   240  	}
   241  
   242  	val, diags := r.ctx.EvaluateExpr(expr, cty.DynamicPseudoType, nil)
   243  	if diags.HasErrors() {
   244  		err := &Error{
   245  			Code:  EvaluationError,
   246  			Level: ErrorLevel,
   247  			Message: fmt.Sprintf(
   248  				"Failed to eval an expression in %s:%d",
   249  				expr.Range().Filename,
   250  				expr.Range().Start.Line,
   251  			),
   252  			Cause: diags.Err(),
   253  		}
   254  		log.Printf("[ERROR] %s", err)
   255  		return err
   256  	}
   257  
   258  	err = cty.Walk(val, func(path cty.Path, v cty.Value) (bool, error) {
   259  		if !v.IsKnown() {
   260  			err := &Error{
   261  				Code:  UnknownValueError,
   262  				Level: WarningLevel,
   263  				Message: fmt.Sprintf(
   264  					"Unknown value found in %s:%d; Please use environment variables or tfvars to set the value",
   265  					expr.Range().Filename,
   266  					expr.Range().Start.Line,
   267  				),
   268  			}
   269  			log.Printf("[WARN] %s; TFLint ignores an expression includes an unknown value.", err)
   270  			return false, err
   271  		}
   272  
   273  		if v.IsNull() {
   274  			err := &Error{
   275  				Code:  NullValueError,
   276  				Level: WarningLevel,
   277  				Message: fmt.Sprintf(
   278  					"Null value found in %s:%d",
   279  					expr.Range().Filename,
   280  					expr.Range().Start.Line,
   281  				),
   282  			}
   283  			log.Printf("[WARN] %s; TFLint ignores an expression includes an null value.", err)
   284  			return false, err
   285  		}
   286  
   287  		return true, nil
   288  	})
   289  
   290  	if err != nil {
   291  		return err
   292  	}
   293  
   294  	switch ret.(type) {
   295  	case *string:
   296  		val, err = convert.Convert(val, cty.String)
   297  	case *int:
   298  		val, err = convert.Convert(val, cty.Number)
   299  	case *[]string:
   300  		val, err = convert.Convert(val, cty.List(cty.String))
   301  	case *[]int:
   302  		val, err = convert.Convert(val, cty.List(cty.Number))
   303  	case *map[string]string:
   304  		val, err = convert.Convert(val, cty.Map(cty.String))
   305  	case *map[string]int:
   306  		val, err = convert.Convert(val, cty.Map(cty.Number))
   307  	}
   308  
   309  	if err != nil {
   310  		err := &Error{
   311  			Code:  TypeConversionError,
   312  			Level: ErrorLevel,
   313  			Message: fmt.Sprintf(
   314  				"Invalid type expression in %s:%d",
   315  				expr.Range().Filename,
   316  				expr.Range().Start.Line,
   317  			),
   318  			Cause: err,
   319  		}
   320  		log.Printf("[ERROR] %s", err)
   321  		return err
   322  	}
   323  
   324  	err = gocty.FromCtyValue(val, ret)
   325  	if err != nil {
   326  		err := &Error{
   327  			Code:  TypeMismatchError,
   328  			Level: ErrorLevel,
   329  			Message: fmt.Sprintf(
   330  				"Invalid type expression in %s:%d",
   331  				expr.Range().Filename,
   332  				expr.Range().Start.Line,
   333  			),
   334  			Cause: err,
   335  		}
   336  		log.Printf("[ERROR] %s", err)
   337  		return err
   338  	}
   339  	return nil
   340  }
   341  
   342  // EvaluateBlock is a wrapper of terraform.BultinEvalContext.EvaluateBlock and gocty.FromCtyValue
   343  func (r *Runner) EvaluateBlock(block *hcl.Block, schema *configschema.Block, ret interface{}) error {
   344  	evaluable, err := isEvaluableBlock(block.Body, schema)
   345  	if err != nil {
   346  		err := &Error{
   347  			Code:  EvaluationError,
   348  			Level: ErrorLevel,
   349  			Message: fmt.Sprintf(
   350  				"Failed to parse a block in %s:%d",
   351  				block.DefRange.Filename,
   352  				block.DefRange.Start.Line,
   353  			),
   354  			Cause: err,
   355  		}
   356  		log.Printf("[ERROR] %s", err)
   357  		return err
   358  	}
   359  
   360  	if !evaluable {
   361  		err := &Error{
   362  			Code:  UnevaluableError,
   363  			Level: WarningLevel,
   364  			Message: fmt.Sprintf(
   365  				"Unevaluable block found in %s:%d",
   366  				block.DefRange.Filename,
   367  				block.DefRange.Start.Line,
   368  			),
   369  		}
   370  		log.Printf("[WARN] %s; TFLint ignores an unevaluable block.", err)
   371  		return err
   372  	}
   373  
   374  	val, _, diags := r.ctx.EvaluateBlock(block.Body, schema, nil, terraform.EvalDataForNoInstanceKey)
   375  	if diags.HasErrors() {
   376  		err := &Error{
   377  			Code:  EvaluationError,
   378  			Level: ErrorLevel,
   379  			Message: fmt.Sprintf(
   380  				"Failed to eval a block in %s:%d",
   381  				block.DefRange.Filename,
   382  				block.DefRange.Start.Line,
   383  			),
   384  			Cause: diags.Err(),
   385  		}
   386  		log.Printf("[ERROR] %s", err)
   387  		return err
   388  	}
   389  
   390  	err = cty.Walk(val, func(path cty.Path, v cty.Value) (bool, error) {
   391  		if !v.IsKnown() {
   392  			err := &Error{
   393  				Code:  UnknownValueError,
   394  				Level: WarningLevel,
   395  				Message: fmt.Sprintf(
   396  					"Unknown value found in %s:%d; Please use environment variables or tfvars to set the value",
   397  					block.DefRange.Filename,
   398  					block.DefRange.Start.Line,
   399  				),
   400  			}
   401  			log.Printf("[WARN] %s; TFLint ignores a block includes an unknown value.", err)
   402  			return false, err
   403  		}
   404  
   405  		return true, nil
   406  	})
   407  	if err != nil {
   408  		return err
   409  	}
   410  
   411  	val, err = cty.Transform(val, func(path cty.Path, v cty.Value) (cty.Value, error) {
   412  		if v.IsNull() {
   413  			log.Printf(
   414  				"[DEBUG] Null value found in %s:%d, but TFLint treats this value as an empty value",
   415  				block.DefRange.Filename,
   416  				block.DefRange.Start.Line,
   417  			)
   418  			return cty.StringVal(""), nil
   419  		}
   420  		return v, nil
   421  	})
   422  	if err != nil {
   423  		return err
   424  	}
   425  
   426  	switch ret.(type) {
   427  	case *map[string]string:
   428  		val, err = convert.Convert(val, cty.Map(cty.String))
   429  	case *map[string]int:
   430  		val, err = convert.Convert(val, cty.Map(cty.Number))
   431  	}
   432  
   433  	if err != nil {
   434  		err := &Error{
   435  			Code:  TypeConversionError,
   436  			Level: ErrorLevel,
   437  			Message: fmt.Sprintf(
   438  				"Invalid type block in %s:%d",
   439  				block.DefRange.Filename,
   440  				block.DefRange.Start.Line,
   441  			),
   442  			Cause: err,
   443  		}
   444  		log.Printf("[ERROR] %s", err)
   445  		return err
   446  	}
   447  
   448  	err = gocty.FromCtyValue(val, ret)
   449  	if err != nil {
   450  		err := &Error{
   451  			Code:  TypeMismatchError,
   452  			Level: ErrorLevel,
   453  			Message: fmt.Sprintf(
   454  				"Invalid type block in %s:%d",
   455  				block.DefRange.Filename,
   456  				block.DefRange.Start.Line,
   457  			),
   458  			Cause: err,
   459  		}
   460  		log.Printf("[ERROR] %s", err)
   461  		return err
   462  	}
   463  	return nil
   464  }
   465  
   466  // TFConfigPath is a wrapper of addrs.Module
   467  func (r *Runner) TFConfigPath() string {
   468  	if r.TFConfig.Path.IsRoot() {
   469  		return "root"
   470  	}
   471  	return r.TFConfig.Path.String()
   472  }
   473  
   474  // LookupIssues returns issues according to the received files
   475  func (r *Runner) LookupIssues(files ...string) Issues {
   476  	if len(files) == 0 {
   477  		return r.Issues
   478  	}
   479  
   480  	issues := Issues{}
   481  	for _, issue := range r.Issues {
   482  		for _, file := range files {
   483  			if file == issue.Range.Filename {
   484  				issues = append(issues, issue)
   485  			}
   486  		}
   487  	}
   488  	return issues
   489  }
   490  
   491  // WalkResourceAttributes searches for resources and passes the appropriate attributes to the walker function
   492  func (r *Runner) WalkResourceAttributes(resource, attributeName string, walker func(*hcl.Attribute) error) error {
   493  	for _, resource := range r.LookupResourcesByType(resource) {
   494  		body, _, diags := resource.Config.PartialContent(&hcl.BodySchema{
   495  			Attributes: []hcl.AttributeSchema{
   496  				{
   497  					Name: attributeName,
   498  				},
   499  			},
   500  		})
   501  		if diags.HasErrors() {
   502  			return diags
   503  		}
   504  
   505  		if attribute, ok := body.Attributes[attributeName]; ok {
   506  			log.Printf("[DEBUG] Walk `%s` attribute", resource.Type+"."+resource.Name+"."+attributeName)
   507  			r.currentExpr = attribute.Expr
   508  			err := walker(attribute)
   509  			r.currentExpr = nil
   510  			if err != nil {
   511  				return err
   512  			}
   513  		}
   514  	}
   515  
   516  	return nil
   517  }
   518  
   519  // WalkResourceBlocks walks all blocks of the passed resource and invokes the passed function
   520  func (r *Runner) WalkResourceBlocks(resource, blockType string, walker func(*hcl.Block) error) error {
   521  	for _, resource := range r.LookupResourcesByType(resource) {
   522  		body, _, diags := resource.Config.PartialContent(&hcl.BodySchema{
   523  			Blocks: []hcl.BlockHeaderSchema{
   524  				{
   525  					Type: blockType,
   526  				},
   527  			},
   528  		})
   529  		if diags.HasErrors() {
   530  			return diags
   531  		}
   532  
   533  		for _, block := range body.Blocks {
   534  			log.Printf("[DEBUG] Walk `%s` block", resource.Type+"."+resource.Name+"."+blockType)
   535  			err := walker(block)
   536  			if err != nil {
   537  				return err
   538  			}
   539  		}
   540  
   541  		// Walk in the same way for dynamic blocks. Note that we are not expanding blocks.
   542  		// Therefore, expressions that use iterator are unevaluable.
   543  		dynBody, _, diags := resource.Config.PartialContent(&hcl.BodySchema{
   544  			Blocks: []hcl.BlockHeaderSchema{
   545  				{
   546  					Type:       "dynamic",
   547  					LabelNames: []string{"name"},
   548  				},
   549  			},
   550  		})
   551  		if diags.HasErrors() {
   552  			return diags
   553  		}
   554  
   555  		for _, block := range dynBody.Blocks {
   556  			if len(block.Labels) == 1 && block.Labels[0] == blockType {
   557  				body, _, diags = block.Body.PartialContent(&hcl.BodySchema{
   558  					Blocks: []hcl.BlockHeaderSchema{
   559  						{
   560  							Type: "content",
   561  						},
   562  					},
   563  				})
   564  				if diags.HasErrors() {
   565  					return diags
   566  				}
   567  
   568  				for _, block := range body.Blocks {
   569  					log.Printf("[DEBUG] Walk dynamic `%s` block", resource.Type+"."+resource.Name+"."+blockType)
   570  					err := walker(block)
   571  					if err != nil {
   572  						return err
   573  					}
   574  				}
   575  			}
   576  		}
   577  	}
   578  
   579  	return nil
   580  }
   581  
   582  // EnsureNoError is a helper for processing when no error occurs
   583  // This function skips processing without returning an error to the caller when the error is warning
   584  func (r *Runner) EnsureNoError(err error, proc func() error) error {
   585  	if err == nil {
   586  		return proc()
   587  	}
   588  
   589  	if appErr, ok := err.(*Error); ok {
   590  		switch appErr.Level {
   591  		case WarningLevel:
   592  			return nil
   593  		case ErrorLevel:
   594  			return appErr
   595  		default:
   596  			panic(appErr)
   597  		}
   598  	} else {
   599  		return err
   600  	}
   601  }
   602  
   603  // IsNullExpr check the passed expression is null
   604  func (r *Runner) IsNullExpr(expr hcl.Expression) (bool, error) {
   605  	evaluable, err := isEvaluableExpr(expr)
   606  	if err != nil {
   607  		return false, err
   608  	}
   609  
   610  	if !evaluable {
   611  		return false, nil
   612  	}
   613  	val, diags := r.ctx.EvaluateExpr(expr, cty.DynamicPseudoType, nil)
   614  	if diags.HasErrors() {
   615  		return false, diags.Err()
   616  	}
   617  	return val.IsNull(), nil
   618  }
   619  
   620  // LookupResourcesByType returns `configs.Resource` list according to the resource type
   621  func (r *Runner) LookupResourcesByType(resourceType string) []*configs.Resource {
   622  	ret := []*configs.Resource{}
   623  
   624  	for _, resource := range r.TFConfig.Module.ManagedResources {
   625  		if resource.Type == resourceType {
   626  			ret = append(ret, resource)
   627  		}
   628  	}
   629  
   630  	return ret
   631  }
   632  
   633  // EachStringSliceExprs iterates an evaluated value and the corresponding expression
   634  // If the given expression is a static list, get an expression for each value
   635  // If not, the given expression is used as it is
   636  func (r *Runner) EachStringSliceExprs(expr hcl.Expression, proc func(val string, expr hcl.Expression)) error {
   637  	var vals []string
   638  	err := r.EvaluateExpr(expr, &vals)
   639  
   640  	exprs, diags := hcl.ExprList(expr)
   641  	if diags.HasErrors() {
   642  		log.Printf("[DEBUG] Expr is not static list: %s", diags)
   643  		for range vals {
   644  			exprs = append(exprs, expr)
   645  		}
   646  	}
   647  
   648  	return r.EnsureNoError(err, func() error {
   649  		for idx, val := range vals {
   650  			proc(val, exprs[idx])
   651  		}
   652  		return nil
   653  	})
   654  }
   655  
   656  // EmitIssue builds an issue and accumulates it
   657  func (r *Runner) EmitIssue(rule Rule, message string, location hcl.Range) {
   658  	if r.TFConfig.Path.IsRoot() {
   659  		r.emitIssue(&Issue{
   660  			Rule:    rule,
   661  			Message: message,
   662  			Range:   location,
   663  		})
   664  	} else {
   665  		for _, modVar := range r.listModuleVars(r.currentExpr) {
   666  			r.emitIssue(&Issue{
   667  				Rule:    rule,
   668  				Message: message,
   669  				Range:   modVar.DeclRange,
   670  				Callers: append(modVar.callers(), location),
   671  			})
   672  		}
   673  	}
   674  }
   675  
   676  func (r *Runner) emitIssue(issue *Issue) {
   677  	if annotations, ok := r.annotations[issue.Range.Filename]; ok {
   678  		for _, annotation := range annotations {
   679  			if annotation.IsAffected(issue) {
   680  				log.Printf("[INFO] %s (%s) is ignored by %s", issue.Range.String(), issue.Rule.Name(), annotation.String())
   681  				return
   682  			}
   683  		}
   684  	}
   685  	r.Issues = append(r.Issues, issue)
   686  }
   687  
   688  func (r *Runner) listModuleVars(expr hcl.Expression) []*moduleVariable {
   689  	ret := []*moduleVariable{}
   690  	for _, ref := range listVarRefs(expr) {
   691  		if modVar, exists := r.modVars[ref.Name]; exists {
   692  			ret = append(ret, modVar.roots()...)
   693  		}
   694  	}
   695  	return ret
   696  }
   697  
   698  // prepareVariableValues prepares Terraform variables from configs, input variables and environment variables.
   699  // Variables in the configuration are overwritten by environment variables.
   700  // Finally, they are overwritten by received input variable on the received order.
   701  // Therefore, CLI flag input variables must be passed at the end of arguments.
   702  // This is the responsibility of the caller.
   703  // See https://www.terraform.io/intro/getting-started/variables.html#assigning-variables
   704  func prepareVariableValues(configVars map[string]*configs.Variable, variables ...terraform.InputValues) map[string]map[string]cty.Value {
   705  	overrideVariables := terraform.DefaultVariableValues(configVars).Override(getTFEnvVariables()).Override(variables...)
   706  
   707  	variableValues := make(map[string]map[string]cty.Value)
   708  	variableValues[""] = make(map[string]cty.Value)
   709  	for k, iv := range overrideVariables {
   710  		variableValues[""][k] = iv.Value
   711  	}
   712  	return variableValues
   713  }
   714  
   715  func isEvaluableExpr(expr hcl.Expression) (bool, error) {
   716  	refs, diags := lang.ReferencesInExpr(expr)
   717  	if diags.HasErrors() {
   718  		return false, diags.Err()
   719  	}
   720  	for _, ref := range refs {
   721  		if !isEvaluableRef(ref) {
   722  			return false, nil
   723  		}
   724  	}
   725  	return true, nil
   726  }
   727  
   728  func isEvaluableBlock(body hcl.Body, schema *configschema.Block) (bool, error) {
   729  	refs, diags := lang.ReferencesInBlock(body, schema)
   730  	if diags.HasErrors() {
   731  		return false, diags.Err()
   732  	}
   733  	for _, ref := range refs {
   734  		if !isEvaluableRef(ref) {
   735  			return false, nil
   736  		}
   737  	}
   738  	return true, nil
   739  }
   740  
   741  func isEvaluableRef(ref *addrs.Reference) bool {
   742  	switch ref.Subject.(type) {
   743  	case addrs.InputVariable:
   744  		return true
   745  	case addrs.TerraformAttr:
   746  		return true
   747  	case addrs.PathAttr:
   748  		return true
   749  	default:
   750  		return false
   751  	}
   752  }
   753  
   754  func listVarRefs(expr hcl.Expression) []addrs.InputVariable {
   755  	refs, diags := lang.ReferencesInExpr(expr)
   756  	if diags.HasErrors() {
   757  		// Maybe this is bug
   758  		panic(diags.Err())
   759  	}
   760  
   761  	ret := []addrs.InputVariable{}
   762  	for _, ref := range refs {
   763  		if varRef, ok := ref.Subject.(addrs.InputVariable); ok {
   764  			ret = append(ret, varRef)
   765  		}
   766  	}
   767  
   768  	return ret
   769  }