Пример #1
0
func (i *Interpolater) valueUserVar(
	scope *InterpolationScope,
	n string,
	v *config.UserVariable,
	result map[string]ast.Variable) error {
	i.VariableValuesLock.Lock()
	defer i.VariableValuesLock.Unlock()
	val, ok := i.VariableValues[v.Name]
	if ok {
		varValue, err := hil.InterfaceToVariable(val)
		if err != nil {
			return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s",
				v.Name, val, err)
		}
		result[n] = varValue
		return nil
	}

	if _, ok := result[n]; !ok && i.Operation == walkValidate {
		result[n] = unknownVariable()
		return nil
	}

	// Look up if we have any variables with this prefix because
	// those are map overrides. Include those.
	for k, val := range i.VariableValues {
		if strings.HasPrefix(k, v.Name+".") {
			keyComponents := strings.Split(k, ".")
			overrideKey := keyComponents[len(keyComponents)-1]

			mapInterface, ok := result["var."+v.Name]
			if !ok {
				return fmt.Errorf("override for non-existent variable: %s", v.Name)
			}

			mapVariable := mapInterface.Value.(map[string]ast.Variable)

			varValue, err := hil.InterfaceToVariable(val)
			if err != nil {
				return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s",
					v.Name, val, err)
			}
			mapVariable[overrideKey] = varValue
		}
	}

	return nil
}
Пример #2
0
func (i *Interpolater) interpolateComplexTypeAttribute(
	resourceID string,
	attributes map[string]string) (ast.Variable, error) {

	attr := attributes[resourceID+".#"]
	log.Printf("[DEBUG] Interpolating computed complex type attribute %s (%s)",
		resourceID, attr)

	// In Terraform's internal dotted representation of list-like attributes, the
	// ".#" count field is marked as unknown to indicate "this whole list is
	// unknown". We must honor that meaning here so computed references can be
	// treated properly during the plan phase.
	if attr == config.UnknownVariableValue {
		return unknownVariable(), nil
	}

	// At this stage we don't know whether the item is a list or a map, so we
	// examine the keys to see whether they are all numeric.
	var numericKeys []string
	var allKeys []string
	numberedListKey := regexp.MustCompile("^" + resourceID + "\\.[0-9]+$")
	otherListKey := regexp.MustCompile("^" + resourceID + "\\.([^#]+)$")
	for id, _ := range attributes {
		if numberedListKey.MatchString(id) {
			numericKeys = append(numericKeys, id)
		}
		if submatches := otherListKey.FindAllStringSubmatch(id, -1); len(submatches) > 0 {
			allKeys = append(allKeys, submatches[0][1])
		}
	}

	if len(numericKeys) == len(allKeys) {
		// This is a list
		var members []string
		for _, key := range numericKeys {
			members = append(members, attributes[key])
		}
		sort.Strings(members)
		return hil.InterfaceToVariable(members)
	} else {
		// This is a map
		members := make(map[string]interface{})
		for _, key := range allKeys {
			members[key] = attributes[resourceID+"."+key]
		}
		return hil.InterfaceToVariable(members)
	}
}
Пример #3
0
// interpolationFuncValues implements the "values" function that yields a list of
// keys of map types within a Terraform configuration.
func interpolationFuncValues(vs map[string]ast.Variable) ast.Function {
	return ast.Function{
		ArgTypes:   []ast.Type{ast.TypeMap},
		ReturnType: ast.TypeList,
		Callback: func(args []interface{}) (interface{}, error) {
			mapVar := args[0].(map[string]ast.Variable)
			keys := make([]string, 0)

			for k, _ := range mapVar {
				keys = append(keys, k)
			}

			sort.Strings(keys)

			values := make([]string, len(keys))
			for index, key := range keys {
				if value, ok := mapVar[key].Value.(string); ok {
					values[index] = value
				} else {
					return "", fmt.Errorf("values(): %q has element with bad type %s",
						key, mapVar[key].Type)
				}
			}

			variable, err := hil.InterfaceToVariable(values)
			if err != nil {
				return nil, err
			}

			return variable.Value, nil
		},
	}
}
Пример #4
0
// Values returns the values for all the variables in the given map.
func (i *Interpolater) Values(
	scope *InterpolationScope,
	vars map[string]config.InterpolatedVariable) (map[string]ast.Variable, error) {
	if scope == nil {
		scope = &InterpolationScope{}
	}

	result := make(map[string]ast.Variable, len(vars))

	// Copy the default variables
	if i.Module != nil && scope != nil {
		mod := i.Module
		if len(scope.Path) > 1 {
			mod = i.Module.Child(scope.Path[1:])
		}
		for _, v := range mod.Config().Variables {
			// Set default variables
			if v.Default == nil {
				continue
			}

			n := fmt.Sprintf("var.%s", v.Name)
			variable, err := hil.InterfaceToVariable(v.Default)
			if err != nil {
				return nil, fmt.Errorf("invalid default map value for %s: %v", v.Name, v.Default)
			}

			result[n] = variable
		}
	}

	for n, rawV := range vars {
		var err error
		switch v := rawV.(type) {
		case *config.CountVariable:
			err = i.valueCountVar(scope, n, v, result)
		case *config.ModuleVariable:
			err = i.valueModuleVar(scope, n, v, result)
		case *config.PathVariable:
			err = i.valuePathVar(scope, n, v, result)
		case *config.ResourceVariable:
			err = i.valueResourceVar(scope, n, v, result)
		case *config.SelfVariable:
			err = i.valueSelfVar(scope, n, v, result)
		case *config.SimpleVariable:
			err = i.valueSimpleVar(scope, n, v, result)
		case *config.UserVariable:
			err = i.valueUserVar(scope, n, v, result)
		default:
			err = fmt.Errorf("%s: unknown variable type: %T", n, rawV)
		}

		if err != nil {
			return nil, err
		}
	}

	return result, nil
}
Пример #5
0
func (i *Interpolater) valueModuleVar(
	scope *InterpolationScope,
	n string,
	v *config.ModuleVariable,
	result map[string]ast.Variable) error {

	// Build the path to the child module we want
	path := make([]string, len(scope.Path), len(scope.Path)+1)
	copy(path, scope.Path)
	path = append(path, v.Name)

	// Grab the lock so that if other interpolations are running or
	// state is being modified, we'll be safe.
	i.StateLock.RLock()
	defer i.StateLock.RUnlock()

	// Get the module where we're looking for the value
	mod := i.State.ModuleByPath(path)
	if mod == nil {
		// If the module doesn't exist, then we can return an empty string.
		// This happens usually only in Refresh() when we haven't populated
		// a state. During validation, we semantically verify that all
		// modules reference other modules, and graph ordering should
		// ensure that the module is in the state, so if we reach this
		// point otherwise it really is a panic.
		result[n] = unknownVariable()

		// During apply this is always an error
		if i.Operation == walkApply {
			return fmt.Errorf(
				"Couldn't find module %q for var: %s",
				v.Name, v.FullKey())
		}
	} else {
		// Get the value from the outputs
		if outputState, ok := mod.Outputs[v.Field]; ok {
			output, err := hil.InterfaceToVariable(outputState.Value)
			if err != nil {
				return err
			}
			result[n] = output
		} else {
			// Same reasons as the comment above.
			result[n] = unknownVariable()

			// During apply this is always an error
			if i.Operation == walkApply {
				return fmt.Errorf(
					"Couldn't find output %q for module var: %s",
					v.Field, v.FullKey())
			}
		}
	}

	return nil
}
Пример #6
0
func (i *Interpolater) interpolateComplexTypeAttribute(
	resourceID string,
	attributes map[string]string) (ast.Variable, error) {

	// We can now distinguish between lists and maps in state by the count field:
	//    - lists (and by extension, sets) use the traditional .# notation
	//    - maps use the newer .% notation
	// Consequently here we can decide how to deal with the keys appropriately
	// based on whether the type is a map of list.
	if lengthAttr, isList := attributes[resourceID+".#"]; isList {
		log.Printf("[DEBUG] Interpolating computed list element attribute %s (%s)",
			resourceID, lengthAttr)

		// In Terraform's internal dotted representation of list-like attributes, the
		// ".#" count field is marked as unknown to indicate "this whole list is
		// unknown". We must honor that meaning here so computed references can be
		// treated properly during the plan phase.
		if lengthAttr == config.UnknownVariableValue {
			return unknownVariable(), nil
		}

		expanded := flatmap.Expand(attributes, resourceID)
		return hil.InterfaceToVariable(expanded)
	}

	if lengthAttr, isMap := attributes[resourceID+".%"]; isMap {
		log.Printf("[DEBUG] Interpolating computed map element attribute %s (%s)",
			resourceID, lengthAttr)

		// In Terraform's internal dotted representation of map attributes, the
		// ".%" count field is marked as unknown to indicate "this whole list is
		// unknown". We must honor that meaning here so computed references can be
		// treated properly during the plan phase.
		if lengthAttr == config.UnknownVariableValue {
			return unknownVariable(), nil
		}

		expanded := flatmap.Expand(attributes, resourceID)
		return hil.InterfaceToVariable(expanded)
	}

	return ast.Variable{}, fmt.Errorf("No complex type %s found", resourceID)
}
Пример #7
0
// interpolationFuncMap creates a map from the parameters passed
// to it.
func interpolationFuncMap() ast.Function {
	return ast.Function{
		ArgTypes:     []ast.Type{},
		ReturnType:   ast.TypeMap,
		Variadic:     true,
		VariadicType: ast.TypeAny,
		Callback: func(args []interface{}) (interface{}, error) {
			outputMap := make(map[string]ast.Variable)

			if len(args)%2 != 0 {
				return nil, fmt.Errorf("requires an even number of arguments, got %d", len(args))
			}

			var firstType *ast.Type
			for i := 0; i < len(args); i += 2 {
				key, ok := args[i].(string)
				if !ok {
					return nil, fmt.Errorf("argument %d represents a key, so it must be a string", i+1)
				}
				val := args[i+1]
				variable, err := hil.InterfaceToVariable(val)
				if err != nil {
					return nil, err
				}
				// Enforce map type homogeneity
				if firstType == nil {
					firstType = &variable.Type
				} else if variable.Type != *firstType {
					return nil, fmt.Errorf("all map values must have the same type, got %s then %s", firstType.Printable(), variable.Type.Printable())
				}
				// Check for duplicate keys
				if _, ok := outputMap[key]; ok {
					return nil, fmt.Errorf("argument %d is a duplicate key: %q", i+1, key)
				}
				outputMap[key] = variable
			}

			return outputMap, nil
		},
	}
}
Пример #8
0
func interfaceToVariableSwallowError(input interface{}) ast.Variable {
	variable, _ := hil.InterfaceToVariable(input)
	return variable
}
Пример #9
0
func TestResourceConfigGetRaw(t *testing.T) {
	cases := []struct {
		Config map[string]interface{}
		Vars   map[string]interface{}
		Key    string
		Value  interface{}
	}{
		// Referencing a list-of-maps variable doesn't work from GetRaw.
		// The ConfigFieldReader currently catches this case and looks up the
		// variable in the config.
		{
			Vars: map[string]interface{}{
				"maplist": []interface{}{
					map[string]interface{}{
						"key": "a",
					},
					map[string]interface{}{
						"key": "b",
					},
				},
			},
			Config: map[string]interface{}{
				"maplist": "${var.maplist}",
			},
			Key:   "maplist.0",
			Value: nil,
		},
		// Reference a map-of-lists variable.
		// The ConfigFieldReader currently catches this case and looks up the
		// variable in the config.
		{
			Vars: map[string]interface{}{
				"listmap": map[string]interface{}{
					"key1": []interface{}{"a", "b"},
					"key2": []interface{}{"c", "d"},
				},
			},
			Config: map[string]interface{}{
				"listmap": "${var.listmap}",
			},
			Key:   "listmap.key1",
			Value: nil,
		},
	}

	for i, tc := range cases {
		var rawC *config.RawConfig
		if tc.Config != nil {
			var err error
			rawC, err = config.NewRawConfig(tc.Config)
			if err != nil {
				t.Fatalf("err: %s", err)
			}
		}

		if tc.Vars != nil {
			vs := make(map[string]ast.Variable)
			for k, v := range tc.Vars {
				hilVar, err := hil.InterfaceToVariable(v)
				if err != nil {
					t.Fatalf("%#v to var: %s", v, err)
				}
				vs["var."+k] = hilVar
			}
			if err := rawC.Interpolate(vs); err != nil {
				t.Fatalf("err: %s", err)
			}
		}

		rc := NewResourceConfig(rawC)
		rc.interpolateForce()

		// Test getting a key
		t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) {
			v, ok := rc.GetRaw(tc.Key)
			if ok && v == nil {
				t.Fatal("(nil, true) returned from GetRaw")
			}

			if !reflect.DeepEqual(v, tc.Value) {
				t.Fatalf("%d bad: %#v", i, v)
			}
		})
	}
}
Пример #10
0
func (i *Interpolater) interpolateComplexTypeAttribute(
	resourceID string,
	attributes map[string]string) (ast.Variable, error) {

	// We can now distinguish between lists and maps in state by the count field:
	//    - lists (and by extension, sets) use the traditional .# notation
	//    - maps use the newer .% notation
	// Consequently here we can decide how to deal with the keys appropriately
	// based on whether the type is a map of list.
	if lengthAttr, isList := attributes[resourceID+".#"]; isList {
		log.Printf("[DEBUG] Interpolating computed list element attribute %s (%s)",
			resourceID, lengthAttr)

		// In Terraform's internal dotted representation of list-like attributes, the
		// ".#" count field is marked as unknown to indicate "this whole list is
		// unknown". We must honor that meaning here so computed references can be
		// treated properly during the plan phase.
		if lengthAttr == config.UnknownVariableValue {
			return unknownVariable(), nil
		}

		keys := make([]string, 0)
		listElementKey := regexp.MustCompile("^" + resourceID + "\\.[0-9]+$")
		for id, _ := range attributes {
			if listElementKey.MatchString(id) {
				keys = append(keys, id)
			}
		}
		sort.Strings(keys)

		var members []string
		for _, key := range keys {
			members = append(members, attributes[key])
		}

		return hil.InterfaceToVariable(members)
	}

	if lengthAttr, isMap := attributes[resourceID+".%"]; isMap {
		log.Printf("[DEBUG] Interpolating computed map element attribute %s (%s)",
			resourceID, lengthAttr)

		// In Terraform's internal dotted representation of map attributes, the
		// ".%" count field is marked as unknown to indicate "this whole list is
		// unknown". We must honor that meaning here so computed references can be
		// treated properly during the plan phase.
		if lengthAttr == config.UnknownVariableValue {
			return unknownVariable(), nil
		}

		resourceFlatMap := make(map[string]string)
		mapElementKey := regexp.MustCompile("^" + resourceID + "\\.([^%]+)$")
		for id, val := range attributes {
			if mapElementKey.MatchString(id) {
				resourceFlatMap[id] = val
			}
		}

		expanded := flatmap.Expand(resourceFlatMap, resourceID)
		return hil.InterfaceToVariable(expanded)
	}

	return ast.Variable{}, fmt.Errorf("No complex type %s found", resourceID)
}
Пример #11
0
func (i *Interpolater) computeResourceMultiVariable(
	scope *InterpolationScope,
	v *config.ResourceVariable) (*ast.Variable, error) {
	i.StateLock.RLock()
	defer i.StateLock.RUnlock()

	unknownVariable := unknownVariable()

	// Get the information about this resource variable, and verify
	// that it exists and such.
	module, cr, err := i.resourceVariableInfo(scope, v)
	if err != nil {
		return nil, err
	}

	// Get the count so we know how many to iterate over
	count, err := cr.Count()
	if err != nil {
		return nil, fmt.Errorf(
			"Error reading %s count: %s",
			v.ResourceId(),
			err)
	}

	// If we have no module in the state yet or count, return empty
	if module == nil || len(module.Resources) == 0 || count == 0 {
		return &ast.Variable{Type: ast.TypeList, Value: []ast.Variable{}}, nil
	}

	var values []string
	for j := 0; j < count; j++ {
		id := fmt.Sprintf("%s.%d", v.ResourceId(), j)

		// If we're dealing with only a single resource, then the
		// ID doesn't have a trailing index.
		if count == 1 {
			id = v.ResourceId()
		}

		r, ok := module.Resources[id]
		if !ok {
			continue
		}

		if r.Primary == nil {
			continue
		}

		if singleAttr, ok := r.Primary.Attributes[v.Field]; ok {
			if singleAttr == config.UnknownVariableValue {
				return &unknownVariable, nil
			}

			values = append(values, singleAttr)
			continue
		}

		// computed list attribute
		_, ok = r.Primary.Attributes[v.Field+".#"]
		if !ok {
			continue
		}
		multiAttr, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes)
		if err != nil {
			return nil, err
		}

		if multiAttr == unknownVariable {
			return &ast.Variable{Type: ast.TypeString, Value: ""}, nil
		}

		for _, element := range multiAttr.Value.([]ast.Variable) {
			strVal := element.Value.(string)
			if strVal == config.UnknownVariableValue {
				return &unknownVariable, nil
			}

			values = append(values, strVal)
		}
	}

	if len(values) == 0 {
		// If the operation is refresh, it isn't an error for a value to
		// be unknown. Instead, we return that the value is computed so
		// that the graph can continue to refresh other nodes. It doesn't
		// matter because the config isn't interpolated anyways.
		//
		// For a Destroy, we're also fine with computed values, since our goal is
		// only to get destroy nodes for existing resources.
		//
		// For an input walk, computed values are okay to return because we're only
		// looking for missing variables to prompt the user for.
		if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput {
			return &unknownVariable, nil
		}

		return nil, fmt.Errorf(
			"Resource '%s' does not have attribute '%s' "+
				"for variable '%s'",
			v.ResourceId(),
			v.Field,
			v.FullKey())
	}

	variable, err := hil.InterfaceToVariable(values)
	return &variable, err
}
Пример #12
0
func TestResourceConfigIsComputed(t *testing.T) {
	cases := []struct {
		Name   string
		Config map[string]interface{}
		Vars   map[string]interface{}
		Key    string
		Result bool
	}{
		{
			Name: "basic value",
			Config: map[string]interface{}{
				"foo": "${var.foo}",
			},
			Vars: map[string]interface{}{
				"foo": unknownValue(),
			},
			Key:    "foo",
			Result: true,
		},

		{
			Name: "set with a computed element",
			Config: map[string]interface{}{
				"foo": "${var.foo}",
			},
			Vars: map[string]interface{}{
				"foo": []string{
					"a",
					unknownValue(),
				},
			},
			Key:    "foo",
			Result: true,
		},

		{
			Name: "set with no computed elements",
			Config: map[string]interface{}{
				"foo": "${var.foo}",
			},
			Vars: map[string]interface{}{
				"foo": []string{
					"a",
					"b",
				},
			},
			Key:    "foo",
			Result: false,
		},

		/*
			{
				Name: "set count with computed elements",
				Config: map[string]interface{}{
					"foo": "${var.foo}",
				},
				Vars: map[string]interface{}{
					"foo": []string{
						"a",
						unknownValue(),
					},
				},
				Key:    "foo.#",
				Result: true,
			},
		*/

		{
			Name: "set count with computed elements",
			Config: map[string]interface{}{
				"foo": []interface{}{"${var.foo}"},
			},
			Vars: map[string]interface{}{
				"foo": []string{
					"a",
					unknownValue(),
				},
			},
			Key:    "foo.#",
			Result: true,
		},

		{
			Name: "nested set with computed elements",
			Config: map[string]interface{}{
				"route": []map[string]interface{}{
					map[string]interface{}{
						"index":   "1",
						"gateway": []interface{}{"${var.foo}"},
					},
				},
			},
			Vars: map[string]interface{}{
				"foo": unknownValue(),
			},
			Key:    "route.0.gateway",
			Result: true,
		},
	}

	for i, tc := range cases {
		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
			var rawC *config.RawConfig
			if tc.Config != nil {
				var err error
				rawC, err = config.NewRawConfig(tc.Config)
				if err != nil {
					t.Fatalf("err: %s", err)
				}
			}

			if tc.Vars != nil {
				vs := make(map[string]ast.Variable)
				for k, v := range tc.Vars {
					hilVar, err := hil.InterfaceToVariable(v)
					if err != nil {
						t.Fatalf("%#v to var: %s", v, err)
					}

					vs["var."+k] = hilVar
				}

				if err := rawC.Interpolate(vs); err != nil {
					t.Fatalf("err: %s", err)
				}
			}

			rc := NewResourceConfig(rawC)
			rc.interpolateForce()

			t.Logf("Config: %#v", rc)

			actual := rc.IsComputed(tc.Key)
			if actual != tc.Result {
				t.Fatalf("bad: %#v", actual)
			}
		})
	}
}
Пример #13
0
func (i *Interpolater) computeResourceMultiVariable(
	scope *InterpolationScope,
	v *config.ResourceVariable) (*ast.Variable, error) {
	i.StateLock.RLock()
	defer i.StateLock.RUnlock()

	unknownVariable := unknownVariable()

	// Get the information about this resource variable, and verify
	// that it exists and such.
	module, cr, err := i.resourceVariableInfo(scope, v)
	if err != nil {
		return nil, err
	}

	// Get the keys for all the resources that are created for this resource
	countMax, err := i.resourceCountMax(module, cr, v)
	if err != nil {
		return nil, err
	}

	// If count is zero, we return an empty list
	if countMax == 0 {
		return &ast.Variable{Type: ast.TypeList, Value: []ast.Variable{}}, nil
	}

	// If we have no module in the state yet or count, return unknown
	if module == nil || len(module.Resources) == 0 {
		return &unknownVariable, nil
	}

	var values []interface{}
	for idx := 0; idx < countMax; idx++ {
		id := fmt.Sprintf("%s.%d", v.ResourceId(), idx)

		// ID doesn't have a trailing index. We try both here, but if a value
		// without a trailing index is found we prefer that. This choice
		// is for legacy reasons: older versions of TF preferred it.
		if id == v.ResourceId()+".0" {
			potential := v.ResourceId()
			if _, ok := module.Resources[potential]; ok {
				id = potential
			}
		}

		r, ok := module.Resources[id]
		if !ok {
			continue
		}

		if r.Primary == nil {
			continue
		}

		if singleAttr, ok := r.Primary.Attributes[v.Field]; ok {
			if singleAttr == config.UnknownVariableValue {
				return &unknownVariable, nil
			}

			values = append(values, singleAttr)
			continue
		}

		// computed list or map attribute
		_, isList := r.Primary.Attributes[v.Field+".#"]
		_, isMap := r.Primary.Attributes[v.Field+".%"]
		if !(isList || isMap) {
			continue
		}
		multiAttr, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes)
		if err != nil {
			return nil, err
		}

		if multiAttr == unknownVariable {
			return &unknownVariable, nil
		}

		values = append(values, multiAttr)
	}

	if len(values) == 0 {
		// If the operation is refresh, it isn't an error for a value to
		// be unknown. Instead, we return that the value is computed so
		// that the graph can continue to refresh other nodes. It doesn't
		// matter because the config isn't interpolated anyways.
		//
		// For a Destroy, we're also fine with computed values, since our goal is
		// only to get destroy nodes for existing resources.
		//
		// For an input walk, computed values are okay to return because we're only
		// looking for missing variables to prompt the user for.
		if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput {
			return &unknownVariable, nil
		}

		return nil, fmt.Errorf(
			"Resource '%s' does not have attribute '%s' "+
				"for variable '%s'",
			v.ResourceId(),
			v.Field,
			v.FullKey())
	}

	variable, err := hil.InterfaceToVariable(values)
	return &variable, err
}
Пример #14
0
func TestResourceConfigGet(t *testing.T) {
	cases := []struct {
		Config map[string]interface{}
		Vars   map[string]interface{}
		Key    string
		Value  interface{}
	}{
		{
			Config: nil,
			Key:    "foo",
			Value:  nil,
		},

		{
			Config: map[string]interface{}{
				"foo": "bar",
			},
			Key:   "foo",
			Value: "bar",
		},

		{
			Config: map[string]interface{}{
				"foo": "${var.foo}",
			},
			Key:   "foo",
			Value: "${var.foo}",
		},

		{
			Config: map[string]interface{}{
				"foo": "${var.foo}",
			},
			Vars:  map[string]interface{}{"foo": unknownValue()},
			Key:   "foo",
			Value: "${var.foo}",
		},

		{
			Config: map[string]interface{}{
				"foo": []interface{}{1, 2, 5},
			},
			Key:   "foo.0",
			Value: 1,
		},

		{
			Config: map[string]interface{}{
				"foo": []interface{}{1, 2, 5},
			},
			Key:   "foo.5",
			Value: nil,
		},

		// get from map
		{
			Config: map[string]interface{}{
				"mapname": []map[string]interface{}{
					map[string]interface{}{"key": 1},
				},
			},
			Key:   "mapname.0.key",
			Value: 1,
		},

		// get from map with dot in key
		{
			Config: map[string]interface{}{
				"mapname": []map[string]interface{}{
					map[string]interface{}{"key.name": 1},
				},
			},
			Key:   "mapname.0.key.name",
			Value: 1,
		},

		// get from map with overlapping key names
		{
			Config: map[string]interface{}{
				"mapname": []map[string]interface{}{
					map[string]interface{}{
						"key.name":   1,
						"key.name.2": 2,
					},
				},
			},
			Key:   "mapname.0.key.name.2",
			Value: 2,
		},
		{
			Config: map[string]interface{}{
				"mapname": []map[string]interface{}{
					map[string]interface{}{
						"key.name":     1,
						"key.name.foo": 2,
					},
				},
			},
			Key:   "mapname.0.key.name",
			Value: 1,
		},
		{
			Config: map[string]interface{}{
				"mapname": []map[string]interface{}{
					map[string]interface{}{
						"listkey": []map[string]interface{}{
							{"key": 3},
						},
					},
				},
			},
			Key:   "mapname.0.listkey.0.key",
			Value: 3,
		},
		// FIXME: this is ambiguous, and matches the nested map
		//        leaving here to catch this behaviour if it changes.
		{
			Config: map[string]interface{}{
				"mapname": []map[string]interface{}{
					map[string]interface{}{
						"key.name":   1,
						"key.name.0": 2,
						"key":        map[string]interface{}{"name": 3},
					},
				},
			},
			Key:   "mapname.0.key.name",
			Value: 3,
		},
		/*
			// TODO: can't access this nested list at all.
			// FIXME: key with name matching substring of nested list can panic
			{
				Config: map[string]interface{}{
					"mapname": []map[string]interface{}{
						map[string]interface{}{
							"key.name": []map[string]interface{}{
								{"subkey": 1},
							},
							"key": 3,
						},
					},
				},
				Key:   "mapname.0.key.name.0.subkey",
				Value: 3,
			},
		*/
	}

	for i, tc := range cases {
		var rawC *config.RawConfig
		if tc.Config != nil {
			var err error
			rawC, err = config.NewRawConfig(tc.Config)
			if err != nil {
				t.Fatalf("err: %s", err)
			}
		}

		if tc.Vars != nil {
			vs := make(map[string]ast.Variable)
			for k, v := range tc.Vars {
				hilVar, err := hil.InterfaceToVariable(v)
				if err != nil {
					t.Fatalf("%#v to var: %s", v, err)
				}

				vs["var."+k] = hilVar
			}

			if err := rawC.Interpolate(vs); err != nil {
				t.Fatalf("err: %s", err)
			}
		}

		rc := NewResourceConfig(rawC)
		rc.interpolateForce()

		// Test getting a key
		t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) {
			v, _ := rc.Get(tc.Key)
			if !reflect.DeepEqual(v, tc.Value) {
				t.Fatalf("%d bad: %#v", i, v)
			}
		})

		// If we have vars, we don't test copying
		if len(tc.Vars) > 0 {
			continue
		}

		// Test copying and equality
		t.Run(fmt.Sprintf("copy-and-equal-%d", i), func(t *testing.T) {
			copy := rc.DeepCopy()
			if !reflect.DeepEqual(copy, rc) {
				t.Fatalf("bad:\n\n%#v\n\n%#v", copy, rc)
			}

			if !copy.Equal(rc) {
				t.Fatalf("copy != rc:\n\n%#v\n\n%#v", copy, rc)
			}
			if !rc.Equal(copy) {
				t.Fatalf("rc != copy:\n\n%#v\n\n%#v", copy, rc)
			}
		})
	}
}
Пример #15
0
func (i *Interpolater) computeResourceVariable(
	scope *InterpolationScope,
	v *config.ResourceVariable) (*ast.Variable, error) {
	id := v.ResourceId()
	if v.Multi {
		id = fmt.Sprintf("%s.%d", id, v.Index)
	}

	i.StateLock.RLock()
	defer i.StateLock.RUnlock()

	unknownVariable := unknownVariable()

	// These variables must be declared early because of the use of GOTO
	var isList bool
	var isMap bool

	// Get the information about this resource variable, and verify
	// that it exists and such.
	module, cr, err := i.resourceVariableInfo(scope, v)
	if err != nil {
		return nil, err
	}

	// If we're requesting "count" its a special variable that we grab
	// directly from the config itself.
	if v.Field == "count" {
		var count int
		if cr != nil {
			count, err = cr.Count()
		} else {
			count, err = i.resourceCountMax(module, cr, v)
		}
		if err != nil {
			return nil, fmt.Errorf(
				"Error reading %s count: %s",
				v.ResourceId(),
				err)
		}

		return &ast.Variable{Type: ast.TypeInt, Value: count}, nil
	}

	// Get the resource out from the state. We know the state exists
	// at this point and if there is a state, we expect there to be a
	// resource with the given name.
	var r *ResourceState
	if module != nil && len(module.Resources) > 0 {
		var ok bool
		r, ok = module.Resources[id]
		if !ok && v.Multi && v.Index == 0 {
			r, ok = module.Resources[v.ResourceId()]
		}
		if !ok {
			r = nil
		}
	}
	if r == nil || r.Primary == nil {
		if i.Operation == walkApply || i.Operation == walkPlan {
			return nil, fmt.Errorf(
				"Resource '%s' not found for variable '%s'",
				v.ResourceId(),
				v.FullKey())
		}

		// If we have no module in the state yet or count, return empty.
		// NOTE(@mitchellh): I actually don't know why this is here. During
		// a refactor I kept this here to maintain the same behavior, but
		// I'm not sure why its here.
		if module == nil || len(module.Resources) == 0 {
			return nil, nil
		}

		goto MISSING
	}

	if attr, ok := r.Primary.Attributes[v.Field]; ok {
		v, err := hil.InterfaceToVariable(attr)
		return &v, err
	}

	// computed list or map attribute
	_, isList = r.Primary.Attributes[v.Field+".#"]
	_, isMap = r.Primary.Attributes[v.Field+".%"]
	if isList || isMap {
		variable, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes)
		return &variable, err
	}

	// At apply time, we can't do the "maybe has it" check below
	// that we need for plans since parent elements might be computed.
	// Therefore, it is an error and we're missing the key.
	//
	// TODO: test by creating a state and configuration that is referencing
	// a non-existent variable "foo.bar" where the state only has "foo"
	// and verify plan works, but apply doesn't.
	if i.Operation == walkApply || i.Operation == walkDestroy {
		goto MISSING
	}

	// We didn't find the exact field, so lets separate the dots
	// and see if anything along the way is a computed set. i.e. if
	// we have "foo.0.bar" as the field, check to see if "foo" is
	// a computed list. If so, then the whole thing is computed.
	if parts := strings.Split(v.Field, "."); len(parts) > 1 {
		for i := 1; i < len(parts); i++ {
			// Lists and sets make this
			key := fmt.Sprintf("%s.#", strings.Join(parts[:i], "."))
			if attr, ok := r.Primary.Attributes[key]; ok {
				v, err := hil.InterfaceToVariable(attr)
				return &v, err
			}

			// Maps make this
			key = fmt.Sprintf("%s", strings.Join(parts[:i], "."))
			if attr, ok := r.Primary.Attributes[key]; ok {
				v, err := hil.InterfaceToVariable(attr)
				return &v, err
			}
		}
	}

