Skip to content

Commit bd7dd54

Browse files
authored
Create a reusable GlobalFieldValue and support reading project from schema (hashicorp#550)
1 parent 32505d3 commit bd7dd54

10 files changed

+168
-51
lines changed

google/field_helpers.go

+67-23
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,85 @@ import (
55
"regexp"
66
)
77

8-
const networkLinkTemplate = "projects/%s/global/networks/%s"
8+
const (
9+
globalLinkTemplate = "projects/%s/global/%s/%s"
10+
globalLinkBasePattern = "projects/(.+)/global/%s/(.+)"
11+
)
12+
13+
// ------------------------------------------------------------
14+
// Field helpers
15+
// ------------------------------------------------------------
16+
17+
func ParseNetworkFieldValue(network string, d TerraformResourceData, config *Config) (*GlobalFieldValue, error) {
18+
return parseGlobalFieldValue("networks", network, "project", d, config, true)
19+
}
920

10-
var networkLinkRegex = regexp.MustCompile("projects/(.+)/global/networks/(.+)")
21+
// ------------------------------------------------------------
22+
// Base helpers used to create helpers for specific fields.
23+
// ------------------------------------------------------------
1124

12-
type NetworkFieldValue struct {
25+
type GlobalFieldValue struct {
1326
Project string
1427
Name string
28+
29+
resourceType string
30+
}
31+
32+
func (f GlobalFieldValue) RelativeLink() string {
33+
if len(f.Name) == 0 {
34+
return ""
35+
}
36+
37+
return fmt.Sprintf(globalLinkTemplate, f.Project, f.resourceType, f.Name)
1538
}
1639

17-
// Parses a `network` supporting 5 different formats:
18-
// - https://www.googleapis.com/compute/{version}/projects/myproject/global/networks/my-network
19-
// - projects/myproject/global/networks/my-network
20-
// - global/networks/my-network (default project is used)
21-
// - my-network (default project is used)
22-
// - "" (empty string). RelativeLink() returns empty. For most API, the behavior is to use the default network.
23-
func ParseNetworkFieldValue(network string, config *Config) *NetworkFieldValue {
24-
if networkLinkRegex.MatchString(network) {
25-
parts := networkLinkRegex.FindStringSubmatch(network)
26-
27-
return &NetworkFieldValue{
40+
// Parses a global field supporting 4 different formats:
41+
// - https://www.googleapis.com/compute/ANY_VERSION/projects/{my-project}/global/{resource_type}/{resource_name}
42+
// - projects/{my-project}/global/{resource_type}/{resource_name}
43+
// - global/{resource_type}/{resource_name} (default project is used)
44+
// - resource_name (default project is used)
45+
// - "" (empty string). RelativeLink() returns empty if isEmptyValid is true.
46+
func parseGlobalFieldValue(resourceType, fieldValue, projectSchemaField string, d TerraformResourceData, config *Config, isEmptyValid bool) (*GlobalFieldValue, error) {
47+
if len(fieldValue) == 0 {
48+
if isEmptyValid {
49+
return &GlobalFieldValue{resourceType: resourceType}, nil
50+
}
51+
return nil, fmt.Errorf("The global field for resource %s cannot be empty", resourceType)
52+
}
53+
54+
r := regexp.MustCompile(fmt.Sprintf(globalLinkBasePattern, resourceType))
55+
56+
if r.MatchString(fieldValue) {
57+
parts := r.FindStringSubmatch(fieldValue)
58+
59+
return &GlobalFieldValue{
2860
Project: parts[1],
2961
Name: parts[2],
30-
}
62+
63+
resourceType: resourceType,
64+
}, nil
3165
}
3266

33-
return &NetworkFieldValue{
34-
Project: config.Project,
35-
Name: GetResourceNameFromSelfLink(network),
67+
project, err := getProjectFromSchema(projectSchemaField, d, config)
68+
if err != nil {
69+
return nil, err
3670
}
71+
72+
return &GlobalFieldValue{
73+
Project: project,
74+
Name: GetResourceNameFromSelfLink(fieldValue),
75+
76+
resourceType: resourceType,
77+
}, nil
3778
}
3879

39-
func (f NetworkFieldValue) RelativeLink() string {
40-
if len(f.Name) == 0 {
41-
return ""
80+
func getProjectFromSchema(projectSchemaField string, d TerraformResourceData, config *Config) (string, error) {
81+
res, ok := d.GetOk(projectSchemaField)
82+
if !ok || len(projectSchemaField) == 0 {
83+
if config.Project != "" {
84+
return config.Project, nil
85+
}
86+
return "", fmt.Errorf("project: required field is not set")
4287
}
43-
44-
return fmt.Sprintf(networkLinkTemplate, f.Project, f.Name)
88+
return res.(string), nil
4589
}

google/field_helpers_test.go

+58-10
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,84 @@
11
package google
22

3-
import "testing"
3+
import (
4+
"testing"
5+
)
46

5-
func TestParseNetworkFieldValue(t *testing.T) {
7+
func TestParseGlobalFieldValue(t *testing.T) {
8+
const resourceType = "networks"
69
cases := map[string]struct {
7-
Network string
10+
FieldValue string
811
ExpectedRelativeLink string
12+
ExpectedError bool
13+
IsEmptyValid bool
14+
ProjectSchemaField string
15+
ProjectSchemaValue string
916
Config *Config
1017
}{
1118
"network is a full self link": {
12-
Network: "https://www.googleapis.com/compute/v1/projects/myproject/global/networks/my-network",
19+
FieldValue: "https://www.googleapis.com/compute/v1/projects/myproject/global/networks/my-network",
1320
ExpectedRelativeLink: "projects/myproject/global/networks/my-network",
1421
},
1522
"network is a relative self link": {
16-
Network: "projects/myproject/global/networks/my-network",
23+
FieldValue: "projects/myproject/global/networks/my-network",
1724
ExpectedRelativeLink: "projects/myproject/global/networks/my-network",
1825
},
1926
"network is a partial relative self link": {
20-
Network: "global/networks/my-network",
21-
ExpectedRelativeLink: "projects/default-project/global/networks/my-network",
27+
FieldValue: "global/networks/my-network",
2228
Config: &Config{Project: "default-project"},
29+
ExpectedRelativeLink: "projects/default-project/global/networks/my-network",
2330
},
2431
"network is the name only": {
25-
Network: "my-network",
32+
FieldValue: "my-network",
33+
Config: &Config{Project: "default-project"},
2634
ExpectedRelativeLink: "projects/default-project/global/networks/my-network",
35+
},
36+
"network is the name only and has a project set in schema": {
37+
FieldValue: "my-network",
38+
ProjectSchemaField: "project",
39+
ProjectSchemaValue: "schema-project",
2740
Config: &Config{Project: "default-project"},
41+
ExpectedRelativeLink: "projects/schema-project/global/networks/my-network",
42+
},
43+
"network is the name only and has a project set in schema but the field is not specified.": {
44+
FieldValue: "my-network",
45+
ProjectSchemaValue: "schema-project",
46+
Config: &Config{Project: "default-project"},
47+
ExpectedRelativeLink: "projects/default-project/global/networks/my-network",
48+
},
49+
"network is empty and it is valid": {
50+
FieldValue: "",
51+
IsEmptyValid: true,
52+
ExpectedRelativeLink: "",
53+
},
54+
"network is empty and it is not valid": {
55+
FieldValue: "",
56+
IsEmptyValid: false,
57+
ExpectedError: true,
2858
},
2959
}
3060

3161
for tn, tc := range cases {
32-
if fieldValue := ParseNetworkFieldValue(tc.Network, tc.Config); fieldValue.RelativeLink() != tc.ExpectedRelativeLink {
33-
t.Fatalf("bad: %s, expected relative link to be '%s' but got '%s'", tn, tc.ExpectedRelativeLink, fieldValue.RelativeLink())
62+
fieldsInSchema := make(map[string]interface{})
63+
64+
if len(tc.ProjectSchemaValue) > 0 && len(tc.ProjectSchemaField) > 0 {
65+
fieldsInSchema[tc.ProjectSchemaField] = tc.ProjectSchemaValue
66+
}
67+
68+
d := &ResourceDataMock{
69+
FieldsInSchema: fieldsInSchema,
70+
}
71+
72+
v, err := parseGlobalFieldValue(resourceType, tc.FieldValue, tc.ProjectSchemaField, d, tc.Config, tc.IsEmptyValid)
73+
74+
if err != nil {
75+
if !tc.ExpectedError {
76+
t.Errorf("bad: %s, did not expect an error. Error: %s", tn, err)
77+
}
78+
} else {
79+
if v.RelativeLink() != tc.ExpectedRelativeLink {
80+
t.Errorf("bad: %s, expected relative link to be '%s' but got '%s'", tn, tc.ExpectedRelativeLink, v.RelativeLink())
81+
}
3482
}
3583
}
3684
}

google/resource_compute_firewall.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,11 @@ func resourceComputeFirewallDelete(d *schema.ResourceData, meta interface{}) err
405405
func resourceFirewall(d *schema.ResourceData, meta interface{}, computeApiVersion ComputeApiVersion) (*computeBeta.Firewall, error) {
406406
config := meta.(*Config)
407407

408+
network, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
409+
if err != nil {
410+
return nil, err
411+
}
412+
408413
// Build up the list of allowed entries
409414
var allowed []*computeBeta.FirewallAllowed
410415
if v := d.Get("allow").(*schema.Set); v.Len() > 0 {
@@ -487,7 +492,7 @@ func resourceFirewall(d *schema.ResourceData, meta interface{}, computeApiVersio
487492
Name: d.Get("name").(string),
488493
Description: d.Get("description").(string),
489494
Direction: d.Get("direction").(string),
490-
Network: ParseNetworkFieldValue(d.Get("network").(string), config).RelativeLink(),
495+
Network: network.RelativeLink(),
491496
Allowed: allowed,
492497
Denied: denied,
493498
SourceRanges: sourceRanges,

google/resource_compute_forwarding_rule.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ func resourceComputeForwardingRule() *schema.Resource {
125125
func resourceComputeForwardingRuleCreate(d *schema.ResourceData, meta interface{}) error {
126126
config := meta.(*Config)
127127

128+
network, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
129+
if err != nil {
130+
return err
131+
}
132+
128133
region, err := getRegion(d, config)
129134
if err != nil {
130135
return err
@@ -148,7 +153,7 @@ func resourceComputeForwardingRuleCreate(d *schema.ResourceData, meta interface{
148153
Description: d.Get("description").(string),
149154
LoadBalancingScheme: d.Get("load_balancing_scheme").(string),
150155
Name: d.Get("name").(string),
151-
Network: ParseNetworkFieldValue(d.Get("network").(string), config).RelativeLink(),
156+
Network: network.RelativeLink(),
152157
PortRange: d.Get("port_range").(string),
153158
Ports: ports,
154159
Subnetwork: d.Get("subnetwork").(string),

google/resource_compute_network_peering.go

+16-4
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ func resourceComputeNetworkPeering() *schema.Resource {
5959

6060
func resourceComputeNetworkPeeringCreate(d *schema.ResourceData, meta interface{}) error {
6161
config := meta.(*Config)
62-
networkFieldValue := ParseNetworkFieldValue(d.Get("network").(string), config)
62+
networkFieldValue, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
63+
if err != nil {
64+
return err
65+
}
6366

6467
request := &compute.NetworksAddPeeringRequest{
6568
Name: d.Get("name").(string),
@@ -86,7 +89,10 @@ func resourceComputeNetworkPeeringRead(d *schema.ResourceData, meta interface{})
8689
config := meta.(*Config)
8790

8891
peeringName := d.Get("name").(string)
89-
networkFieldValue := ParseNetworkFieldValue(d.Get("network").(string), config)
92+
networkFieldValue, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
93+
if err != nil {
94+
return err
95+
}
9096

9197
network, err := config.clientCompute.Networks.Get(networkFieldValue.Project, networkFieldValue.Name).Do()
9298
if err != nil {
@@ -113,8 +119,14 @@ func resourceComputeNetworkPeeringDelete(d *schema.ResourceData, meta interface{
113119

114120
// Remove the `network` to `peer_network` peering
115121
name := d.Get("name").(string)
116-
networkFieldValue := ParseNetworkFieldValue(d.Get("network").(string), config)
117-
peerNetworkFieldValue := ParseNetworkFieldValue(d.Get("peer_network").(string), config)
122+
networkFieldValue, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
123+
if err != nil {
124+
return err
125+
}
126+
peerNetworkFieldValue, err := ParseNetworkFieldValue(d.Get("peer_network").(string), d, config)
127+
if err != nil {
128+
return err
129+
}
118130

119131
request := &compute.NetworksRemovePeeringRequest{
120132
Name: name,

google/resource_compute_router.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,11 @@ func resourceComputeRouterCreate(d *schema.ResourceData, meta interface{}) error
9999
mutexKV.Lock(routerLock)
100100
defer mutexKV.Unlock(routerLock)
101101

102-
network := ParseNetworkFieldValue(d.Get("network").(string), config)
102+
network, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
103+
if err != nil {
104+
return err
105+
}
106+
103107
routersService := config.clientCompute.Routers
104108

105109
router := &compute.Router{

google/resource_compute_router_test.go

-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ func testAccComputeRouterNoRegion(providerRegion string) string {
166166
name = "router-test-subnetwork-%s"
167167
network = "${google_compute_network.foobar.name}"
168168
ip_cidr_range = "10.0.0.0/16"
169-
ip_cidr_range = "10.0.0.0/16"
170169
region = "%s"
171170
}
172171
resource "google_compute_router" "foobar" {

google/resource_compute_subnetwork.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ func resourceComputeSubnetwork() *schema.Resource {
9999

100100
func resourceComputeSubnetworkCreate(d *schema.ResourceData, meta interface{}) error {
101101
config := meta.(*Config)
102+
network, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
103+
if err != nil {
104+
return err
105+
}
102106

103107
region, err := getRegion(d, config)
104108
if err != nil {
@@ -117,7 +121,7 @@ func resourceComputeSubnetworkCreate(d *schema.ResourceData, meta interface{}) e
117121
IpCidrRange: d.Get("ip_cidr_range").(string),
118122
PrivateIpGoogleAccess: d.Get("private_ip_google_access").(bool),
119123
SecondaryIpRanges: expandSecondaryRanges(d.Get("secondary_ip_range").([]interface{})),
120-
Network: ParseNetworkFieldValue(d.Get("network").(string), config).RelativeLink(),
124+
Network: network.RelativeLink(),
121125
}
122126

123127
log.Printf("[DEBUG] Subnetwork insert request: %#v", subnetwork)

google/resource_compute_vpn_gateway.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ func resourceComputeVpnGateway() *schema.Resource {
5858

5959
func resourceComputeVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error {
6060
config := meta.(*Config)
61+
network, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
62+
if err != nil {
63+
return err
64+
}
6165

6266
region, err := getRegion(d, config)
6367
if err != nil {
@@ -70,7 +74,6 @@ func resourceComputeVpnGatewayCreate(d *schema.ResourceData, meta interface{}) e
7074
}
7175

7276
name := d.Get("name").(string)
73-
network := ParseNetworkFieldValue(d.Get("network").(string), config)
7477

7578
vpnGatewaysService := compute.NewTargetVpnGatewaysService(config.clientCompute)
7679

google/utils.go

+1-8
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,7 @@ func getRegionFromInstanceState(is *terraform.InstanceState, config *Config) (st
5959
// back to the provider's value if not given. If the provider's value is not
6060
// given, an error is returned.
6161
func getProject(d *schema.ResourceData, config *Config) (string, error) {
62-
res, ok := d.GetOk("project")
63-
if !ok {
64-
if config.Project != "" {
65-
return config.Project, nil
66-
}
67-
return "", fmt.Errorf("project: required field is not set")
68-
}
69-
return res.(string), nil
62+
return getProjectFromSchema("project", d, config)
7063
}
7164

7265
func getProjectFromInstanceState(is *terraform.InstanceState, config *Config) (string, error) {

0 commit comments

Comments
 (0)