mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-07-03 08:42:39 +00:00
![Nic Cope](/assets/img/avatar_default.png)
* 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
316 lines
10 KiB
Go
316 lines
10 KiB
Go
package google
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
)
|
|
|
|
const (
|
|
globalLinkTemplate = "projects/%s/global/%s/%s"
|
|
globalLinkBasePattern = "projects/(.+)/global/%s/(.+)"
|
|
zonalLinkTemplate = "projects/%s/zones/%s/%s/%s"
|
|
zonalLinkBasePattern = "projects/(.+)/zones/(.+)/%s/(.+)"
|
|
zonalPartialLinkBasePattern = "zones/(.+)/%s/(.+)"
|
|
regionalLinkTemplate = "projects/%s/regions/%s/%s/%s"
|
|
regionalLinkBasePattern = "projects/(.+)/regions/(.+)/%s/(.+)"
|
|
regionalPartialLinkBasePattern = "regions/(.+)/%s/(.+)"
|
|
organizationLinkTemplate = "organizations/%s/%s/%s"
|
|
organizationBasePattern = "organizations/(.+)/%s/(.+)"
|
|
)
|
|
|
|
// ------------------------------------------------------------
|
|
// Field helpers
|
|
// ------------------------------------------------------------
|
|
|
|
func ParseNetworkFieldValue(network string, d TerraformResourceData, config *Config) (*GlobalFieldValue, error) {
|
|
return parseGlobalFieldValue("networks", network, "project", d, config, true)
|
|
}
|
|
|
|
func ParseSubnetworkFieldValue(subnetwork string, d TerraformResourceData, config *Config) (*RegionalFieldValue, error) {
|
|
return parseRegionalFieldValue("subnetworks", subnetwork, "project", "region", d, config, true)
|
|
}
|
|
|
|
func ParseSslCertificateFieldValue(sslCertificate string, d TerraformResourceData, config *Config) (*GlobalFieldValue, error) {
|
|
return parseGlobalFieldValue("sslCertificates", sslCertificate, "project", d, config, false)
|
|
}
|
|
|
|
func ParseHttpHealthCheckFieldValue(healthCheck string, d TerraformResourceData, config *Config) (*GlobalFieldValue, error) {
|
|
return parseGlobalFieldValue("httpHealthChecks", healthCheck, "project", d, config, false)
|
|
}
|
|
|
|
func ParseDiskFieldValue(disk string, d TerraformResourceData, config *Config) (*ZonalFieldValue, error) {
|
|
return parseZonalFieldValue("disks", disk, "project", "zone", d, config, false)
|
|
}
|
|
|
|
func ParseOrganizationCustomRoleName(role string) (*OrganizationFieldValue, error) {
|
|
return parseOrganizationFieldValue("roles", role, false)
|
|
}
|
|
|
|
func ParseAcceleratorFieldValue(accelerator string, d TerraformResourceData, config *Config) (*ZonalFieldValue, error) {
|
|
return parseZonalFieldValue("acceleratorTypes", accelerator, "project", "zone", d, config, false)
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
// Base helpers used to create helpers for specific fields.
|
|
// ------------------------------------------------------------
|
|
|
|
type GlobalFieldValue struct {
|
|
Project string
|
|
Name string
|
|
|
|
resourceType string
|
|
}
|
|
|
|
func (f GlobalFieldValue) RelativeLink() string {
|
|
if len(f.Name) == 0 {
|
|
return ""
|
|
}
|
|
|
|
return fmt.Sprintf(globalLinkTemplate, f.Project, f.resourceType, f.Name)
|
|
}
|
|
|
|
// Parses a global field supporting 5 different formats:
|
|
// - https://www.googleapis.com/compute/ANY_VERSION/projects/{my_project}/global/{resource_type}/{resource_name}
|
|
// - projects/{my_project}/global/{resource_type}/{resource_name}
|
|
// - global/{resource_type}/{resource_name}
|
|
// - resource_name
|
|
// - "" (empty string). RelativeLink() returns empty if isEmptyValid is true.
|
|
//
|
|
// If the project is not specified, it first tries to get the project from the `projectSchemaField` and then fallback on the default project.
|
|
func parseGlobalFieldValue(resourceType, fieldValue, projectSchemaField string, d TerraformResourceData, config *Config, isEmptyValid bool) (*GlobalFieldValue, error) {
|
|
if len(fieldValue) == 0 {
|
|
if isEmptyValid {
|
|
return &GlobalFieldValue{resourceType: resourceType}, nil
|
|
}
|
|
return nil, fmt.Errorf("The global field for resource %s cannot be empty", resourceType)
|
|
}
|
|
|
|
r := regexp.MustCompile(fmt.Sprintf(globalLinkBasePattern, resourceType))
|
|
if parts := r.FindStringSubmatch(fieldValue); parts != nil {
|
|
return &GlobalFieldValue{
|
|
Project: parts[1],
|
|
Name: parts[2],
|
|
|
|
resourceType: resourceType,
|
|
}, nil
|
|
}
|
|
|
|
project, err := getProjectFromSchema(projectSchemaField, d, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &GlobalFieldValue{
|
|
Project: project,
|
|
Name: GetResourceNameFromSelfLink(fieldValue),
|
|
|
|
resourceType: resourceType,
|
|
}, nil
|
|
}
|
|
|
|
type ZonalFieldValue struct {
|
|
Project string
|
|
Zone string
|
|
Name string
|
|
|
|
resourceType string
|
|
}
|
|
|
|
func (f ZonalFieldValue) RelativeLink() string {
|
|
if len(f.Name) == 0 {
|
|
return ""
|
|
}
|
|
|
|
return fmt.Sprintf(zonalLinkTemplate, f.Project, f.Zone, f.resourceType, f.Name)
|
|
}
|
|
|
|
// Parses a zonal field supporting 5 different formats:
|
|
// - https://www.googleapis.com/compute/ANY_VERSION/projects/{my_project}/zones/{zone}/{resource_type}/{resource_name}
|
|
// - projects/{my_project}/zones/{zone}/{resource_type}/{resource_name}
|
|
// - zones/{zone}/{resource_type}/{resource_name}
|
|
// - resource_name
|
|
// - "" (empty string). RelativeLink() returns empty if isEmptyValid is true.
|
|
//
|
|
// If the project is not specified, it first tries to get the project from the `projectSchemaField` and then fallback on the default project.
|
|
// If the zone is not specified, it takes the value of `zoneSchemaField`.
|
|
func parseZonalFieldValue(resourceType, fieldValue, projectSchemaField, zoneSchemaField string, d TerraformResourceData, config *Config, isEmptyValid bool) (*ZonalFieldValue, error) {
|
|
if len(fieldValue) == 0 {
|
|
if isEmptyValid {
|
|
return &ZonalFieldValue{resourceType: resourceType}, nil
|
|
}
|
|
return nil, fmt.Errorf("The zonal field for resource %s cannot be empty.", resourceType)
|
|
}
|
|
|
|
r := regexp.MustCompile(fmt.Sprintf(zonalLinkBasePattern, resourceType))
|
|
if parts := r.FindStringSubmatch(fieldValue); parts != nil {
|
|
return &ZonalFieldValue{
|
|
Project: parts[1],
|
|
Zone: parts[2],
|
|
Name: parts[3],
|
|
resourceType: resourceType,
|
|
}, nil
|
|
}
|
|
|
|
project, err := getProjectFromSchema(projectSchemaField, d, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r = regexp.MustCompile(fmt.Sprintf(zonalPartialLinkBasePattern, resourceType))
|
|
if parts := r.FindStringSubmatch(fieldValue); parts != nil {
|
|
return &ZonalFieldValue{
|
|
Project: project,
|
|
Zone: parts[1],
|
|
Name: parts[2],
|
|
resourceType: resourceType,
|
|
}, nil
|
|
}
|
|
|
|
if len(zoneSchemaField) == 0 {
|
|
return nil, fmt.Errorf("Invalid field format. Got '%s', expected format '%s'", fieldValue, fmt.Sprintf(globalLinkTemplate, "{project}", resourceType, "{name}"))
|
|
}
|
|
|
|
zone, ok := d.GetOk(zoneSchemaField)
|
|
if !ok {
|
|
return nil, fmt.Errorf("A zone must be specified")
|
|
}
|
|
|
|
return &ZonalFieldValue{
|
|
Project: project,
|
|
Zone: zone.(string),
|
|
Name: GetResourceNameFromSelfLink(fieldValue),
|
|
resourceType: resourceType,
|
|
}, nil
|
|
}
|
|
|
|
func getProjectFromSchema(projectSchemaField string, d TerraformResourceData, config *Config) (string, error) {
|
|
res, ok := d.GetOk(projectSchemaField)
|
|
if ok && projectSchemaField != "" {
|
|
return res.(string), nil
|
|
}
|
|
if config.Project != "" {
|
|
return config.Project, nil
|
|
}
|
|
return "", fmt.Errorf("%s: required field is not set", projectSchemaField)
|
|
}
|
|
|
|
type OrganizationFieldValue struct {
|
|
OrgId string
|
|
Name string
|
|
|
|
resourceType string
|
|
}
|
|
|
|
func (f OrganizationFieldValue) RelativeLink() string {
|
|
if len(f.Name) == 0 {
|
|
return ""
|
|
}
|
|
|
|
return fmt.Sprintf(organizationLinkTemplate, f.OrgId, f.resourceType, f.Name)
|
|
}
|
|
|
|
// Parses an organization field with the following formats:
|
|
// - organizations/{my_organizations}/{resource_type}/{resource_name}
|
|
func parseOrganizationFieldValue(resourceType, fieldValue string, isEmptyValid bool) (*OrganizationFieldValue, error) {
|
|
if len(fieldValue) == 0 {
|
|
if isEmptyValid {
|
|
return &OrganizationFieldValue{resourceType: resourceType}, nil
|
|
}
|
|
return nil, fmt.Errorf("The organization field for resource %s cannot be empty", resourceType)
|
|
}
|
|
|
|
r := regexp.MustCompile(fmt.Sprintf(organizationBasePattern, resourceType))
|
|
if parts := r.FindStringSubmatch(fieldValue); parts != nil {
|
|
return &OrganizationFieldValue{
|
|
OrgId: parts[1],
|
|
Name: parts[2],
|
|
|
|
resourceType: resourceType,
|
|
}, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("Invalid field format. Got '%s', expected format '%s'", fieldValue, fmt.Sprintf(organizationLinkTemplate, "{org_id}", resourceType, "{name}"))
|
|
}
|
|
|
|
type RegionalFieldValue struct {
|
|
Project string
|
|
Region string
|
|
Name string
|
|
|
|
resourceType string
|
|
}
|
|
|
|
func (f RegionalFieldValue) RelativeLink() string {
|
|
if len(f.Name) == 0 {
|
|
return ""
|
|
}
|
|
|
|
return fmt.Sprintf(regionalLinkTemplate, f.Project, f.Region, f.resourceType, f.Name)
|
|
}
|
|
|
|
// Parses a regional field supporting 5 different formats:
|
|
// - https://www.googleapis.com/compute/ANY_VERSION/projects/{my_project}/regions/{region}/{resource_type}/{resource_name}
|
|
// - projects/{my_project}/regions/{region}/{resource_type}/{resource_name}
|
|
// - regions/{region}/{resource_type}/{resource_name}
|
|
// - resource_name
|
|
// - "" (empty string). RelativeLink() returns empty if isEmptyValid is true.
|
|
//
|
|
// If the project is not specified, it first tries to get the project from the `projectSchemaField` and then fallback on the default project.
|
|
// If the region is not specified, it first tries to get the region from the `regionSchemaField` and then fallback on the default region.
|
|
func parseRegionalFieldValue(resourceType, fieldValue, projectSchemaField, regionSchemaField string, d TerraformResourceData, config *Config, isEmptyValid bool) (*RegionalFieldValue, error) {
|
|
if len(fieldValue) == 0 {
|
|
if isEmptyValid {
|
|
return &RegionalFieldValue{resourceType: resourceType}, nil
|
|
}
|
|
return nil, fmt.Errorf("The regional field for resource %s cannot be empty.", resourceType)
|
|
}
|
|
|
|
r := regexp.MustCompile(fmt.Sprintf(regionalLinkBasePattern, resourceType))
|
|
if parts := r.FindStringSubmatch(fieldValue); parts != nil {
|
|
return &RegionalFieldValue{
|
|
Project: parts[1],
|
|
Region: parts[2],
|
|
Name: parts[3],
|
|
resourceType: resourceType,
|
|
}, nil
|
|
}
|
|
|
|
project, err := getProjectFromSchema(projectSchemaField, d, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r = regexp.MustCompile(fmt.Sprintf(regionalPartialLinkBasePattern, resourceType))
|
|
if parts := r.FindStringSubmatch(fieldValue); parts != nil {
|
|
return &RegionalFieldValue{
|
|
Project: project,
|
|
Region: parts[1],
|
|
Name: parts[2],
|
|
resourceType: resourceType,
|
|
}, nil
|
|
}
|
|
|
|
region, err := getRegionFromSchema(regionSchemaField, d, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &RegionalFieldValue{
|
|
Project: project,
|
|
Region: region,
|
|
Name: GetResourceNameFromSelfLink(fieldValue),
|
|
resourceType: resourceType,
|
|
}, nil
|
|
}
|
|
|
|
func getRegionFromSchema(regionSchemaField string, d TerraformResourceData, config *Config) (string, error) {
|
|
res, ok := d.GetOk(regionSchemaField)
|
|
if ok && regionSchemaField != "" {
|
|
return res.(string), nil
|
|
}
|
|
if config.Region != "" {
|
|
return config.Region, nil
|
|
}
|
|
return "", fmt.Errorf("%s: required field is not set", regionSchemaField)
|
|
}
|