Skip to content

Commit

Permalink
feat: update compute instance template resource to add field network_…
Browse files Browse the repository at this point in the history
…attachment (#8108) (#5761)

Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician authored Jun 9, 2023
1 parent 547968d commit d4c436e
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .changelog/8108.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
compute: Added field `network_attachment` to `google_compute_instance_template`
```
124 changes: 124 additions & 0 deletions google-beta/bootstrap_test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,130 @@ func BootstrapSharedCaPoolInLocation(t *testing.T, location string) string {
return poolName
}

func BootstrapSubnet(t *testing.T, subnetName string, networkName string) string {
projectID := acctest.GetTestProjectFromEnv()
region := acctest.GetTestRegionFromEnv()

config := BootstrapConfig(t)
if config == nil {
t.Fatal("Could not bootstrap config.")
}

computeService := config.NewComputeClient(config.UserAgent)
if computeService == nil {
t.Fatal("Could not create compute client.")
}

// In order to create a networkAttachment we need to bootstrap a subnet.
_, err := computeService.Subnetworks.Get(projectID, region, subnetName).Do()
if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 404) {
log.Printf("[DEBUG] Subnet %q not found, bootstrapping", subnetName)

networkUrl := fmt.Sprintf("%sprojects/%s/global/networks/%s", config.ComputeBasePath, projectID, networkName)
url := fmt.Sprintf("%sprojects/%s/regions/%s/subnetworks", config.ComputeBasePath, projectID, region)

subnetObj := map[string]interface{}{
"name": subnetName,
"region ": region,
"network": networkUrl,
"ipCidrRange": "10.77.1.0/28",
}

res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Project: projectID,
RawURL: url,
UserAgent: config.UserAgent,
Body: subnetObj,
Timeout: 4 * time.Minute,
})

log.Printf("Response is, %s", res)
if err != nil {
t.Fatalf("Error bootstrapping test subnet %s: %s", subnetName, err)
}

log.Printf("[DEBUG] Waiting for network creation to finish")
err = ComputeOperationWaitTime(config, res, projectID, "Error bootstrapping test subnet", config.UserAgent, 4*time.Minute)
if err != nil {
t.Fatalf("Error bootstrapping test subnet %s: %s", subnetName, err)
}
}

subnet, err := computeService.Subnetworks.Get(projectID, region, subnetName).Do()

if subnet == nil {
t.Fatalf("Error getting test subnet %s: is nil", subnetName)
}

if err != nil {
t.Fatalf("Error getting test subnet %s: %s", subnetName, err)
}
return subnet.Name
}

func BootstrapNetworkAttachment(t *testing.T, networkAttachmentName string, subnetName string) string {
projectID := acctest.GetTestProjectFromEnv()
region := acctest.GetTestRegionFromEnv()

config := BootstrapConfig(t)
if config == nil {
return ""
}

computeService := config.NewComputeClient(config.UserAgent)
if computeService == nil {
return ""
}

networkAttachment, err := computeService.NetworkAttachments.Get(projectID, region, networkAttachmentName).Do()
if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 404) {
// Create Network Attachment Here.
log.Printf("[DEBUG] Network Attachment %s not found, bootstrapping", networkAttachmentName)
url := fmt.Sprintf("%sprojects/%s/regions/%s/networkAttachments", config.ComputeBasePath, projectID, region)

subnetURL := fmt.Sprintf("%sprojects/%s/regions/%s/subnetworks/%s", config.ComputeBasePath, projectID, region, subnetName)
networkAttachmentObj := map[string]interface{}{
"name": networkAttachmentName,
"region": region,
"subnetworks": []string{subnetURL},
"connectionPreference": "ACCEPT_AUTOMATIC",
}

res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Project: projectID,
RawURL: url,
UserAgent: config.UserAgent,
Body: networkAttachmentObj,
Timeout: 4 * time.Minute,
})
if err != nil {
t.Fatalf("Error bootstrapping test Network Attachment %s: %s", networkAttachmentName, err)
}

log.Printf("[DEBUG] Waiting for network creation to finish")
err = ComputeOperationWaitTime(config, res, projectID, "Error bootstrapping shared test subnet", config.UserAgent, 4*time.Minute)
if err != nil {
t.Fatalf("Error bootstrapping test Network Attachment %s: %s", networkAttachmentName, err)
}
}

networkAttachment, err = computeService.NetworkAttachments.Get(projectID, region, networkAttachmentName).Do()

if networkAttachment == nil {
t.Fatalf("Error getting test network attachment %s: is nil", networkAttachmentName)
}

if err != nil {
t.Fatalf("Error getting test Network Attachment %s: %s", networkAttachmentName, err)
}

return networkAttachment.Name
}

func setupProjectsAndGetAccessToken(org, billing, pid, service string, config *transport_tpg.Config) (string, error) {
// Create project-1 and project-2
rmService := config.NewResourceManagerClient(config.UserAgent)
Expand Down
85 changes: 85 additions & 0 deletions google-beta/resource_compute_instance_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,46 @@ func TestAccComputeInstanceTemplate_sourceImageEncryptionKey(t *testing.T) {
})
}

func TestAccComputeInstanceTemplate_NetworkAttachment(t *testing.T) {
t.Parallel()

var instanceTemplate compute.InstanceTemplate

testNetworkName := BootstrapSharedTestNetwork(t, "attachment-network")
subnetName := BootstrapSubnet(t, "tf-test-subnet", testNetworkName)
networkAttachmentName := BootstrapNetworkAttachment(t, "tf-test-attachment", subnetName)

// Need to have the full network attachment name in the format project/{project_id}/regions/{region_id}/networkAttachments/{networkAttachmentName}
fullFormNetworkAttachmentName := fmt.Sprintf("projects/%s/regions/%s/networkAttachments/%s", acctest.GetTestProjectFromEnv(), acctest.GetTestRegionFromEnv(), networkAttachmentName)

context := map[string]interface{}{
"subnet": subnetName,
"suffix": (RandString(t, 10)),
"network_attachment": fullFormNetworkAttachmentName,
}

VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckComputeInstanceTemplateDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccComputeInstanceTemplate_network_attachment(context),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeInstanceTemplateExists(
t, "google_compute_instance_template.foobar", &instanceTemplate),
testAccCheckComputeInstanceTemplateHasNetworkAttachment(&instanceTemplate, fmt.Sprintf("https://www.googleapis.com/compute/beta/%s", fullFormNetworkAttachmentName)),
),
},
{
ResourceName: "google_compute_instance_template.foobar",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckComputeInstanceTemplateDestroyProducer(t *testing.T) func(s *terraform.State) error {
return func(s *terraform.State) error {
config := GoogleProviderConfig(t)
Expand Down Expand Up @@ -1447,6 +1487,17 @@ func testAccCheckComputeInstanceTemplateHasMinCpuPlatform(instanceTemplate *comp
}
}

func testAccCheckComputeInstanceTemplateHasNetworkAttachment(instanceTemplate *compute.InstanceTemplate, networkAttachmentName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
for _, networkInterface := range instanceTemplate.Properties.NetworkInterfaces {
if networkInterface.NetworkAttachment != "" && networkInterface.NetworkAttachment == networkAttachmentName {
return nil
}
}
return fmt.Errorf("Network Attachment %s, was not found in the instance template", networkAttachmentName)
}
}

func testAccCheckComputeInstanceTemplateHasInstanceResourcePolicies(instanceTemplate *compute.InstanceTemplate, resourcePolicy string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resourcePolicyActual := instanceTemplate.Properties.ResourcePolicies[0]
Expand Down Expand Up @@ -3238,3 +3289,37 @@ resource "google_compute_instance_template" "template" {
}
`, context)
}

