diff --git a/.changes/v3.6.0/770-features.md b/.changes/v3.6.0/770-features.md new file mode 100644 index 000000000..796116e23 --- /dev/null +++ b/.changes/v3.6.0/770-features.md @@ -0,0 +1,2 @@ +* `vcd_catalog_item` allows using `ovf_url` to upload vApp template from URL [GH-770] +* `vcd_catalog_item` update allows changing `name` and `description` [GH-770] diff --git a/CHANGELOG.md b/CHANGELOG.md index fcd5b88b1..bd27601de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.6.0 (TBC) + +Changes in progress for v3.6.0 are available at [.changes/v3.6.0](https://github.com/vmware/terraform-provider-vcd/tree/master/.changes/v3.6.0) until the release. + ## 3.5.1 (January 13, 2022) ## BUG FIXES diff --git a/PREVIOUS_VERSION b/PREVIOUS_VERSION index c0c4025db..d2613df37 100644 --- a/PREVIOUS_VERSION +++ b/PREVIOUS_VERSION @@ -1 +1 @@ -v3.5.0 +v3.5.1 diff --git a/VERSION b/VERSION index d2613df37..130165bc0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v3.5.1 +v3.6.0 diff --git a/go.mod b/go.mod index d1cf71c68..f1c484ca2 100644 --- a/go.mod +++ b/go.mod @@ -7,5 +7,5 @@ require ( github.com/hashicorp/go-version v1.3.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.0 github.com/kr/pretty v0.2.1 - github.com/vmware/go-vcloud-director/v2 v2.14.0 + github.com/vmware/go-vcloud-director/v2 v2.15.0-alpha.1 ) diff --git a/go.sum b/go.sum index 127cf34ac..10d22e062 100644 --- a/go.sum +++ b/go.sum @@ -319,8 +319,8 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/vmware/go-vcloud-director/v2 v2.14.0 h1:ZSw1xuHbk0JomkpT0qr1R6Q9VWtb9vEsTnVYcqm9+oQ= -github.com/vmware/go-vcloud-director/v2 v2.14.0/go.mod h1:2BS1yw61VN34WI0/nUYoInFvBc3Zcuf84d4ESiAAl68= +github.com/vmware/go-vcloud-director/v2 v2.15.0-alpha.1 h1:WKK9z1KsFv3AW+a1U26FwVpBmLoUNIBGh5CMZHT3y6U= +github.com/vmware/go-vcloud-director/v2 v2.15.0-alpha.1/go.mod h1:2BS1yw61VN34WI0/nUYoInFvBc3Zcuf84d4ESiAAl68= 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/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/vcd/catalogitem.go b/vcd/catalogitem.go index 3bd13b02f..2b6b9ec13 100644 --- a/vcd/catalogitem.go +++ b/vcd/catalogitem.go @@ -2,6 +2,7 @@ package vcd import ( "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "log" "strings" @@ -10,36 +11,36 @@ import ( ) // Deletes catalog item which can be vApp template OVA or media ISO file -func deleteCatalogItem(d *schema.ResourceData, vcdClient *VCDClient) error { +func deleteCatalogItem(d *schema.ResourceData, vcdClient *VCDClient) diag.Diagnostics { log.Printf("[TRACE] Catalog item delete started") adminOrg, err := vcdClient.GetAdminOrgFromResource(d) if err != nil { - return fmt.Errorf(errorRetrievingOrg, err) + return diag.Errorf(errorRetrievingOrg, err) } catalog, err := adminOrg.GetCatalogByName(d.Get("catalog").(string), false) if err != nil { log.Printf("[DEBUG] Unable to find catalog. Removing from tfstate") - return fmt.Errorf("unable to find catalog") + return diag.Errorf("unable to find catalog") } catalogItemName := d.Get("name").(string) catalogItem, err := catalog.GetCatalogItemByName(catalogItemName, false) if err != nil { log.Printf("[DEBUG] Unable to find catalog item. Removing from tfstate") - return fmt.Errorf("unable to find catalog item %s", catalogItemName) + return diag.Errorf("unable to find catalog item %s", catalogItemName) } err = catalogItem.Delete() if err != nil { log.Printf("[DEBUG] Error removing catalog item %s", err) - return fmt.Errorf("error removing catalog item %s", err) + return diag.Errorf("error removing catalog item %s", err) } _, err = catalog.GetCatalogItemByName(catalogItemName, true) if err == nil { - return fmt.Errorf("catalog item %s still found after deletion", catalogItemName) + return diag.Errorf("catalog item %s still found after deletion", catalogItemName) } log.Printf("[TRACE] Catalog item delete completed: %s", catalogItemName) diff --git a/vcd/config_test.go b/vcd/config_test.go index c7829094c..f55f6bf16 100644 --- a/vcd/config_test.go +++ b/vcd/config_test.go @@ -151,6 +151,7 @@ type TestConfig struct { } `json:"logging"` Ova struct { OvaPath string `json:"ovaPath,omitempty"` + OvfUrl string `json:"ovfUrl,omitempty"` UploadPieceSize int64 `json:"uploadPieceSize,omitempty"` UploadProgress bool `json:"uploadProgress,omitempty"` OvaTestFileName string `json:"ovaTestFileName,omitempty"` @@ -910,7 +911,7 @@ func createSuiteCatalogAndItem(config TestConfig) { fmt.Printf("Creating catalog item for test suite...\n") task, err := catalog.UploadOvf(ovaFilePath, testSuiteCatalogOVAItem, "Test suite purpose", 20*1024*1024) if err != nil { - fmt.Printf("error uploading new catalog item: %#v", err) + fmt.Printf("error uploading new catalog item: %s", err) panic(err) } diff --git a/vcd/datasource_vcd_catalog_item.go b/vcd/datasource_vcd_catalog_item.go index 4b8d390bf..f81042807 100644 --- a/vcd/datasource_vcd_catalog_item.go +++ b/vcd/datasource_vcd_catalog_item.go @@ -1,10 +1,14 @@ package vcd -import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) func datasourceVcdCatalogItem() *schema.Resource { return &schema.Resource{ - Read: dataSourceVcdCatalogItemRead, + ReadContext: dataSourceVcdCatalogItemRead, Schema: map[string]*schema.Schema{ "org": { Type: schema.TypeString, @@ -57,6 +61,6 @@ func datasourceVcdCatalogItem() *schema.Resource { } } -func dataSourceVcdCatalogItemRead(d *schema.ResourceData, meta interface{}) error { +func dataSourceVcdCatalogItemRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { return genericVcdCatalogItemRead(d, meta, "datasource") } diff --git a/vcd/datasource_vcd_catalog_media.go b/vcd/datasource_vcd_catalog_media.go index f216685e2..5e6afcf36 100644 --- a/vcd/datasource_vcd_catalog_media.go +++ b/vcd/datasource_vcd_catalog_media.go @@ -1,12 +1,14 @@ package vcd import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func datasourceVcdCatalogMedia() *schema.Resource { return &schema.Resource{ - Read: dataSourceVcdMediaRead, + ReadContext: dataSourceVcdMediaRead, Schema: map[string]*schema.Schema{ "org": { @@ -92,6 +94,6 @@ func datasourceVcdCatalogMedia() *schema.Resource { } } -func dataSourceVcdMediaRead(d *schema.ResourceData, meta interface{}) error { +func dataSourceVcdMediaRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { return genericVcdMediaRead(d, meta, "datasource") } diff --git a/vcd/resource_vcd_catalog_item.go b/vcd/resource_vcd_catalog_item.go index 925c170b5..7a69a07d6 100644 --- a/vcd/resource_vcd_catalog_item.go +++ b/vcd/resource_vcd_catalog_item.go @@ -1,7 +1,9 @@ package vcd import ( + "context" "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "log" "strings" "time" @@ -12,12 +14,12 @@ import ( func resourceVcdCatalogItem() *schema.Resource { return &schema.Resource{ - Create: resourceVcdCatalogItemCreate, - Delete: resourceVcdCatalogItemDelete, - Read: resourceVcdCatalogItemRead, - Update: resourceVcdCatalogItemUpdate, + CreateContext: resourceVcdCatalogItemCreate, + DeleteContext: resourceVcdCatalogItemDelete, + ReadContext: resourceVcdCatalogItemRead, + UpdateContext: resourceVcdCatalogItemUpdate, Importer: &schema.ResourceImporter{ - State: resourceVcdCatalogItemImport, + StateContext: resourceVcdCatalogItemImport, }, Schema: map[string]*schema.Schema{ "org": { @@ -36,13 +38,11 @@ func resourceVcdCatalogItem() *schema.Resource { "name": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: true, Description: "catalog item name", }, "description": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, }, "created": &schema.Schema{ Type: schema.TypeString, @@ -50,22 +50,28 @@ func resourceVcdCatalogItem() *schema.Resource { Description: "Time stamp of when the item was created", }, "ova_path": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "absolute or relative path to OVA", + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"ova_path", "ovf_url"}, + Description: "Absolute or relative path to OVA", + }, + "ovf_url": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"ova_path", "ovf_url"}, + Description: "URL of OVF file", }, "upload_piece_size": &schema.Schema{ Type: schema.TypeInt, Optional: true, - ForceNew: false, Default: 1, Description: "size of upload file piece size in mega bytes", }, "show_upload_progress": &schema.Schema{ Type: schema.TypeBool, Optional: true, - ForceNew: false, Description: "shows upload progress in stdout", }, "metadata": { @@ -79,35 +85,64 @@ func resourceVcdCatalogItem() *schema.Resource { } } -func resourceVcdCatalogItemCreate(d *schema.ResourceData, meta interface{}) error { +func resourceVcdCatalogItemCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { log.Printf("[TRACE] Catalog item creation initiated") vcdClient := meta.(*VCDClient) adminOrg, err := vcdClient.GetAdminOrgFromResource(d) if err != nil { - return fmt.Errorf(errorRetrievingOrg, err) + return diag.Errorf(errorRetrievingOrg, err) } catalogName := d.Get("catalog").(string) catalog, err := adminOrg.GetCatalogByName(catalogName, false) if err != nil { - log.Printf("[DEBUG] Error finding Catalog: %#v", err) - return fmt.Errorf("error finding Catalog: %#v", err) + log.Printf("[DEBUG] Error finding Catalog: %s", err) + return diag.Errorf("error finding Catalog: %s", err) } - uploadPieceSize := d.Get("upload_piece_size").(int) + var diagError diag.Diagnostics itemName := d.Get("name").(string) + if d.Get("ova_path").(string) != "" { + diagError = uploadFile(d, catalog, itemName) + } else if d.Get("ovf_url").(string) != "" { + diagError = uploadFromUrl(d, catalog, itemName) + } else { + return diag.Errorf("`ova_path` or `ovf_url` value is missing %s", err) + } + if diagError != nil { + return diagError + } + + item, err := catalog.GetCatalogItemByName(itemName, true) + if err != nil { + return diag.Errorf("error retrieving catalog item %s: %s", itemName, err) + } + d.SetId(item.CatalogItem.ID) + + log.Printf("[TRACE] Catalog item created: %s", itemName) + + err = createOrUpdateCatalogItemMetadata(d, meta) + if diagError != nil { + return diag.FromErr(err) + } + + return resourceVcdCatalogItemRead(ctx, d, meta) +} + +func uploadFile(d *schema.ResourceData, catalog *govcd.Catalog, itemName string) diag.Diagnostics { + uploadPieceSize := d.Get("upload_piece_size").(int) task, err := catalog.UploadOvf(d.Get("ova_path").(string), itemName, d.Get("description").(string), int64(uploadPieceSize)*1024*1024) // Convert from megabytes to bytes if err != nil { - log.Printf("[DEBUG] Error uploading new catalog item: %#v", err) - return fmt.Errorf("error uploading new catalog item: %#v", err) + log.Printf("[DEBUG] Error uploading new catalog item: %s", err) + return diag.Errorf("error uploading new catalog item: %s", err) } if d.Get("show_upload_progress").(bool) { for { if err := getError(task); err != nil { - return err + return diag.FromErr(err) } logForScreen("vcd_catalog_item", fmt.Sprintf("vcd_catalog_item."+itemName+": Upload progress "+task.GetUploadProgress()+"%%\n")) if task.GetUploadProgress() == "100.00" { @@ -117,12 +152,26 @@ func resourceVcdCatalogItemCreate(d *schema.ResourceData, meta interface{}) erro } } + return finishHandlingTask(d, *task.Task, itemName) +} + +func uploadFromUrl(d *schema.ResourceData, catalog *govcd.Catalog, itemName string) diag.Diagnostics { + task, err := catalog.UploadOvfByLink(d.Get("ovf_url").(string), itemName, d.Get("description").(string)) + if err != nil { + log.Printf("[DEBUG] Error uploading new catalog item from URL: %s", err) + return diag.Errorf("error uploading new catalog item from URL: %s", err) + } + + return finishHandlingTask(d, task, itemName) +} + +func finishHandlingTask(d *schema.ResourceData, task govcd.Task, itemName string) diag.Diagnostics { if d.Get("show_upload_progress").(bool) { for { progress, err := task.GetTaskProgress() if err != nil { - log.Printf("VCD Error importing new catalog item: %#v", err) - return fmt.Errorf("VCD Error importing new catalog item: %#v", err) + log.Printf("VCD Error importing new catalog item: %s", err) + return diag.Errorf("VCD Error importing new catalog item: %s", err) } logForScreen("vcd_catalog_item", fmt.Sprintf("vcd_catalog_item."+itemName+": VCD import catalog item progress "+progress+"%%\n")) if progress == "100" { @@ -132,68 +181,80 @@ func resourceVcdCatalogItemCreate(d *schema.ResourceData, meta interface{}) erro } } - err = task.WaitTaskCompletion() + err := task.WaitTaskCompletion() if err != nil { - return fmt.Errorf("error waiting for task to complete: %+v", err) + return diag.Errorf("error waiting for task to complete: %+v", err) } - - item, err := catalog.GetCatalogItemByName(itemName, true) - if err != nil { - return fmt.Errorf("error retrieving catalog item %s: %s", itemName, err) - } - d.SetId(item.CatalogItem.ID) - - log.Printf("[TRACE] Catalog item created: %#v", itemName) - - err = createOrUpdateCatalogItemMetadata(d, meta) - if err != nil { - return fmt.Errorf("error adding catalog item metadata: %s", err) - } - - return resourceVcdCatalogItemRead(d, meta) + return nil } -func resourceVcdCatalogItemRead(d *schema.ResourceData, meta interface{}) error { +func resourceVcdCatalogItemRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { return genericVcdCatalogItemRead(d, meta, "resource") } -func genericVcdCatalogItemRead(d *schema.ResourceData, meta interface{}, origin string) error { +func genericVcdCatalogItemRead(d *schema.ResourceData, meta interface{}, origin string) diag.Diagnostics { catalogItem, err := findCatalogItem(d, meta.(*VCDClient), origin) if err != nil { log.Printf("[DEBUG] Unable to find media item: %s", err) - return err + return diag.Errorf("Unable to find media item: %s", err) } if catalogItem == nil { log.Printf("[DEBUG] Unable to find media item: %s. Removing from tfstate", err) - return err + return diag.Errorf("Unable to find media item") } vAppTemplate, err := catalogItem.GetVAppTemplate() if err != nil { - return err + return diag.Errorf("Unable to find Vapp template: %s", err) } metadata, err := vAppTemplate.GetMetadata() if err != nil { - return err + return diag.Errorf("Unable to find meta data: %s", err) } dSet(d, "name", catalogItem.CatalogItem.Name) dSet(d, "created", vAppTemplate.VAppTemplate.DateCreated) dSet(d, "description", catalogItem.CatalogItem.Description) err = d.Set("metadata", getMetadataStruct(metadata.MetadataEntry)) + if err != nil { + return diag.Errorf("Unable to set meta data: %s", err) + } - return err + return nil } -func resourceVcdCatalogItemDelete(d *schema.ResourceData, meta interface{}) error { +func resourceVcdCatalogItemDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { return deleteCatalogItem(d, meta.(*VCDClient)) } -// currently updates only metadata -func resourceVcdCatalogItemUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceVcdCatalogItemUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + if d.HasChange("description") || d.HasChange("name") { + catalogItem, err := findCatalogItem(d, meta.(*VCDClient), "resource") + if err != nil { + log.Printf("[DEBUG] Unable to find media item: %s", err) + return diag.Errorf("Unable to find media item: %s", err) + } + if catalogItem == nil { + log.Printf("[DEBUG] Unable to find media item: %s. Removing from tfstate", err) + return diag.Errorf("Unable to find media item") + } + + vAppTemplate, err := catalogItem.GetVAppTemplate() + if err != nil { + return diag.Errorf("Unable to find Vapp template: %s", err) + } + + vAppTemplate.VAppTemplate.Description = d.Get("description").(string) + vAppTemplate.VAppTemplate.Name = d.Get("name").(string) + _, err = vAppTemplate.Update() + if err != nil { + return diag.Errorf("error updating catalog item: %s", err) + } + } + err := createOrUpdateCatalogItemMetadata(d, meta) if err != nil { - return fmt.Errorf("error updating catalog item metadata: %s", err) + return diag.Errorf("error updating catalog item metadata: %s", err) } return nil } @@ -205,7 +266,7 @@ func createOrUpdateCatalogItemMetadata(d *schema.ResourceData, meta interface{}) catalogItem, err := findCatalogItem(d, meta.(*VCDClient), "resource") if err != nil { log.Printf("[DEBUG] Unable to find media item: %s", err) - return err + return fmt.Errorf("%s", err) } // We have to add metadata to template to see in UI @@ -250,7 +311,7 @@ func createOrUpdateCatalogItemMetadata(d *schema.ResourceData, meta interface{}) // // Example import path (id): org_name.catalog_name.catalog_item_name // Note: the separator can be changed using Provider.import_separator or variable VCD_IMPORT_SEPARATOR -func resourceVcdCatalogItemImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { +func resourceVcdCatalogItemImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { resourceURI := strings.Split(d.Id(), ImportSeparator) if len(resourceURI) != 3 { return nil, fmt.Errorf("resource name must be specified as org.catalog.catalog_item") diff --git a/vcd/resource_vcd_catalog_item_test.go b/vcd/resource_vcd_catalog_item_test.go index 79cc0f775..912710889 100644 --- a/vcd/resource_vcd_catalog_item_test.go +++ b/vcd/resource_vcd_catalog_item_test.go @@ -14,24 +14,43 @@ import ( var TestAccVcdCatalogItem = "TestAccVcdCatalogItemBasic" var TestAccVcdCatalogItemDescription = "TestAccVcdCatalogItemBasicDescription" +var TestAccVcdCatalogItemFromUrl = "TestAccVcdCatalogItemBasicFromUrl" +var TestAccVcdCatalogItemDescriptionFromUrl = "TestAccVcdCatalogItemBasicDescriptionFromUrl" +var TestAccVcdCatalogItemFromUrlUpdated = "TestAccVcdCatalogItemBasicFromUrlUpdated" +var TestAccVcdCatalogItemDescriptionFromUrlUpdated = "TestAccVcdCatalogItemBasicDescriptionFromUrlUpdated" func TestAccVcdCatalogItemBasic(t *testing.T) { preTestChecks(t) + if testConfig.Ova.OvfUrl == "" { + t.Skip("Variables Ova.OvfUrl must be set") + } + var params = StringMap{ - "Org": testConfig.VCD.Org, - "Catalog": testSuiteCatalogName, - "CatalogItemName": TestAccVcdCatalogItem, - "Description": TestAccVcdCatalogItemDescription, - "OvaPath": testConfig.Ova.OvaPath, - "UploadPieceSize": testConfig.Ova.UploadPieceSize, - "UploadProgress": testConfig.Ova.UploadProgress, - "Tags": "catalog", + "Org": testConfig.VCD.Org, + "Catalog": testSuiteCatalogName, + "CatalogItemName": TestAccVcdCatalogItem, + "CatalogItemNameFromUrl": TestAccVcdCatalogItemFromUrl, + "CatalogItemNameFromUrlUpdated": TestAccVcdCatalogItemFromUrlUpdated, + "Description": TestAccVcdCatalogItemDescription, + "DescriptionFromUrl": TestAccVcdCatalogItemDescriptionFromUrl, + "DescriptionFromUrlUpdated": TestAccVcdCatalogItemDescriptionFromUrlUpdated, + "OvaPath": testConfig.Ova.OvaPath, + "OvfUrl": testConfig.Ova.OvfUrl, + "UploadPieceSize": testConfig.Ova.UploadPieceSize, + "UploadProgress": testConfig.Ova.UploadProgress, + "UploadProgressFromUrl": testConfig.Ova.UploadProgress, + "Tags": "catalog", } configText := templateFill(testAccCheckVcdCatalogItemBasic, params) params["FuncName"] = t.Name() + "-Update" updateConfigText := templateFill(testAccCheckVcdCatalogItemUpdate, params) + params["FuncName"] = t.Name() + "-FromUrl" + fromUrlConfigText := templateFill(testAccCheckVcdCatalogItemFromUrl, params) + params["FuncName"] = t.Name() + "-FromUrlUpdate" + fromUrlConfigTextUpdate := templateFill(testAccCheckVcdCatalogItemFromUrlUpdated, params) + if vcdShortTest { t.Skip(acceptanceTestsSkipped) return @@ -74,6 +93,36 @@ func TestAccVcdCatalogItemBasic(t *testing.T) { "vcd_catalog_item."+TestAccVcdCatalogItem, "metadata.catalogItem_metadata3", "catalogItem Metadata3"), ), }, + resource.TestStep{ + Config: fromUrlConfigText, + Check: resource.ComposeTestCheckFunc( + testAccCheckVcdCatalogItemExists("vcd_catalog_item."+TestAccVcdCatalogItemFromUrl), + resource.TestCheckResourceAttr( + "vcd_catalog_item."+TestAccVcdCatalogItemFromUrl, "name", TestAccVcdCatalogItemFromUrl), + resource.TestCheckResourceAttr( + "vcd_catalog_item."+TestAccVcdCatalogItemFromUrl, "description", TestAccVcdCatalogItemDescriptionFromUrl), + resource.TestCheckResourceAttr( + "vcd_catalog_item."+TestAccVcdCatalogItemFromUrl, "metadata.catalogItem_metadata", "catalogItem Metadata"), + resource.TestCheckResourceAttr( + "vcd_catalog_item."+TestAccVcdCatalogItemFromUrl, "metadata.catalogItem_metadata2", "catalogItem Metadata2"), + resource.TestCheckResourceAttr( + "vcd_catalog_item."+TestAccVcdCatalogItemFromUrl, "metadata.catalogItem_metadata3", "catalogItem Metadata3"), + ), + }, + resource.TestStep{ + Config: fromUrlConfigTextUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckVcdCatalogItemExists("vcd_catalog_item."+TestAccVcdCatalogItemFromUrl), + resource.TestCheckResourceAttr( + "vcd_catalog_item."+TestAccVcdCatalogItemFromUrl, "name", TestAccVcdCatalogItemFromUrlUpdated), + resource.TestCheckResourceAttr( + "vcd_catalog_item."+TestAccVcdCatalogItemFromUrl, "description", TestAccVcdCatalogItemDescriptionFromUrlUpdated), + resource.TestCheckResourceAttr( + "vcd_catalog_item."+TestAccVcdCatalogItemFromUrl, "metadata.catalogItem_metadata", "catalogItem Metadata"), + resource.TestCheckResourceAttr( + "vcd_catalog_item."+TestAccVcdCatalogItemFromUrl, "metadata.catalogItem_metadata2", "catalogItem Metadata2_2"), + ), + }, }, }) postTestChecks(t) @@ -193,3 +242,38 @@ const testAccCheckVcdCatalogItemUpdate = ` } } ` + +const testAccCheckVcdCatalogItemFromUrl = ` + resource "vcd_catalog_item" "{{.CatalogItemNameFromUrl}}" { + org = "{{.Org}}" + catalog = "{{.Catalog}}" + + name = "{{.CatalogItemNameFromUrl}}" + description = "{{.DescriptionFromUrl}}" + ovf_url = "{{.OvfUrl}}" + show_upload_progress = "{{.UploadProgressFromUrl}}" + + metadata = { + catalogItem_metadata = "catalogItem Metadata" + catalogItem_metadata2 = "catalogItem Metadata2" + catalogItem_metadata3 = "catalogItem Metadata3" + } +} +` + +const testAccCheckVcdCatalogItemFromUrlUpdated = ` + resource "vcd_catalog_item" "{{.CatalogItemNameFromUrl}}" { + org = "{{.Org}}" + catalog = "{{.Catalog}}" + + name = "{{.CatalogItemNameFromUrlUpdated}}" + description = "{{.DescriptionFromUrlUpdated}}" + ovf_url = "{{.OvfUrl}}" + show_upload_progress = "{{.UploadProgressFromUrl}}" + + metadata = { + catalogItem_metadata = "catalogItem Metadata" + catalogItem_metadata2 = "catalogItem Metadata2_2" + } +} +` diff --git a/vcd/resource_vcd_catalog_media.go b/vcd/resource_vcd_catalog_media.go index 2cff55864..34e418e40 100644 --- a/vcd/resource_vcd_catalog_media.go +++ b/vcd/resource_vcd_catalog_media.go @@ -1,7 +1,9 @@ package vcd import ( + "context" "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "log" "strings" "time" @@ -12,12 +14,12 @@ import ( func resourceVcdCatalogMedia() *schema.Resource { return &schema.Resource{ - Create: resourceVcdMediaCreate, - Delete: resourceVcdMediaDelete, - Read: resourceVcdMediaRead, - Update: resourceVcdMediaUpdate, + CreateContext: resourceVcdMediaCreate, + DeleteContext: resourceVcdMediaDelete, + ReadContext: resourceVcdMediaRead, + UpdateContext: resourceVcdMediaUpdate, Importer: &schema.ResourceImporter{ - State: resourceVcdCatalogMediaImport, + StateContext: resourceVcdCatalogMediaImport, }, Schema: map[string]*schema.Schema{ @@ -110,35 +112,35 @@ func resourceVcdCatalogMedia() *schema.Resource { } } -func resourceVcdMediaCreate(d *schema.ResourceData, meta interface{}) error { +func resourceVcdMediaCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { log.Printf("[TRACE] Catalog media creation initiated") vcdClient := meta.(*VCDClient) adminOrg, err := vcdClient.GetAdminOrgFromResource(d) if err != nil { - return fmt.Errorf(errorRetrievingOrg, err) + return diag.Errorf(errorRetrievingOrg, err) } catalogName := d.Get("catalog").(string) catalog, err := adminOrg.GetCatalogByName(catalogName, false) if err != nil { - log.Printf("Error finding Catalog: %#v", err) - return fmt.Errorf("error finding Catalog: %#v", err) + log.Printf("Error finding Catalog: %s", err) + return diag.Errorf("error finding Catalog: %s", err) } uploadPieceSize := d.Get("upload_piece_size").(int) mediaName := d.Get("name").(string) task, err := catalog.UploadMediaImage(mediaName, d.Get("description").(string), d.Get("media_path").(string), int64(uploadPieceSize)*1024*1024) // Convert from megabytes to bytes) if err != nil { - log.Printf("Error uploading new catalog media: %#v", err) - return fmt.Errorf("error uploading new catalog media: %#v", err) + log.Printf("Error uploading new catalog media: %s", err) + return diag.Errorf("error uploading new catalog media: %s", err) } if d.Get("show_upload_progress").(bool) { for { if err := getError(task); err != nil { - return err + return diag.FromErr(err) } logForScreen("vcd_catalog_media", fmt.Sprintf("vcd_catalog_media."+mediaName+": Upload progress "+task.GetUploadProgress()+"%%\n")) @@ -153,8 +155,8 @@ func resourceVcdMediaCreate(d *schema.ResourceData, meta interface{}) error { for { progress, err := task.GetTaskProgress() if err != nil { - log.Printf("vCD Error importing new catalog item: %#v", err) - return fmt.Errorf("vCD Error importing new catalog item: %#v", err) + log.Printf("vCD Error importing new catalog item: %s", err) + return diag.Errorf("vCD Error importing new catalog item: %s", err) } logForScreen("vcd_catalog_media", fmt.Sprintf("vcd_catalog_media."+mediaName+": vCD import catalog item progress "+progress+"%%\n")) if progress == "100" { @@ -166,49 +168,49 @@ func resourceVcdMediaCreate(d *schema.ResourceData, meta interface{}) error { err = task.WaitTaskCompletion() if err != nil { - return fmt.Errorf("error waiting for task to complete: %+v", err) + return diag.Errorf("error waiting for task to complete: %+v", err) } log.Printf("[TRACE] Catalog media created: %#v", mediaName) err = createOrUpdateMediaItemMetadata(d, meta) if err != nil { - return fmt.Errorf("error adding media item metadata: %s", err) + return diag.Errorf("error adding media item metadata: %s", err) } - return resourceVcdMediaRead(d, meta) + return resourceVcdMediaRead(ctx, d, meta) } -func resourceVcdMediaRead(d *schema.ResourceData, meta interface{}) error { +func resourceVcdMediaRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { return genericVcdMediaRead(d, meta, "resource") } -func genericVcdMediaRead(d *schema.ResourceData, meta interface{}, origin string) error { +func genericVcdMediaRead(d *schema.ResourceData, meta interface{}, origin string) diag.Diagnostics { vcdClient := meta.(*VCDClient) org, err := vcdClient.GetAdminOrgFromResource(d) if err != nil { - return fmt.Errorf(errorRetrievingOrg, err) + return diag.Errorf(errorRetrievingOrg, err) } catalog, err := org.GetCatalogByName(d.Get("catalog").(string), false) if err != nil { log.Printf("[DEBUG] Unable to find catalog.") - return fmt.Errorf("unable to find catalog: %s", err) + return diag.Errorf("unable to find catalog: %s", err) } var media *govcd.Media if origin == "datasource" { if !nameOrFilterIsSet(d) { - return fmt.Errorf(noNameOrFilterError, "vcd_catalog_media") + return diag.Errorf(noNameOrFilterError, "vcd_catalog_media") } filter, hasFilter := d.GetOk("filter") if hasFilter { media, err = getMediaByFilter(catalog, filter, vcdClient.Client.IsSysAdmin) if err != nil { - return err + return diag.FromErr(err) } } } @@ -219,7 +221,7 @@ func genericVcdMediaRead(d *schema.ResourceData, meta interface{}, origin string identifier = d.Get("name").(string) } if identifier == "" { - return fmt.Errorf("media identifier is empty") + return diag.Errorf("media identifier is empty") } media, err = catalog.GetMediaByNameOrId(identifier, false) } @@ -230,7 +232,7 @@ func genericVcdMediaRead(d *schema.ResourceData, meta interface{}, origin string } if err != nil { log.Printf("[DEBUG] Unable to find media: %s", err) - return err + return diag.FromErr(err) } d.SetId(media.Media.ID) @@ -238,7 +240,7 @@ func genericVcdMediaRead(d *schema.ResourceData, meta interface{}, origin string mediaRecord, err := catalog.QueryMedia(media.Media.Name) if err != nil { log.Printf("[DEBUG] Unable to query media: %s", err) - return err + return diag.FromErr(err) } dSet(d, "name", media.Media.Name) @@ -254,25 +256,27 @@ func genericVcdMediaRead(d *schema.ResourceData, meta interface{}, origin string metadata, err := media.GetMetadata() if err != nil { log.Printf("[DEBUG] Unable to find media item metadata: %s", err) - return err + return diag.FromErr(err) } err = d.Set("metadata", getMetadataStruct(metadata.MetadataEntry)) - - return err + if err != nil { + return diag.FromErr(err) + } + return nil } -func resourceVcdMediaDelete(d *schema.ResourceData, meta interface{}) error { +func resourceVcdMediaDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { return deleteCatalogItem(d, meta.(*VCDClient)) } // currently updates only metadata -func resourceVcdMediaUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceVcdMediaUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { err := createOrUpdateMediaItemMetadata(d, meta) if err != nil { - return fmt.Errorf("error updating media item metadata: %s", err) + return diag.Errorf("error updating media item metadata: %s", err) } - return resourceVcdMediaRead(d, meta) + return resourceVcdMediaRead(ctx, d, meta) } func createOrUpdateMediaItemMetadata(d *schema.ResourceData, meta interface{}) error { @@ -339,7 +343,7 @@ func createOrUpdateMediaItemMetadata(d *schema.ResourceData, meta interface{}) e // // Example resource name (_resource_name_): vcd_catalog_media.my-media // Example import path (_the_id_string_): org.catalog.my-media-name -func resourceVcdCatalogMediaImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { +func resourceVcdCatalogMediaImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { resourceURI := strings.Split(d.Id(), ImportSeparator) if len(resourceURI) != 3 { return nil, fmt.Errorf("resource name must be specified as org.catalog.my-media-name") diff --git a/vcd/resource_vcd_global_role.go b/vcd/resource_vcd_global_role.go index 0285ad95e..4fba39326 100644 --- a/vcd/resource_vcd_global_role.go +++ b/vcd/resource_vcd_global_role.go @@ -71,7 +71,7 @@ func resourceGlobalRoleCreate(ctx context.Context, d *schema.ResourceData, meta inputRights, err := getRights(vcdClient, nil, "global role create", d) if err != nil { - return diag.Errorf("%s", err) + return diag.FromErr(err) } globalRole, err := vcdClient.Client.CreateGlobalRole(&types.GlobalRole{ Name: globalRoleName, @@ -91,7 +91,7 @@ func resourceGlobalRoleCreate(ctx context.Context, d *schema.ResourceData, meta inputTenants, err := getTenants(vcdClient, "global role create", d) if err != nil { - return diag.Errorf("%s", err) + return diag.FromErr(err) } if publishToAllTenants { err = globalRole.PublishAllTenants() @@ -202,7 +202,7 @@ func resourceGlobalRoleUpdate(ctx context.Context, d *schema.ResourceData, meta if changedRights { inputRights, err = getRights(vcdClient, nil, "global role update", d) if err != nil { - return diag.Errorf("%s", err) + return diag.FromErr(err) } } @@ -238,7 +238,7 @@ func resourceGlobalRoleUpdate(ctx context.Context, d *schema.ResourceData, meta if changedTenants { inputTenants, err = getTenants(vcdClient, "global role create", d) if err != nil { - return diag.Errorf("%s", err) + return diag.FromErr(err) } if publishToAllTenants { err = globalRole.PublishAllTenants() diff --git a/vcd/resource_vcd_rights_bundle.go b/vcd/resource_vcd_rights_bundle.go index 270697ba8..e3619d887 100644 --- a/vcd/resource_vcd_rights_bundle.go +++ b/vcd/resource_vcd_rights_bundle.go @@ -69,7 +69,7 @@ func resourceRightsBundleCreate(ctx context.Context, d *schema.ResourceData, met inputRights, err := getRights(vcdClient, nil, "rights bundle create", d) if err != nil { - return diag.Errorf("%s", err) + return diag.FromErr(err) } rightsBundle, err := vcdClient.Client.CreateRightsBundle(&types.RightsBundle{ Name: rightsBundleName, @@ -89,7 +89,7 @@ func resourceRightsBundleCreate(ctx context.Context, d *schema.ResourceData, met inputTenants, err := getTenants(vcdClient, "rights bundle create", d) if err != nil { - return diag.Errorf("%s", err) + return diag.FromErr(err) } if publishToAllTenants { err = rightsBundle.PublishAllTenants() @@ -198,7 +198,7 @@ func resourceRightsBundleUpdate(ctx context.Context, d *schema.ResourceData, met if changedRights { inputRights, err = getRights(vcdClient, nil, "rights bundle update", d) if err != nil { - return diag.Errorf("%s", err) + return diag.FromErr(err) } } @@ -234,7 +234,7 @@ func resourceRightsBundleUpdate(ctx context.Context, d *schema.ResourceData, met if changedTenants { inputTenants, err = getTenants(vcdClient, "rights bundle create", d) if err != nil { - return diag.Errorf("%s", err) + return diag.FromErr(err) } if publishToAllTenants { err = rightsBundle.PublishAllTenants() diff --git a/vcd/resource_vcd_role.go b/vcd/resource_vcd_role.go index 1d1fcb7a3..9b51b931d 100644 --- a/vcd/resource_vcd_role.go +++ b/vcd/resource_vcd_role.go @@ -72,7 +72,7 @@ func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interf // Check rights early, so that we can show a friendly error message when there are missing implied rights inputRights, err := getRights(vcdClient, org, "role create", d) if err != nil { - return diag.Errorf("%s", err) + return diag.FromErr(err) } role, err := org.CreateRole(&types.Role{ @@ -123,7 +123,7 @@ func genericRoleRead(ctx context.Context, d *schema.ResourceData, meta interface d.SetId("") return nil } - return diag.Errorf("%s", err) + return diag.FromErr(err) } d.SetId(role.Role.ID) @@ -177,7 +177,7 @@ func resourceRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interf inputRights, err := getRights(vcdClient, org, "role update", d) if err != nil { - return diag.Errorf("%s", err) + return diag.FromErr(err) } if len(inputRights) > 0 { diff --git a/vcd/sample_vcd_test_config.json b/vcd/sample_vcd_test_config.json index 10ed12f13..d239ac984 100644 --- a/vcd/sample_vcd_test_config.json +++ b/vcd/sample_vcd_test_config.json @@ -118,6 +118,7 @@ }, "ova": { "ovaPath": "../test-resources/test_vapp_template.ova", + "ovfUrl": "https://raw.githubusercontent.com/vmware/go-vcloud-director/master/test-resources/test_vapp_template_ovf/descriptor.ovf", "//": "Size in megabytes", "uploadPieceSize": 5, "uploadProgress": true, diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 22706ffe6..b1ad818e8 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -6,7 +6,7 @@ description: |- The VMware Cloud Director provider is used to interact with the resources supported by VMware Cloud Director. The provider needs to be configured with the proper credentials before it can be used. --- -# VMware Cloud Director Provider 3.5 +# VMware Cloud Director Provider 3.6 The VMware Cloud Director provider is used to interact with the resources supported by VMware Cloud Director. The provider needs to be configured with the proper credentials before it can be used. diff --git a/website/docs/r/catalog_item.html.markdown b/website/docs/r/catalog_item.html.markdown index f31c67f7a..90a26e6d6 100644 --- a/website/docs/r/catalog_item.html.markdown +++ b/website/docs/r/catalog_item.html.markdown @@ -39,10 +39,11 @@ The following arguments are supported: * `org` - (Optional) The name of organization to use, optional if defined at provider level. Useful when connected as sysadmin working across different organisations * `catalog` - (Required) The name of the catalog where to upload OVA file * `name` - (Required) Item name in catalog -* `description` - (Optional) - Description of item -* `ova_path` - (Required) - Absolute or relative path to file to upload +* `description` - (Optional) Description of item +* `ova_path` - (Optional) Absolute or relative path to file to upload +* `ovf_url` - (Optional; *v3.6+*) URL to OVF file. Only OVF (not OVA) files are supported by VCD uploading by URL * `upload_piece_size` - (Optional) - Size in MB for splitting upload size. It can possibly impact upload performance. Default 1MB. -* `show_upload_progress` - (Optional) - Default false. Allows to see upload progress. (See note below) +* `show_upload_progress` - (Optional) - Default false. Allows seeing upload progress. (See note below) * `metadata` - (Optional; *v2.5+*) Key value map of metadata to assign ### A note about upload progress