Skip to content

Commit

Permalink
Add vcd_catalog_vapp_template resource and data source for managing v…
Browse files Browse the repository at this point in the history
…App Templates (#899)

Signed-off-by: abarreiro <abarreiro@vmware.com>
  • Loading branch information
adambarreiro authored Oct 19, 2022
1 parent 867c7eb commit d9aa0ed
Show file tree
Hide file tree
Showing 21 changed files with 1,325 additions and 96 deletions.
2 changes: 2 additions & 0 deletions .changes/v3.8.0/899-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* **New Resource:** `vcd_catalog_vapp_template` to manage the upload and usage of vApp Templates [GH-899]
* **New Data Source:** `vcd_catalog_vapp_template` to fetch existing vApp Templates [GH-899]
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/hashicorp/go-version v1.5.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.17.0
github.com/kr/pretty v0.2.1
github.com/vmware/go-vcloud-director/v2 v2.17.0-alpha.2
github.com/vmware/go-vcloud-director/v2 v2.17.0-alpha.4
)

require (
Expand Down Expand Up @@ -58,5 +58,3 @@ require (
google.golang.org/grpc v1.46.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
)

replace github.com/vmware/go-vcloud-director/v2 => github.com/adambarreiro/go-vcloud-director/v2 v2.17.0-alpha.1.0.20220928073049-f27fe7e7496a
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C6
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/adambarreiro/go-vcloud-director/v2 v2.17.0-alpha.1.0.20220928073049-f27fe7e7496a h1:ZPYLW1zn8TDhBmP0AOyev4RNvBwocEha+MbhSvQVUbE=
github.com/adambarreiro/go-vcloud-director/v2 v2.17.0-alpha.1.0.20220928073049-f27fe7e7496a/go.mod h1:VRA1ZLDf6CtL1atU1ceMj6/3h9HJg+zjBLaMNODF1qQ=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
Expand Down Expand Up @@ -205,6 +203,8 @@ github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvC
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmware/go-vcloud-director/v2 v2.17.0-alpha.4 h1:ZoTabulEjlPKOUcjoj1t6W5F5b/4riPae5fqysC25tE=
github.com/vmware/go-vcloud-director/v2 v2.17.0-alpha.4/go.mod h1:VRA1ZLDf6CtL1atU1ceMj6/3h9HJg+zjBLaMNODF1qQ=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
Expand Down
1 change: 0 additions & 1 deletion vcd/catalogitem.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ func deleteCatalogItem(d *schema.ResourceData, vcdClient *VCDClient) diag.Diagno
}

