github.com/mohanarpit/terraform@v0.6.16-0.20160909104007-291f29853544/terraform/interpolate.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"regexp"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/hashicorp/hil"
    13  	"github.com/hashicorp/hil/ast"
    14  	"github.com/hashicorp/terraform/config"
    15  	"github.com/hashicorp/terraform/config/module"
    16  	"github.com/hashicorp/terraform/flatmap"
    17  )
    18  
    19  const (
    20  	// VarEnvPrefix is the prefix of variables that are read from
    21  	// the environment to set variables here.
    22  	VarEnvPrefix = "TF_VAR_"
    23  )
    24  
    25  // Interpolater is the structure responsible for determining the values
    26  // for interpolations such as `aws_instance.foo.bar`.
    27  type Interpolater struct {
    28  	Operation          walkOperation
    29  	Module             *module.Tree
    30  	State              *State
    31  	StateLock          *sync.RWMutex
    32  	VariableValues     map[string]interface{}
    33  	VariableValuesLock *sync.Mutex
    34  }
    35  
    36  // InterpolationScope is the current scope of execution. This is required
    37  // since some variables which are interpolated are dependent on what we're
    38  // operating on and where we are.
    39  type InterpolationScope struct {
    40  	Path     []string
    41  	Resource *Resource
    42  }
    43  
    44  // Values returns the values for all the variables in the given map.
    45  func (i *Interpolater) Values(
    46  	scope *InterpolationScope,
    47  	vars map[string]config.InterpolatedVariable) (map[string]ast.Variable, error) {
    48  	result := make(map[string]ast.Variable, len(vars))
    49  
    50  	// Copy the default variables
    51  	if i.Module != nil && scope != nil {
    52  		mod := i.Module
    53  		if len(scope.Path) > 1 {
    54  			mod = i.Module.Child(scope.Path[1:])
    55  		}
    56  		for _, v := range mod.Config().Variables {
    57  			// Set default variables
    58  			if v.Default == nil {
    59  				continue
    60  			}
    61  
    62  			n := fmt.Sprintf("var.%s", v.Name)
    63  			variable, err := hil.InterfaceToVariable(v.Default)
    64  			if err != nil {
    65  				return nil, fmt.Errorf("invalid default map value for %s: %v", v.Name, v.Default)
    66  			}
    67  
    68  			result[n] = variable
    69  		}
    70  	}
    71  
    72  	for n, rawV := range vars {
    73  		var err error
    74  		switch v := rawV.(type) {
    75  		case *config.CountVariable:
    76  			err = i.valueCountVar(scope, n, v, result)
    77  		case *config.ModuleVariable:
    78  			err = i.valueModuleVar(scope, n, v, result)
    79  		case *config.PathVariable:
    80  			err = i.valuePathVar(scope, n, v, result)
    81  		case *config.ResourceVariable:
    82  			err = i.valueResourceVar(scope, n, v, result)
    83  		case *config.SelfVariable:
    84  			err = i.valueSelfVar(scope, n, v, result)
    85  		case *config.SimpleVariable:
    86  			err = i.valueSimpleVar(scope, n, v, result)
    87  		case *config.UserVariable:
    88  			err = i.valueUserVar(scope, n, v, result)
    89  		default:
    90  			err = fmt.Errorf("%s: unknown variable type: %T", n, rawV)
    91  		}
    92  
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  	}
    97  
    98  	return result, nil
    99  }
   100  
   101  func (i *Interpolater) valueCountVar(
   102  	scope *InterpolationScope,
   103  	n string,
   104  	v *config.CountVariable,
   105  	result map[string]ast.Variable) error {
   106  	switch v.Type {
   107  	case config.CountValueIndex:
   108  		if scope.Resource == nil {
   109  			return fmt.Errorf("%s: count.index is only valid within resources", n)
   110  		}
   111  		result[n] = ast.Variable{
   112  			Value: scope.Resource.CountIndex,
   113  			Type:  ast.TypeInt,
   114  		}
   115  		return nil
   116  	default:
   117  		return fmt.Errorf("%s: unknown count type: %#v", n, v.Type)
   118  	}
   119  }
   120  
   121  func unknownVariable() ast.Variable {
   122  	return ast.Variable{
   123  		Type:  ast.TypeString,
   124  		Value: config.UnknownVariableValue,
   125  	}
   126  }
   127  
   128  func (i *Interpolater) valueModuleVar(
   129  	scope *InterpolationScope,
   130  	n string,
   131  	v *config.ModuleVariable,
   132  	result map[string]ast.Variable) error {
   133  
   134  	// Build the path to the child module we want
   135  	path := make([]string, len(scope.Path), len(scope.Path)+1)
   136  	copy(path, scope.Path)
   137  	path = append(path, v.Name)
   138  
   139  	// Grab the lock so that if other interpolations are running or
   140  	// state is being modified, we'll be safe.
   141  	i.StateLock.RLock()
   142  	defer i.StateLock.RUnlock()
   143  
   144  	// Get the module where we're looking for the value
   145  	mod := i.State.ModuleByPath(path)
   146  	if mod == nil {
   147  		// If the module doesn't exist, then we can return an empty string.
   148  		// This happens usually only in Refresh() when we haven't populated
   149  		// a state. During validation, we semantically verify that all
   150  		// modules reference other modules, and graph ordering should
   151  		// ensure that the module is in the state, so if we reach this
   152  		// point otherwise it really is a panic.
   153  		result[n] = unknownVariable()
   154  	} else {
   155  		// Get the value from the outputs
   156  		if outputState, ok := mod.Outputs[v.Field]; ok {
   157  			output, err := hil.InterfaceToVariable(outputState.Value)
   158  			if err != nil {
   159  				return err
   160  			}
   161  			result[n] = output
   162  		} else {
   163  			// Same reasons as the comment above.
   164  			result[n] = unknownVariable()
   165  		}
   166  	}
   167  
   168  	return nil
   169  }
   170  
   171  func (i *Interpolater) valuePathVar(
   172  	scope *InterpolationScope,
   173  	n string,
   174  	v *config.PathVariable,
   175  	result map[string]ast.Variable) error {
   176  	switch v.Type {
   177  	case config.PathValueCwd:
   178  		wd, err := os.Getwd()
   179  		if err != nil {
   180  			return fmt.Errorf(
   181  				"Couldn't get cwd for var %s: %s",
   182  				v.FullKey(), err)
   183  		}
   184  
   185  		result[n] = ast.Variable{
   186  			Value: wd,
   187  			Type:  ast.TypeString,
   188  		}
   189  	case config.PathValueModule:
   190  		if t := i.Module.Child(scope.Path[1:]); t != nil {
   191  			result[n] = ast.Variable{
   192  				Value: t.Config().Dir,
   193  				Type:  ast.TypeString,
   194  			}
   195  		}
   196  	case config.PathValueRoot:
   197  		result[n] = ast.Variable{
   198  			Value: i.Module.Config().Dir,
   199  			Type:  ast.TypeString,
   200  		}
   201  	default:
   202  		return fmt.Errorf("%s: unknown path type: %#v", n, v.Type)
   203  	}
   204  
   205  	return nil
   206  
   207  }
   208  
   209  func (i *Interpolater) valueResourceVar(
   210  	scope *InterpolationScope,
   211  	n string,
   212  	v *config.ResourceVariable,
   213  	result map[string]ast.Variable) error {
   214  	// If we're computing all dynamic fields, then module vars count
   215  	// and we mark it as computed.
   216  	if i.Operation == walkValidate {
   217  		result[n] = ast.Variable{
   218  			Value: config.UnknownVariableValue,
   219  			Type:  ast.TypeString,
   220  		}
   221  		return nil
   222  	}
   223  
   224  	var variable *ast.Variable
   225  	var err error
   226  
   227  	if v.Multi && v.Index == -1 {
   228  		variable, err = i.computeResourceMultiVariable(scope, v)
   229  	} else {
   230  		variable, err = i.computeResourceVariable(scope, v)
   231  	}
   232  
   233  	if err != nil {
   234  		return err
   235  	}
   236  
   237  	if variable == nil {
   238  		// During the input walk we tolerate missing variables because
   239  		// we haven't yet had a chance to refresh state, so dynamic data may
   240  		// not yet be complete.
   241  		// If it truly is missing, we'll catch it on a later walk.
   242  		// This applies only to graph nodes that interpolate during the
   243  		// config walk, e.g. providers.
   244  		if i.Operation == walkInput {
   245  			result[n] = ast.Variable{
   246  				Value: config.UnknownVariableValue,
   247  				Type:  ast.TypeString,
   248  			}
   249  			return nil
   250  		}
   251  
   252  		return fmt.Errorf("variable %q is nil, but no error was reported", v.Name)
   253  	}
   254  
   255  	result[n] = *variable
   256  	return nil
   257  }
   258  
   259  func (i *Interpolater) valueSelfVar(
   260  	scope *InterpolationScope,
   261  	n string,
   262  	v *config.SelfVariable,
   263  	result map[string]ast.Variable) error {
   264  	if scope == nil || scope.Resource == nil {
   265  		return fmt.Errorf(
   266  			"%s: invalid scope, self variables are only valid on resources", n)
   267  	}
   268  
   269  	rv, err := config.NewResourceVariable(fmt.Sprintf(
   270  		"%s.%s.%d.%s",
   271  		scope.Resource.Type,
   272  		scope.Resource.Name,
   273  		scope.Resource.CountIndex,
   274  		v.Field))
   275  	if err != nil {
   276  		return err
   277  	}
   278  
   279  	return i.valueResourceVar(scope, n, rv, result)
   280  }
   281  
   282  func (i *Interpolater) valueSimpleVar(
   283  	scope *InterpolationScope,
   284  	n string,
   285  	v *config.SimpleVariable,
   286  	result map[string]ast.Variable) error {
   287  	// SimpleVars are never handled by Terraform's interpolator
   288  	result[n] = ast.Variable{
   289  		Value: config.UnknownVariableValue,
   290  		Type:  ast.TypeString,
   291  	}
   292  	return nil
   293  }
   294  
   295  func (i *Interpolater) valueUserVar(
   296  	scope *InterpolationScope,
   297  	n string,
   298  	v *config.UserVariable,
   299  	result map[string]ast.Variable) error {
   300  	i.VariableValuesLock.Lock()
   301  	defer i.VariableValuesLock.Unlock()
   302  	val, ok := i.VariableValues[v.Name]
   303  	if ok {
   304  		varValue, err := hil.InterfaceToVariable(val)
   305  		if err != nil {
   306  			return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s",
   307  				v.Name, val, err)
   308  		}
   309  		result[n] = varValue
   310  		return nil
   311  	}
   312  
   313  	if _, ok := result[n]; !ok && i.Operation == walkValidate {
   314  		result[n] = unknownVariable()
   315  		return nil
   316  	}
   317  
   318  	// Look up if we have any variables with this prefix because
   319  	// those are map overrides. Include those.
   320  	for k, val := range i.VariableValues {
   321  		if strings.HasPrefix(k, v.Name+".") {
   322  			keyComponents := strings.Split(k, ".")
   323  			overrideKey := keyComponents[len(keyComponents)-1]
   324  
   325  			mapInterface, ok := result["var."+v.Name]
   326  			if !ok {
   327  				return fmt.Errorf("override for non-existent variable: %s", v.Name)
   328  			}
   329  
   330  			mapVariable := mapInterface.Value.(map[string]ast.Variable)
   331  
   332  			varValue, err := hil.InterfaceToVariable(val)
   333  			if err != nil {
   334  				return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s",
   335  					v.Name, val, err)
   336  			}
   337  			mapVariable[overrideKey] = varValue
   338  		}
   339  	}
   340  
   341  	return nil
   342  }
   343  
   344  func (i *Interpolater) computeResourceVariable(
   345  	scope *InterpolationScope,
   346  	v *config.ResourceVariable) (*ast.Variable, error) {
   347  	id := v.ResourceId()
   348  	if v.Multi {
   349  		id = fmt.Sprintf("%s.%d", id, v.Index)
   350  	}
   351  
   352  	i.StateLock.RLock()
   353  	defer i.StateLock.RUnlock()
   354  
   355  	unknownVariable := unknownVariable()
   356  
   357  	// These variables must be declared early because of the use of GOTO
   358  	var isList bool
   359  	var isMap bool
   360  
   361  	// Get the information about this resource variable, and verify
   362  	// that it exists and such.
   363  	module, cr, err := i.resourceVariableInfo(scope, v)
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	// If we're requesting "count" its a special variable that we grab
   369  	// directly from the config itself.
   370  	if v.Field == "count" {
   371  		count, err := cr.Count()
   372  		if err != nil {
   373  			return nil, fmt.Errorf(
   374  				"Error reading %s count: %s",
   375  				v.ResourceId(),
   376  				err)
   377  		}
   378  
   379  		return &ast.Variable{Type: ast.TypeInt, Value: count}, nil
   380  	}
   381  
   382  	// If we have no module in the state yet or count, return empty
   383  	if module == nil || len(module.Resources) == 0 {
   384  		return nil, nil
   385  	}
   386  
   387  	// Get the resource out from the state. We know the state exists
   388  	// at this point and if there is a state, we expect there to be a
   389  	// resource with the given name.
   390  	r, ok := module.Resources[id]
   391  	if !ok && v.Multi && v.Index == 0 {
   392  		r, ok = module.Resources[v.ResourceId()]
   393  	}
   394  	if !ok {
   395  		r = nil
   396  	}
   397  	if r == nil {
   398  		goto MISSING
   399  	}
   400  
   401  	if r.Primary == nil {
   402  		goto MISSING
   403  	}
   404  
   405  	if attr, ok := r.Primary.Attributes[v.Field]; ok {
   406  		return &ast.Variable{Type: ast.TypeString, Value: attr}, nil
   407  	}
   408  
   409  	// computed list or map attribute
   410  	_, isList = r.Primary.Attributes[v.Field+".#"]
   411  	_, isMap = r.Primary.Attributes[v.Field+".%"]
   412  	if isList || isMap {
   413  		variable, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes)
   414  		return &variable, err
   415  	}
   416  
   417  	// At apply time, we can't do the "maybe has it" check below
   418  	// that we need for plans since parent elements might be computed.
   419  	// Therefore, it is an error and we're missing the key.
   420  	//
   421  	// TODO: test by creating a state and configuration that is referencing
   422  	// a non-existent variable "foo.bar" where the state only has "foo"
   423  	// and verify plan works, but apply doesn't.
   424  	if i.Operation == walkApply || i.Operation == walkDestroy {
   425  		goto MISSING
   426  	}
   427  
   428  	// We didn't find the exact field, so lets separate the dots
   429  	// and see if anything along the way is a computed set. i.e. if
   430  	// we have "foo.0.bar" as the field, check to see if "foo" is
   431  	// a computed list. If so, then the whole thing is computed.
   432  	if parts := strings.Split(v.Field, "."); len(parts) > 1 {
   433  		for i := 1; i < len(parts); i++ {
   434  			// Lists and sets make this
   435  			key := fmt.Sprintf("%s.#", strings.Join(parts[:i], "."))
   436  			if attr, ok := r.Primary.Attributes[key]; ok {
   437  				return &ast.Variable{Type: ast.TypeString, Value: attr}, nil
   438  			}
   439  
   440  			// Maps make this
   441  			key = fmt.Sprintf("%s", strings.Join(parts[:i], "."))
   442  			if attr, ok := r.Primary.Attributes[key]; ok {
   443  				return &ast.Variable{Type: ast.TypeString, Value: attr}, nil
   444  			}
   445  		}
   446  	}
   447  
   448  MISSING:
   449  	// Validation for missing interpolations should happen at a higher
   450  	// semantic level. If we reached this point and don't have variables,
   451  	// just return the computed value.
   452  	if scope == nil && scope.Resource == nil {
   453  		return &unknownVariable, nil
   454  	}
   455  
   456  	// If the operation is refresh, it isn't an error for a value to
   457  	// be unknown. Instead, we return that the value is computed so
   458  	// that the graph can continue to refresh other nodes. It doesn't
   459  	// matter because the config isn't interpolated anyways.
   460  	//
   461  	// For a Destroy, we're also fine with computed values, since our goal is
   462  	// only to get destroy nodes for existing resources.
   463  	//
   464  	// For an input walk, computed values are okay to return because we're only
   465  	// looking for missing variables to prompt the user for.
   466  	if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput {
   467  		return &unknownVariable, nil
   468  	}
   469  
   470  	return nil, fmt.Errorf(
   471  		"Resource '%s' does not have attribute '%s' "+
   472  			"for variable '%s'",
   473  		id,
   474  		v.Field,
   475  		v.FullKey())
   476  }
   477  
   478  func (i *Interpolater) computeResourceMultiVariable(
   479  	scope *InterpolationScope,
   480  	v *config.ResourceVariable) (*ast.Variable, error) {
   481  	i.StateLock.RLock()
   482  	defer i.StateLock.RUnlock()
   483  
   484  	unknownVariable := unknownVariable()
   485  
   486  	// Get the information about this resource variable, and verify
   487  	// that it exists and such.
   488  	module, cr, err := i.resourceVariableInfo(scope, v)
   489  	if err != nil {
   490  		return nil, err
   491  	}
   492  
   493  	// Get the count so we know how many to iterate over
   494  	count, err := cr.Count()
   495  	if err != nil {
   496  		return nil, fmt.Errorf(
   497  			"Error reading %s count: %s",
   498  			v.ResourceId(),
   499  			err)
   500  	}
   501  
   502  	// If count is zero, we return an empty list
   503  	if count == 0 {
   504  		return &ast.Variable{Type: ast.TypeList, Value: []ast.Variable{}}, nil
   505  	}
   506  
   507  	// If we have no module in the state yet or count, return unknown
   508  	if module == nil || len(module.Resources) == 0 {
   509  		return &unknownVariable, nil
   510  	}
   511  
   512  	var values []interface{}
   513  	for j := 0; j < count; j++ {
   514  		id := fmt.Sprintf("%s.%d", v.ResourceId(), j)
   515  
   516  		// If we're dealing with only a single resource, then the
   517  		// ID doesn't have a trailing index.
   518  		if count == 1 {
   519  			id = v.ResourceId()
   520  		}
   521  
   522  		r, ok := module.Resources[id]
   523  		if !ok {
   524  			continue
   525  		}
   526  
   527  		if r.Primary == nil {
   528  			continue
   529  		}
   530  
   531  		if singleAttr, ok := r.Primary.Attributes[v.Field]; ok {
   532  			if singleAttr == config.UnknownVariableValue {
   533  				return &unknownVariable, nil
   534  			}
   535  
   536  			values = append(values, singleAttr)
   537  			continue
   538  		}
   539  
   540  		// computed list or map attribute
   541  		_, isList := r.Primary.Attributes[v.Field+".#"]
   542  		_, isMap := r.Primary.Attributes[v.Field+".%"]
   543  		if !(isList || isMap) {
   544  			continue
   545  		}
   546  		multiAttr, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes)
   547  		if err != nil {
   548  			return nil, err
   549  		}
   550  
   551  		if multiAttr == unknownVariable {
   552  			return &ast.Variable{Type: ast.TypeString, Value: ""}, nil
   553  		}
   554  
   555  		values = append(values, multiAttr)
   556  	}
   557  
   558  	if len(values) == 0 {
   559  		// If the operation is refresh, it isn't an error for a value to
   560  		// be unknown. Instead, we return that the value is computed so
   561  		// that the graph can continue to refresh other nodes. It doesn't
   562  		// matter because the config isn't interpolated anyways.
   563  		//
   564  		// For a Destroy, we're also fine with computed values, since our goal is
   565  		// only to get destroy nodes for existing resources.
   566  		//
   567  		// For an input walk, computed values are okay to return because we're only
   568  		// looking for missing variables to prompt the user for.
   569  		if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput {
   570  			return &unknownVariable, nil
   571  		}
   572  
   573  		return nil, fmt.Errorf(
   574  			"Resource '%s' does not have attribute '%s' "+
   575  				"for variable '%s'",
   576  			v.ResourceId(),
   577  			v.Field,
   578  			v.FullKey())
   579  	}
   580  
   581  	variable, err := hil.InterfaceToVariable(values)
   582  	return &variable, err
   583  }
   584  
   585  func (i *Interpolater) interpolateComplexTypeAttribute(
   586  	resourceID string,
   587  	attributes map[string]string) (ast.Variable, error) {
   588  
   589  	// We can now distinguish between lists and maps in state by the count field:
   590  	//    - lists (and by extension, sets) use the traditional .# notation
   591  	//    - maps use the newer .% notation
   592  	// Consequently here we can decide how to deal with the keys appropriately
   593  	// based on whether the type is a map of list.
   594  	if lengthAttr, isList := attributes[resourceID+".#"]; isList {
   595  		log.Printf("[DEBUG] Interpolating computed list element attribute %s (%s)",
   596  			resourceID, lengthAttr)
   597  
   598  		// In Terraform's internal dotted representation of list-like attributes, the
   599  		// ".#" count field is marked as unknown to indicate "this whole list is
   600  		// unknown". We must honor that meaning here so computed references can be
   601  		// treated properly during the plan phase.
   602  		if lengthAttr == config.UnknownVariableValue {
   603  			return unknownVariable(), nil
   604  		}
   605  
   606  		keys := make([]string, 0)
   607  		listElementKey := regexp.MustCompile("^" + resourceID + "\\.[0-9]+$")
   608  		for id := range attributes {
   609  			if listElementKey.MatchString(id) {
   610  				keys = append(keys, id)
   611  			}
   612  		}
   613  		sort.Strings(keys)
   614  
   615  		var members []string
   616  		for _, key := range keys {
   617  			members = append(members, attributes[key])
   618  		}
   619  
   620  		return hil.InterfaceToVariable(members)
   621  	}
   622  
   623  	if lengthAttr, isMap := attributes[resourceID+".%"]; isMap {
   624  		log.Printf("[DEBUG] Interpolating computed map element attribute %s (%s)",
   625  			resourceID, lengthAttr)
   626  
   627  		// In Terraform's internal dotted representation of map attributes, the
   628  		// ".%" count field is marked as unknown to indicate "this whole list is
   629  		// unknown". We must honor that meaning here so computed references can be
   630  		// treated properly during the plan phase.
   631  		if lengthAttr == config.UnknownVariableValue {
   632  			return unknownVariable(), nil
   633  		}
   634  
   635  		resourceFlatMap := make(map[string]string)
   636  		mapElementKey := regexp.MustCompile("^" + resourceID + "\\.([^%]+)$")
   637  		for id, val := range attributes {
   638  			if mapElementKey.MatchString(id) {
   639  				resourceFlatMap[id] = val
   640  			}
   641  		}
   642  
   643  		expanded := flatmap.Expand(resourceFlatMap, resourceID)
   644  		return hil.InterfaceToVariable(expanded)
   645  	}
   646  
   647  	return ast.Variable{}, fmt.Errorf("No complex type %s found", resourceID)
   648  }
   649  
   650  func (i *Interpolater) resourceVariableInfo(
   651  	scope *InterpolationScope,
   652  	v *config.ResourceVariable) (*ModuleState, *config.Resource, error) {
   653  	// Get the module tree that contains our current path. This is
   654  	// either the current module (path is empty) or a child.
   655  	modTree := i.Module
   656  	if len(scope.Path) > 1 {
   657  		modTree = i.Module.Child(scope.Path[1:])
   658  	}
   659  
   660  	// Get the resource from the configuration so we can verify
   661  	// that the resource is in the configuration and so we can access
   662  	// the configuration if we need to.
   663  	var cr *config.Resource
   664  	for _, r := range modTree.Config().Resources {
   665  		if r.Id() == v.ResourceId() {
   666  			cr = r
   667  			break
   668  		}
   669  	}
   670  	if cr == nil {
   671  		return nil, nil, fmt.Errorf(
   672  			"Resource '%s' not found for variable '%s'",
   673  			v.ResourceId(),
   674  			v.FullKey())
   675  	}
   676  
   677  	// Get the relevant module
   678  	module := i.State.ModuleByPath(scope.Path)
   679  	return module, cr, nil
   680  }