terraform-provider-google/google/compute_instance_helpers.go
Nic Cope 94a405d179 Add Alias IP and Guest Accelerator support to Instance Templates (#639)
* Move AliasIpRange helpers into utils

To reflect the fact they'll be used by multiple resources.

* Pass Config to build helpers, not meta

It's the only thing meta is used for.

* Refactor getNetwork util methods to return early for the happy path.

* Update compute APIs

compute.Instance.MinCpuPlatform is now GA.

* Fix panic in TestComputeInstanceMigrateState

This seemed to be a pre-existing issue, i.e. I could repro it in master.

--- FAIL: TestComputeInstanceMigrateState (0.00s)
panic: interface conversion: interface {} is nil, not *google.Config [recovered]
        panic: interface conversion: interface {} is nil, not *google.Config

goroutine 85 [running]:
testing.tRunner.func1(0xc4205d60f0)
        /usr/local/Cellar/go/1.9.1/libexec/src/testing/testing.go:711 +0x2d2
panic(0x203acc0, 0xc4205d2080)
        /usr/local/Cellar/go/1.9.1/libexec/src/runtime/panic.go:491 +0x283
github.com/terraform-providers/terraform-provider-google/google.migrateStateV3toV4(0xc4205f2000, 0x0, 0x0, 0x0, 0x48, 0xc4205f2000)
        /Users/negz/control/go/src/github.com/terraform-providers/terraform-provider-google/google/resource_compute_instance_migrate.go:182 +0x2405
github.com/terraform-providers/terraform-provider-google/google.resourceComputeInstanceMigrateState(0x2, 0xc4205f2000, 0x0, 0x0, 0x0, 0x0, 0xe0000000000)
        /Users/negz/control/go/src/github.com/terraform-providers/terraform-provider-google/google/resource_compute_instance_migrate.go:48 +0x21a
github.com/terraform-providers/terraform-provider-google/google.runInstanceMigrateTest(0xc4205d60f0, 0x2260816, 0x8, 0x227d23a, 0x20, 0x2, 0xc4205ec0f0, 0xc4205ec120, 0x0,
 0x0)
        /Users/negz/control/go/src/github.com/terraform-providers/terraform-provider-google/google/resource_compute_instance_migrate_test.go:803 +0xc1
github.com/terraform-providers/terraform-provider-google/google.TestComputeInstanceMigrateState(0xc4205d60f0)
        /Users/negz/control/go/src/github.com/terraform-providers/terraform-provider-google/google/resource_compute_instance_migrate_test.go:71 +0xc84
testing.tRunner(0xc4205d60f0, 0x22d81c0)
        /usr/local/Cellar/go/1.9.1/libexec/src/testing/testing.go:746 +0xd0
created by testing.(*T).Run
        /usr/local/Cellar/go/1.9.1/libexec/src/testing/testing.go:789 +0x2de
FAIL    github.com/terraform-providers/terraform-provider-google/google 0.035s

* Use only the v1 API for resource_compute_instance

Alias IP ranges, Accelerators, and min CPU platform are now GA.

* Move common instance code into utils.go

Methods used by both resource_compute_instance and
resource_compute_instance_template are currently spread between their respective
files, and utils.go.

This commit moves them all into utils.go for the sake of consistency. It may be
worth considering an instance_common.go file or similar.

* Unify compute_instance and compute_instance_template network_interface and service_account code

This has the side effect of enabling Alias IP range support for
compute_instance_templates.

* Add tests for compute instance template Alias IP ranges

* Mark instance template region as computed

We compute it from the subnet its network interfaces are in. Note this
is not new behaviour - I believe it was erroneously missing the computed
flag.

* Support guest accelerators for instance templates

Since most of the code is already there.

* Add a test for using 'address' rather than 'network_ip' for instance templates

* Don't mark assigned_nat_ip as deprecated

* Remove network_interface schema fields that don't make sense for a compute instance template

* Add newline after count in instance template docs

* Don't try to dedupe guest accelerator expansion code

The API calls to Google to create guest accelerators take different values
for instances and instance templates. Instance templates don't have a zone
and can thus *only* be passed a guest accelerator name.

* Use ParseNetworkFieldValue instead of getNetworkLink

* Add support for parsing regional fields, and subnetworks specifically

Currently unused because subnetworks may have a separate project from that
of the instance using them, which complicates looking up the project field.

* Fall back to provider region when parsing regional field values

Also slightly refactors getXFromSchema field helper functions for readability.

* Revert to assigned_nat_ip in compute instance docs

* Add beta scaffolding to compute instance and compute instance template

Note these resources don't currently use beta features - this is futureproofing.

* Fix indentation in comment about instance template alias IP ranges

* Consolidate metadata helper functions in metadata.go

* Move compute instance (and template) related helpers into their own file
2017-11-28 10:01:27 -08:00

222 lines
6.8 KiB
Go

package google
import (
"fmt"
"regexp"
"github.com/hashicorp/terraform/helper/schema"
computeBeta "google.golang.org/api/compute/v0.beta"
)
func expandAliasIpRanges(ranges []interface{}) []*computeBeta.AliasIpRange {
ipRanges := make([]*computeBeta.AliasIpRange, 0, len(ranges))
for _, raw := range ranges {
data := raw.(map[string]interface{})
ipRanges = append(ipRanges, &computeBeta.AliasIpRange{
IpCidrRange: data["ip_cidr_range"].(string),
SubnetworkRangeName: data["subnetwork_range_name"].(string),
})
}
return ipRanges
}
func flattenAliasIpRange(ranges []*computeBeta.AliasIpRange) []map[string]interface{} {
rangesSchema := make([]map[string]interface{}, 0, len(ranges))
for _, ipRange := range ranges {
rangesSchema = append(rangesSchema, map[string]interface{}{
"ip_cidr_range": ipRange.IpCidrRange,
"subnetwork_range_name": ipRange.SubnetworkRangeName,
})
}
return rangesSchema
}
func flattenScheduling(scheduling *computeBeta.Scheduling) []map[string]interface{} {
result := make([]map[string]interface{}, 0, 1)
schedulingMap := map[string]interface{}{
"on_host_maintenance": scheduling.OnHostMaintenance,
"preemptible": scheduling.Preemptible,
}
if scheduling.AutomaticRestart != nil {
schedulingMap["automatic_restart"] = *scheduling.AutomaticRestart
}
result = append(result, schedulingMap)
return result
}
func getProjectAndRegionFromSubnetworkLink(subnetwork string) (string, string) {
r := regexp.MustCompile(SubnetworkLinkRegex)
if !r.MatchString(subnetwork) {
return "", ""
}
matches := r.FindStringSubmatch(subnetwork)
return matches[1], matches[2]
}
func flattenAccessConfigs(accessConfigs []*computeBeta.AccessConfig) ([]map[string]interface{}, string) {
flattened := make([]map[string]interface{}, len(accessConfigs))
natIP := ""
for i, ac := range accessConfigs {
flattened[i] = map[string]interface{}{
"nat_ip": ac.NatIP,
"assigned_nat_ip": ac.NatIP,
}
if natIP == "" {
natIP = ac.NatIP
}
}
return flattened, natIP
}
func flattenNetworkInterfaces(networkInterfaces []*computeBeta.NetworkInterface) ([]map[string]interface{}, string, string, string) {
flattened := make([]map[string]interface{}, len(networkInterfaces))
var region, internalIP, externalIP string
for i, iface := range networkInterfaces {
var ac []map[string]interface{}
ac, externalIP = flattenAccessConfigs(iface.AccessConfigs)
var project string
project, region = getProjectAndRegionFromSubnetworkLink(iface.Subnetwork)
flattened[i] = map[string]interface{}{
"address": iface.NetworkIP,
"network_ip": iface.NetworkIP,
"network": iface.Network,
"subnetwork": iface.Subnetwork,
"subnetwork_project": project,
"access_config": ac,
"alias_ip_range": flattenAliasIpRange(iface.AliasIpRanges),
}
// Instance template interfaces never have names, so they're absent
// in the instance template network_interface schema. We want to use the
// same flattening code for both resource types, so we avoid trying to
// set the name field when it's not set at the GCE end.
if iface.Name != "" {
flattened[i]["name"] = iface.Name
}
if internalIP == "" {
internalIP = iface.NetworkIP
}
}
return flattened, region, internalIP, externalIP
}
func expandAccessConfigs(configs []interface{}) []*computeBeta.AccessConfig {
acs := make([]*computeBeta.AccessConfig, len(configs))
for i, raw := range configs {
data := raw.(map[string]interface{})
acs[i] = &computeBeta.AccessConfig{
Type: "ONE_TO_ONE_NAT",
NatIP: data["nat_ip"].(string),
}
}
return acs
}
func expandNetworkInterfaces(d *schema.ResourceData, config *Config) ([]*computeBeta.NetworkInterface, error) {
project, err := getProject(d, config)
if err != nil {
return nil, err
}
region, err := getRegion(d, config)
if err != nil {
return nil, err
}
configs := d.Get("network_interface").([]interface{})
ifaces := make([]*computeBeta.NetworkInterface, len(configs))
for i, raw := range configs {
data := raw.(map[string]interface{})
network := data["network"].(string)
subnetwork := data["subnetwork"].(string)
if (network == "" && subnetwork == "") || (network != "" && subnetwork != "") {
return nil, fmt.Errorf("exactly one of network or subnetwork must be provided")
}
nf, err := ParseNetworkFieldValue(network, d, config)
if err != nil {
return nil, fmt.Errorf("cannot determine selflink for subnetwork '%s': %s", subnetwork, err)
}
subnetworkProject := data["subnetwork_project"].(string)
subnetLink, err := getSubnetworkLink(config, project, region, subnetworkProject, subnetwork)
if err != nil {
return nil, fmt.Errorf("cannot determine selflink for subnetwork '%s': %s", subnetwork, err)
}
ifaces[i] = &computeBeta.NetworkInterface{
NetworkIP: data["network_ip"].(string),
Network: nf.RelativeLink(),
Subnetwork: subnetLink,
AccessConfigs: expandAccessConfigs(data["access_config"].([]interface{})),
AliasIpRanges: expandAliasIpRanges(data["alias_ip_range"].([]interface{})),
}
// network_ip is deprecated. We want address to win if both are set.
if data["address"].(string) != "" {
ifaces[i].NetworkIP = data["address"].(string)
}
}
return ifaces, nil
}
func flattenServiceAccounts(serviceAccounts []*computeBeta.ServiceAccount) []map[string]interface{} {
result := make([]map[string]interface{}, len(serviceAccounts))
for i, serviceAccount := range serviceAccounts {
result[i] = map[string]interface{}{
"email": serviceAccount.Email,
"scopes": schema.NewSet(stringScopeHashcode, convertStringArrToInterface(serviceAccount.Scopes)),
}
}
return result
}
func expandServiceAccounts(configs []interface{}) []*computeBeta.ServiceAccount {
accounts := make([]*computeBeta.ServiceAccount, len(configs))
for i, raw := range configs {
data := raw.(map[string]interface{})
accounts[i] = &computeBeta.ServiceAccount{
Email: data["email"].(string),
Scopes: canonicalizeServiceScopes(convertStringSet(data["scopes"].(*schema.Set))),
}
if accounts[i].Email == "" {
accounts[i].Email = "default"
}
}
return accounts
}
func flattenGuestAccelerators(accelerators []*computeBeta.AcceleratorConfig) []map[string]interface{} {
acceleratorsSchema := make([]map[string]interface{}, len(accelerators))
for i, accelerator := range accelerators {
acceleratorsSchema[i] = map[string]interface{}{
"count": accelerator.AcceleratorCount,
"type": accelerator.AcceleratorType,
}
}
return acceleratorsSchema
}
func resourceInstanceTags(d *schema.ResourceData) *computeBeta.Tags {
// Calculate the tags
var tags *computeBeta.Tags
if v := d.Get("tags"); v != nil {
vs := v.(*schema.Set)
tags = new(computeBeta.Tags)
tags.Items = make([]string, vs.Len())
for i, v := range vs.List() {
tags.Items[i] = v.(string)
}
tags.Fingerprint = d.Get("tags_fingerprint").(string)
}
return tags
}