diff --git a/nsxt/provider.go b/nsxt/provider.go index 66e28d00d..3589b0a93 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -501,6 +501,7 @@ func Provider() *schema.Provider { "nsxt_vpc_gateway_policy": resourceNsxtVPCGatewayPolicy(), "nsxt_policy_share": resourceNsxtPolicyShare(), "nsxt_policy_shared_resource": resourceNsxtPolicySharedResource(), + "nsxt_policy_gateway_connection": resourceNsxtPolicyGatewayConnection(), }, ConfigureFunc: providerConfigure, diff --git a/nsxt/resource_nsxt_policy_gateway_connection.go b/nsxt/resource_nsxt_policy_gateway_connection.go new file mode 100644 index 000000000..552691131 --- /dev/null +++ b/nsxt/resource_nsxt_policy_gateway_connection.go @@ -0,0 +1,205 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var gatewayConnectionSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "advertise_outbound_route_filter": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "AdvertiseOutboundRouteFilter", + }, + }, + "tier0_path": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "Tier0Path", + }, + }, + "aggregate_routes": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "array", + SdkFieldName: "AggregateRoutes", + }, + }, +} + +func resourceNsxtPolicyGatewayConnection() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicyGatewayConnectionCreate, + Read: resourceNsxtPolicyGatewayConnectionRead, + Update: resourceNsxtPolicyGatewayConnectionUpdate, + Delete: resourceNsxtPolicyGatewayConnectionDelete, + Importer: &schema.ResourceImporter{ + State: nsxtPolicyPathResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(gatewayConnectionSchema), + } +} + +func resourceNsxtPolicyGatewayConnectionExists(id string, connector client.Connector, isGlobalManager bool) (bool, error) { + var err error + + client := clientLayer.NewGatewayConnectionsClient(connector) + _, err = client.Get(id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtPolicyGatewayConnectionCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateID(d, m, resourceNsxtPolicyGatewayConnectionExists) + if err != nil { + return err + } + + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.GatewayConnection{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, gatewayConnectionSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating GatewayConnection with ID %s", id) + + client := clientLayer.NewGatewayConnectionsClient(connector) + err = client.Patch(id, obj) + if err != nil { + return handleCreateError("GatewayConnection", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtPolicyGatewayConnectionRead(d, m) +} + +func resourceNsxtPolicyGatewayConnectionRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining GatewayConnection ID") + } + + client := clientLayer.NewGatewayConnectionsClient(connector) + + obj, err := client.Get(id) + if err != nil { + return handleReadError(d, "GatewayConnection", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, gatewayConnectionSchema, "", nil) +} + +func resourceNsxtPolicyGatewayConnectionUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining GatewayConnection ID") + } + + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.GatewayConnection{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, gatewayConnectionSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewGatewayConnectionsClient(connector) + _, err := client.Update(id, obj) + if err != nil { + return handleUpdateError("GatewayConnection", id, err) + } + + return resourceNsxtPolicyGatewayConnectionRead(d, m) +} + +func resourceNsxtPolicyGatewayConnectionDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining GatewayConnection ID") + } + + connector := getPolicyConnector(m) + + client := clientLayer.NewGatewayConnectionsClient(connector) + err := client.Delete(id) + + if err != nil { + return handleDeleteError("GatewayConnection", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_policy_gateway_connection_test.go b/nsxt/resource_nsxt_policy_gateway_connection_test.go new file mode 100644 index 000000000..0da2a2f32 --- /dev/null +++ b/nsxt/resource_nsxt_policy_gateway_connection_test.go @@ -0,0 +1,194 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestPolicyGatewayConnectionCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "aggregate_routes": "192.168.240.0/24", +} + +var accTestPolicyGatewayConnectionUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "aggregate_routes": "192.168.241.0/24", +} + +func TestAccResourceNsxtPolicyGatewayConnection_basic(t *testing.T) { + testResourceName := "nsxt_policy_gateway_connection.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyGatewayConnectionCheckDestroy(state, accTestPolicyGatewayConnectionUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyGatewayConnectionTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyGatewayConnectionExists(accTestPolicyGatewayConnectionCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyGatewayConnectionCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyGatewayConnectionCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "aggregate_routes.0", accTestPolicyGatewayConnectionCreateAttributes["aggregate_routes"]), + resource.TestCheckResourceAttrSet(testResourceName, "tier0_path"), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtPolicyGatewayConnectionTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyGatewayConnectionExists(accTestPolicyGatewayConnectionUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyGatewayConnectionUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyGatewayConnectionUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "aggregate_routes.0", accTestPolicyGatewayConnectionUpdateAttributes["aggregate_routes"]), + resource.TestCheckResourceAttrSet(testResourceName, "tier0_path"), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtPolicyGatewayConnectionMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyGatewayConnectionExists(accTestPolicyGatewayConnectionCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtPolicyGatewayConnection_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_policy_gateway_connection.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyGatewayConnectionCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyGatewayConnectionMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccNsxtPolicyGatewayConnectionExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy GatewayConnection resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy GatewayConnection resource ID not set in resources") + } + + exists, err := resourceNsxtPolicyGatewayConnectionExists(resourceID, connector, testAccIsGlobalManager()) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy GatewayConnection %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtPolicyGatewayConnectionCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_policy_gateway_connection" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtPolicyGatewayConnectionExists(resourceID, connector, testAccIsGlobalManager()) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy GatewayConnection %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtPolicyGatewayConnectionTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestPolicyGatewayConnectionCreateAttributes + } else { + attrMap = accTestPolicyGatewayConnectionUpdateAttributes + } + return fmt.Sprintf(` +data "nsxt_policy_edge_cluster" "EC" { + display_name = "%s" +} + +resource "nsxt_policy_tier0_gateway" "test" { + display_name = "terraformt0gw" + edge_cluster_path = data.nsxt_policy_edge_cluster.EC.path +} + +resource "nsxt_policy_gateway_connection" "test" { + display_name = "%s" + description = "%s" + tier0_path = nsxt_policy_tier0_gateway.test.path + aggregate_routes = ["%s"] + + tag { + scope = "scope1" + tag = "tag1" + } +}`, getEdgeClusterName(), attrMap["display_name"], attrMap["description"], attrMap["aggregate_routes"]) +} + +func testAccNsxtPolicyGatewayConnectionMinimalistic() string { + return fmt.Sprintf(` +resource "nsxt_policy_gateway_connection" "test" { + display_name = "%s" + +}`, accTestPolicyGatewayConnectionUpdateAttributes["display_name"]) +} diff --git a/website/docs/r/policy_gateway_connection.html.markdown b/website/docs/r/policy_gateway_connection.html.markdown new file mode 100644 index 000000000..567cacd11 --- /dev/null +++ b/website/docs/r/policy_gateway_connection.html.markdown @@ -0,0 +1,63 @@ +--- +subcategory: "Beta" +layout: "nsxt" +page_title: "NSXT: nsxt_policy_gateway_connection" +description: A resource to configure a GatewayConnection. +--- + +# nsxt_policy_gateway_connection + +This resource provides a method for the management of a GatewayConnection. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_tier0_gateway" "test" { + display_name = "test-t0gw" +} + +resource "nsxt_policy_gateway_connection" "test" { + display_name = "test" + description = "Terraform provisioned GatewayConnection" + tier0_path = data.nsxt_policy_tier0_gateway.test.path + aggregate_routes = ["192.168.240.0/24"] + +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `advertise_outbound_route_filter` - (Optional) Path of a prefixlist object that will have Transit gateway to tier-0 gateway advertise route filter. +* `tier0_path` - (Optional) Tier-0 gateway object path +* `aggregate_routes` - (Optional) Configure aggregate TGW_PREFIXES routes on Tier-0 gateway for prefixes owned by TGW gateway. +If not specified then in-use prefixes are configured as TGW_PREFIXES routes on Tier-0 gateway. + + + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_gateway_connection.test PATH +``` + +The above command imports GatewayConnection named `test` with the policy path `PATH`.