Skip to content

Commit bc1dba4

Browse files
Add support for Network Endpoint Group to use serverlessDeployment for API Gateway (#5434) (#4041)
* initial creation of google-beta file .erb file for network endpoint groups * Add support for serverless Network Endpoint Groups to use property serverlessDeployment * Update serverless_deployment fields to accurately represent current state, which only provides support for API Gateway * Added tests * update files to autogenerate serverlessDeployment option for Network Endpoint Groups * Remove manually created resource_compute_network_endpoint_group file so it can be autogenerated * Add tag to mark serverlessDeployment to be available only via beta client * Rename test file to allow for embedded ruby * New tests should be in a resource_compute_region_network_endpoint_group_test.go.erb file, not resource_compute_network_endpoint_group_test.go.erb * Use Cloud Run resources based off of auto-generated unit tests * Spin up a new API Gateway resource to connect with Serverless NEG * Remove requirement to provide resource field, and update url_mask to use <gateway> label to specify >1 endpoints * Fix url_mask to parse out intended endpoint from gateway * typo fix * Conditional imports for beta usage * Update naming conventions used in test * update api config name Signed-off-by: Modular Magician <[email protected]>
1 parent e66a915 commit bc1dba4

File tree

4 files changed

+260
-9
lines changed

4 files changed

+260
-9
lines changed

.changelog/5434.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
compute: Added field `serverless_deployment` to `google_compute_network_endpoint_group` (beta only) for API Gateway resources
3+
```

google-beta/resource_compute_region_network_endpoint_group.go

Lines changed: 152 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ character, which cannot be a dash.`,
6464
Optional: true,
6565
ForceNew: true,
6666
Description: `Only valid when networkEndpointType is "SERVERLESS".
67-
Only one of cloud_run, app_engine or cloud_function may be set.`,
67+
Only one of cloud_run, app_engine, cloud_function or serverless_deployment may be set.`,
6868
MaxItems: 1,
6969
Elem: &schema.Resource{
7070
Schema: map[string]*schema.Schema{
@@ -99,14 +99,14 @@ Example value: "v1", "v2".`,
9999
},
100100
},
101101
},
102-
ExactlyOneOf: []string{"app_engine", "cloud_function", "cloud_run"},
102+
ExactlyOneOf: []string{"app_engine", "cloud_function", "cloud_run", "serverless_deployment"},
103103
},
104104
"cloud_function": {
105105
Type: schema.TypeList,
106106
Optional: true,
107107
ForceNew: true,
108108
Description: `Only valid when networkEndpointType is "SERVERLESS".
109-
Only one of cloud_run, app_engine or cloud_function may be set.`,
109+
Only one of cloud_run, app_engine, cloud_function or serverless_deployment may be set.`,
110110
MaxItems: 1,
111111
Elem: &schema.Resource{
112112
Schema: map[string]*schema.Schema{
@@ -134,14 +134,14 @@ will parse them to { function = "function1" } and { function = "function2" } res
134134
},
135135
},
136136
},
137-
ExactlyOneOf: []string{"app_engine", "cloud_function", "cloud_run"},
137+
ExactlyOneOf: []string{"app_engine", "cloud_function", "cloud_run", "serverless_deployment"},
138138
},
139139
"cloud_run": {
140140
Type: schema.TypeList,
141141
Optional: true,
142142
ForceNew: true,
143143
Description: `Only valid when networkEndpointType is "SERVERLESS".
144-
Only one of cloud_run, app_engine or cloud_function may be set.`,
144+
Only one of cloud_run, app_engine, cloud_function or serverless_deployment may be set.`,
145145
MaxItems: 1,
146146
Elem: &schema.Resource{
147147
Schema: map[string]*schema.Schema{
@@ -179,7 +179,7 @@ and { service="bar2", tag="foo2" } respectively.`,
179179
},
180180
},
181181
},
182-
ExactlyOneOf: []string{"cloud_run", "cloud_function", "app_engine"},
182+
ExactlyOneOf: []string{"cloud_run", "cloud_function", "app_engine", "serverless_deployment"},
183183
},
184184
"description": {
185185
Type: schema.TypeString,
@@ -196,6 +196,50 @@ you create the resource.`,
196196
Description: `Type of network endpoints in this network endpoint group. Defaults to SERVERLESS Default value: "SERVERLESS" Possible values: ["SERVERLESS"]`,
197197
Default: "SERVERLESS",
198198
},
199+
"serverless_deployment": {
200+
Type: schema.TypeList,
201+
Optional: true,
202+
ForceNew: true,
203+
Description: `Only valid when networkEndpointType is "SERVERLESS".
204+
Only one of cloudRun, appEngine, cloudFunction or serverlessDeployment may be set.`,
205+
MaxItems: 1,
206+
Elem: &schema.Resource{
207+
Schema: map[string]*schema.Schema{
208+
"platform": {
209+
Type: schema.TypeString,
210+
Required: true,
211+
ForceNew: true,
212+
Description: `The platform of the NEG backend target(s). Possible values:
213+
API Gateway: apigateway.googleapis.com`,
214+
},
215+
"url_mask": {
216+
Type: schema.TypeString,
217+
Required: true,
218+
ForceNew: true,
219+
Description: `A template to parse platform-specific fields from a request URL. URL mask allows for routing to multiple resources
220+
on the same serverless platform without having to create multiple Network Endpoint Groups and backend resources.
221+
The fields parsed by this template are platform-specific and are as follows: API Gateway: The gateway ID,
222+
App Engine: The service and version, Cloud Functions: The function name, Cloud Run: The service and tag`,
223+
},
224+
"resource": {
225+
Type: schema.TypeString,
226+
Optional: true,
227+
ForceNew: true,
228+
Description: `The user-defined name of the workload/instance. This value must be provided explicitly or in the urlMask.
229+
The resource identified by this value is platform-specific and is as follows: API Gateway: The gateway ID, App Engine: The service name,
230+
Cloud Functions: The function name, Cloud Run: The service name`,
231+
},
232+
"version": {
233+
Type: schema.TypeString,
234+
Optional: true,
235+
ForceNew: true,
236+
Description: `The optional resource version. The version identified by this value is platform-specific and is follows:
237+
API Gateway: Unused, App Engine: The service version, Cloud Functions: Unused, Cloud Run: The service tag`,
238+
},
239+
},
240+
},
241+
ExactlyOneOf: []string{"app_engine", "cloud_function", "cloud_run", "serverless_deployment"},
242+
},
199243
"project": {
200244
Type: schema.TypeString,
201245
Optional: true,
@@ -255,6 +299,12 @@ func resourceComputeRegionNetworkEndpointGroupCreate(d *schema.ResourceData, met
255299
} else if v, ok := d.GetOkExists("cloud_function"); !isEmptyValue(reflect.ValueOf(cloudFunctionProp)) && (ok || !reflect.DeepEqual(v, cloudFunctionProp)) {
256300
obj["cloudFunction"] = cloudFunctionProp
257301
}
302+
serverlessDeploymentProp, err := expandComputeRegionNetworkEndpointGroupServerlessDeployment(d.Get("serverless_deployment"), d, config)
303+
if err != nil {
304+
return err
305+
} else if v, ok := d.GetOkExists("serverless_deployment"); !isEmptyValue(reflect.ValueOf(serverlessDeploymentProp)) && (ok || !reflect.DeepEqual(v, serverlessDeploymentProp)) {
306+
obj["serverlessDeployment"] = serverlessDeploymentProp
307+
}
258308
regionProp, err := expandComputeRegionNetworkEndpointGroupRegion(d.Get("region"), d, config)
259309
if err != nil {
260310
return err
@@ -360,6 +410,9 @@ func resourceComputeRegionNetworkEndpointGroupRead(d *schema.ResourceData, meta
360410
if err := d.Set("cloud_function", flattenComputeRegionNetworkEndpointGroupCloudFunction(res["cloudFunction"], d, config)); err != nil {
361411
return fmt.Errorf("Error reading RegionNetworkEndpointGroup: %s", err)
362412
}
413+
if err := d.Set("serverless_deployment", flattenComputeRegionNetworkEndpointGroupServerlessDeployment(res["serverlessDeployment"], d, config)); err != nil {
414+
return fmt.Errorf("Error reading RegionNetworkEndpointGroup: %s", err)
415+
}
363416
if err := d.Set("region", flattenComputeRegionNetworkEndpointGroupRegion(res["region"], d, config)); err != nil {
364417
return fmt.Errorf("Error reading RegionNetworkEndpointGroup: %s", err)
365418
}
@@ -526,6 +579,38 @@ func flattenComputeRegionNetworkEndpointGroupCloudFunctionUrlMask(v interface{},
526579
return v
527580
}
528581

582+
func flattenComputeRegionNetworkEndpointGroupServerlessDeployment(v interface{}, d *schema.ResourceData, config *Config) interface{} {
583+
if v == nil {
584+
return nil
585+
}
586+
original := v.(map[string]interface{})
587+
transformed := make(map[string]interface{})
588+
transformed["platform"] =
589+
flattenComputeRegionNetworkEndpointGroupServerlessDeploymentPlatform(original["platform"], d, config)
590+
transformed["resource"] =
591+
flattenComputeRegionNetworkEndpointGroupServerlessDeploymentResource(original["resource"], d, config)
592+
transformed["version"] =
593+
flattenComputeRegionNetworkEndpointGroupServerlessDeploymentVersion(original["version"], d, config)
594+
transformed["url_mask"] =
595+
flattenComputeRegionNetworkEndpointGroupServerlessDeploymentUrlMask(original["urlMask"], d, config)
596+
return []interface{}{transformed}
597+
}
598+
func flattenComputeRegionNetworkEndpointGroupServerlessDeploymentPlatform(v interface{}, d *schema.ResourceData, config *Config) interface{} {
599+
return v
600+
}
601+
602+
func flattenComputeRegionNetworkEndpointGroupServerlessDeploymentResource(v interface{}, d *schema.ResourceData, config *Config) interface{} {
603+
return v
604+
}
605+
606+
func flattenComputeRegionNetworkEndpointGroupServerlessDeploymentVersion(v interface{}, d *schema.ResourceData, config *Config) interface{} {
607+
return v
608+
}
609+
610+
func flattenComputeRegionNetworkEndpointGroupServerlessDeploymentUrlMask(v interface{}, d *schema.ResourceData, config *Config) interface{} {
611+
return v
612+
}
613+
529614
func flattenComputeRegionNetworkEndpointGroupRegion(v interface{}, d *schema.ResourceData, config *Config) interface{} {
530615
if v == nil {
531616
return v
@@ -674,6 +759,67 @@ func expandComputeRegionNetworkEndpointGroupCloudFunctionUrlMask(v interface{},
674759
return v, nil
675760
}
676761

762+
func expandComputeRegionNetworkEndpointGroupServerlessDeployment(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
763+
l := v.([]interface{})
764+
if len(l) == 0 {
765+
return nil, nil
766+
}
767+
768+
if l[0] == nil {
769+
transformed := make(map[string]interface{})
770+
return transformed, nil
771+
}
772+
raw := l[0]
773+
original := raw.(map[string]interface{})
774+
transformed := make(map[string]interface{})
775+
776+
transformedPlatform, err := expandComputeRegionNetworkEndpointGroupServerlessDeploymentPlatform(original["platform"], d, config)
777+
if err != nil {
778+
return nil, err
779+
} else if val := reflect.ValueOf(transformedPlatform); val.IsValid() && !isEmptyValue(val) {
780+
transformed["platform"] = transformedPlatform
781+
}
782+
783+
transformedResource, err := expandComputeRegionNetworkEndpointGroupServerlessDeploymentResource(original["resource"], d, config)
784+
if err != nil {
785+
return nil, err
786+
} else if val := reflect.ValueOf(transformedResource); val.IsValid() && !isEmptyValue(val) {
787+
transformed["resource"] = transformedResource
788+
}
789+
790+
transformedVersion, err := expandComputeRegionNetworkEndpointGroupServerlessDeploymentVersion(original["version"], d, config)
791+
if err != nil {
792+
return nil, err
793+
} else if val := reflect.ValueOf(transformedVersion); val.IsValid() && !isEmptyValue(val) {
794+
transformed["version"] = transformedVersion
795+
}
796+
797+
transformedUrlMask, err := expandComputeRegionNetworkEndpointGroupServerlessDeploymentUrlMask(original["url_mask"], d, config)
798+
if err != nil {
799+
return nil, err
800+
} else if val := reflect.ValueOf(transformedUrlMask); val.IsValid() && !isEmptyValue(val) {
801+
transformed["urlMask"] = transformedUrlMask
802+
}
803+
804+
return transformed, nil
805+
}
806+
807+
func expandComputeRegionNetworkEndpointGroupServerlessDeploymentPlatform(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
808+
return v, nil
809+
}
810+
811+
func expandComputeRegionNetworkEndpointGroupServerlessDeploymentResource(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
812+
return v, nil
813+
}
814+
815+
func expandComputeRegionNetworkEndpointGroupServerlessDeploymentVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
816+
return v, nil
817+
}
818+
819+
func expandComputeRegionNetworkEndpointGroupServerlessDeploymentUrlMask(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
820+
return v, nil
821+
}
822+
677823
func expandComputeRegionNetworkEndpointGroupRegion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
678824
f, err := parseGlobalFieldValue("regions", v.(string), "project", d, config, true)
679825
if err != nil {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package google
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
7+
)
8+
9+
func TestAccComputeRegionNetworkEndpointGroup_negWithServerlessDeployment(t *testing.T) {
10+
t.Parallel()
11+
12+
context := map[string]interface{}{
13+
"random_suffix": randString(t, 10),
14+
}
15+
16+
vcrTest(t, resource.TestCase{
17+
PreCheck: func() { testAccPreCheck(t) },
18+
Providers: testAccProviders,
19+
CheckDestroy: testAccCheckComputeRegionNetworkEndpointGroupDestroyProducer(t),
20+
Steps: []resource.TestStep{
21+
{
22+
Config: testAccComputeRegionNetworkEndpointGroup_negWithServerlessDeployment(context),
23+
},
24+
{
25+
ResourceName: "google_compute_region_network_endpoint_group.test_neg",
26+
ImportState: true,
27+
ImportStateVerify: true,
28+
ImportStateVerifyIgnore: []string{"region"},
29+
},
30+
},
31+
})
32+
}
33+
34+
func testAccComputeRegionNetworkEndpointGroup_negWithServerlessDeployment(context map[string]interface{}) string {
35+
return Nprintf(`
36+
resource "google_api_gateway_api" "api_gw" {
37+
api_id = "tf-test-%{random_suffix}"
38+
}
39+
40+
resource "google_api_gateway_api_config" "api_gw" {
41+
api = google_api_gateway_api.api_gw.api_id
42+
api_config_id = "tf-test-config-%{random_suffix}"
43+
44+
openapi_documents {
45+
document {
46+
path = "spec.yaml"
47+
contents = filebase64("test-fixtures/apigateway/openapi.yaml")
48+
}
49+
}
50+
51+
lifecycle {
52+
create_before_destroy = true
53+
}
54+
}
55+
56+
resource "google_api_gateway_gateway" "api_gw" {
57+
api_config = google_api_gateway_api_config.api_gw.id
58+
gateway_id = "tf-test-%{random_suffix}"
59+
}
60+
61+
resource "google_compute_region_network_endpoint_group" "test_neg" {
62+
name = "test-serverless-neg"
63+
network_endpoint_type = "SERVERLESS"
64+
region = "us-central1"
65+
serverless_deployment {
66+
platform = "apigateway.googleapis.com"
67+
url_mask = format("<gateway>%s/hello", trimprefix(google_api_gateway_gateway.api_gw.default_hostname, "tf-test-%{random_suffix}"))
68+
}
69+
}
70+
`, context)
71+
}

website/docs/r/compute_region_network_endpoint_group.html.markdown

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,21 +233,27 @@ The following arguments are supported:
233233
* `cloud_run` -
234234
(Optional)
235235
Only valid when networkEndpointType is "SERVERLESS".
236-
Only one of cloud_run, app_engine or cloud_function may be set.
236+
Only one of cloud_run, app_engine, cloud_function or serverless_deployment may be set.
237237
Structure is [documented below](#nested_cloud_run).
238238

239239
* `app_engine` -
240240
(Optional)
241241
Only valid when networkEndpointType is "SERVERLESS".
242-
Only one of cloud_run, app_engine or cloud_function may be set.
242+
Only one of cloud_run, app_engine, cloud_function or serverless_deployment may be set.
243243
Structure is [documented below](#nested_app_engine).
244244

245245
* `cloud_function` -
246246
(Optional)
247247
Only valid when networkEndpointType is "SERVERLESS".
248-
Only one of cloud_run, app_engine or cloud_function may be set.
248+
Only one of cloud_run, app_engine, cloud_function or serverless_deployment may be set.
249249
Structure is [documented below](#nested_cloud_function).
250250

251+
* `serverless_deployment` -
252+
(Optional, [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html))
253+
Only valid when networkEndpointType is "SERVERLESS".
254+
Only one of cloudRun, appEngine, cloudFunction or serverlessDeployment may be set.
255+
Structure is [documented below](#nested_serverless_deployment).
256+
251257
* `project` - (Optional) The ID of the project in which the resource belongs.
252258
If it is not provided, the provider project is used.
253259

@@ -318,6 +324,31 @@ The following arguments are supported:
318324
can be backed by the same Serverless NEG with URL mask "/". The URL mask
319325
will parse them to { function = "function1" } and { function = "function2" } respectively.
320326

327+
<a name="nested_serverless_deployment"></a>The `serverless_deployment` block supports:
328+
329+
* `platform` -
330+
(Required)
331+
The platform of the NEG backend target(s). Possible values:
332+
API Gateway: apigateway.googleapis.com
333+
334+
* `resource` -
335+
(Optional)
336+
The user-defined name of the workload/instance. This value must be provided explicitly or in the urlMask.
337+
The resource identified by this value is platform-specific and is as follows: API Gateway: The gateway ID, App Engine: The service name,
338+
Cloud Functions: The function name, Cloud Run: The service name
339+
340+
* `version` -
341+
(Optional)
342+
The optional resource version. The version identified by this value is platform-specific and is follows:
343+
API Gateway: Unused, App Engine: The service version, Cloud Functions: Unused, Cloud Run: The service tag
344+
345+
* `url_mask` -
346+
(Required)
347+
A template to parse platform-specific fields from a request URL. URL mask allows for routing to multiple resources
348+
on the same serverless platform without having to create multiple Network Endpoint Groups and backend resources.
349+
The fields parsed by this template are platform-specific and are as follows: API Gateway: The gateway ID,
350+
App Engine: The service and version, Cloud Functions: The function name, Cloud Run: The service and tag
351+
321352
## Attributes Reference
322353

323354
In addition to the arguments listed above, the following computed attributes are exported:

0 commit comments

Comments
 (0)