Skip to content

Commit 9d67964

Browse files
afedorchRavi Tandon
authored andcommitted
Impossible to create a route rule pointing to private IP in the same subnet
1 parent f80ca6c commit 9d67964

File tree

8 files changed

+439
-0
lines changed

8 files changed

+439
-0
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
## 3.9.1 (Unreleased)
2+
3+
### Added
4+
- Support for attaching Route Table to Subnet. Issue [#270](https://github.com/terraform-providers/terraform-provider-oci/issues/270)
5+
6+
27
## 3.9.0 (December 04, 2018)
38

49
### Added
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
### Use Route Table Attachment to avoid cyclic dependency between Subnet and Route Table
2+
3+
This example uses the `oci_core_route_table_attachment` resource to resolve this dependency cycle problem:
4+
* oci_core_vnic_attachment.ExampleVnicAttachment
5+
* oci_core_private_ip.TFPrivateIP
6+
* oci_core_route_table.ExampleRouteTable
7+
* oci_core_subnet.ExampleSubnet
8+
* oci_core_instance.ExampleInstance
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
variable "tenancy_ocid" {}
2+
variable "user_ocid" {}
3+
variable "fingerprint" {}
4+
variable "private_key_path" {}
5+
variable "compartment_ocid" {}
6+
variable "region" {}
7+
8+
variable "instance_image_ocid" {
9+
type = "map"
10+
11+
default = {
12+
// See https://docs.us-phoenix-1.oraclecloud.com/images/
13+
// Oracle-provided image "Oracle-Linux-7.4-2018.02.21-1"
14+
us-phoenix-1 = "ocid1.image.oc1.phx.aaaaaaaaupbfz5f5hdvejulmalhyb6goieolullgkpumorbvxlwkaowglslq"
15+
16+
us-ashburn-1 = "ocid1.image.oc1.iad.aaaaaaaajlw3xfie2t5t52uegyhiq2npx7bqyu4uvi2zyu3w3mqayc2bxmaa"
17+
eu-frankfurt-1 = "ocid1.image.oc1.eu-frankfurt-1.aaaaaaaa7d3fsb6272srnftyi4dphdgfjf6gurxqhmv6ileds7ba3m2gltxq"
18+
uk-london-1 = "ocid1.image.oc1.uk-london-1.aaaaaaaaa6h6gj6v4n56mqrbgnosskq63blyv2752g36zerymy63cfkojiiq"
19+
}
20+
}
21+
22+
variable "availability_domain" {
23+
default = 3
24+
}
25+
26+
data "oci_identity_availability_domains" "ADs" {
27+
compartment_id = "${var.tenancy_ocid}"
28+
}
29+
30+
variable "instance_shape" {
31+
default = "VM.Standard1.8"
32+
}
33+
34+
provider "oci" {
35+
tenancy_ocid = "${var.tenancy_ocid}"
36+
user_ocid = "${var.user_ocid}"
37+
fingerprint = "${var.fingerprint}"
38+
private_key_path = "${var.private_key_path}"
39+
region = "${var.region}"
40+
}
41+
42+
resource "oci_core_virtual_network" "ExampleVCN" {
43+
cidr_block = "10.1.0.0/16"
44+
compartment_id = "${var.compartment_ocid}"
45+
display_name = "TFExampleVCN"
46+
dns_label = "tfexamplevcn"
47+
}
48+
49+
resource "oci_core_subnet" "ExampleSubnet" {
50+
availability_domain = "${lookup(data.oci_identity_availability_domains.ADs.availability_domains[var.availability_domain - 1],"name")}"
51+
cidr_block = "10.1.20.0/24"
52+
display_name = "TFExampleSubnet"
53+
dns_label = "tfexamplesubnet"
54+
55+
compartment_id = "${var.compartment_ocid}"
56+
vcn_id = "${oci_core_virtual_network.ExampleVCN.id}"
57+
}
58+
59+
resource "oci_core_route_table_attachment" "ExampleRouteTableAttachment" {
60+
subnet_id = "${oci_core_subnet.ExampleSubnet.id}"
61+
route_table_id = "${oci_core_route_table.ExampleRouteTable.id}"
62+
}
63+
64+
resource "oci_core_private_ip" "TFPrivateIP" {
65+
vnic_id = "${oci_core_vnic_attachment.ExampleVnicAttachment.vnic_id}"
66+
display_name = "someDisplayName"
67+
hostname_label = "somehostnamelabel"
68+
}
69+
70+
resource "oci_core_route_table" "ExampleRouteTable" {
71+
compartment_id = "${var.compartment_ocid}"
72+
vcn_id = "${oci_core_virtual_network.ExampleVCN.id}"
73+
display_name = "TFExampleRouteTable"
74+
75+
route_rules {
76+
destination = "0.0.0.0/0"
77+
destination_type = "CIDR_BLOCK"
78+
network_entity_id = "${oci_core_private_ip.TFPrivateIP.id}"
79+
}
80+
}
81+
82+
resource "oci_core_vnic_attachment" "ExampleVnicAttachment" {
83+
create_vnic_details {
84+
subnet_id = "${oci_core_subnet.ExampleSubnet.id}"
85+
skip_source_dest_check = true
86+
}
87+
88+
instance_id = "${oci_core_instance.ExampleInstance.id}"
89+
}
90+
91+
# Create Instance
92+
resource "oci_core_instance" "ExampleInstance" {
93+
availability_domain = "${lookup(data.oci_identity_availability_domains.ADs.availability_domains[var.availability_domain - 1],"name")}"
94+
compartment_id = "${var.compartment_ocid}"
95+
display_name = "TFInstance"
96+
hostname_label = "instance"
97+
image = "${var.instance_image_ocid[var.region]}"
98+
shape = "${var.instance_shape}"
99+
100+
create_vnic_details {
101+
subnet_id = "${oci_core_subnet.ExampleSubnet.id}"
102+
skip_source_dest_check = true
103+
assign_public_ip = true
104+
}
105+
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
2+
3+
package provider
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"strings"
9+
10+
"github.com/hashicorp/terraform/helper/schema"
11+
12+
oci_core "github.com/oracle/oci-go-sdk/core"
13+
)
14+
15+
func RouteTableAttachmentResource() *schema.Resource {
16+
return &schema.Resource{
17+
Importer: &schema.ResourceImporter{
18+
State: schema.ImportStatePassthrough,
19+
},
20+
Timeouts: DefaultTimeout,
21+
Create: createRouteTableAttachment,
22+
Delete: deleteRouteTableAttachment,
23+
Read: readRouteTableAttachment,
24+
Schema: map[string]*schema.Schema{
25+
// Required
26+
"subnet_id": {
27+
Type: schema.TypeString,
28+
Required: true,
29+
ForceNew: true,
30+
},
31+
"route_table_id": {
32+
Type: schema.TypeString,
33+
Required: true,
34+
ForceNew: true,
35+
},
36+
},
37+
}
38+
}
39+
40+
func createRouteTableAttachment(d *schema.ResourceData, m interface{}) error {
41+
sync := &RouteTableAttachmentResourceCrud{}
42+
sync.D = d
43+
sync.Client = m.(*OracleClients).virtualNetworkClient
44+
45+
return CreateResource(d, sync)
46+
}
47+
48+
func deleteRouteTableAttachment(d *schema.ResourceData, m interface{}) error {
49+
sync := &RouteTableAttachmentResourceCrud{}
50+
sync.D = d
51+
sync.Client = m.(*OracleClients).virtualNetworkClient
52+
sync.DisableNotFoundRetries = true
53+
54+
return DeleteResource(d, sync)
55+
}
56+
57+
func readRouteTableAttachment(d *schema.ResourceData, m interface{}) error {
58+
sync := &RouteTableAttachmentResourceCrud{}
59+
sync.D = d
60+
sync.Client = m.(*OracleClients).virtualNetworkClient
61+
sync.DisableNotFoundRetries = true
62+
63+
return ReadResource(sync)
64+
}
65+
66+
type RouteTableAttachmentResourceCrud struct {
67+
BaseCrud
68+
Client *oci_core.VirtualNetworkClient
69+
Res *oci_core.Subnet
70+
DisableNotFoundRetries bool
71+
}
72+
73+
func getRouteTableAttachmentId(subnetId string, routeTableId string) string {
74+
return subnetId + "/" + routeTableId
75+
}
76+
77+
func parseRouteTableAttachmentId(id string) (subnetId string, routTableId string, err error) {
78+
parts := strings.Split(id, "/")
79+
if len(parts) < 2 {
80+
err = fmt.Errorf("illegal id %s encountered", id)
81+
}
82+
subnetId, routTableId = parts[0], parts[1]
83+
84+
return
85+
}
86+
87+
func (s *RouteTableAttachmentResourceCrud) ID() string {
88+
return getRouteTableAttachmentId(*s.Res.Id, *s.Res.RouteTableId)
89+
}
90+
91+
func (s *RouteTableAttachmentResourceCrud) Create() error {
92+
request := oci_core.UpdateSubnetRequest{}
93+
94+
if routeTableId, ok := s.D.GetOkExists("route_table_id"); ok {
95+
tmp := routeTableId.(string)
96+
request.RouteTableId = &tmp
97+
}
98+
99+
if subnetId, ok := s.D.GetOkExists("subnet_id"); ok {
100+
tmp := subnetId.(string)
101+
request.SubnetId = &tmp
102+
}
103+
104+
request.RequestMetadata.RetryPolicy = getRetryPolicy(s.DisableNotFoundRetries, "core")
105+
response, err := s.Client.UpdateSubnet(context.Background(), request)
106+
if err != nil {
107+
return err
108+
}
109+
110+
s.Res = &response.Subnet
111+
return nil
112+
}
113+
114+
func (s *RouteTableAttachmentResourceCrud) Get() error {
115+
116+
subnetId, routeTableId, err := parseRouteTableAttachmentId(s.D.Id())
117+
if err != nil {
118+
return err
119+
}
120+
request := oci_core.GetSubnetRequest{}
121+
request.SubnetId = &subnetId
122+
request.RequestMetadata.RetryPolicy = getRetryPolicy(s.DisableNotFoundRetries, "core")
123+
response, err := s.Client.GetSubnet(context.Background(), request)
124+
if err != nil {
125+
return err
126+
}
127+
128+
if response.Subnet.RouteTableId == nil || *response.Subnet.RouteTableId != routeTableId {
129+
return fmt.Errorf("inconsistent route table id received: %s expected: %s", *response.Subnet.RouteTableId, routeTableId)
130+
}
131+
132+
s.Res = &response.Subnet
133+
return nil
134+
}
135+
136+
func (s *RouteTableAttachmentResourceCrud) Delete() error {
137+
138+
var subnetIdStr = s.D.Get("subnet_id").(string)
139+
var retryPolicy = getRetryPolicy(s.DisableNotFoundRetries, "core")
140+
141+
subnetRequest := oci_core.GetSubnetRequest{}
142+
subnetRequest.SubnetId = &subnetIdStr
143+
subnetRequest.RequestMetadata.RetryPolicy = retryPolicy
144+
subnetResponse, err := s.Client.GetSubnet(context.Background(), subnetRequest)
145+
if err != nil {
146+
return err
147+
}
148+
149+
vcnRequest := oci_core.GetVcnRequest{}
150+
vcnRequest.VcnId = subnetResponse.VcnId
151+
vcnRequest.RequestMetadata.RetryPolicy = retryPolicy
152+
vcnResponse, err := s.Client.GetVcn(context.Background(), vcnRequest)
153+
if err != nil {
154+
return err
155+
}
156+
157+
updateSubnetRequest := oci_core.UpdateSubnetRequest{}
158+
updateSubnetRequest.RouteTableId = vcnResponse.DefaultRouteTableId
159+
updateSubnetRequest.SubnetId = &subnetIdStr
160+
updateSubnetRequest.RequestMetadata.RetryPolicy = retryPolicy
161+
updateSubnetResponse, err := s.Client.UpdateSubnet(context.Background(), updateSubnetRequest)
162+
if err != nil {
163+
return err
164+
}
165+
166+
s.Res = &updateSubnetResponse.Subnet
167+
return nil
168+
}
169+
170+
func (s *RouteTableAttachmentResourceCrud) SetData() error {
171+
172+
s.D.Set("subnet_id", *s.Res.Id)
173+
174+
if s.Res.RouteTableId != nil {
175+
s.D.Set("route_table_id", *s.Res.RouteTableId)
176+
}
177+
178+
return nil
179+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
2+
3+
package provider
4+
5+
import (
6+
"fmt"
7+
"testing"
8+
9+
"github.com/hashicorp/terraform/helper/resource"
10+
"github.com/hashicorp/terraform/terraform"
11+
)
12+
13+
var (
14+
routeTableAttachmentRepresentation = map[string]interface{}{
15+
"subnet_id": Representation{repType: Required, create: `${oci_core_subnet.test_subnet.id}`},
16+
"route_table_id": Representation{repType: Required, create: `${oci_core_route_table.test_route_table.id}`},
17+
}
18+
19+
RouteTableResourceAttachmentDependencies = SubnetResourceConfig
20+
)
21+
22+
func TestCoreRouteTableAttachmentResource_basic(t *testing.T) {
23+
provider := testAccProvider
24+
config := testProviderConfig()
25+
26+
compartmentId := getEnvSettingWithBlankDefault("compartment_ocid")
27+
compartmentIdVariableStr := fmt.Sprintf("variable \"compartment_id\" { default = \"%s\" }\n", compartmentId)
28+
29+
resourceName := "oci_core_route_table_attachment.test_route_table_attachment"
30+
31+
var resId string
32+
var routeTableIdFromRT string
33+
34+
resource.Test(t, resource.TestCase{
35+
PreCheck: func() { testAccPreCheck(t) },
36+
Providers: map[string]terraform.ResourceProvider{
37+
"oci": provider,
38+
},
39+
Steps: []resource.TestStep{
40+
// verify create
41+
{
42+
Config: config + compartmentIdVariableStr + RouteTableResourceAttachmentDependencies +
43+
generateResourceFromRepresentationMap("oci_core_route_table_attachment", "test_route_table_attachment", Required, Create, routeTableAttachmentRepresentation),
44+
Check: resource.ComposeAggregateTestCheckFunc(
45+
func(s *terraform.State) (err error) {
46+
resId, err = fromInstanceState(s, resourceName, "id")
47+
if err != nil {
48+
return err
49+
}
50+
51+
routeTableIdFromRT, err := fromInstanceState(s, "oci_core_route_table.test_route_table", "id")
52+
if err != nil {
53+
return err
54+
}
55+
56+
routeTableIdFromSubnet, err := fromInstanceState(s, "oci_core_subnet.test_subnet", "route_table_id")
57+
if routeTableIdFromRT != routeTableIdFromSubnet {
58+
return fmt.Errorf("requested routeTable was not attached to the subnet")
59+
}
60+
return err
61+
},
62+
),
63+
},
64+
// verify delete
65+
{
66+
Config: config + compartmentIdVariableStr + RouteTableResourceAttachmentDependencies,
67+
ExpectNonEmptyPlan: true,
68+
Check: resource.ComposeAggregateTestCheckFunc(
69+
func(s *terraform.State) (err error) {
70+
routeTableIdFromSubnet, err := fromInstanceState(s, "oci_core_subnet.test_subnet", "route_table_id")
71+
if routeTableIdFromRT == routeTableIdFromSubnet {
72+
return fmt.Errorf("requested routeTable was not detached from the subnet")
73+
}
74+
return err
75+
},
76+
),
77+
},
78+
// create attachment to import
79+
{
80+
Config: config + compartmentIdVariableStr + RouteTableResourceAttachmentDependencies +
81+
generateResourceFromRepresentationMap("oci_core_route_table_attachment", "test_route_table_attachment", Required, Create, routeTableAttachmentRepresentation),
82+
},
83+
// verify resource import
84+
{
85+
Config: config,
86+
ImportState: true,
87+
ImportStateVerify: true,
88+
ImportStateVerifyIgnore: []string{},
89+
ResourceName: resourceName,
90+
},
91+
},
92+
})
93+
}

0 commit comments

Comments
 (0)