diff --git a/CHANGELOG.md b/CHANGELOG.md index baa60fdf5..5da170c13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ FEATURES: * **New build commands** `make test-env-init` and `make test-env-apply` can configure an empty vCD to run the test suite. See `TESTING.md` for details. * `resource/vcd_org_vdc` added Org VDC update and full state read - [GH-275] * `resource/vcd_org_vdc` added Org VDC metadata support - [GH-276] +* `resource/vcd_snat` added ability to choose network name and type. [GH-282] +* `resource/vcd_dnat` added ability to choose network name and type. [GH-282] IMPROVEMENTS: * `resource/vcd_org_vdc`: Fix ignoring of resource guarantee values - [GH-265] @@ -22,6 +24,10 @@ IMPROVEMENTS: * Change resource handling to use locking mechanism when resource parallel handling is not supported by vCD. [GH-255] * Fix issue when vApp is power cycled during member VM deletion. [GH-261] +BUG FIXES: + +* `resource/vcd_dnat and resource/vcd_snat` - fix resource destroy as it would still leave NAT rule in edge gateway. Fix works if network_name and network_type is used. [GH-282] + ## 2.3.0 (May 29, 2019) IMPROVEMENTS: diff --git a/go.mod b/go.mod index 2d3eae24d..b9317248c 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,5 @@ go 1.12 require ( github.com/hashicorp/terraform v0.12.0 - github.com/vmware/go-vcloud-director/v2 v2.3.0-beta.2 + github.com/vmware/go-vcloud-director/v2 v2.3.0-beta.3 ) diff --git a/go.sum b/go.sum index 754d24b79..b6dd75a76 100644 --- a/go.sum +++ b/go.sum @@ -301,8 +301,8 @@ github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4A github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.1+incompatible h1:RMF1enSPeKTlXrXdOcqjFUElywVZjjC6pqse21bKbEU= github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmware/go-vcloud-director/v2 v2.3.0-beta.2 h1:3lB0heHFS+0SKcuWsQAx2YZXoxDeZXeHsEme5sYaTHw= -github.com/vmware/go-vcloud-director/v2 v2.3.0-beta.2/go.mod h1:HonlGxbjJ1NAibWh99eE4/S2l6ZOZ5KJzKK1rh2a9vc= +github.com/vmware/go-vcloud-director/v2 v2.3.0-beta.3 h1:PRs0pkY3Uvt5YmASrGvbPobqU/V5hYc7hlDEPCI7jEM= +github.com/vmware/go-vcloud-director/v2 v2.3.0-beta.3/go.mod h1:HonlGxbjJ1NAibWh99eE4/S2l6ZOZ5KJzKK1rh2a9vc= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= diff --git a/scripts/runtest.sh b/scripts/runtest.sh index babd278be..cf72609f9 100755 --- a/scripts/runtest.sh +++ b/scripts/runtest.sh @@ -92,13 +92,13 @@ function acceptance_test { if [ -n "$VERBOSE" ] then echo "# check for config file" - echo "TF_ACC=1 go test -tags '$tags' -v -timeout 90m ." + echo "TF_ACC=1 go test -tags '$tags' -v -timeout 120m ." fi if [ -z "$DRY_RUN" ] then check_for_config_file - TF_ACC=1 go test -tags "$tags" -v -timeout 90m . + TF_ACC=1 go test -tags "$tags" -v -timeout 120m . fi } @@ -111,13 +111,13 @@ function multiple_test { if [ -n "$VERBOSE" ] then echo "# check for config file" - echo "TF_ACC=1 go test -v -timeout 90m -tags 'api multivm multinetwork' -run '$filter' ." + echo "TF_ACC=1 go test -v -timeout 120m -tags 'api multivm multinetwork' -run '$filter' ." fi if [ -z "$DRY_RUN" ] then check_for_config_file - TF_ACC=1 go test -v -timeout 90m -tags 'api multivm multinetwork' -run "$filter" . + TF_ACC=1 go test -v -timeout 120m -tags 'api multivm multinetwork' -run "$filter" . fi } diff --git a/vcd/resource_vcd_dnat.go b/vcd/resource_vcd_dnat.go index c22f27d12..23101d981 100644 --- a/vcd/resource_vcd_dnat.go +++ b/vcd/resource_vcd_dnat.go @@ -3,10 +3,12 @@ package vcd import ( "fmt" "log" + "strconv" "strings" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + "github.com/vmware/go-vcloud-director/v2/govcd" "github.com/vmware/go-vcloud-director/v2/types/v56" ) @@ -15,6 +17,7 @@ func resourceVcdDNAT() *schema.Resource { Create: resourceVcdDNATCreate, Delete: resourceVcdDNATDelete, Read: resourceVcdDNATRead, + Update: resourceVcdDNATUpdate, Schema: map[string]*schema.Schema{ "edge_gateway": &schema.Schema{ @@ -35,49 +38,46 @@ func resourceVcdDNAT() *schema.Resource { "network_name": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, }, "network_type": &schema.Schema{ Type: schema.TypeString, Optional: true, - Default: "ext", - ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"ext", "org"}, false), }, "external_ip": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: true, }, "port": &schema.Schema{ Type: schema.TypeInt, - Required: true, - ForceNew: true, + Optional: true, + Computed: true, }, "translated_port": &schema.Schema{ Type: schema.TypeInt, Optional: true, - ForceNew: true, + Computed: true, }, "internal_ip": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: true, }, "protocol": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, Default: "TCP", // keep back compatibility as was hardcoded previously }, "icmp_sub_type": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, - Default: "any", + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, }, }, } @@ -86,9 +86,6 @@ func resourceVcdDNAT() *schema.Resource { func resourceVcdDNATCreate(d *schema.ResourceData, meta interface{}) error { vcdClient := meta.(*VCDClient) - // Multiple VCD components need to run operations on the Edge Gateway, as - // the edge gateway will throw back an error if it is already performing an - // operation we must wait until we can acquire a lock on the client vcdClient.lockParentEdgeGtw(d) defer vcdClient.unLockParentEdgeGtw(d) @@ -98,48 +95,52 @@ func resourceVcdDNATCreate(d *schema.ResourceData, meta interface{}) error { translatedPortString = getPortString(d.Get("translated_port").(int)) } - edgeGateway, err := vcdClient.GetEdgeGatewayFromResource(d, "edge_gateway") - if err != nil { - return fmt.Errorf(errorUnableToFindEdgeGateway, err) - } - protocol := d.Get("protocol").(string) icmpSubType := d.Get("icmp_sub_type").(string) if strings.ToUpper(protocol) != "ICMP" { icmpSubType = "" } - var orgVdcnetwork *types.OrgVDCNetwork - providedNetworkName := d.Get("network_name") - if nil != providedNetworkName && "" != providedNetworkName.(string) { - orgVdcnetwork, err = getNetwork(d, vcdClient, providedNetworkName.(string)) - } - // TODO enable when external network supported - /*else { - _, _ = fmt.Fprint(GetTerraformStdout(), "WARNING: This resource will require network_name and network_type in the next major version \n") - }*/ + edgeGateway, err := vcdClient.GetEdgeGatewayFromResource(d, "edge_gateway") if err != nil { - return fmt.Errorf("unable to find orgVdcnetwork: %s, err: %s", providedNetworkName.(string), err) + return fmt.Errorf(errorUnableToFindEdgeGateway, err) } - if nil != providedNetworkName && providedNetworkName != "" { - task, err := edgeGateway.AddNATPortMappingWithUplink(orgVdcnetwork, "DNAT", - d.Get("external_ip").(string), - portString, - d.Get("internal_ip").(string), - translatedPortString, protocol, - icmpSubType) + networkName := d.Get("network_name").(string) + networkType := d.Get("network_type").(string) + + var natRule *types.NatRule + + if networkName != "" && networkType == "org" { + orgVdcNetwork, err := getOrgVdcNetwork(d, vcdClient, networkName) if err != nil { - return fmt.Errorf("error setting DNAT rules: %#v", err) + return fmt.Errorf("unable to find orgVdcNetwork: %s, err: %s", networkName, err) } - err = task.WaitTaskCompletion() + natRule, err = edgeGateway.AddDNATRule(govcd.NatRule{NetworkHref: orgVdcNetwork.HREF, + ExternalIP: d.Get("external_ip").(string), ExternalPort: portString, + InternalIP: d.Get("internal_ip").(string), InternalPort: translatedPortString, + Protocol: protocol, IcmpSubType: icmpSubType, Description: d.Get("description").(string)}) + if err != nil { - return fmt.Errorf("error completing tasks: %#v", err) + return fmt.Errorf("error creating DNAT rule: %#v", err) + } + } else if networkName != "" && networkType == "ext" { + externalNetwork, err := govcd.GetExternalNetwork(vcdClient.VCDClient, networkName) + if err != nil { + return fmt.Errorf("unable to find external network: %s, err: %s", networkName, err) } + natRule, err = edgeGateway.AddDNATRule(govcd.NatRule{NetworkHref: externalNetwork.ExternalNetwork.HREF, + ExternalIP: d.Get("external_ip").(string), ExternalPort: portString, + InternalIP: d.Get("internal_ip").(string), InternalPort: translatedPortString, + Protocol: protocol, IcmpSubType: icmpSubType, Description: d.Get("description").(string)}) + if err != nil { + return fmt.Errorf("error creating DNAT rule: %#v", err) + } } else { // TODO remove when major release is done + _, _ = fmt.Fprint(GetTerraformStdout(), "WARNING: This resource will require network_name and network_type in the next major version \n") task, err := edgeGateway.AddNATPortMapping("DNAT", d.Get("external_ip").(string), portString, @@ -156,8 +157,8 @@ func resourceVcdDNATCreate(d *schema.ResourceData, meta interface{}) error { } } - if nil != providedNetworkName && "" != providedNetworkName { - d.SetId(orgVdcnetwork.Name + ":" + d.Get("external_ip").(string) + ":" + portString + " > " + d.Get("internal_ip").(string) + ":" + translatedPortString) + if networkName != "" { + d.SetId(natRule.ID) } else { d.SetId(d.Get("external_ip").(string) + ":" + portString + " > " + d.Get("internal_ip").(string) + ":" + translatedPortString) } @@ -167,27 +168,56 @@ func resourceVcdDNATCreate(d *schema.ResourceData, meta interface{}) error { func resourceVcdDNATRead(d *schema.ResourceData, meta interface{}) error { vcdClient := meta.(*VCDClient) - e, err := vcdClient.GetEdgeGatewayFromResource(d, "edge_gateway") + edgeGateway, err := vcdClient.GetEdgeGatewayFromResource(d, "edge_gateway") if err != nil { return fmt.Errorf(errorUnableToFindEdgeGateway, err) } + // Terraform refresh won't work if Rule was edit in advanced edge gateway UI. vCD API uses elements to map edge gtw IDs + // and UI will reset the tag element on update. + var found bool - providedNetworkName := d.Get("network_name") - if nil != providedNetworkName && providedNetworkName.(string) != "" { - for _, r := range e.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { - if r.RuleType == "DNAT" && r.GatewayNatRule.Interface.Name == d.Get("network_name") && - r.GatewayNatRule.OriginalIP == d.Get("external_ip").(string) && - r.GatewayNatRule.OriginalPort == getPortString(d.Get("port").(int)) { - found = true - d.Set("internal_ip", r.GatewayNatRule.TranslatedIP) - } + networkName := d.Get("network_name") + if nil != networkName && networkName.(string) != "" { + natRule, err := edgeGateway.GetNatRule(d.Id()) + if err != nil { + log.Printf("rule %s (stored in in Advanced GW case) not found: %s. Removing from state.", d.Id(), err) + d.SetId("") + } + + portInt, _ := strconv.Atoi(natRule.GatewayNatRule.OriginalPort) + translatedPortInt, _ := strconv.Atoi(natRule.GatewayNatRule.TranslatedPort) + + d.Set("description", natRule.Description) + d.Set("external_ip", natRule.GatewayNatRule.OriginalIP) + d.Set("port", portInt) + d.Set("internal_ip", natRule.GatewayNatRule.TranslatedIP) + d.Set("translated_port", translatedPortInt) + d.Set("protocol", natRule.GatewayNatRule.Protocol) + d.Set("icmp_sub_type", natRule.GatewayNatRule.IcmpSubType) + d.Set("network_name", natRule.GatewayNatRule.Interface.Name) + + orgVdcNetwork, _ := getOrgVdcNetwork(d, vcdClient, natRule.GatewayNatRule.Interface.Name) + if orgVdcNetwork != nil { + d.Set("network_type", "org") + found = true + } + externalNetwork, extNetwErr := govcd.GetExternalNetwork(vcdClient.VCDClient, natRule.GatewayNatRule.Interface.Name) + if extNetwErr != nil && strings.Contains(extNetwErr.Error(), "could not find external network named") { + d.Set("network_type", "ext") + found = true + } + if orgVdcNetwork != nil && extNetwErr == nil { + return fmt.Errorf("found external network or org VCD network with same name: %s", natRule.GatewayNatRule.Interface.Name) + } else if orgVdcNetwork == nil && externalNetwork == (&govcd.ExternalNetwork{}) { + log.Printf("didn't find external network or org VCD network with name: %s", natRule.GatewayNatRule.Interface.Name) + found = false } } else { // TODO remove when major release is done - for _, r := range e.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { + for _, r := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { if r.RuleType == "DNAT" && r.GatewayNatRule.OriginalIP == d.Get("external_ip").(string) && r.GatewayNatRule.OriginalPort == getPortString(d.Get("port").(int)) { @@ -220,31 +250,95 @@ func resourceVcdDNATDelete(d *schema.ResourceData, meta interface{}) error { } edgeGateway, err := vcdClient.GetEdgeGatewayFromResource(d, "edge_gateway") + if err != nil { + return fmt.Errorf(errorUnableToFindEdgeGateway, err) + } + + if d.Get("network_name").(string) != "" { + err = edgeGateway.RemoveNATRule(d.Id()) + if err != nil { + return fmt.Errorf("error deleting SNAT rule: %#v", err) + } + } else { + // this for back compatibility when network name and network type isn't provided - TODO remove with major release + task, err := edgeGateway.RemoveNATPortMapping("DNAT", + d.Get("external_ip").(string), + portString, + d.Get("internal_ip").(string), + translatedPortString) + if err != nil { + return fmt.Errorf("error setting DNAT rules: %#v", err) + } + err = task.WaitTaskCompletion() + if err != nil { + return fmt.Errorf("error completing tasks: %#v", err) + } + } + return nil +} + +func resourceVcdDNATUpdate(d *schema.ResourceData, meta interface{}) error { + + vcdClient := meta.(*VCDClient) + + // Update supports only when network name and network type provided + networkName := d.Get("network_name") + if networkName == nil || networkName.(string) == "" { + return fmt.Errorf("update works only when network_name and network_type is provided and rule created using them \n") + } + + edgeGateway, err := vcdClient.GetEdgeGatewayFromResource(d, "edge_gateway") if err != nil { return fmt.Errorf(errorUnableToFindEdgeGateway, err) } - task, err := edgeGateway.RemoveNATPortMapping("DNAT", - d.Get("external_ip").(string), - portString, - d.Get("internal_ip").(string), - translatedPortString) + + natRule, err := edgeGateway.GetNatRule(d.Id()) if err != nil { - return fmt.Errorf("error setting DNAT rules: %#v", err) + log.Printf(" rule %s not found: %s. Removing from state.", d.Id(), err) + d.SetId("") + } + + natRule.GatewayNatRule.OriginalIP = d.Get("external_ip").(string) + natRule.GatewayNatRule.OriginalPort = getPortString(d.Get("port").(int)) + natRule.GatewayNatRule.TranslatedIP = d.Get("internal_ip").(string) + natRule.GatewayNatRule.TranslatedPort = getPortString(d.Get("translated_port").(int)) + natRule.GatewayNatRule.Protocol = d.Get("protocol").(string) + natRule.GatewayNatRule.IcmpSubType = d.Get("icmp_sub_type").(string) + natRule.Description = d.Get("description").(string) + + if d.Get("network_type").(string) == "org" { + orgVdcNetwork, err := getOrgVdcNetwork(d, vcdClient, d.Get("network_name").(string)) + if orgVdcNetwork == nil || err != nil { + return fmt.Errorf("unable to find orgVdcNetwork: %s, err: %s", networkName, err) + } + natRule.GatewayNatRule.Interface.Name = orgVdcNetwork.Name + natRule.GatewayNatRule.Interface.HREF = orgVdcNetwork.HREF + + } else if d.Get("network_type").(string) == "ext" { + externalNetwork, _ := govcd.GetExternalNetwork(vcdClient.VCDClient, d.Get("network_name").(string)) + if externalNetwork == nil || externalNetwork == (&govcd.ExternalNetwork{}) || err != nil { + return fmt.Errorf("unable to find external network: %s, err: %s", networkName, err) + } + natRule.GatewayNatRule.Interface.Name = externalNetwork.ExternalNetwork.Name + natRule.GatewayNatRule.Interface.HREF = externalNetwork.ExternalNetwork.HREF + } else { + return fmt.Errorf("network_type isn't provided or not `ext` or `org` ") } - err = task.WaitTaskCompletion() + _, err = edgeGateway.UpdateNatRule(natRule) if err != nil { - return fmt.Errorf("error completing tasks: %#v", err) + return fmt.Errorf("unable to update nat Rule: err: %s", err) } - return nil + + return resourceVcdSNATRead(d, meta) } -func getNetwork(d *schema.ResourceData, vcdClient *VCDClient, networkname string) (*types.OrgVDCNetwork, error) { +func getOrgVdcNetwork(d *schema.ResourceData, vcdClient *VCDClient, networkname string) (*types.OrgVDCNetwork, error) { _, vdc, err := vcdClient.GetOrgAndVdcFromResource(d) if err != nil { - return &types.OrgVDCNetwork{}, fmt.Errorf(errorRetrievingOrgAndVdc, err) + return nil, fmt.Errorf(errorRetrievingOrgAndVdc, err) } network, err := vdc.FindVDCNetwork(networkname) diff --git a/vcd/resource_vcd_dnat_test.go b/vcd/resource_vcd_dnat_test.go index 9427c4320..94e3229c2 100644 --- a/vcd/resource_vcd_dnat_test.go +++ b/vcd/resource_vcd_dnat_test.go @@ -4,6 +4,7 @@ package vcd import ( "fmt" + "regexp" "testing" "github.com/hashicorp/terraform/helper/resource" @@ -14,14 +15,14 @@ import ( var baseDnatName string = "TestAccVcdDNAT" var orgVdcNetworkName = "TestAccVcdDNAT_BasicNetwork" -func TestAccVcdDNAT_Basic(t *testing.T) { +func TestAccVcdDNAT_WithOrgNetw(t *testing.T) { if testConfig.Networking.ExternalIp == "" { t.Skip("Variable networking.externalIp must be set to run DNAT tests") return } var e govcd.EdgeGateway - var dnatName string = baseDnatName + "_Basic" + var dnatName = baseDnatName + "_WithOrgNetw" var params = StringMap{ "Org": testConfig.VCD.Org, "Vdc": testConfig.VCD.Vdc, @@ -33,9 +34,10 @@ func TestAccVcdDNAT_Basic(t *testing.T) { "StartIpAddress": "10.10.102.51", "EndIpAddress": "10.10.102.100", "Tags": "gateway", + "Description": "test run1", } - configText := templateFill(testAccCheckVcdDnat_basic, params) + configText := templateFill(testAccCheckVcdDnatWithOrgNetw, params) if vcdShortTest { t.Skip(acceptanceTestsSkipped) return @@ -47,45 +49,54 @@ func TestAccVcdDNAT_Basic(t *testing.T) { CheckDestroy: testAccCheckVcdDNATDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: configText, + Config: configText, + ExpectError: regexp.MustCompile(`After applying this step and refreshing, the plan was not empty:`), Check: resource.ComposeTestCheckFunc( testAccCheckVcdDNATExists("vcd_dnat."+dnatName, &e), resource.TestCheckResourceAttr( "vcd_dnat."+dnatName, "network_name", orgVdcNetworkName), + resource.TestCheckResourceAttr( + "vcd_dnat."+dnatName, "network_type", "org"), resource.TestCheckResourceAttr( "vcd_dnat."+dnatName, "external_ip", testConfig.Networking.ExternalIp), resource.TestCheckResourceAttr( "vcd_dnat."+dnatName, "port", "7777"), resource.TestCheckResourceAttr( "vcd_dnat."+dnatName, "internal_ip", "10.10.102.60"), + resource.TestCheckResourceAttr( + "vcd_dnat."+dnatName, "description", "test run1"), ), }, }, }) } -func TestAccVcdDNAT_tlate(t *testing.T) { +func TestAccVcdDNAT_WithExtNetw(t *testing.T) { if testConfig.Networking.ExternalIp == "" { t.Skip("Variable networking.externalIp must be set to run DNAT tests") return } var e govcd.EdgeGateway - var dnatName string = baseDnatName + "_tlate" + var dnatName = baseDnatName + "_WithExtNetw" var params = StringMap{ - "Org": testConfig.VCD.Org, - "Vdc": testConfig.VCD.Vdc, - "EdgeGateway": testConfig.Networking.EdgeGateway, - "ExternalIp": testConfig.Networking.ExternalIp, - "DnatName": dnatName, - "OrgVdcNetworkName": "TestAccVcdDNAT_BasicNetwork", - "Gateway": "10.10.102.1", - "StartIpAddress": "10.10.102.51", - "EndIpAddress": "10.10.102.100", - "Tags": "gateway", + "Org": testConfig.VCD.Org, + "Vdc": testConfig.VCD.Vdc, + "EdgeGateway": testConfig.Networking.EdgeGateway, + "ExternalIp": testConfig.Networking.ExternalIp, + "DnatName": dnatName, + "OrgVdcNetworkName": "TestAccVcdDNAT_BasicNetwork", + "ExternalNetworkName": testConfig.Networking.ExternalNetwork, + "Gateway": "10.10.102.1", + "StartIpAddress": "10.10.102.51", + "EndIpAddress": "10.10.102.100", + "Tags": "gateway", + "Description": "test run2", } - configText := templateFill(testAccCheckVcdDnat_tlate, params) + configText := templateFill(testAccCheckVcdDnatWithExtNetw, params) + params["FuncName"] = t.Name() + "-Update" + updateText := templateFill(testAccCheckVcdDnatWithExtNetwUpdate, params) if vcdShortTest { t.Skip(acceptanceTestsSkipped) return @@ -97,11 +108,14 @@ func TestAccVcdDNAT_tlate(t *testing.T) { CheckDestroy: testAccCheckVcdDNATDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: configText, + Config: configText, + ExpectError: regexp.MustCompile(`After applying this step and refreshing, the plan was not empty:`), Check: resource.ComposeTestCheckFunc( - testAccCheckVcdDNATtlateExists("vcd_dnat."+dnatName, &e), + testAccCheckVcdDNATExists("vcd_dnat."+dnatName, &e), resource.TestCheckResourceAttr( - "vcd_dnat."+dnatName, "network_name", orgVdcNetworkName), + "vcd_dnat."+dnatName, "network_name", testConfig.Networking.ExternalNetwork), + resource.TestCheckResourceAttr( + "vcd_dnat."+dnatName, "network_type", "ext"), resource.TestCheckResourceAttr( "vcd_dnat."+dnatName, "external_ip", testConfig.Networking.ExternalIp), resource.TestCheckResourceAttr( @@ -110,6 +124,29 @@ func TestAccVcdDNAT_tlate(t *testing.T) { "vcd_dnat."+dnatName, "internal_ip", "10.10.102.60"), resource.TestCheckResourceAttr( "vcd_dnat."+dnatName, "translated_port", "77"), + resource.TestCheckResourceAttr( + "vcd_dnat."+dnatName, "description", "test run2"), + ), + }, + resource.TestStep{ + Config: updateText, + ExpectError: regexp.MustCompile(`After applying this step and refreshing, the plan was not empty:`), + Check: resource.ComposeTestCheckFunc( + testAccCheckVcdDNATExists("vcd_dnat."+dnatName, &e), + resource.TestCheckResourceAttr( + "vcd_dnat."+dnatName, "network_name", testConfig.Networking.ExternalNetwork), + resource.TestCheckResourceAttr( + "vcd_dnat."+dnatName, "network_type", "ext"), + resource.TestCheckResourceAttr( + "vcd_dnat."+dnatName, "external_ip", testConfig.Networking.ExternalIp), + resource.TestCheckResourceAttr( + "vcd_dnat."+dnatName, "port", "8888"), + resource.TestCheckResourceAttr( + "vcd_dnat."+dnatName, "internal_ip", "10.10.102.80"), + resource.TestCheckResourceAttr( + "vcd_dnat."+dnatName, "translated_port", "88"), + resource.TestCheckResourceAttr( + "vcd_dnat."+dnatName, "description", "test run2"), ), }, }, @@ -120,11 +157,11 @@ func testAccCheckVcdDNATExists(n string, gateway *govcd.EdgeGateway) resource.Te return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", n) + return fmt.Errorf("not found: %s", n) } if rs.Primary.ID == "" { - return fmt.Errorf("No DNAT ID is set") + return fmt.Errorf("DNAT ID is not set") } conn := testAccProvider.Meta().(*VCDClient) @@ -137,57 +174,13 @@ func testAccCheckVcdDNATExists(n string, gateway *govcd.EdgeGateway) resource.Te return fmt.Errorf(errorUnableToFindEdgeGateway, err) } - var found bool - for _, v := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { - if v.RuleType == "DNAT" && - v.GatewayNatRule.OriginalIP == testConfig.Networking.ExternalIp && - v.GatewayNatRule.OriginalPort == "7777" && - v.GatewayNatRule.TranslatedIP == "10.10.102.60" { - found = true - } - } - if !found { - return fmt.Errorf("DNAT rule was not found") - } - - *gateway = edgeGateway - - return nil - } -} - -func testAccCheckVcdDNATtlateExists(n string, gateway *govcd.EdgeGateway) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("no DNAT ID is set") - } - - conn := testAccProvider.Meta().(*VCDClient) - - gatewayName := rs.Primary.Attributes["edge_gateway"] - - edgeGateway, err := conn.GetEdgeGateway(testConfig.VCD.Org, testConfig.VCD.Vdc, gatewayName) + natRule, err := edgeGateway.GetNatRule(rs.Primary.ID) if err != nil { - return fmt.Errorf(errorUnableToFindEdgeGateway, err) + return err } - var found bool - for _, v := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { - if v.RuleType == "DNAT" && - v.GatewayNatRule.OriginalIP == testConfig.Networking.ExternalIp && - v.GatewayNatRule.OriginalPort == "7777" && - v.GatewayNatRule.TranslatedIP == "10.10.102.60" && - v.GatewayNatRule.TranslatedPort == "77" { - found = true - } - } - if !found { - return fmt.Errorf("DNAT rule was not found") + if nil == natRule { + return fmt.Errorf("rule isn't found") } *gateway = edgeGateway @@ -229,31 +222,6 @@ func testAccCheckVcdDNATDestroy(s *terraform.State) error { return nil } -const testAccCheckVcdDnat_basic = ` -resource "vcd_network_routed" "{{.OrgVdcNetworkName}}" { - name = "{{.OrgVdcNetworkName}}" - org = "{{.Org}}" - vdc = "{{.Vdc}}" - edge_gateway = "{{.EdgeGateway}}" - gateway = "{{.Gateway}}" - - dhcp_pool { - start_address = "{{.StartIpAddress}}" - end_address = "{{.EndIpAddress}}" - } -} -resource "vcd_dnat" "{{.DnatName}}" { - org = "{{.Org}}" - vdc = "{{.Vdc}}" - network_name = "{{.OrgVdcNetworkName}}" - edge_gateway = "{{.EdgeGateway}}" - external_ip = "{{.ExternalIp}}" - port = 7777 - internal_ip = "10.10.102.60" - depends_on = ["vcd_network_routed.{{.OrgVdcNetworkName}}"] -} -` - func TestAccVcdDNAT_ForBackCompability(t *testing.T) { if testConfig.Networking.ExternalIp == "" { t.Skip("Variable networking.externalIp must be set to run DNAT tests") @@ -261,7 +229,7 @@ func TestAccVcdDNAT_ForBackCompability(t *testing.T) { } var e govcd.EdgeGateway - var dnatName string = baseDnatName + "_tlate" + var dnatName string = baseDnatName + "_ForBackCompabilit" var params = StringMap{ "Org": testConfig.VCD.Org, "Vdc": testConfig.VCD.Vdc, @@ -373,18 +341,7 @@ func testAccCheckVcdDNATDestroyForBackCompability(s *terraform.State) error { return nil } -const testAccCheckVcdDnat_ForBackCompability = ` -resource "vcd_dnat" "{{.DnatName}}" { - org = "{{.Org}}" - vdc = "{{.Vdc}}" - edge_gateway = "{{.EdgeGateway}}" - external_ip = "{{.ExternalIp}}" - port = 7777 - internal_ip = "10.10.102.60" - translated_port = 77 -} -` -const testAccCheckVcdDnat_tlate = ` +const testAccCheckVcdDnatWithOrgNetw = ` resource "vcd_network_routed" "{{.OrgVdcNetworkName}}" { name = "{{.OrgVdcNetworkName}}" org = "{{.Org}}" @@ -397,17 +354,56 @@ resource "vcd_network_routed" "{{.OrgVdcNetworkName}}" { end_address = "{{.EndIpAddress}}" } } +resource "vcd_dnat" "{{.DnatName}}" { + org = "{{.Org}}" + vdc = "{{.Vdc}}" + network_name = "{{.OrgVdcNetworkName}}" + network_type = "org" + edge_gateway = "{{.EdgeGateway}}" + external_ip = "{{.ExternalIp}}" + port = 7777 + internal_ip = "10.10.102.60" + description = "{{.Description}}" + depends_on = ["vcd_network_routed.{{.OrgVdcNetworkName}}"] +} +` +const testAccCheckVcdDnat_ForBackCompability = ` resource "vcd_dnat" "{{.DnatName}}" { org = "{{.Org}}" vdc = "{{.Vdc}}" - network_name = "{{.OrgVdcNetworkName}}" - network_type = "org" edge_gateway = "{{.EdgeGateway}}" external_ip = "{{.ExternalIp}}" port = 7777 internal_ip = "10.10.102.60" translated_port = 77 - depends_on = ["vcd_network_routed.{{.OrgVdcNetworkName}}"] +} +` +const testAccCheckVcdDnatWithExtNetw = ` +resource "vcd_dnat" "{{.DnatName}}" { + org = "{{.Org}}" + vdc = "{{.Vdc}}" + network_name = "{{.ExternalNetworkName}}" + network_type = "ext" + edge_gateway = "{{.EdgeGateway}}" + external_ip = "{{.ExternalIp}}" + port = 7777 + internal_ip = "10.10.102.60" + translated_port = 77 + description = "{{.Description}}" +} +` +const testAccCheckVcdDnatWithExtNetwUpdate = ` +resource "vcd_dnat" "{{.DnatName}}" { + org = "{{.Org}}" + vdc = "{{.Vdc}}" + network_name = "{{.ExternalNetworkName}}" + network_type = "ext" + edge_gateway = "{{.EdgeGateway}}" + external_ip = "{{.ExternalIp}}" + port = 8888 + internal_ip = "10.10.102.80" + translated_port = 88 + description = "{{.Description}}" } ` diff --git a/vcd/resource_vcd_snat.go b/vcd/resource_vcd_snat.go index b5764ec93..75ea497b1 100644 --- a/vcd/resource_vcd_snat.go +++ b/vcd/resource_vcd_snat.go @@ -4,7 +4,9 @@ import ( "fmt" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + "github.com/vmware/go-vcloud-director/v2/govcd" "github.com/vmware/go-vcloud-director/v2/types/v56" + "log" ) func resourceVcdSNAT() *schema.Resource { @@ -12,6 +14,7 @@ func resourceVcdSNAT() *schema.Resource { Create: resourceVcdSNATCreate, Delete: resourceVcdSNATDelete, Read: resourceVcdSNATRead, + Update: resourceVcdSNATUpdate, Schema: map[string]*schema.Schema{ "org": { @@ -32,25 +35,25 @@ func resourceVcdSNAT() *schema.Resource { "network_name": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, }, "network_type": &schema.Schema{ Type: schema.TypeString, Optional: true, Default: "ext", - ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"ext", "org"}, false), }, "external_ip": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: true, }, "internal_ip": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, }, }, } @@ -59,9 +62,6 @@ func resourceVcdSNAT() *schema.Resource { func resourceVcdSNATCreate(d *schema.ResourceData, meta interface{}) error { vcdClient := meta.(*VCDClient) - // Multiple VCD components need to run operations on the Edge Gateway, as - // the edge gatway will throw back an error if it is already performing an - // operation we must wait until we can acquire a lock on the client vcdClient.lockParentEdgeGtw(d) defer vcdClient.unLockParentEdgeGtw(d) @@ -70,32 +70,37 @@ func resourceVcdSNATCreate(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf(errorUnableToFindEdgeGateway, err) } - // TODO add support of external network - var orgVdcnetwork *types.OrgVDCNetwork - providedNetworkName := d.Get("network_name") - if nil != providedNetworkName && "" != providedNetworkName.(string) { - orgVdcnetwork, err = getNetwork(d, vcdClient, providedNetworkName.(string)) - } - // TODO enable when external network supported - /*else { - _, _ = fmt.Fprint(GetTerraformStdout(), "WARNING: this resource will require network_name and network_type in the next major version \n") - }*/ - if err != nil { - return fmt.Errorf("unable to find orgVdcnetwork: %s, err: %s", providedNetworkName.(string), err) - } + networkName := d.Get("network_name").(string) + networkType := d.Get("network_type").(string) - if nil != providedNetworkName && providedNetworkName != "" { - task, err := edgeGateway.AddNATRule(orgVdcnetwork, "SNAT", - d.Get("external_ip").(string), d.Get("internal_ip").(string)) + var natRule *types.NatRule + + if networkName != "" && networkType == "org" { + orgVdcNetwork, err := getOrgVdcNetwork(d, vcdClient, networkName) if err != nil { - return fmt.Errorf("error setting SNAT rules: %#v", err) + return fmt.Errorf("unable to find orgVdcNetwork: %s, err: %s", networkName, err) } - err = task.WaitTaskCompletion() + + natRule, err = edgeGateway.AddSNATRule(orgVdcNetwork.HREF, d.Get("external_ip").(string), + d.Get("internal_ip").(string), d.Get("description").(string)) if err != nil { - return err + return fmt.Errorf("error creating SNAT rule: %#v", err) + } + } else if networkName != "" && networkType == "ext" { + externalNetwork, err := govcd.GetExternalNetwork(vcdClient.VCDClient, networkName) + if err != nil { + return fmt.Errorf("unable to find external network: %s, err: %s", networkName, err) + } + + natRule, err = edgeGateway.AddSNATRule(externalNetwork.ExternalNetwork.HREF, d.Get("external_ip").(string), + d.Get("internal_ip").(string), d.Get("description").(string)) + if err != nil { + return fmt.Errorf("error creating SNAT rule: %#v", err) } } else { + _, _ = fmt.Fprint(GetTerraformStdout(), "WARNING: this resource will require network_name and network_type in the next major version \n") // TODO remove when major release is done + // this for back compatibility when network name and network type isn't provided - this assign rule only for first external network task, err := edgeGateway.AddNATMapping("SNAT", d.Get("internal_ip").(string), d.Get("external_ip").(string)) if err != nil { @@ -107,8 +112,8 @@ func resourceVcdSNATCreate(d *schema.ResourceData, meta interface{}) error { } } - if nil != providedNetworkName && "" != providedNetworkName { - d.SetId(orgVdcnetwork.Name + ":" + d.Get("internal_ip").(string)) + if networkName != "" { + d.SetId(natRule.ID) } else { // TODO remove when major release is done d.SetId(d.Get("internal_ip").(string)) @@ -119,23 +124,44 @@ func resourceVcdSNATCreate(d *schema.ResourceData, meta interface{}) error { func resourceVcdSNATRead(d *schema.ResourceData, meta interface{}) error { vcdClient := meta.(*VCDClient) - e, err := vcdClient.GetEdgeGatewayFromResource(d, "edge_gateway") + edgeGateway, err := vcdClient.GetEdgeGatewayFromResource(d, "edge_gateway") if err != nil { return fmt.Errorf(errorUnableToFindEdgeGateway, err) } + // Terraform refresh won't work if Rule was edit in advanced edge gateway UI. vCD API uses tag elements to map edge gtw IDs + // and UI will reset the tag element on update. + var found bool - providedNetworkName := d.Get("network_name") - if nil != providedNetworkName && providedNetworkName.(string) != "" { - for _, r := range e.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { - if r.RuleType == "SNAT" && r.GatewayNatRule.TranslatedIP == d.Get("internal_ip").(string) && r.GatewayNatRule.Interface.Name == d.Get("network_name").(string) { - found = true - d.Set("external_ip", r.GatewayNatRule.OriginalIP) + networkName := d.Get("network_name") + if nil != networkName && networkName.(string) != "" { + natRule, err := edgeGateway.GetNatRule(d.Id()) + if err != nil { + log.Printf("rule %s (stored in in Advanced GW case) not found: %s. Removing from state.", d.Id(), err) + d.SetId("") + } + + d.Set("description", natRule.Description) + d.Set("external_ip", natRule.GatewayNatRule.OriginalIP) + d.Set("internal_ip", natRule.GatewayNatRule.TranslatedIP) + d.Set("network_name", natRule.GatewayNatRule.Interface.Name) + + orgVdcNetwork, _ := getOrgVdcNetwork(d, vcdClient, natRule.GatewayNatRule.Interface.Name) + if orgVdcNetwork != nil { + d.Set("network_type", "org") + } else { + externalNetwork, _ := govcd.GetExternalNetwork(vcdClient.VCDClient, natRule.GatewayNatRule.Interface.Name) + if externalNetwork != nil && externalNetwork != (&govcd.ExternalNetwork{}) { + d.Set("network_type", "ext") + } else { + return fmt.Errorf("didn't find external network or org VCD network with name: %s", natRule.GatewayNatRule.Interface.Name) } } + + found = true } else { // TODO remove after network_name becomes mandatory - for _, r := range e.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { + for _, r := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { if r.RuleType == "SNAT" && r.GatewayNatRule.OriginalIP == d.Get("internal_ip").(string) { found = true d.Set("external_ip", r.GatewayNatRule.TranslatedIP) @@ -153,9 +179,6 @@ func resourceVcdSNATRead(d *schema.ResourceData, meta interface{}) error { func resourceVcdSNATDelete(d *schema.ResourceData, meta interface{}) error { vcdClient := meta.(*VCDClient) - // Multiple VCD components need to run operations on the Edge Gateway, as - // the edge gatway will throw back an error if it is already performing an - // operation we must wait until we can aquire a lock on the client vcdClient.lockParentEdgeGtw(d) defer vcdClient.unLockParentEdgeGtw(d) @@ -164,16 +187,76 @@ func resourceVcdSNATDelete(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf(errorUnableToFindEdgeGateway, err) } - task, err := edgeGateway.RemoveNATMapping("SNAT", d.Get("internal_ip").(string), - d.Get("external_ip").(string), - "") + if d.Get("network_name").(string) != "" { + err = edgeGateway.RemoveNATRule(d.Id()) + if err != nil { + return fmt.Errorf("error deleting SNAT rule: %#v", err) + } + } else { + // this for back compatibility when network name and network type isn't provided - TODO remove with major release + task, err := edgeGateway.RemoveNATMapping("SNAT", d.Get("internal_ip").(string), + d.Get("external_ip").(string), + "") + if err != nil { + return fmt.Errorf("error deleting SNAT rule: %#v", err) + } + err = task.WaitTaskCompletion() + if err != nil { + return err + } + + } + return nil +} + +func resourceVcdSNATUpdate(d *schema.ResourceData, meta interface{}) error { + + vcdClient := meta.(*VCDClient) + + // Update supports only when network name and network type provided + networkName := d.Get("network_name") + if nil == networkName || networkName.(string) == "" { + return fmt.Errorf("update works only when network_name and network_type is provided and rule created using them \n") + } + + edgeGateway, err := vcdClient.GetEdgeGatewayFromResource(d, "edge_gateway") if err != nil { - return fmt.Errorf("error setting SNAT rules: %#v", err) + return fmt.Errorf(errorUnableToFindEdgeGateway, err) } - err = task.WaitTaskCompletion() + + natRule, err := edgeGateway.GetNatRule(d.Id()) if err != nil { - return err + log.Printf(" rule %s not found: %s. Removing from state.", d.Id(), err) + d.SetId("") } - return nil + natRule.GatewayNatRule.OriginalIP = d.Get("external_ip").(string) + natRule.GatewayNatRule.TranslatedIP = d.Get("internal_ip").(string) + natRule.Description = d.Get("description").(string) + + if d.Get("network_type").(string) == "org" { + orgVdcNetwork, err := getOrgVdcNetwork(d, vcdClient, d.Get("network_name").(string)) + if orgVdcNetwork == nil || err != nil { + return fmt.Errorf("unable to find orgVdcNetwork: %s, err: %s", networkName, err) + } + natRule.GatewayNatRule.Interface.Name = orgVdcNetwork.Name + natRule.GatewayNatRule.Interface.HREF = orgVdcNetwork.HREF + + } else if d.Get("network_type").(string) == "ext" { + externalNetwork, _ := govcd.GetExternalNetwork(vcdClient.VCDClient, d.Get("network_name").(string)) + if externalNetwork == nil || externalNetwork == (&govcd.ExternalNetwork{}) || err != nil { + return fmt.Errorf("unable to find external network: %s, err: %s", networkName, err) + } + natRule.GatewayNatRule.Interface.Name = externalNetwork.ExternalNetwork.Name + natRule.GatewayNatRule.Interface.HREF = externalNetwork.ExternalNetwork.HREF + } else { + return fmt.Errorf("network_type isn't provided or not `ext` or `org` ") + } + + _, err = edgeGateway.UpdateNatRule(natRule) + if err != nil { + return fmt.Errorf("unable to update nat Rule: err: %s", err) + } + + return resourceVcdSNATRead(d, meta) } diff --git a/vcd/resource_vcd_snat_test.go b/vcd/resource_vcd_snat_test.go index 08baaba8f..2bbd1dec3 100644 --- a/vcd/resource_vcd_snat_test.go +++ b/vcd/resource_vcd_snat_test.go @@ -13,6 +13,7 @@ import ( var orgVdcNetworkNameForSnat = "TestAccVcdDNAT_BasicNetworkForSnat" var startIpAddress = "10.10.102.51" +var updateIpAddress = "10.10.102.52" func TestAccVcdSNAT_Basic(t *testing.T) { if testConfig.Networking.ExternalIp == "" { @@ -33,11 +34,14 @@ func TestAccVcdSNAT_Basic(t *testing.T) { "OrgVdcNetworkName": orgVdcNetworkNameForSnat, "Gateway": "10.10.102.1", "StartIpAddress": startIpAddress, + "UpdateIpAddress": updateIpAddress, "EndIpAddress": "10.10.102.100", "Tags": "gateway", } configText := templateFill(testAccCheckVcdSnat_basic, params) + params["FuncName"] = t.Name() + "-Update" + updateText := templateFill(testAccCheckVcdSnat_update, params) if vcdShortTest { t.Skip(acceptanceTestsSkipped) return @@ -51,8 +55,6 @@ func TestAccVcdSNAT_Basic(t *testing.T) { Steps: []resource.TestStep{ resource.TestStep{ Config: configText, - // TODO remove with Terraform 0.12 support - //ExpectError: regexp.MustCompile(`After applying this step and refreshing, the plan was not empty:`), Check: resource.ComposeTestCheckFunc( testAccCheckVcdSNATExists("vcd_snat."+snatName, &e), resource.TestCheckResourceAttr( @@ -63,6 +65,18 @@ func TestAccVcdSNAT_Basic(t *testing.T) { "vcd_snat."+snatName, "internal_ip", startIpAddress), ), }, + resource.TestStep{ + Config: updateText, + Check: resource.ComposeTestCheckFunc( + testAccCheckVcdSNATExists("vcd_snat."+snatName, &e), + resource.TestCheckResourceAttr( + "vcd_snat."+snatName, "network_name", orgVdcNetworkNameForSnat), + resource.TestCheckResourceAttr( + "vcd_snat."+snatName, "external_ip", testConfig.Networking.ExternalIp), + resource.TestCheckResourceAttr( + "vcd_snat."+snatName, "internal_ip", updateIpAddress), + ), + }, }, }) } @@ -88,16 +102,13 @@ func testAccCheckVcdSNATExists(n string, gateway *govcd.EdgeGateway) resource.Te return fmt.Errorf(errorRetrievingVdcFromOrg, testConfig.VCD.Vdc, testConfig.VCD.Org, err) } - var found bool - for _, v := range edgeGateway.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { - if v.RuleType == "SNAT" && v.GatewayNatRule.Interface.Name == orgVdcNetworkNameForSnat && - v.GatewayNatRule.OriginalIP == testConfig.Networking.ExternalIp && - v.GatewayNatRule.TranslatedIP == startIpAddress { - found = true - } + natRule, err := edgeGateway.GetNatRule(rs.Primary.ID) + if err != nil { + return err } - if !found { - return fmt.Errorf("SNAT rule was not found") + + if nil == natRule { + return fmt.Errorf("rule isn't found") } *gateway = edgeGateway @@ -170,8 +181,6 @@ func TestAccVcdSNAT_BackCompability(t *testing.T) { Steps: []resource.TestStep{ resource.TestStep{ Config: configText, - // TODO remove with Terraform 0.12 support - //ExpectError: regexp.MustCompile(`After applying this step and refreshing, the plan was not empty:`), Check: resource.ComposeTestCheckFunc( testAccCheckVcdSNATExistsForBackCompability("vcd_snat."+snatName, &e), resource.TestCheckResourceAttr( @@ -283,6 +292,32 @@ resource "vcd_snat" "{{.SnatName}}" { } ` +const testAccCheckVcdSnat_update = ` +resource "vcd_network_routed" "{{.OrgVdcNetworkName}}" { + name = "{{.OrgVdcNetworkName}}" + org = "{{.Org}}" + vdc = "{{.Vdc}}" + edge_gateway = "{{.EdgeGateway}}" + gateway = "{{.Gateway}}" + + static_ip_pool { + start_address = "{{.StartIpAddress}}" + end_address = "{{.EndIpAddress}}" + } +} + +resource "vcd_snat" "{{.SnatName}}" { + org = "{{.Org}}" + vdc = "{{.Vdc}}" + edge_gateway = "{{.EdgeGateway}}" + network_name = "{{.OrgVdcNetworkName}}" + network_type = "org" + external_ip = "{{.ExternalIp}}" + internal_ip = "{{.UpdateIpAddress}}" + depends_on = ["vcd_network_routed.{{.OrgVdcNetworkName}}"] +} +` + const testAccCheckVcdSnat_forBackCompability = ` resource "vcd_snat" "{{.SnatName}}" { org = "{{.Org}}" diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/edgegateway.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/edgegateway.go index beee5a493..1cdf2ca3e 100644 --- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/edgegateway.go +++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/edgegateway.go @@ -6,11 +6,11 @@ package govcd import ( "bytes" + "crypto/rand" "encoding/xml" "fmt" "net/http" "net/url" - "path" "regexp" "strconv" "strings" @@ -42,15 +42,15 @@ func NewEdgeGateway(cli *Client) *EdgeGateway { // Struct which covers NAT rule fields type NatRule struct { - natType string - networkHref string - externalIP string - externalPort string - internalIP string - internalPort string - protocol string - icmpSubType string - description string + NatType string + NetworkHref string + ExternalIP string + ExternalPort string + InternalIP string + InternalPort string + Protocol string + IcmpSubType string + Description string } func (eGW *EdgeGateway) AddDhcpPool(network *types.OrgVDCNetwork, dhcppool []interface{}) (Task, error) { @@ -145,15 +145,22 @@ func (eGW *EdgeGateway) AddDhcpPool(network *types.OrgVDCNetwork, dhcppool []int } -// Temporary fix for existing Remove functions as it doesn't support network interfaces -// TODO: remove this function when ruleId is available -func (eGW *EdgeGateway) RemoveNATMappingRule(networkHref, natType, externalIP, internalIP, port string) (Task, error) { - return eGW.RemoveNATPortRule(networkHref, natType, externalIP, port, internalIP, port) +// Deprecated: use one of RemoveNATRuleAsync, RemoveNATRule +func (eGW *EdgeGateway) RemoveNATMapping(natType, externalIP, internalIP, port string) (Task, error) { + return eGW.RemoveNATPortMapping(natType, externalIP, port, internalIP, port) } -// Temporary fix for existing Remove functions as it doesn't support network interfaces -// TODO: remove this function when ruleId is available -func (eGW *EdgeGateway) RemoveNATPortRule(networkHref, natType, externalIP, externalPort, internalIP, internalPort string) (Task, error) { +// Deprecated: use one of RemoveNATRuleAsync, RemoveNATRule +func (eGW *EdgeGateway) RemoveNATPortMapping(natType, externalIP, externalPort, internalIP, internalPort string) (Task, error) { + // Find uplink interface + var uplink types.Reference + for _, gi := range eGW.EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface { + if gi.InterfaceType != "uplink" { + continue + } + uplink = *gi.Network + } + newEdgeConfig := eGW.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration // Take care of the NAT service @@ -166,17 +173,10 @@ func (eGW *EdgeGateway) RemoveNATPortRule(networkHref, natType, externalIP, exte for _, natRule := range newEdgeConfig.NatService.NatRule { - // Kludgy IF to avoid deleting DNAT rules not created by us. - // If matches, let's skip it and continue the loop - interfaceId, err := extractObjectIDfromPath(natRule.GatewayNatRule.Interface.HREF) - networkHrefId, err := extractObjectIDfromPath(networkHref) - if err != nil { - return Task{}, nil - } if natRule.RuleType == natType && natRule.GatewayNatRule.OriginalIP == externalIP && natRule.GatewayNatRule.OriginalPort == externalPort && - interfaceId == networkHrefId { + natRule.GatewayNatRule.Interface.HREF == uplink.HREF { util.Logger.Printf("[DEBUG] REMOVING %s Rule: %#v", natRule.RuleType, natRule.GatewayNatRule) continue } @@ -200,84 +200,86 @@ func (eGW *EdgeGateway) RemoveNATPortRule(networkHref, natType, externalIP, exte } -// Temporary fix to support parsing network Id from HREF -// TODO: remove this function when ruleId is available -func extractObjectIDfromPath(locationPath string) (string, error) { - if locationPath == "" { - return "", fmt.Errorf("unable to get ID from empty path") +// RemoveNATRule removes NAT removes NAT rule identified by ID and handles task. Returns error if issues rise. +// Old functions RemoveNATPortMapping and RemoveNATMapping removed using rule details +// and expected interface to be of external network type. +func (eGW *EdgeGateway) RemoveNATRule(id string) error { + task, err := eGW.RemoveNATRuleAsync(id) + if err != nil { + return fmt.Errorf("error removing DNAT rule: %#v", err) } - - cleanPath := path.Clean(locationPath) // Removes trailing slash if there is one - splitPath := strings.Split(cleanPath, "/") - - if len(splitPath) < 2 { - return "", fmt.Errorf("path does not contain url path: %s", splitPath) + err = task.WaitTaskCompletion() + if err != nil { + return fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err)) } - objectID := splitPath[len(splitPath)-1] - - return objectID, nil -} - -func (eGW *EdgeGateway) RemoveNATMapping(natType, externalIP, internalIP, port string) (Task, error) { - return eGW.RemoveNATPortMapping(natType, externalIP, port, internalIP, port) + return nil } -func (eGW *EdgeGateway) RemoveNATPortMapping(natType, externalIP, externalPort, internalIP, internalPort string) (Task, error) { - // Find uplink interface - var uplink types.Reference - for _, gi := range eGW.EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface { - if gi.InterfaceType != "uplink" { - continue - } - uplink = *gi.Network +// RemoveNATRuleAsync removes NAT rule or returns an error. +// Old functions RemoveNATPortMapping and RemoveNATMapping removed using rule details +// and expected interface to be of external network type. +func (eGW *EdgeGateway) RemoveNATRuleAsync(id string) (Task, error) { + if id == "" { + return Task{}, fmt.Errorf("provided id is empty") } - newEdgeConfig := eGW.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration - - // Take care of the NAT service - newNatService := &types.NatService{} - - newNatService.IsEnabled = newEdgeConfig.NatService.IsEnabled - newNatService.NatType = newEdgeConfig.NatService.NatType - newNatService.Policy = newEdgeConfig.NatService.Policy - newNatService.ExternalIP = newEdgeConfig.NatService.ExternalIP - - for _, natRule := range newEdgeConfig.NatService.NatRule { + err := eGW.Refresh() + if err != nil { + return Task{}, fmt.Errorf("error refreshing edge gateway: %#v", err) + } - if natRule.RuleType == natType && - natRule.GatewayNatRule.OriginalIP == externalIP && - natRule.GatewayNatRule.OriginalPort == externalPort && - natRule.GatewayNatRule.Interface.HREF == uplink.HREF { - util.Logger.Printf("[DEBUG] REMOVING %s Rule: %#v", natRule.RuleType, natRule.GatewayNatRule) - continue + natServiceToUpdate := eGW.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService + ruleIndex := -1 + if natServiceToUpdate != nil { + for n, existingNatRule := range natServiceToUpdate.NatRule { + if existingNatRule.ID == id { + ruleIndex = n + break + } } - util.Logger.Printf("[DEBUG] KEEPING %s Rule: %#v", natRule.RuleType, natRule.GatewayNatRule) - newNatService.NatRule = append(newNatService.NatRule, natRule) + } else { + return Task{}, fmt.Errorf("edge gateway doesn't have NAT rules") } - newEdgeConfig.NatService = newNatService + if ruleIndex == -1 { + return Task{}, fmt.Errorf("edge gateway doesn't have rule with such Id") + } + + if len(natServiceToUpdate.NatRule) > 1 { + natServiceToUpdate.NatRule = append(natServiceToUpdate.NatRule[:ruleIndex], natServiceToUpdate.NatRule[ruleIndex+1:]...) + } else { + natServiceToUpdate.NatRule = nil + } newRules := &types.EdgeGatewayServiceConfiguration{ Xmlns: types.XMLNamespaceVCloud, - NatService: newNatService, + NatService: natServiceToUpdate, } - apiEndpoint, _ := url.ParseRequestURI(eGW.EdgeGateway.HREF) - apiEndpoint.Path += "/action/configureServices" + egwConfigureHref, _ := url.ParseRequestURI(eGW.EdgeGateway.HREF) + egwConfigureHref.Path += "/action/configureServices" // Return the task - return eGW.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, + return eGW.client.ExecuteTaskRequest(egwConfigureHref.String(), http.MethodPost, "application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml", "error reconfiguring Edge Gateway: %s", newRules) - } -// AddDNATRule creates DNAT rule and return refreshed existing NAT rules array or error -// Allows to assign specific network Org VDC or external. Old function AddNATPortMapping and -// AddNATMapping assigns rule to first external network -func (eGW *EdgeGateway) AddDNATRule(ruleDetails NatRule) ([]*types.NatRule, error) { +// AddDNATRule creates DNAT rule and returns the NAT struct that was created or an error. +// Allows assigning a specific Org VDC or an external network. +// When edge gateway is advanced vCD API uses element to map with NSX edge gateway ID. A known issue is +// that updating rule using User interface resets and as result mapping is lost. +// Getting using NatRule.ID won't be valid anymore. +// Old functions AddNATPortMapping and AddNATMapping assigned rule only to first external network +func (eGW *EdgeGateway) AddDNATRule(ruleDetails NatRule) (*types.NatRule, error) { + mappingId, err := getPseudoUuid() + if err != nil { + return nil, err + } + originalDescription := ruleDetails.Description + ruleDetails.Description = mappingId - ruleDetails.natType = "DNAT" + ruleDetails.NatType = "DNAT" task, err := eGW.AddNATRuleAsync(ruleDetails) if err != nil { return nil, fmt.Errorf("error creating DNAT rule: %#v", err) @@ -287,22 +289,48 @@ func (eGW *EdgeGateway) AddDNATRule(ruleDetails NatRule) ([]*types.NatRule, erro return nil, fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err)) } + var createdNatRule *types.NatRule + err = eGW.Refresh() if err != nil { return nil, fmt.Errorf("error refreshing edge gateway: %#v", err) } - return eGW.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule, nil + for _, natRule := range eGW.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { + if natRule.Description == mappingId { + createdNatRule = natRule + break + } + } + + if createdNatRule == nil { + return nil, fmt.Errorf("error creating DNAT rule, didn't match created rule") + } + + createdNatRule.Description = originalDescription + + return eGW.UpdateNatRule(createdNatRule) } -// AddSNATRule creates SNAT rule and returns refreshed existing NAT rules array or error -// Allows to assign specific network Org VDC or external. Old function AddNATPortMapping and -// AddNATMapping assigns rule to first external network -func (eGW *EdgeGateway) AddSNATRule(networkHref, externalIP, internalIP, description string) ([]*types.NatRule, error) { +// AddSNATRule creates SNAT rule and returns created NAT rule or error. +// Allows assigning a specific Org VDC or an external network. +// Old functions AddNATPortMapping and AddNATMapping aren't correct as assigned rule only to first external network +func (eGW *EdgeGateway) AddSNATRule(networkHref, externalIP, internalIP, description string) (*types.NatRule, error) { + + // As vCD API doesn't return rule ID we get it manually: + // * create rule with description which value is our generated Id + // * find rule which has description with our generated Id + // * get the real (vCD's) rule Id + // * update description with real value and return nat rule + + mappingId, err := getPseudoUuid() + if err != nil { + return nil, err + } - task, err := eGW.AddNATRuleAsync(NatRule{networkHref: networkHref, natType: "SNAT", externalIP: externalIP, - externalPort: "any", internalIP: internalIP, internalPort: "any", - icmpSubType: "", protocol: "any", description: description}) + task, err := eGW.AddNATRuleAsync(NatRule{NetworkHref: networkHref, NatType: "SNAT", ExternalIP: externalIP, + ExternalPort: "any", InternalIP: internalIP, InternalPort: "any", + IcmpSubType: "", Protocol: "any", Description: mappingId}) if err != nil { return nil, fmt.Errorf("error creating SNAT rule: %#v", err) } @@ -311,87 +339,172 @@ func (eGW *EdgeGateway) AddSNATRule(networkHref, externalIP, internalIP, descrip return nil, fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err)) } + var createdNatRule *types.NatRule + err = eGW.Refresh() if err != nil { return nil, fmt.Errorf("error refreshing edge gateway: %#v", err) } - return eGW.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule, nil + for _, natRule := range eGW.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { + if natRule.Description == mappingId { + createdNatRule = natRule + break + } + } + + if createdNatRule == nil { + return nil, fmt.Errorf("error creating SNAT rule, didn't match created rule") + } + + createdNatRule.Description = description + + return eGW.UpdateNatRule(createdNatRule) +} + +// getPseudoUuid creates unique ID/UUID +func getPseudoUuid() (string, error) { + + b := make([]byte, 16) + _, err := rand.Read(b) + if err != nil { + return "", err + } + + uuid := fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) + + return uuid, nil +} + +// UpdateNatRule updates NAT rule and handles task. Returns updated NAT rule or error. +func (eGW *EdgeGateway) UpdateNatRule(natRule *types.NatRule) (*types.NatRule, error) { + task, err := eGW.UpdateNatRuleAsync(natRule) + if err != nil { + return nil, fmt.Errorf("error updating NAT rule: %#v", err) + } + err = task.WaitTaskCompletion() + if err != nil { + return nil, fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err)) + } + + return eGW.GetNatRule(natRule.ID) +} + +// UpdateNatRuleAsync updates NAT rule and returns task or error. +func (eGW *EdgeGateway) UpdateNatRuleAsync(natRule *types.NatRule) (Task, error) { + if natRule.GatewayNatRule.Protocol != "" && !isValidProtocol(natRule.GatewayNatRule.Protocol) { + return Task{}, fmt.Errorf("provided protocol is not one of TCP, UDP, TCPUDP, ICMP, ANY") + } + + if strings.ToUpper(natRule.GatewayNatRule.Protocol) == "ICMP" && !isValidIcmpSubType(natRule.GatewayNatRule.IcmpSubType) { + return Task{}, fmt.Errorf("provided icmp sub type is not correct") + } + + err := eGW.Refresh() + if err != nil { + return Task{}, fmt.Errorf("error refreshing edge gateway: %#v", err) + } + + natServiceToUpdate := eGW.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService + + if natServiceToUpdate != nil { + for n, existingNatRule := range natServiceToUpdate.NatRule { + if existingNatRule.ID == natRule.ID { + natServiceToUpdate.NatRule[n] = natRule + } + } + } else { + return Task{}, fmt.Errorf("edge gateway doesn't have such nat rule") + } + + newRules := &types.EdgeGatewayServiceConfiguration{ + Xmlns: types.XMLNamespaceVCloud, + NatService: natServiceToUpdate, + } + + egwConfigureHref, _ := url.ParseRequestURI(eGW.EdgeGateway.HREF) + egwConfigureHref.Path += "/action/configureServices" + + // Return the task + return eGW.client.ExecuteTaskRequest(egwConfigureHref.String(), http.MethodPost, + "application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml", "error reconfiguring Edge Gateway: %s", newRules) +} + +// GetNatRule returns NAT rule or error. +func (eGW *EdgeGateway) GetNatRule(id string) (*types.NatRule, error) { + err := eGW.Refresh() + if err != nil { + return nil, fmt.Errorf("error refreshing edge gateway: %#v", err) + } + + if eGW.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService != nil { + for _, natRule := range eGW.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule { + if natRule.ID == id { + return natRule, nil + } + } + } + + return nil, ErrorEntityNotFound } // AddNATRuleAsync creates NAT rule and return task or err -// Allows to assign specific network Org VDC or external. Old function AddNATPortMapping and -// AddNATMapping assigns rule to first external network +// Allows assigning specific network Org VDC or external. Old function AddNATPortMapping and +// AddNATMapping function shouldn't be used because assigns rule to first external network func (eGW *EdgeGateway) AddNATRuleAsync(ruleDetails NatRule) (Task, error) { - if !isValidProtocol(ruleDetails.protocol) { + if !isValidProtocol(ruleDetails.Protocol) { return Task{}, fmt.Errorf("provided protocol is not one of TCP, UDP, TCPUDP, ICMP, ANY") } - if strings.ToUpper(ruleDetails.protocol) == "ICMP" && !isValidIcmpSubType(ruleDetails.icmpSubType) { + if strings.ToUpper(ruleDetails.Protocol) == "ICMP" && !isValidIcmpSubType(ruleDetails.IcmpSubType) { return Task{}, fmt.Errorf("provided icmp sub type is not correct") } - newEdgeConfig := eGW.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration + currentEdgeConfig := eGW.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration // Take care of the NAT service newNatService := &types.NatService{} - if newEdgeConfig.NatService == nil { + if currentEdgeConfig.NatService == nil { newNatService.IsEnabled = true } else { - newNatService.IsEnabled = newEdgeConfig.NatService.IsEnabled - newNatService.NatType = newEdgeConfig.NatService.NatType - newNatService.Policy = newEdgeConfig.NatService.Policy - newNatService.ExternalIP = newEdgeConfig.NatService.ExternalIP - - for _, natRule := range newEdgeConfig.NatService.NatRule { - - // Kludgy IF to avoid deleting DNAT rules not created by us. - // If matches, let's skip it and continue the loop - if natRule.RuleType == ruleDetails.natType && - natRule.GatewayNatRule.OriginalIP == ruleDetails.externalIP && - natRule.GatewayNatRule.OriginalPort == ruleDetails.externalPort && - natRule.GatewayNatRule.TranslatedIP == ruleDetails.internalIP && - natRule.GatewayNatRule.TranslatedPort == ruleDetails.internalPort && - natRule.GatewayNatRule.Interface.HREF == ruleDetails.networkHref { - continue - } - - newNatService.NatRule = append(newNatService.NatRule, natRule) - } + newNatService.IsEnabled = currentEdgeConfig.NatService.IsEnabled + newNatService.NatType = currentEdgeConfig.NatService.NatType + newNatService.Policy = currentEdgeConfig.NatService.Policy + newNatService.ExternalIP = currentEdgeConfig.NatService.ExternalIP + newNatService.NatRule = currentEdgeConfig.NatService.NatRule } - //add rule + //construct new rule natRule := &types.NatRule{ - RuleType: ruleDetails.natType, + RuleType: ruleDetails.NatType, IsEnabled: true, - Description: ruleDetails.description, + Description: ruleDetails.Description, GatewayNatRule: &types.GatewayNatRule{ Interface: &types.Reference{ - HREF: ruleDetails.networkHref, + HREF: ruleDetails.NetworkHref, }, - OriginalIP: ruleDetails.externalIP, - OriginalPort: ruleDetails.externalPort, - TranslatedIP: ruleDetails.internalIP, - TranslatedPort: ruleDetails.internalPort, - Protocol: ruleDetails.protocol, - IcmpSubType: ruleDetails.icmpSubType, + OriginalIP: ruleDetails.ExternalIP, + OriginalPort: ruleDetails.ExternalPort, + TranslatedIP: ruleDetails.InternalIP, + TranslatedPort: ruleDetails.InternalPort, + Protocol: ruleDetails.Protocol, + IcmpSubType: ruleDetails.IcmpSubType, }, } - newNatService.NatRule = append(newNatService.NatRule, natRule) - - newEdgeConfig.NatService = newNatService + newNatService.NatRule = append(newNatService.NatRule, natRule) + currentEdgeConfig.NatService = newNatService newRules := &types.EdgeGatewayServiceConfiguration{ Xmlns: types.XMLNamespaceVCloud, NatService: newNatService, } - apiEndpoint, _ := url.ParseRequestURI(eGW.EdgeGateway.HREF) - apiEndpoint.Path += "/action/configureServices" + egwConfigureHref, _ := url.ParseRequestURI(eGW.EdgeGateway.HREF) + egwConfigureHref.Path += "/action/configureServices" // Return the task - return eGW.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, + return eGW.client.ExecuteTaskRequest(egwConfigureHref.String(), http.MethodPost, "application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml", "error reconfiguring Edge Gateway: %s", newRules) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 7e931d098..61fe0f413 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -216,7 +216,7 @@ github.com/ulikunitz/xz/internal/hash # github.com/vmihailenco/msgpack v4.0.1+incompatible github.com/vmihailenco/msgpack github.com/vmihailenco/msgpack/codes -# github.com/vmware/go-vcloud-director/v2 v2.3.0-beta.2 +# github.com/vmware/go-vcloud-director/v2 v2.3.0-beta.3 github.com/vmware/go-vcloud-director/v2/govcd github.com/vmware/go-vcloud-director/v2/types/v56 github.com/vmware/go-vcloud-director/v2/util diff --git a/website/docs/r/dnat.html.markdown b/website/docs/r/dnat.html.markdown index 50c0ea4d4..e56720c61 100644 --- a/website/docs/r/dnat.html.markdown +++ b/website/docs/r/dnat.html.markdown @@ -11,6 +11,8 @@ description: |- Provides a vCloud Director DNAT resource. This can be used to create, modify, and delete destination NATs to map an external IP/port to an internal IP/port. +!> **Warning:** When advanced edge gateway is used and the rule is updated using UI, then Id mapping will be lost and terraform won't find the rule anymore and remove it from state. + ## Example Usage ```hcl @@ -28,6 +30,9 @@ resource "vcd_dnat" "web" { resource "vcd_dnat" "forIcmp" { org = "my-org" # Optional vdc = "my-vdc" # Optional + + network_name = "my-external-network" + network_type = "ext" edge_gateway = "Edge Gateway Name" external_ip = "78.101.10.20" @@ -49,5 +54,7 @@ The following arguments are supported: * `internal_ip` - (Required) The IP of the VM to map to * `protocol` - (Optional; *v2.0+*) The protocol type. Possible values are TCP, UDP, TCPUDP, ICMP, ANY. TCP is default to be backward compatible with previous version * `icmp_sub_type` - (Optional; *v2.0+*) The name of ICMP type. Possible values are address-mask-request, destination-unreachable, echo-request, echo-reply, parameter-problem, redirect, router-advertisement, router-solicitation, source-quench, time-exceeded, timestamp-request, timestamp-reply, any +* `network_type` - (Optional; *v2.4+*) Type of the network on which to apply the NAT rule. Possible values org or ext. *`network_type` will be a required field in the next major version.* +* `network_name` - (Optional; *v2.4+*) The name of the network on which to apply the SNAT. *`network_name` will be a required field in the next major version.* * `org` - (Optional; *v2.0+*) The name of organization to use, optional if defined at provider level. Useful when connected as sysadmin working across different organisations -* `vdc` - (Optional; *v2.0+*) The name of VDC to use, optional if defined at provider level +* `vdc` - (Optional; *v2.0+*) The name of VDC to use, optional if defined at provider level \ No newline at end of file diff --git a/website/docs/r/snat.html.markdown b/website/docs/r/snat.html.markdown index a4938b9ce..1c6d5c16e 100644 --- a/website/docs/r/snat.html.markdown +++ b/website/docs/r/snat.html.markdown @@ -11,11 +11,15 @@ description: |- Provides a vCloud Director SNAT resource. This can be used to create, modify, and delete source NATs to allow vApps to send external traffic. +!> **Warning:** When advanced edge gateway is used and the rule is updated using UI, then Id mapping will be lost and terraform won't find the rule anymore and remove it from state. + ## Example Usage ```hcl resource "vcd_snat" "outbound" { edge_gateway = "Edge Gateway Name" + network_name = "my-org-vdc-network" + network_type = "org" external_ip = "78.101.10.20" internal_ip = "10.10.0.0/24" } @@ -28,5 +32,7 @@ The following arguments are supported: * `edge_gateway` - (Required) The name of the edge gateway on which to apply the SNAT * `external_ip` - (Required) One of the external IPs available on your Edge Gateway * `internal_ip` - (Required) The IP or IP Range of the VM(s) to map from +* `network_type` - (Optional; *v2.4+*) Type of the network on which to apply the NAT rule. Possible values org or ext. *`network_type` will be a required field in the next major version.* +* `network_name` - (Optional; *v2.4+*) The name of the network on which to apply the SNAT. *`network_name` will be a required field in the next major version.* * `org` - (Optional; *v2.0+*) The name of organization to use, optional if defined at provider level. Useful when connected as sysadmin working across different organisations -* `vdc` - (Optional; *v2.0+*) The name of VDC to use, optional if defined at provider level +* `vdc` - (Optional; *v2.0+*) The name of VDC to use, optional if defined at provider level \ No newline at end of file