func testAccComputeInstanceTemplate_network_attachment(context map[string]interface{}) string {
return Nprintf(`
data "google_compute_image" "my_image" {
family = "debian-11"
project = "debian-cloud"
}
resource "google_compute_instance_template" "foobar" {
name = "tf-test-instance-template-%{suffix}"
machine_type = "e2-medium"
disk {
source_image = data.google_compute_image.my_image.self_link
auto_delete = true
disk_size_gb = 10
boot = true
}
network_interface {
network = "default"
}
network_interface {
network_attachment = "%{network_attachment}"
}
metadata = {
foo = "bar"
}
}
`, context)
}
27 changes: 25 additions & 2 deletions google-beta/services/compute/compute_instance_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,14 @@ func flattenNetworkInterfaces(d *schema.ResourceData, config *transport_tpg.Conf
if internalIP == "" {
internalIP = iface.NetworkIP
}

if iface.NetworkAttachment != "" {
networkAttachment, err := tpgresource.GetRelativePath(iface.NetworkAttachment)
if err != nil {
return nil, "", "", "", err
}
flattened[i]["network_attachment"] = networkAttachment
}
}
return flattened, region, internalIP, externalIP, nil
}
Expand Down Expand Up @@ -334,10 +342,24 @@ func expandNetworkInterfaces(d tpgresource.TerraformResourceData, config *transp
for i, raw := range configs {
data := raw.(map[string]interface{})

var networkAttachment = ""
network := data["network"].(string)
subnetwork := data["subnetwork"].(string)
if network == "" && subnetwork == "" {
return nil, fmt.Errorf("exactly one of network or subnetwork must be provided")
if networkAttachmentObj, ok := data["network_attachment"]; ok {
networkAttachment = networkAttachmentObj.(string)
}
// Checks if networkAttachment is not specified in resource, network or subnetwork have to be specifed.
if networkAttachment == "" && network == "" && subnetwork == "" {
return nil, fmt.Errorf("exactly one of network, subnetwork, or network_attachment must be provided")
}

if networkAttachment != "" {
if network != "" {
return nil, fmt.Errorf("Cannot have a network provided with networkAttachment given that networkAttachment is associated with a network already")
}
if subnetwork != "" {
return nil, fmt.Errorf("Cannot have a subnetwork provided with networkAttachment given that networkAttachment is associated with a subnetwork already")
}
}

nf, err := tpgresource.ParseNetworkFieldValue(network, d, config)
Expand All @@ -354,6 +376,7 @@ func expandNetworkInterfaces(d tpgresource.TerraformResourceData, config *transp
ifaces[i] = &compute.NetworkInterface{
NetworkIP: data["network_ip"].(string),
Network: nf.RelativeLink(),
NetworkAttachment: networkAttachment,
Subnetwork: sf.RelativeLink(),
AccessConfigs: expandAccessConfigs(data["access_config"].([]interface{})),
AliasIpRanges: expandAliasIpRanges(data["alias_ip_range"].([]interface{})),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,15 @@ Google Cloud KMS.`,
Description: `The name of the subnetwork to attach this interface to. The subnetwork must exist in the same region this instance will be created in. Either network or subnetwork must be provided.`,
},

"network_attachment": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName,
Description: `The URL of the network attachment that this interface should connect to in the following format: projects/{projectNumber}/regions/{region_name}/networkAttachments/{network_attachment_name}.`,
},

"subnetwork_project": {
Type: schema.TypeString,
Optional: true,
Expand Down
2 changes: 2 additions & 0 deletions website/docs/d/compute_instance_template.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ The `disk_encryption_key` block supports:
to. The subnetwork must exist in the same `region` this instance will be
created in. Either `network` or `subnetwork` must be provided.

* `network_interface` - (Optional, [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html)) The URL of the network attachment that this interface should connect to in the following format: projects/{projectNumber}/regions/{region_name}/networkAttachments/{network_attachment_name}. s

* `subnetwork_project` - The ID of the project in which the subnetwork belongs.
If it is not provided, the provider project is used.

Expand Down
2 changes: 2 additions & 0 deletions website/docs/r/compute_instance_template.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,8 @@ The following arguments are supported:
* `subnetwork` - (Optional) the name of the subnetwork to attach this interface
to. The subnetwork must exist in the same `region` this instance will be
created in. Either `network` or `subnetwork` must be provided.

* `network_interface` - (Optional, [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html)) The URL of the network attachment that this interface should connect to in the following format: projects/{projectNumber}/regions/{region_name}/networkAttachments/{network_attachment_name}.

* `subnetwork_project` - (Optional) The ID of the project in which the subnetwork belongs.
If it is not provided, the provider project is used.
Expand Down

0 comments on commit d4c436e

Please sign in to comment.