From 6be0b673dc6cdac9c5354a33443d54281bd5e328 Mon Sep 17 00:00:00 2001 From: Dainius Date: Fri, 20 Dec 2024 10:35:50 +0200 Subject: [PATCH] TM Provider Gateway (#1361) --- .changes/v4.0.0/1361-features.md | 3 + go.mod | 2 +- go.sum | 4 +- vcd/config_test.go | 1 + vcd/datasource_vcd_tm_provider_gateway.go | 66 +++++ vcd/datasource_vcd_tm_tier0_gateway.go | 71 +++++ vcd/provider.go | 3 + vcd/resource_vcd_tm_provider_gateway.go | 260 ++++++++++++++++++ vcd/resource_vcd_tm_provider_gateway_test.go | 193 +++++++++++++ vcd/sample_vcd_test_config_tm.json | 3 +- vcd/tm_common_test.go | 19 ++ .../docs/d/tm_provider_gateway.html.markdown | 39 +++ website/docs/d/tm_tier0_gateway.html.markdown | 38 +++ .../docs/r/tm_provider_gateway.html.markdown | 78 ++++++ website/vcd.erb | 9 + 15 files changed, 785 insertions(+), 4 deletions(-) create mode 100644 .changes/v4.0.0/1361-features.md create mode 100644 vcd/datasource_vcd_tm_provider_gateway.go create mode 100644 vcd/datasource_vcd_tm_tier0_gateway.go create mode 100644 vcd/resource_vcd_tm_provider_gateway.go create mode 100644 vcd/resource_vcd_tm_provider_gateway_test.go create mode 100644 website/docs/d/tm_provider_gateway.html.markdown create mode 100644 website/docs/d/tm_tier0_gateway.html.markdown create mode 100644 website/docs/r/tm_provider_gateway.html.markdown diff --git a/.changes/v4.0.0/1361-features.md b/.changes/v4.0.0/1361-features.md new file mode 100644 index 000000000..c90a49a8d --- /dev/null +++ b/.changes/v4.0.0/1361-features.md @@ -0,0 +1,3 @@ +* **New Resource:** `vcd_tm_provider_gateway` to manage TM Provider Gateways [GH-1361] +* **New Data Source:** `vcd_tm_provider_gateway` to read IP Spaces [GH-1361] +* **New Data Source:** `vcd_tm_tier0_gateway` to read available Tier 0 Gateways in NSX-T [GH-1361] diff --git a/go.mod b/go.mod index a40510a40..40621eb24 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/kr/pretty v0.3.1 - github.com/vmware/go-vcloud-director/v3 v3.0.0-alpha.11 + github.com/vmware/go-vcloud-director/v3 v3.0.0-alpha.12 ) require ( diff --git a/go.sum b/go.sum index 29ca80a36..edd100f4a 100644 --- a/go.sum +++ b/go.sum @@ -149,8 +149,8 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/vmware/go-vcloud-director/v3 v3.0.0-alpha.11 h1:jdOEeoS8t/9bEe6ahZTRhvxg3o88IthDVtz5zMeoKjQ= -github.com/vmware/go-vcloud-director/v3 v3.0.0-alpha.11/go.mod h1:68KHsVns52dsq/w5JQYzauaU/+NAi1FmCxhBrFc/VoQ= +github.com/vmware/go-vcloud-director/v3 v3.0.0-alpha.12 h1:WLMb6TygvaQasOcw32yomqb0FODvqjlAtpA1QONec4k= +github.com/vmware/go-vcloud-director/v3 v3.0.0-alpha.12/go.mod h1:68KHsVns52dsq/w5JQYzauaU/+NAi1FmCxhBrFc/VoQ= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= diff --git a/vcd/config_test.go b/vcd/config_test.go index f4a264caf..a43f08305 100644 --- a/vcd/config_test.go +++ b/vcd/config_test.go @@ -119,6 +119,7 @@ type TestConfig struct { NsxtManagerUsername string `json:"nsxtManagerUsername"` NsxtManagerPassword string `json:"nsxtManagerPassword"` NsxtManagerUrl string `json:"nsxtManagerUrl"` + NsxtTier0Gateway string `json:"nsxtTier0Gateway"` CreateVcenter bool `json:"createVcenter"` VcenterUsername string `json:"vcenterUsername"` diff --git a/vcd/datasource_vcd_tm_provider_gateway.go b/vcd/datasource_vcd_tm_provider_gateway.go new file mode 100644 index 000000000..04c76698e --- /dev/null +++ b/vcd/datasource_vcd_tm_provider_gateway.go @@ -0,0 +1,66 @@ +package vcd + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/go-vcloud-director/v3/govcd" + "github.com/vmware/go-vcloud-director/v3/types/v56" +) + +func datasourceVcdTmProviderGateway() *schema.Resource { + return &schema.Resource{ + ReadContext: datasourceVcdTmProviderGatewayRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: fmt.Sprintf("Name of %s", labelTmProviderGateway), + }, + "region_id": { + Type: schema.TypeString, + Required: true, + Description: fmt.Sprintf("Parent %s of %s", labelTmRegion, labelTmProviderGateway), + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: fmt.Sprintf("Description of %s", labelTmProviderGateway), + }, + "nsxt_tier0_gateway_id": { + Type: schema.TypeString, + Computed: true, + Description: fmt.Sprintf("Parent %s of %s", labelTmTier0Gateway, labelTmProviderGateway), + }, + "ip_space_ids": { + Type: schema.TypeSet, + Computed: true, + Description: fmt.Sprintf("A set of supervisor IDs used in this %s", labelTmRegion), + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: fmt.Sprintf("Status of %s", labelTmProviderGateway), + }, + }, + } +} + +func datasourceVcdTmProviderGatewayRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + vcdClient := meta.(*VCDClient) + getProviderGateway := func(name string) (*govcd.TmProviderGateway, error) { + return vcdClient.GetTmProviderGatewayByNameAndRegionId(name, d.Get("region_id").(string)) + } + c := dsReadConfig[*govcd.TmProviderGateway, types.TmProviderGateway]{ + entityLabel: labelTmProviderGateway, + getEntityFunc: getProviderGateway, + stateStoreFunc: setTmProviderGatewayData, + } + return readDatasource(ctx, d, meta, c) +} diff --git a/vcd/datasource_vcd_tm_tier0_gateway.go b/vcd/datasource_vcd_tm_tier0_gateway.go new file mode 100644 index 000000000..1f1a902c4 --- /dev/null +++ b/vcd/datasource_vcd_tm_tier0_gateway.go @@ -0,0 +1,71 @@ +package vcd + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/go-vcloud-director/v3/govcd" + "github.com/vmware/go-vcloud-director/v3/types/v56" +) + +const labelTmTier0Gateway = "TM Tier 0 Gateway" + +func datasourceVcdTmTier0Gateway() *schema.Resource { + return &schema.Resource{ + ReadContext: datasourceVcdTmTier0GatewayRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: fmt.Sprintf("Display Name of %s", labelTmTier0Gateway), + }, + "region_id": { + Type: schema.TypeString, + Required: true, + Description: fmt.Sprintf("Parent %s ID", labelTmRegion), + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: fmt.Sprintf("Description of %s", labelTmTier0Gateway), + }, + "parent_tier_0_id": { + Type: schema.TypeString, + Computed: true, + Description: fmt.Sprintf("Parent Tier 0 Gateway of %s", labelTmTier0Gateway), + }, + "already_imported": { + Type: schema.TypeBool, + Computed: true, + Description: fmt.Sprintf("Defines if the T0 is already imported of %s", labelTmTier0Gateway), + }, + }, + } +} + +func datasourceVcdTmTier0GatewayRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + vcdClient := meta.(*VCDClient) + getT0ByName := func(name string) (*govcd.TmTier0Gateway, error) { + return vcdClient.GetTmTier0GatewayWithContextByName(name, d.Get("region_id").(string), true) + } + + c := dsReadConfig[*govcd.TmTier0Gateway, types.TmTier0Gateway]{ + entityLabel: labelTmTier0Gateway, + getEntityFunc: getT0ByName, + stateStoreFunc: setTmTier0GatewayData, + } + return readDatasource(ctx, d, meta, c) +} + +func setTmTier0GatewayData(_ *VCDClient, d *schema.ResourceData, t *govcd.TmTier0Gateway) error { + d.SetId(t.TmTier0Gateway.ID) // So far the API returns plain UUID (not URN) + dSet(d, "name", t.TmTier0Gateway.DisplayName) + dSet(d, "description", t.TmTier0Gateway.Description) + dSet(d, "parent_tier_0_id", t.TmTier0Gateway.ParentTier0ID) + dSet(d, "already_imported", t.TmTier0Gateway.AlreadyImported) + + return nil +} diff --git a/vcd/provider.go b/vcd/provider.go index 39a28b7f8..0421750f0 100644 --- a/vcd/provider.go +++ b/vcd/provider.go @@ -187,6 +187,8 @@ var globalDataSourceMap = map[string]*schema.Resource{ "vcd_tm_vcenter": datasourceVcdTmVcenter(), // 4.0 "vcd_tm_content_library_item": datasourceVcdTmContentLibraryItem(), // 4.0 "vcd_tm_ip_space": datasourceVcdTmIpSpace(), // 4.0 + "vcd_tm_tier0_gateway": datasourceVcdTmTier0Gateway(), // 4.0 + "vcd_tm_provider_gateway": datasourceVcdTmProviderGateway(), // 4.0 } var globalResourceMap = map[string]*schema.Resource{ @@ -321,6 +323,7 @@ var globalResourceMap = map[string]*schema.Resource{ "vcd_tm_region": resourceVcdTmRegion(), // 4.0 "vcd_tm_org_vdc": resourceTmOrgVdc(), // 4.0 "vcd_tm_ip_space": resourceVcdTmIpSpace(), // 4.0 + "vcd_tm_provider_gateway": resourceVcdTmProviderGateway(), // 4.0 } // Provider returns a terraform.ResourceProvider. diff --git a/vcd/resource_vcd_tm_provider_gateway.go b/vcd/resource_vcd_tm_provider_gateway.go new file mode 100644 index 000000000..74b445ca0 --- /dev/null +++ b/vcd/resource_vcd_tm_provider_gateway.go @@ -0,0 +1,260 @@ +package vcd + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/go-vcloud-director/v3/govcd" + "github.com/vmware/go-vcloud-director/v3/types/v56" +) + +const labelTmProviderGateway = "TM Provider Gateway" +const labelTmProviderGatewayIpSpaceAssociations = "TM IP Space Associations" + +// TM Provider Gateway has an asymmetric API - it requires are least one IP Space reference when +// creating a Provider Gateway, but it will not return Associated IP Spaces afterwards. Instead, +// to update associated IP Spaces one must use separate API endpoint and structure +// (`TmIpSpaceAssociation`) to manage associations after initial Provider Gateway creation + +func resourceVcdTmProviderGateway() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceVcdTmProviderGatewayCreate, + ReadContext: resourceVcdTmProviderGatewayRead, + UpdateContext: resourceVcdTmProviderGatewayUpdate, + DeleteContext: resourceVcdTmProviderGatewayDelete, + Importer: &schema.ResourceImporter{ + StateContext: resourceVcdTmProviderGatewayImport, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: fmt.Sprintf("Name of %s", labelTmProviderGateway), + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: fmt.Sprintf("Description of %s", labelTmProviderGateway), + }, + "region_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: fmt.Sprintf("Parent %s of %s", labelTmRegion, labelTmProviderGateway), + }, + "nsxt_tier0_gateway_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: fmt.Sprintf("Parent %s of %s", labelTmTier0Gateway, labelTmProviderGateway), + }, + "ip_space_ids": { + Type: schema.TypeSet, + Required: true, + Description: fmt.Sprintf("A set of supervisor IDs used in this %s", labelTmRegion), + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: fmt.Sprintf("Status of %s", labelTmProviderGateway), + }, + }, + } +} + +func resourceVcdTmProviderGatewayCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + vcdClient := meta.(*VCDClient) + c := crudConfig[*govcd.TmProviderGateway, types.TmProviderGateway]{ + entityLabel: labelTmProviderGateway, + getTypeFunc: getTmProviderGatewayType, + stateStoreFunc: setTmProviderGatewayData, + createFunc: vcdClient.CreateTmProviderGateway, + resourceReadFunc: resourceVcdTmProviderGatewayRead, + } + return createResource(ctx, d, meta, c) +} + +func resourceVcdTmProviderGatewayUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + vcdClient := meta.(*VCDClient) + + // Update IP Space associations using separate endpoint (more details at the top of file) + if d.HasChange("ip_space_ids") { + previous, new := d.GetChange("ip_space_ids") + previousSet := previous.(*schema.Set) + newSet := new.(*schema.Set) + + toRemoveSet := previousSet.Difference(newSet) + toAddSet := newSet.Difference(previousSet) + + // Adding new ones first, because it can happen that all previous IP Spaces are removed and + // new ones added, however API prohibits removal of all IP Space associations for Provider + // Gateway (at least one IP Space must always be associated) + err := addIpSpaceAssociations(vcdClient, d.Id(), convertSchemaSetToSliceOfStrings(toAddSet)) + if err != nil { + return diag.FromErr(err) + } + + // Remove associations that are no more in configuration + err = removeIpSpaceAssociations(vcdClient, d.Id(), convertSchemaSetToSliceOfStrings(toRemoveSet)) + if err != nil { + return diag.FromErr(err) + } + } + + // This is the default entity update path - other fields can be updated, by updating IP Space itself + if d.HasChangeExcept("ip_space_ids") { + c := crudConfig[*govcd.TmProviderGateway, types.TmProviderGateway]{ + entityLabel: labelTmProviderGateway, + getTypeFunc: getTmProviderGatewayType, + getEntityFunc: vcdClient.GetTmProviderGatewayById, + resourceReadFunc: resourceVcdTmProviderGatewayRead, + } + + return updateResource(ctx, d, meta, c) + } + + return nil +} + +func resourceVcdTmProviderGatewayRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + vcdClient := meta.(*VCDClient) + + c := crudConfig[*govcd.TmProviderGateway, types.TmProviderGateway]{ + entityLabel: labelTmProviderGateway, + getEntityFunc: vcdClient.GetTmProviderGatewayById, + stateStoreFunc: setTmProviderGatewayData, + } + return readResource(ctx, d, meta, c) +} + +func resourceVcdTmProviderGatewayDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + vcdClient := meta.(*VCDClient) + + c := crudConfig[*govcd.TmProviderGateway, types.TmProviderGateway]{ + entityLabel: labelTmProviderGateway, + getEntityFunc: vcdClient.GetTmProviderGatewayById, + } + + return deleteResource(ctx, d, meta, c) +} + +func resourceVcdTmProviderGatewayImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + resourceURI := strings.Split(d.Id(), ImportSeparator) + if len(resourceURI) != 2 { + return nil, fmt.Errorf("resource name must be specified as region-name.provider-gateway-name") + } + regionName, providerGatewayName := resourceURI[0], resourceURI[1] + + vcdClient := meta.(*VCDClient) + region, err := vcdClient.GetRegionByName(regionName) + if err != nil { + return nil, fmt.Errorf("error retrieving %s by name '%s': %s", labelTmRegion, regionName, err) + } + + providerGateway, err := vcdClient.GetTmProviderGatewayByNameAndRegionId(providerGatewayName, region.Region.ID) + if err != nil { + return nil, fmt.Errorf("error retrieving Provider Gateway: %s", err) + } + + d.SetId(providerGateway.TmProviderGateway.ID) + return []*schema.ResourceData{d}, nil +} + +func getTmProviderGatewayType(vcdClient *VCDClient, d *schema.ResourceData) (*types.TmProviderGateway, error) { + t := &types.TmProviderGateway{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + RegionRef: types.OpenApiReference{ID: d.Get("region_id").(string)}, + BackingRef: types.OpenApiReference{ID: d.Get("nsxt_tier0_gateway_id").(string)}, + } + + ipSpaceIds := convertSchemaSetToSliceOfStrings(d.Get("ip_space_ids").(*schema.Set)) + t.IPSpaceRefs = convertSliceOfStringsToOpenApiReferenceIds(ipSpaceIds) + + // Update operation fails if the ID is not set for update + if d.Id() != "" { + t.ID = d.Id() + } + + // IP Spaces associations are populated on create only. Updates are done using separate endpoint + // (more details at the top of file) + if d.Id() != "" { + t.IPSpaceRefs = []types.OpenApiReference{} + } + + return t, nil +} + +func setTmProviderGatewayData(vcdClient *VCDClient, d *schema.ResourceData, p *govcd.TmProviderGateway) error { + if p == nil || p.TmProviderGateway == nil { + return fmt.Errorf("nil entity received") + } + + d.SetId(p.TmProviderGateway.ID) + dSet(d, "name", p.TmProviderGateway.Name) + dSet(d, "description", p.TmProviderGateway.Description) + dSet(d, "region_id", p.TmProviderGateway.RegionRef.ID) + dSet(d, "nsxt_tier0_gateway_id", p.TmProviderGateway.BackingRef.ID) + dSet(d, "status", p.TmProviderGateway.Status) + + // IP Space Associations have to be read separatelly after creation (more details at the top of file) + associations, err := vcdClient.GetAllTmIpSpaceAssociationsByProviderGatewayId(p.TmProviderGateway.ID) + if err != nil { + return fmt.Errorf("error retrieving %s for %s", labelTmProviderGatewayIpSpaceAssociations, labelTmProviderGateway) + } + associationIds := make([]string, len(associations)) + for index, singleAssociation := range associations { + associationIds[index] = singleAssociation.TmIpSpaceAssociation.IPSpaceRef.ID + } + + err = d.Set("ip_space_ids", associationIds) + if err != nil { + return fmt.Errorf("error storing 'ip_space_ids': %s", err) + } + + return nil +} + +func addIpSpaceAssociations(vcdClient *VCDClient, providerGatewayId string, addIpSpaceIds []string) error { + for _, addIpSpaceId := range addIpSpaceIds { + at := &types.TmIpSpaceAssociation{ + IPSpaceRef: &types.OpenApiReference{ID: addIpSpaceId}, + ProviderGatewayRef: &types.OpenApiReference{ID: providerGatewayId}, + } + _, err := vcdClient.CreateTmIpSpaceAssociation(at) + if err != nil { + return fmt.Errorf("error adding new %s for %s with ID '%s': %s", + labelTmProviderGatewayIpSpaceAssociations, labelTmIpSpace, addIpSpaceId, err) + } + } + + return nil +} + +func removeIpSpaceAssociations(vcdClient *VCDClient, providerGatewayId string, removeIpSpaceIds []string) error { + existingIpSpaceAssociations, err := vcdClient.GetAllTmIpSpaceAssociationsByProviderGatewayId(providerGatewayId) + if err != nil { + return fmt.Errorf("error reading %s for update: %s", labelTmProviderGatewayIpSpaceAssociations, err) + } + + for _, singleIpSpaceId := range removeIpSpaceIds { + for _, singleAssociation := range existingIpSpaceAssociations { + if singleAssociation.TmIpSpaceAssociation.IPSpaceRef.ID == singleIpSpaceId { + err = singleAssociation.Delete() + if err != nil { + return fmt.Errorf("error removing %s '%s' for %s '%s': %s", + labelTmProviderGatewayIpSpaceAssociations, singleAssociation.TmIpSpaceAssociation.ID, labelTmIpSpace, singleIpSpaceId, err) + } + } + } + } + + return nil +} diff --git a/vcd/resource_vcd_tm_provider_gateway_test.go b/vcd/resource_vcd_tm_provider_gateway_test.go new file mode 100644 index 000000000..1c3a6fa27 --- /dev/null +++ b/vcd/resource_vcd_tm_provider_gateway_test.go @@ -0,0 +1,193 @@ +//go:build tm || ALL || functional + +package vcd + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccVcdTmProviderGateway(t *testing.T) { + preTestChecks(t) + skipIfNotSysAdmin(t) + skipIfNotTm(t) + + vCenterHcl, vCenterHclRef := getVCenterHcl(t) + nsxManagerHcl, nsxManagerHclRef := getNsxManagerHcl(t) + regionHcl, regionHclRef := getRegionHcl(t, vCenterHclRef, nsxManagerHclRef) + ipSpace1Hcl, ipSpace1HclRef := getIpSpaceHcl(t, regionHclRef, "1", "1") + ipSpace2Hcl, ipSpace2HclRef := getIpSpaceHcl(t, regionHclRef, "2", "2") + + var params = StringMap{ + "Testname": t.Name(), + "VcenterRef": vCenterHclRef, + "RegionId": fmt.Sprintf("%s.id", regionHclRef), + "RegionName": t.Name(), + "IpSpace1Id": fmt.Sprintf("%s.id", ipSpace1HclRef), + "IpSpace2Id": fmt.Sprintf("%s.id", ipSpace2HclRef), + "Tier0Gateway": testConfig.Tm.NsxtTier0Gateway, + + "Tags": "tm", + } + testParamsNotEmpty(t, params) + + // TODO: TM: There shouldn't be a need to create `preRequisites` separately, but region + // creation fails if it is spawned instantly after adding vCenter, therefore this extra step + // give time (with additional 'refresh' and 'refresh storage policies' operations on vCenter) + skipBinaryTest := "# skip-binary-test: prerequisite buildup for acceptance tests" + configText0 := templateFill(vCenterHcl+nsxManagerHcl+skipBinaryTest, params) + params["FuncName"] = t.Name() + "-step0" + + preRequisites := vCenterHcl + nsxManagerHcl + regionHcl + ipSpace1Hcl + ipSpace2Hcl + configText1 := templateFill(preRequisites+testAccVcdTmProviderGatewayStep1, params) + params["FuncName"] = t.Name() + "-step2" + configText2 := templateFill(preRequisites+testAccVcdTmProviderGatewayStep2, params) + params["FuncName"] = t.Name() + "-step3" + configText3 := templateFill(preRequisites+testAccVcdTmProviderGatewayStep3, params) + params["FuncName"] = t.Name() + "-step4" + configText4 := templateFill(preRequisites+testAccVcdTmProviderGatewayStep4DS, params) + + debugPrintf("#[DEBUG] CONFIGURATION step1: %s\n", configText1) + debugPrintf("#[DEBUG] CONFIGURATION step2: %s\n", configText2) + debugPrintf("#[DEBUG] CONFIGURATION step3: %s\n", configText3) + debugPrintf("#[DEBUG] CONFIGURATION step4: %s\n", configText4) + if vcdShortTest { + t.Skip(acceptanceTestsSkipped) + return + } + + cachedProviderGateway := &testCachedFieldValue{} + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: configText0, + }, + { + Config: configText1, + Check: resource.ComposeTestCheckFunc( + cachedProviderGateway.cacheTestResourceFieldValue("vcd_tm_provider_gateway.test", "id"), + resource.TestCheckResourceAttrSet("vcd_tm_provider_gateway.test", "id"), + resource.TestCheckResourceAttrSet("vcd_tm_provider_gateway.test", "region_id"), + resource.TestCheckResourceAttrSet("vcd_tm_provider_gateway.test", "nsxt_tier0_gateway_id"), + resource.TestCheckResourceAttr("vcd_tm_provider_gateway.test", "name", t.Name()), + resource.TestCheckResourceAttr("vcd_tm_provider_gateway.test", "description", "Made using Terraform"), + resource.TestCheckResourceAttr("vcd_tm_provider_gateway.test", "ip_space_ids.#", "1"), + ), + }, + { + Config: configText2, + Check: resource.ComposeTestCheckFunc( + cachedProviderGateway.testCheckCachedResourceFieldValue("vcd_tm_provider_gateway.test", "id"), + resource.TestCheckResourceAttrSet("vcd_tm_provider_gateway.test", "id"), + resource.TestCheckResourceAttrSet("vcd_tm_provider_gateway.test", "region_id"), + resource.TestCheckResourceAttrSet("vcd_tm_provider_gateway.test", "nsxt_tier0_gateway_id"), + resource.TestCheckResourceAttr("vcd_tm_provider_gateway.test", "name", t.Name()), + resource.TestCheckResourceAttr("vcd_tm_provider_gateway.test", "description", "Made using Terraform updated"), + resource.TestCheckResourceAttr("vcd_tm_provider_gateway.test", "ip_space_ids.#", "1"), + ), + }, + { + Config: configText3, + Check: resource.ComposeTestCheckFunc( + cachedProviderGateway.testCheckCachedResourceFieldValue("vcd_tm_provider_gateway.test", "id"), + resource.TestCheckResourceAttrSet("vcd_tm_provider_gateway.test", "id"), + resource.TestCheckResourceAttrSet("vcd_tm_provider_gateway.test", "region_id"), + resource.TestCheckResourceAttrSet("vcd_tm_provider_gateway.test", "nsxt_tier0_gateway_id"), + resource.TestCheckResourceAttr("vcd_tm_provider_gateway.test", "name", t.Name()+"-updated"), + resource.TestCheckResourceAttr("vcd_tm_provider_gateway.test", "description", ""), + resource.TestCheckResourceAttr("vcd_tm_provider_gateway.test", "ip_space_ids.#", "3"), + ), + }, + { + Config: configText4, + Check: resource.ComposeTestCheckFunc( + resourceFieldsEqual("vcd_tm_provider_gateway.test", "data.vcd_tm_provider_gateway.test", nil), + ), + }, + { + ResourceName: "vcd_tm_provider_gateway.test", + ImportState: true, + ImportStateVerify: true, + ImportStateId: testConfig.Tm.Region + ImportSeparator + params["Testname"].(string) + "-updated", + }, + }, + }) + + postTestChecks(t) +} + +const testAccVcdTmProviderGatewayPrereqs = ` +resource "vcd_tm_ip_space" "test" { + name = "{{.Testname}}" + description = "description test" + region_id = {{.RegionId}} + external_scope = "12.12.0.0/30" + default_quota_max_subnet_size = 24 + default_quota_max_cidr_count = 1 + default_quota_max_ip_count = 1 + + internal_scope { + name = "scope1" + cidr = "10.0.0.0/28" + } +} + +resource "vcd_tm_ip_space" "test2" { + name = "{{.Testname}}-2" + description = "description test" + region_id = {{.RegionId}} + external_scope = "13.12.0.0/30" + default_quota_max_subnet_size = 24 + default_quota_max_cidr_count = 1 + default_quota_max_ip_count = 1 + + internal_scope { + name = "scope1" + cidr = "9.0.0.0/28" + } +} + +data "vcd_tm_tier0_gateway" "test" { + name = "{{.Tier0Gateway}}" + region_id = {{.RegionId}} +} +` + +const testAccVcdTmProviderGatewayStep1 = testAccVcdTmProviderGatewayPrereqs + ` +resource "vcd_tm_provider_gateway" "test" { + name = "{{.Testname}}" + description = "Made using Terraform" + region_id = {{.RegionId}} + nsxt_tier0_gateway_id = data.vcd_tm_tier0_gateway.test.id + ip_space_ids = [ vcd_tm_ip_space.test.id ] +} +` + +const testAccVcdTmProviderGatewayStep2 = testAccVcdTmProviderGatewayPrereqs + ` +resource "vcd_tm_provider_gateway" "test" { + name = "{{.Testname}}" + description = "Made using Terraform updated" + region_id = {{.RegionId}} + nsxt_tier0_gateway_id = data.vcd_tm_tier0_gateway.test.id + ip_space_ids = [ vcd_tm_ip_space.test2.id ] +} +` + +const testAccVcdTmProviderGatewayStep3 = testAccVcdTmProviderGatewayPrereqs + ` +resource "vcd_tm_provider_gateway" "test" { + name = "{{.Testname}}-updated" + region_id = {{.RegionId}} + nsxt_tier0_gateway_id = data.vcd_tm_tier0_gateway.test.id + ip_space_ids = [ vcd_tm_ip_space.test2.id, vcd_tm_ip_space.test.id, {{.IpSpace1Id}} ] +} +` + +const testAccVcdTmProviderGatewayStep4DS = testAccVcdTmProviderGatewayStep3 + ` +data "vcd_tm_provider_gateway" "test" { + name = vcd_tm_provider_gateway.test.name + region_id = {{.RegionId}} +} +` diff --git a/vcd/sample_vcd_test_config_tm.json b/vcd/sample_vcd_test_config_tm.json index 77eba0745..17d5aff54 100644 --- a/vcd/sample_vcd_test_config_tm.json +++ b/vcd/sample_vcd_test_config_tm.json @@ -58,6 +58,7 @@ "createNsxtManager": true, "nsxtManagerUsername": "admin", "nsxtManagerPassword": "", - "nsxtManagerUrl": "https://nsxmanager.my-company.com" + "nsxtManagerUrl": "https://nsxmanager.my-company.com", + "nsxtEdgeCluster": "edge-cluster-name" } } diff --git a/vcd/tm_common_test.go b/vcd/tm_common_test.go index a5137678b..4fa4f5ad0 100644 --- a/vcd/tm_common_test.go +++ b/vcd/tm_common_test.go @@ -156,3 +156,22 @@ resource "vcd_tm_content_library" "content_library" { } `, "vcd_tm_content_library.content_library" } + +func getIpSpaceHcl(t *testing.T, regionHclRef, nameSuffix, octet3 string) (string, string) { + return ` +resource "vcd_tm_ip_space" "test-` + nameSuffix + `" { + name = "` + t.Name() + nameSuffix + `" + description = "Made using Terraform" + region_id = ` + regionHclRef + `.id + external_scope = "43.12.` + octet3 + `.0/30" + default_quota_max_subnet_size = 24 + default_quota_max_cidr_count = 1 + default_quota_max_ip_count = 1 + + internal_scope { + name = "scope3" + cidr = "32.0.` + octet3 + `.0/24" + } +} + `, `vcd_tm_ip_space.test-` + nameSuffix +} diff --git a/website/docs/d/tm_provider_gateway.html.markdown b/website/docs/d/tm_provider_gateway.html.markdown new file mode 100644 index 000000000..3be291806 --- /dev/null +++ b/website/docs/d/tm_provider_gateway.html.markdown @@ -0,0 +1,39 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_tm_provider_gateway" +sidebar_current: "docs-vcd-data-source-tm-provider-gateway" +description: |- + Provides a VMware Cloud Foundation Tenant Manager Provider Gateway data source. +--- + +# vcd\_tm\_provider\_gateway + +Provides a VMware Cloud Foundation Tenant Manager Provider Gateway data source. + +## Example Usage + +```hcl +data "vcd_tm_region" "demo" { + name = "region-one" +} + +data "vcd_tm_provider_gateway" "demo" { + name = "Demo Provider Gateway" + region_id = data.vcd_tm_region.demo.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of Provider Gateway +* `region_id` - (Required) An ID of Region. Can be looked up using + [vcd_tm_region](/providers/vmware/vcd/latest/docs/data-sources/tm_region) data source + + +## Attribute Reference + +All the arguments and attributes defined in +[`vcd_tm_provider_gateway`](/providers/vmware/vcd/latest/docs/resources/tm_provider_gateway) +resource are available. \ No newline at end of file diff --git a/website/docs/d/tm_tier0_gateway.html.markdown b/website/docs/d/tm_tier0_gateway.html.markdown new file mode 100644 index 000000000..0c0323b0f --- /dev/null +++ b/website/docs/d/tm_tier0_gateway.html.markdown @@ -0,0 +1,38 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_tm_tier0_gateway" +sidebar_current: "docs-vcd-data-source-tm-tier0-gateway" +description: |- + Provides a VMware Cloud Foundation Tenant Manager Tier 0 Gateway data source. +--- + +# vcd\_tm\_tier0\_gateway + +Provides a VMware Cloud Foundation Tenant Manager Tier 0 Gateway data source. + +## Example Usage + +```hcl +data "vcd_tm_region" "demo" { + name = "region-one" +} + +data "vcd_tm_tier0_gateway" "demo" { + name = "my-tier0-gateway" + region_id = data.vcd_tm_region.demo.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of TM Tier 0 Gateway originating in NSX-T +* `region_id` - (Required) An ID of Region. Can be looked up using + [vcd_tm_region](/providers/vmware/vcd/latest/docs/data-sources/tm_region) data source + +## Attribute Reference + +* `description` - Description of the Tier 0 Gateway +* `parent_tier_0_id` - Parent Tier 0 Gateway ID if this is a Tier 0 VRF +* `already_imported` - Boolean flag if the Tier 0 Gateway is already consumed diff --git a/website/docs/r/tm_provider_gateway.html.markdown b/website/docs/r/tm_provider_gateway.html.markdown new file mode 100644 index 000000000..53094e18d --- /dev/null +++ b/website/docs/r/tm_provider_gateway.html.markdown @@ -0,0 +1,78 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_tm_provider_gateway" +sidebar_current: "docs-vcd-resource-tm-provider-gateway" +description: |- + Provides a VMware Cloud Foundation Tenant Manager Provider Gateway resource. +--- + +# vcd\_tm\_provider\_gateway + +Provides a VMware Cloud Foundation Tenant Manager Provider Gateway resource. + +## Example Usage + +```hcl +data "vcd_tm_region" "demo" { + name = "region-one" +} + +data "vcd_tm_tier0_gateway" "demo" { + name = "my-tier0-gateway" + region_id = data.vcd_tm_region.demo.id +} + +data "vcd_tm_ip_space" "demo" { + name = "demo-ip-space" + region_id = data.vcd_tm_region.demo.id +} + +data "vcd_tm_ip_space" "demo2" { + name = "demo-ip-space-2" + region_id = data.vcd_tm_region.demo.id +} + +resource "vcd_tm_provider_gateway" "demo" { + name = "Demo Provider Gateway" + description = "Terraform Provider Gateway" + region_id = data.vcd_tm_region.demo.id + nsxt_tier0_gateway_id = data.vcd_tm_tier0_gateway.demo.id + ip_space_ids = [data.vcd_tm_ip_space.demo.id, data.vcd_tm_ip_space.demo2.id] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) A name for Provider Gateway +* `description` - (Optional) A description for Provider Gateway +* `region_id` - (Required) A Region ID for Provider Gateway +* `nsxt_tier0_gateway_id` - (Required) An existing NSX-T Tier 0 Gateway +* `ip_space_ids` - (Required) A set of IP Space IDs that should be assigned to this Provider Gateway + +## Attribute Reference + +* `status` - Current status of the entity. Possible values are: + * `PENDING` - Desired entity configuration has been received by system and is pending realization + * `CONFIGURING` - The system is in process of realizing the entity + * `REALIZED` - The entity is successfully realized in the system + * `REALIZATION_FAILED` - There are some issues and the system is not able to realize the entity + * `UNKNOWN` - Current state of entity is unknown + +## Importing + +~> **Note:** The current implementation of Terraform import can only import resources into the +state. It does not generate configuration. However, an experimental feature in Terraform 1.5+ allows +also code generation. See [Importing resources][importing-resources] for more information. + +An existing Provider Gateway configuration can be [imported][docs-import] into this resource via +supplying path for it. An example is below: + +[docs-import]: https://www.terraform.io/docs/import/ + +``` +terraform import vcd_tm_provider_gateway.imported my-region-name.my-provider-gateway +``` + +The above would import the `my-provider-gateway` Provider Gateway in Region `my-region-name` diff --git a/website/vcd.erb b/website/vcd.erb index f1443207b..6358d52e0 100644 --- a/website/vcd.erb +++ b/website/vcd.erb @@ -517,6 +517,12 @@ > vcd_tm_ip_space + > + vcd_tm_provider_gateway + + > + vcd_tm_tier0_gateway + > @@ -906,6 +912,9 @@ > vcd_tm_ip_space + > + vcd_tm_provider_gateway +