예제 #1
0
func (i *Interpolater) interpolateListAttribute(
	resourceID string,
	attributes map[string]string) (string, error) {

	attr := attributes[resourceID+".#"]
	log.Printf("[DEBUG] Interpolating computed list 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 attr, nil
	}

	// Otherwise we gather the values from the list-like attribute and return
	// them.
	var members []string
	numberedListMember := regexp.MustCompile("^" + resourceID + "\\.[0-9]+$")
	for id, value := range attributes {
		if numberedListMember.MatchString(id) {
			members = append(members, value)
		}
	}

	sort.Strings(members)
	return config.NewStringList(members).String(), nil
}
예제 #2
0
func (i *Interpolater) interpolateListAttribute(
	resourceID string,
	attributes map[string]string) (string, error) {

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

	var members []string
	numberedListMember := regexp.MustCompile("^" + resourceID + "\\.[0-9]+$")
	for id, value := range attributes {
		if numberedListMember.MatchString(id) {
			members = append(members, value)
		}
	}

	sort.Strings(members)
	return config.NewStringList(members).String(), nil
}
예제 #3
0
func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) {
	i := getInterpolaterFixture(t)
	scope := &InterpolationScope{
		Path: rootModulePath,
	}

	name_servers := []string{
		"ns-1334.awsdns-38.org",
		"ns-1680.awsdns-18.co.uk",
		"ns-498.awsdns-62.com",
		"ns-601.awsdns-11.net",
		"ns-000.awsdns-38.org",
		"ns-444.awsdns-18.co.uk",
		"ns-666.awsdns-11.net",
		"ns-999.awsdns-62.com",
	}

	// More than 1 element
	expectedNameServers := config.NewStringList(name_servers[0:4]).String()
	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers", ast.Variable{
		Value: expectedNameServers,
		Type:  ast.TypeString,
	})
	// More than 1 element in both
	expectedNameServers = config.NewStringList(name_servers).String()
	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers", ast.Variable{
		Value: expectedNameServers,
		Type:  ast.TypeString,
	})

	// Exactly 1 element
	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners", ast.Variable{
		Value: config.NewStringList([]string{"red"}).String(),
		Type:  ast.TypeString,
	})
	// Exactly 1 element in both
	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners", ast.Variable{
		Value: config.NewStringList([]string{"red", "blue"}).String(),
		Type:  ast.TypeString,
	})

	// Zero elements
	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing", ast.Variable{
		Value: config.NewStringList([]string{}).String(),
		Type:  ast.TypeString,
	})
	// Zero + zero elements
	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.nothing", ast.Variable{
		Value: config.NewStringList([]string{"", ""}).String(),
		Type:  ast.TypeString,
	})
	// Zero + 1 element
	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special", ast.Variable{
		Value: config.NewStringList([]string{"extra"}).String(),
		Type:  ast.TypeString,
	})

	// Maps still need to work
	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{
		Value: "reindeer",
		Type:  ast.TypeString,
	})
	// Maps still need to work in both
	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name", ast.Variable{
		Value: config.NewStringList([]string{"reindeer", "white-hart"}).String(),
		Type:  ast.TypeString,
	})
}
예제 #4
0
func TestInterpolator_resourceMultiAttributes(t *testing.T) {
	lock := new(sync.RWMutex)
	state := &State{
		Modules: []*ModuleState{
			&ModuleState{
				Path: rootModulePath,
				Resources: map[string]*ResourceState{
					"aws_route53_zone.yada": &ResourceState{
						Type:         "aws_route53_zone",
						Dependencies: []string{},
						Primary: &InstanceState{
							ID: "AAABBBCCCDDDEEE",
							Attributes: map[string]string{
								"name_servers.#": "4",
								"name_servers.0": "ns-1334.awsdns-38.org",
								"name_servers.1": "ns-1680.awsdns-18.co.uk",
								"name_servers.2": "ns-498.awsdns-62.com",
								"name_servers.3": "ns-601.awsdns-11.net",
								"listeners.#":    "1",
								"listeners.0":    "red",
								"tags.#":         "1",
								"tags.Name":      "reindeer",
								"nothing.#":      "0",
							},
						},
					},
				},
			},
		},
	}

	i := &Interpolater{
		Module:    testModule(t, "interpolate-multi-vars"),
		StateLock: lock,
		State:     state,
	}

	scope := &InterpolationScope{
		Path: rootModulePath,
	}

	name_servers := []string{
		"ns-1334.awsdns-38.org",
		"ns-1680.awsdns-18.co.uk",
		"ns-498.awsdns-62.com",
		"ns-601.awsdns-11.net",
	}
	expectedNameServers := config.NewStringList(name_servers).String()

	// More than 1 element
	testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{
		Value: expectedNameServers,
		Type:  ast.TypeString,
	})

	// Exactly 1 element
	testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners", ast.Variable{
		Value: config.NewStringList([]string{"red"}).String(),
		Type:  ast.TypeString,
	})

	// Zero elements
	testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing", ast.Variable{
		Value: config.NewStringList([]string{}).String(),
		Type:  ast.TypeString,
	})

	// Maps still need to work
	testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{
		Value: "reindeer",
		Type:  ast.TypeString,
	})
}
예제 #5
0
func (i *Interpolater) computeResourceMultiVariable(
	scope *InterpolationScope,
	v *config.ResourceVariable) (string, error) {
	i.StateLock.RLock()
	defer i.StateLock.RUnlock()

	// 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 "", err
	}

	// Get the count so we know how many to iterate over
	count, err := cr.Count()
	if err != nil {
		return "", 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 "", 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
		}

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

		if config.IsStringList(attr) {
			for _, s := range config.StringList(attr).Slice() {
				values = append(values, s)
			}
			continue
		}

		// If any value is unknown, the whole thing is unknown
		if attr == config.UnknownVariableValue {
			return config.UnknownVariableValue, nil
		}

		values = append(values, attr)
	}

	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 config.UnknownVariableValue, nil
		}

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

	return config.NewStringList(values).String(), nil
}
예제 #6
0
func (i *Interpolater) computeResourceMultiVariable(
	scope *InterpolationScope,
	v *config.ResourceVariable) (string, error) {
	i.StateLock.RLock()
	defer i.StateLock.RUnlock()

	// 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 "", err
	}

	// Get the count so we know how many to iterate over
	count, err := cr.Count()
	if err != nil {
		return "", 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 "", nil
	}

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

		// 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
		}

		attr, ok := r.Primary.Attributes[v.Field]
		if !ok {
			continue
		}

		values = append(values, attr)
	}

	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.
		if i.Operation == walkRefresh {
			return config.UnknownVariableValue, nil
		}

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

	return config.NewStringList(values).String(), nil
}