MISSING:
	// Validation for missing interpolations should happen at a higher
	// semantic level. If we reached this point and don't have variables,
	// just return the computed value.
	if scope == nil && scope.Resource == nil {
		return &unknownVariable, nil
	}

	// If the operation is refresh, it isn't an error for a value to
	// be unknown. Instead, we return that the value is computed so
	// that the graph can continue to refresh other nodes. It doesn't
	// matter because the config isn't interpolated anyways.
	//
	// For a Destroy, we're also fine with computed values, since our goal is
	// only to get destroy nodes for existing resources.
	//
	// For an input walk, computed values are okay to return because we're only
	// looking for missing variables to prompt the user for.
	if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput {
		return &unknownVariable, nil
	}

	return nil, fmt.Errorf(
		"Resource '%s' does not have attribute '%s' "+
			"for variable '%s'",
		id,
		v.Field,
		v.FullKey())
}
Пример #16
0
func TestResourceConfigCheckSet(t *testing.T) {
	cases := []struct {
		Name   string
		Config map[string]interface{}
		Vars   map[string]interface{}
		Input  []string
		Errs   bool
	}{
		{
			Name: "computed basic",
			Config: map[string]interface{}{
				"foo": "${var.foo}",
			},
			Vars: map[string]interface{}{
				"foo": unknownValue(),
			},
			Input: []string{"foo"},
			Errs:  false,
		},

		{
			Name: "basic",
			Config: map[string]interface{}{
				"foo": "bar",
			},
			Vars:  nil,
			Input: []string{"foo"},
			Errs:  false,
		},

		{
			Name: "basic with not set",
			Config: map[string]interface{}{
				"foo": "bar",
			},
			Vars:  nil,
			Input: []string{"foo", "bar"},
			Errs:  true,
		},

		{
			Name: "basic with one computed",
			Config: map[string]interface{}{
				"foo": "bar",
				"bar": "${var.foo}",
			},
			Vars: map[string]interface{}{
				"foo": unknownValue(),
			},
			Input: []string{"foo", "bar"},
			Errs:  false,
		},
	}

	for i, tc := range cases {
		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
			var rawC *config.RawConfig
			if tc.Config != nil {
				var err error
				rawC, err = config.NewRawConfig(tc.Config)
				if err != nil {
					t.Fatalf("err: %s", err)
				}
			}

			if tc.Vars != nil {
				vs := make(map[string]ast.Variable)
				for k, v := range tc.Vars {
					hilVar, err := hil.InterfaceToVariable(v)
					if err != nil {
						t.Fatalf("%#v to var: %s", v, err)
					}

					vs["var."+k] = hilVar
				}

				if err := rawC.Interpolate(vs); err != nil {
					t.Fatalf("err: %s", err)
				}
			}

			rc := NewResourceConfig(rawC)
			rc.interpolateForce()

			t.Logf("Config: %#v", rc)

			errs := rc.CheckSet(tc.Input)
			if tc.Errs != (len(errs) > 0) {
				t.Fatalf("bad: %#v", errs)
			}
		})
	}
}
Пример #17
0
func TestResourceConfigGet(t *testing.T) {
	cases := []struct {
		Config map[string]interface{}
		Vars   map[string]interface{}
		Key    string
		Value  interface{}
	}{
		{
			Config: nil,
			Key:    "foo",
			Value:  nil,
		},

		{
			Config: map[string]interface{}{
				"foo": "bar",
			},
			Key:   "foo",
			Value: "bar",
		},

		{
			Config: map[string]interface{}{
				"foo": "${var.foo}",
			},
			Key:   "foo",
			Value: "${var.foo}",
		},

		{
			Config: map[string]interface{}{
				"foo": "${var.foo}",
			},
			Vars:  map[string]interface{}{"foo": unknownValue()},
			Key:   "foo",
			Value: "${var.foo}",
		},

		{
			Config: map[string]interface{}{
				"foo": []interface{}{1, 2, 5},
			},
			Key:   "foo.0",
			Value: 1,
		},

		{
			Config: map[string]interface{}{
				"foo": []interface{}{1, 2, 5},
			},
			Key:   "foo.5",
			Value: nil,
		},

		// get from map
		{
			Config: map[string]interface{}{
				"mapname": []map[string]interface{}{
					map[string]interface{}{"key": 1},
				},
			},
			Key:   "mapname.0.key",
			Value: 1,
		},

		// get from map with dot in key
		{
			Config: map[string]interface{}{
				"mapname": []map[string]interface{}{
					map[string]interface{}{"key.name": 1},
				},
			},
			Key:   "mapname.0.key.name",
			Value: 1,
		},

		// get from map with overlapping key names
		{
			Config: map[string]interface{}{
				"mapname": []map[string]interface{}{
					map[string]interface{}{
						"key.name":   1,
						"key.name.2": 2,
					},
				},
			},
			Key:   "mapname.0.key.name.2",
			Value: 2,
		},
		{
			Config: map[string]interface{}{
				"mapname": []map[string]interface{}{
					map[string]interface{}{
						"key.name":     1,
						"key.name.foo": 2,
					},
				},
			},
			Key:   "mapname.0.key.name",
			Value: 1,
		},
		{
			Config: map[string]interface{}{
				"mapname": []map[string]interface{}{
					map[string]interface{}{
						"listkey": []map[string]interface{}{
							{"key": 3},
						},
					},
				},
			},
			Key:   "mapname.0.listkey.0.key",
			Value: 3,
		},

		// A map assigned to a list via interpolation should Get a non-existent
		// value. The test code now also checks that Get doesn't return (nil,
		// true), which it previously did for this configuration.
		{
			Config: map[string]interface{}{
				"maplist": "${var.maplist}",
			},
			Key:   "maplist.0",
			Value: nil,
		},

		// Reference list of maps variable.
		// This does not work from GetRaw.
		{
			Vars: map[string]interface{}{
				"maplist": []interface{}{
					map[string]interface{}{
						"key": "a",
					},
					map[string]interface{}{
						"key": "b",
					},
				},
			},
			Config: map[string]interface{}{
				"maplist": "${var.maplist}",
			},
			Key:   "maplist.0",
			Value: map[string]interface{}{"key": "a"},
		},

		// Reference a map-of-lists variable.
		// This does not work from GetRaw.
		{
			Vars: map[string]interface{}{
				"listmap": map[string]interface{}{
					"key1": []interface{}{"a", "b"},
					"key2": []interface{}{"c", "d"},
				},
			},
			Config: map[string]interface{}{
				"listmap": "${var.listmap}",
			},
			Key:   "listmap.key1",
			Value: []interface{}{"a", "b"},
		},

		// FIXME: this is ambiguous, and matches the nested map
		//        leaving here to catch this behaviour if it changes.
		{
			Config: map[string]interface{}{
				"mapname": []map[string]interface{}{
					map[string]interface{}{
						"key.name":   1,
						"key.name.0": 2,
						"key":        map[string]interface{}{"name": 3},
					},
				},
			},
			Key:   "mapname.0.key.name",
			Value: 3,
		},
		/*
			// TODO: can't access this nested list at all.
			// FIXME: key with name matching substring of nested list can panic
			{
				Config: map[string]interface{}{
					"mapname": []map[string]interface{}{
						map[string]interface{}{
							"key.name": []map[string]interface{}{
								{"subkey": 1},
							},
							"key": 3,
						},
					},
				},
				Key:   "mapname.0.key.name.0.subkey",
				Value: 3,
			},
		*/
	}

	for i, tc := range cases {
		var rawC *config.RawConfig
		if tc.Config != nil {
			var err error
			rawC, err = config.NewRawConfig(tc.Config)
			if err != nil {
				t.Fatalf("err: %s", err)
			}
		}

		if tc.Vars != nil {
			vs := make(map[string]ast.Variable)
			for k, v := range tc.Vars {
				hilVar, err := hil.InterfaceToVariable(v)
				if err != nil {
					t.Fatalf("%#v to var: %s", v, err)
				}

				vs["var."+k] = hilVar
			}

			if err := rawC.Interpolate(vs); err != nil {
				t.Fatalf("err: %s", err)
			}
		}

		rc := NewResourceConfig(rawC)
		rc.interpolateForce()

		// Test getting a key
		t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) {
			v, ok := rc.Get(tc.Key)
			if ok && v == nil {
				t.Fatal("(nil, true) returned from Get")
			}

			if !reflect.DeepEqual(v, tc.Value) {
				t.Fatalf("%d bad: %#v", i, v)
			}
		})

		// If we have vars, we don't test copying
		if len(tc.Vars) > 0 {
			continue
		}

		// Test copying and equality
		t.Run(fmt.Sprintf("copy-and-equal-%d", i), func(t *testing.T) {
			copy := rc.DeepCopy()
			if !reflect.DeepEqual(copy, rc) {
				t.Fatalf("bad:\n\n%#v\n\n%#v", copy, rc)
			}

			if !copy.Equal(rc) {
				t.Fatalf("copy != rc:\n\n%#v\n\n%#v", copy, rc)
			}
			if !rc.Equal(copy) {
				t.Fatalf("rc != copy:\n\n%#v\n\n%#v", copy, rc)
			}
		})
	}
}