// Finds catalog item which can be vApp template OVA or media ISO file
// TODO: This function should be updated in the context of Issue #502
func findCatalogItem(d *schema.ResourceData, vcdClient *VCDClient, origin string) (*govcd.CatalogItem, error) {
log.Printf("[TRACE] Catalog item read initiated")

Expand Down
1 change: 1 addition & 0 deletions vcd/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ type TestConfig struct {
CatalogItemWithMultiVms string `json:"catalogItemWithMultiVms,omitempty"`
VmName1InMultiVmItem string `json:"vmName1InMultiVmItem,omitempty"`
VmName2InMultiVmItem string `json:"VmName2InMultiVmItem,omitempty"`
NsxtCatalogItem string `json:"nsxtCatalogItem,omitempty"`
NsxtBackedCatalogName string `json:"nsxtBackedCatalogName,omitempty"`
} `json:"catalog"`
} `json:"vcd"`
Expand Down
30 changes: 30 additions & 0 deletions vcd/datasource_not_found_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,36 @@ func addMandatoryParams(dataSourceName string, mandatoryFields []string, t *test
case "catalog":
testParamsNotEmpty(t, StringMap{"VCD.Catalog.Name": testConfig.VCD.Catalog.Name})
templateFields = templateFields + `catalog = "` + testConfig.VCD.Catalog.Name + `"` + "\n"
case "catalog_id":
testParamsNotEmpty(t, StringMap{
"VCD.Org": testConfig.VCD.Org,
"VCD.Catalog.Name": testConfig.VCD.Catalog.Name})
org, err := vcdClient.GetOrgByName(testConfig.VCD.Org)
if err != nil {
t.Skip("No suitable Organization found for this test")
return ""
}
catalog, err := org.GetCatalogByName(testConfig.VCD.Catalog.Name, false)
if err != nil {
t.Skip("No suitable Catalog found for this test")
return ""
}
templateFields = templateFields + `catalog_id = "` + catalog.Catalog.ID + `"` + "\n"
case "vdc_id":
testParamsNotEmpty(t, StringMap{
"VCD.Org": testConfig.VCD.Org,
"VCD.Vdc": testConfig.VCD.Vdc})
org, err := vcdClient.GetOrgByName(testConfig.VCD.Org)
if err != nil {
t.Skip("No suitable Organization found for this test")
return ""
}
vdc, err := org.GetVDCByName(testConfig.VCD.Vdc, false)
if err != nil {
t.Skip("No suitable VDC found for this test")
return ""
}
templateFields = templateFields + `vdc_id = "` + vdc.Vdc.ID + `"` + "\n"
case "vapp_name":
testParamsNotEmpty(t, StringMap{"VCD.Org": testConfig.VCD.Org, "testConfig.Nsxt.Vdc": testConfig.Nsxt.Vdc})
vapp, err := getAvailableVapp()
Expand Down
2 changes: 1 addition & 1 deletion vcd/datasource_vcd_catalog_item_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestAccVcdCatalogAndItemDatasource(t *testing.T) {
datasourceCatalogItem := "data.vcd_catalog_item." + testSuiteCatalogOVAItem
resourceCatalogItem := "vcd_catalog_item." + TestCatalogItemDS
resource.Test(t, resource.TestCase{
PreCheck: func() { preRunChecks(t, params) },
PreCheck: func() { preRunChecks(t) },
ProviderFactories: testAccProviders,
CheckDestroy: catalogItemDestroyed(testSuiteCatalogName, TestCatalogItemDS),
Steps: []resource.TestStep{
Expand Down
4 changes: 2 additions & 2 deletions vcd/datasource_vcd_catalog_media_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestAccVcdCatalogAndMediaDatasource(t *testing.T) {
debugPrintf("#[DEBUG] CONFIGURATION: %s", configText)

resource.Test(t, resource.TestCase{
PreCheck: func() { preRunChecks(t, params) },
PreCheck: func() { preRunChecks(t) },
ProviderFactories: testAccProviders,
CheckDestroy: catalogMediaDestroyed(testConfig.VCD.Catalog.Name, TestCatalogMediaDS),
Steps: []resource.TestStep{
Expand All @@ -54,7 +54,7 @@ func TestAccVcdCatalogAndMediaDatasource(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckVcdCatalogMediaExists("vcd_catalog_media."+TestAccVcdDataSourceMedia),
resource.TestMatchOutput("owner_name", regexp.MustCompile(`^\S+`)),
resource.TestMatchOutput("creation_date", regexp.MustCompile(`^^\d{4}-\d{2}-\d{2}.*`)),
resource.TestMatchOutput("creation_date", regexp.MustCompile(`^\d{4}-\d{2}-\d{2}.*`)),
resource.TestCheckOutput("status", "RESOLVED"),
resource.TestMatchOutput("storage_profile_name", regexp.MustCompile(`^\S+`)),
testCheckMediaNonStringOutputs(),
Expand Down
81 changes: 81 additions & 0 deletions vcd/datasource_vcd_catalog_vapp_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package vcd

import (
"context"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func datasourceVcdCatalogVappTemplate() *schema.Resource {
return &schema.Resource{
ReadContext: datasourceVcdCatalogVappTemplateRead,
Schema: map[string]*schema.Schema{
"org": {
Type: schema.TypeString,
Optional: true,
Description: "The name of organization to use, optional if defined at provider " +
"level. Useful when connected as sysadmin working across different organizations",
},
"catalog_id": {
Type: schema.TypeString,
Optional: true,
Description: "ID of the catalog containing the vApp Template. Can't be used if a specific VDC identifier is set",
ExactlyOneOf: []string{"catalog_id", "vdc_id"},
},
"vdc_id": {
Type: schema.TypeString,
Optional: true,
Description: "ID of the VDC to which the vApp Template belongs. Can't be used if a specific Catalog identifier is set",
ExactlyOneOf: []string{"catalog_id", "vdc_id"},
},
"name": {
Type: schema.TypeString,
Optional: true,
Description: "Name of the vApp Template. It is optional when a filter is provided",
ExactlyOneOf: []string{"name", "filter"},
},
"description": {
Type: schema.TypeString,
Computed: true,
},
"created": {
Type: schema.TypeString,
Computed: true,
Description: "Timestamp of when the vApp Template was created",
},
"metadata": {
Type: schema.TypeMap,
Computed: true,
Description: "Key and value pairs from the metadata of the vApp template",
},
"vm_names": {
Type: schema.TypeSet,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Computed: true,
Description: "Set of VM names within the vApp template",
},
"filter": {
Type: schema.TypeList,
MaxItems: 1,
MinItems: 1,
Optional: true,
Description: "Criteria for retrieving a vApp Template by various attributes",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name_regex": elementNameRegex,
"date": elementDate,
"earliest": elementEarliest,
"latest": elementLatest,
"metadata": elementMetadata,
},
},
},
},
}
}

func datasourceVcdCatalogVappTemplateRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return genericVcdCatalogVappTemplateRead(ctx, d, meta, "datasource")
}
138 changes: 138 additions & 0 deletions vcd/datasource_vcd_catalog_vapp_template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//go:build catalog || ALL || functional
// +build catalog ALL functional

package vcd

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

// Test catalog and vApp Template data sources.
// Using a catalog data source we reference a vApp Template data source.
// Using a vApp Template data source we create another vApp Template.
// where the description is the first data source ID.
func TestAccVcdCatalogAndVappTemplateDatasource(t *testing.T) {
preTestChecks(t)
createdVAppTemplateName := t.Name()

var params = StringMap{
"Org": testConfig.VCD.Org,
"Vdc": testConfig.Nsxt.Vdc,
"Catalog": testConfig.VCD.Catalog.NsxtBackedCatalogName,
"VAppTemplate": testConfig.VCD.Catalog.NsxtCatalogItem,
}
testParamsNotEmpty(t, params)

configText := templateFill(testAccCheckVcdCatalogVAppTemplateDS, params)
if vcdShortTest {
t.Skip(acceptanceTestsSkipped)
return
}
debugPrintf("#[DEBUG] CONFIGURATION: %s", configText)

datasourceCatalog := "data.vcd_catalog." + params["Catalog"].(string)
datasourceVdc := "data.vcd_org_vdc." + params["Vdc"].(string)
datasourceCatalogVappTemplate1 := "data.vcd_catalog_vapp_template." + params["VAppTemplate"].(string) + "_1"
datasourceCatalogVappTemplate2 := "data.vcd_catalog_vapp_template." + params["VAppTemplate"].(string) + "_2"
datasourceCatalogVappTemplate3 := "data.vcd_catalog_vapp_template." + params["VAppTemplate"].(string) + "_3"
datasourceCatalogVappTemplate4 := "data.vcd_catalog_vapp_template." + params["VAppTemplate"].(string) + "_4"

resource.Test(t, resource.TestCase{
PreCheck: func() { preRunChecks(t) },
ProviderFactories: testAccProviders,
CheckDestroy: catalogVAppTemplateDestroyed(testSuiteCatalogName, createdVAppTemplateName),
Steps: []resource.TestStep{
{
Config: configText,
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr(datasourceCatalogVappTemplate1, "id", regexp.MustCompile(`urn:vcloud:vapptemplate:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)),
// Check that the attributes from the retrieved vApp Template match the related elements
resource.TestCheckResourceAttrPair(datasourceCatalog, "id", datasourceCatalogVappTemplate1, "catalog_id"),
resource.TestCheckResourceAttrPair(datasourceVdc, "id", datasourceCatalogVappTemplate1, "vdc_id"),
resource.TestCheckResourceAttrSet(datasourceCatalogVappTemplate1, "vm_names.0"),

// Check both data sources fetched by VDC and Catalog ID are equal
resource.TestCheckResourceAttrPair(datasourceCatalogVappTemplate1, "id", datasourceCatalogVappTemplate2, "id"),
resource.TestCheckResourceAttrPair(datasourceCatalogVappTemplate1, "catalog_id", datasourceCatalogVappTemplate2, "catalog_id"),
resource.TestCheckResourceAttrPair(datasourceCatalogVappTemplate1, "vdc_id", datasourceCatalogVappTemplate2, "vdc_id"),
resource.TestCheckResourceAttrSet(datasourceCatalogVappTemplate2, "vm_names.0"),

// Check data sources with filter. Not using resourceFieldsEqual here as we'd need to exclude all filtering options by hardcoding the combinations.
resource.TestCheckResourceAttrPair(datasourceCatalogVappTemplate3, "id", datasourceCatalogVappTemplate1, "id"),
resource.TestCheckResourceAttrPair(datasourceCatalogVappTemplate3, "catalog_id", datasourceCatalogVappTemplate1, "catalog_id"),
resource.TestCheckResourceAttrPair(datasourceCatalogVappTemplate3, "vdc_id", datasourceCatalogVappTemplate1, "vdc_id"),
resource.TestCheckResourceAttrSet(datasourceCatalogVappTemplate3, "vm_names.0"),
resource.TestCheckResourceAttrPair(datasourceCatalogVappTemplate4, "id", datasourceCatalogVappTemplate1, "id"),
resource.TestCheckResourceAttrPair(datasourceCatalogVappTemplate4, "catalog_id", datasourceCatalogVappTemplate1, "catalog_id"),
resource.TestCheckResourceAttrPair(datasourceCatalogVappTemplate4, "vdc_id", datasourceCatalogVappTemplate1, "vdc_id"),
resource.TestCheckResourceAttrSet(datasourceCatalogVappTemplate4, "vm_names.0"),
),
},
},
})
postTestChecks(t)
}

func catalogVAppTemplateDestroyed(catalog, itemName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*VCDClient)
org, err := conn.GetOrgByName(testConfig.VCD.Org)
if err != nil {
return err
}
cat, err := org.GetCatalogByName(catalog, false)
if err != nil {
return err
}
_, err = cat.GetVAppTemplateByName(itemName)
if err == nil {
return fmt.Errorf("vApp Template %s not deleted", itemName)
}
return nil
}
}

const testAccCheckVcdCatalogVAppTemplateDS = `
data "vcd_catalog" "{{.Catalog}}" {
org = "{{.Org}}"
name = "{{.Catalog}}"
}
data "vcd_org_vdc" "{{.Vdc}}" {
org = "{{.Org}}"
name = "{{.Vdc}}"
}
data "vcd_catalog_vapp_template" "{{.VAppTemplate}}_1" {
org = "{{.Org}}"
catalog_id = data.vcd_catalog.{{.Catalog}}.id
name = "{{.VAppTemplate}}"
}
data "vcd_catalog_vapp_template" "{{.VAppTemplate}}_2" {
org = "{{.Org}}"
vdc_id = data.vcd_org_vdc.{{.Vdc}}.id
name = "{{.VAppTemplate}}"
}
data "vcd_catalog_vapp_template" "{{.VAppTemplate}}_3" {
org = "{{.Org}}"
catalog_id = data.vcd_catalog.{{.Catalog}}.id
filter {
name_regex = "{{.VAppTemplate}}"
}
}
data "vcd_catalog_vapp_template" "{{.VAppTemplate}}_4" {
org = "{{.Org}}"
vdc_id = data.vcd_org_vdc.{{.Vdc}}.id
filter {
name_regex = "{{.VAppTemplate}}"
}
}
`
2 changes: 1 addition & 1 deletion vcd/datasource_vcd_storage_profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestAccVcdStorageProfileDS(t *testing.T) {
debugPrintf("#[DEBUG] CONFIGURATION: %s", configText)

resource.Test(t, resource.TestCase{
PreCheck: func() { preRunChecks(t, params) },
PreCheck: func() { preRunChecks(t) },
ProviderFactories: testAccProviders,
Steps: []resource.TestStep{
{
Expand Down
Loading

0 comments on commit d9aa0ed

Please sign in to comment.