From cec895d9f7c1573c5d1288136a593d8dc36367bb Mon Sep 17 00:00:00 2001 From: Van Thong Nguyen Date: Tue, 8 Feb 2022 13:28:06 +0100 Subject: [PATCH 1/9] add location resource --- gridscale/provider.go | 1 + gridscale/resource_gridscale_location.go | 316 +++++++++++++++++++++++ 2 files changed, 317 insertions(+) create mode 100644 gridscale/resource_gridscale_location.go diff --git a/gridscale/provider.go b/gridscale/provider.go index e47646070..d41ee7c9b 100644 --- a/gridscale/provider.go +++ b/gridscale/provider.go @@ -106,6 +106,7 @@ func Provider() *schema.Provider { "gridscale_marketplace_application": resourceGridscaleMarketplaceApplication(), "gridscale_marketplace_application_import": resourceGridscaleImportedMarketplaceApplication(), "gridscale_ssl_certificate": resourceGridscaleSSLCert(), + "gridscale_location": resourceGridscaleLocation(), }, ConfigureFunc: providerConfigure, diff --git a/gridscale/resource_gridscale_location.go b/gridscale/resource_gridscale_location.go new file mode 100644 index 000000000..d0bf64afa --- /dev/null +++ b/gridscale/resource_gridscale_location.go @@ -0,0 +1,316 @@ +package gridscale + +import ( + "context" + "fmt" + "log" + "net/http" + "time" + + "github.com/gridscale/gsclient-go/v3" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + errHandler "github.com/terraform-providers/terraform-provider-gridscale/gridscale/error-handler" +) + +func resourceGridscaleLocation() *schema.Resource { + return &schema.Resource{ + Create: resourceGridscaleLocationCreate, + Read: resourceGridscaleLocationRead, + Delete: resourceGridscaleLocationDelete, + Update: resourceGridscaleLocationUpdate, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "The human-readable name of the object. It supports the full UTF-8 character set, with a maximum of 64 characters.", + Required: true, + }, + "labels": { + Type: schema.TypeSet, + Description: "List of labels.", + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "parent_location_uuid": { + Type: schema.TypeString, + Description: "The location_uuid of an existing public location in which to create the private location.", + Required: true, + ForceNew: true, + }, + "cpunode_count": { + Type: schema.TypeInt, + Description: "The number of dedicated cpunodes to assigne to the private location.", + Required: true, + }, + "product_no": { + Type: schema.TypeInt, + Description: "The product number of a valid and available dedicated cpunode article.", + Required: true, + ForceNew: true, + }, + "iata": { + Type: schema.TypeString, + Description: "IATA airport code, which works as a location identifier.", + Computed: true, + }, + "country": { + Type: schema.TypeString, + Description: "The human-readable name of the location. It supports the full UTF-8 character set, with a maximum of 64 characters.", + Computed: true, + }, + "active": { + Type: schema.TypeBool, + Description: "True if the location is active.", + Computed: true, + }, + "cpunode_count_change_requested": { + Type: schema.TypeInt, + Description: "The requested number of dedicated cpunodes.", + Computed: true, + }, + "product_no_change_requested": { + Type: schema.TypeInt, + Description: "The product number of a valid and available dedicated cpunode article.", + Computed: true, + }, + "parent_location_uuid_change_requested": { + Type: schema.TypeInt, + Description: "The location_uuid of an existing public location in which to create the private location.", + Computed: true, + }, + "public": { + Type: schema.TypeBool, + Description: "True if this location is publicly available or a private location.", + Computed: true, + }, + "certification_list": { + Type: schema.TypeString, + Description: "List of certifications.", + Computed: true, + }, + "city": { + Type: schema.TypeString, + Description: "The human-readable name of the location. It supports the full UTF-8 character set, with a maximum of 64 characters.", + Computed: true, + }, + "data_protection_agreement": { + Type: schema.TypeString, + Description: "Data protection agreement.", + Computed: true, + }, + "geo_location": { + Type: schema.TypeString, + Description: "Geo location.", + Computed: true, + }, + "green_energy": { + Type: schema.TypeString, + Description: "Green energy.", + Computed: true, + }, + "operator_certification_list": { + Type: schema.TypeString, + Description: "List of operator certifications.", + Computed: true, + }, + "owner": { + Type: schema.TypeString, + Description: "The human-readable name of the owner.", + Computed: true, + }, + "owner_website": { + Type: schema.TypeString, + Description: "The website of the owner.", + Computed: true, + }, + "site_name": { + Type: schema.TypeString, + Description: "The human-readable name of the website.", + Computed: true, + }, + "hardware_profiles": { + Type: schema.TypeString, + Description: "List of supported hardware profiles.", + Computed: true, + }, + "has_rocket_storage": { + Type: schema.TypeString, + Description: "TRUE if the location supports rocket storage.", + Computed: true, + }, + "has_server_provisioning": { + Type: schema.TypeString, + Description: "TRUE if the location supports server provisioning.", + Computed: true, + }, + "object_storage_region": { + Type: schema.TypeString, + Description: "The region of the object storage.", + Computed: true, + }, + "backup_center_location_uuid": { + Type: schema.TypeString, + Description: "The location_uuid of a backup location.", + Computed: true, + }, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(5 * time.Minute), + }, + } +} + +func resourceGridscaleLocationRead(d *schema.ResourceData, meta interface{}) error { + errorPrefix := fmt.Sprintf("read location (%s) resource -", d.Id()) + client := meta.(*gsclient.Client) + loc, err := client.GetLocation(context.Background(), d.Id()) + if err != nil { + if requestError, ok := err.(gsclient.RequestError); ok { + if requestError.StatusCode == 404 { + d.SetId("") + return nil + } + } + return fmt.Errorf("%s error: %v", errorPrefix, err) + } + locProp := loc.Properties + if err = d.Set("name", locProp.Name); err != nil { + return fmt.Errorf("%s error setting name: %v", errorPrefix, err) + } + if err = d.Set("iata", locProp.Iata); err != nil { + return fmt.Errorf("%s error setting iata: %v", errorPrefix, err) + } + if err = d.Set("country", locProp.Country); err != nil { + return fmt.Errorf("%s error setting country: %v", errorPrefix, err) + } + if err = d.Set("active", locProp.Active); err != nil { + return fmt.Errorf("%s error setting active: %v", errorPrefix, err) + } + if err = d.Set("cpunode_count_change_requested", locProp.ChangeRequested.CPUNodeCount); err != nil { + return fmt.Errorf("%s error setting cpunode_count_change_requested: %v", errorPrefix, err) + } + if err = d.Set("product_no_change_requested", locProp.ChangeRequested.ProductNo); err != nil { + return fmt.Errorf("%s error setting product_no_change_requested: %v", errorPrefix, err) + } + if err = d.Set("parent_location_uuid_change_requested", locProp.ChangeRequested.ParentLocationUUID); err != nil { + return fmt.Errorf("%s error setting parent_location_uuid_change_requested: %v", errorPrefix, err) + } + if err = d.Set("cpunode_count", locProp.CPUNodeCount); err != nil { + return fmt.Errorf("%s error setting cpunode_count: %v", errorPrefix, err) + } + if err = d.Set("public", locProp.Public); err != nil { + return fmt.Errorf("%s error setting public: %v", errorPrefix, err) + } + if err = d.Set("product_no", locProp.ProductNo); err != nil { + return fmt.Errorf("%s error setting product_no: %v", errorPrefix, err) + } + if err = d.Set("certification_list", locProp.LocationInformation.CertificationList); err != nil { + return fmt.Errorf("%s error setting certification_list: %v", errorPrefix, err) + } + if err = d.Set("city", locProp.LocationInformation.City); err != nil { + return fmt.Errorf("%s error setting city: %v", errorPrefix, err) + } + if err = d.Set("data_protection_agreement", locProp.LocationInformation.DataProtectionAgreement); err != nil { + return fmt.Errorf("%s error setting data_protection_agreement: %v", errorPrefix, err) + } + if err = d.Set("geo_location", locProp.LocationInformation.GeoLocation); err != nil { + return fmt.Errorf("%s error setting geo_location: %v", errorPrefix, err) + } + if err = d.Set("green_energy", locProp.LocationInformation.GreenEnergy); err != nil { + return fmt.Errorf("%s error setting green_energy: %v", errorPrefix, err) + } + if err = d.Set("operator_certification_list", locProp.LocationInformation.OperatorCertificationList); err != nil { + return fmt.Errorf("%s error setting operator_certification_list: %v", errorPrefix, err) + } + if err = d.Set("owner", locProp.LocationInformation.Owner); err != nil { + return fmt.Errorf("%s error setting owner: %v", errorPrefix, err) + } + if err = d.Set("owner_website", locProp.LocationInformation.OwnerWebsite); err != nil { + return fmt.Errorf("%s error setting owner_website: %v", errorPrefix, err) + } + if err = d.Set("site_name", locProp.LocationInformation.SiteName); err != nil { + return fmt.Errorf("%s error setting site_name: %v", errorPrefix, err) + } + if err = d.Set("hardware_profiles", locProp.Features.HardwareProfiles); err != nil { + return fmt.Errorf("%s error setting hardware_profiles: %v", errorPrefix, err) + } + if err = d.Set("has_rocket_storage", locProp.Features.HasRocketStorage); err != nil { + return fmt.Errorf("%s error setting has_rocket_storage: %v", errorPrefix, err) + } + if err = d.Set("has_server_provisioning", locProp.Features.HasServerProvisioning); err != nil { + return fmt.Errorf("%s error setting has_server_provisioning: %v", errorPrefix, err) + } + if err = d.Set("object_storage_region", locProp.Features.ObjectStorageRegion); err != nil { + return fmt.Errorf("%s error setting object_storage_region: %v", errorPrefix, err) + } + if err = d.Set("backup_center_location_uuid", locProp.Features.BackupCenterLocationUUID); err != nil { + return fmt.Errorf("%s error setting backup_center_location_uuid: %v", errorPrefix, err) + } + if err = d.Set("labels", locProp.Labels); err != nil { + return fmt.Errorf("%s error setting labels: %v", errorPrefix, err) + } + return nil +} + +func resourceGridscaleLocationCreate(d *schema.ResourceData, meta interface{}) error { + errorPrefix := "create location resource -" + client := meta.(*gsclient.Client) + requestBody := gsclient.LocationCreateRequest{ + Name: d.Get("name").(string), + Labels: convSOStrings(d.Get("labels").(*schema.Set).List()), + ParentLocationUUID: d.Get("parent_location_uuid").(string), + CPUNodeCount: d.Get("cpunode_count").(int), + ProductNo: d.Get("product_no").(int), + } + + ctx, cancel := context.WithTimeout(context.Background(), d.Timeout(schema.TimeoutCreate)) + defer cancel() + response, err := client.CreateLocation(ctx, requestBody) + if err != nil { + return fmt.Errorf("%s error: %v", errorPrefix, err) + } + d.SetId(response.ObjectUUID) + log.Printf("The id for the new location has been set to %v", response.ObjectUUID) + return resourceGridscaleLocationRead(d, meta) +} + +func resourceGridscaleLocationUpdate(d *schema.ResourceData, meta interface{}) error { + errorPrefix := fmt.Sprintf("update location (%s) resource -", d.Id()) + client := meta.(*gsclient.Client) + labels := convSOStrings(d.Get("labels").(*schema.Set).List()) + cpunodeCount := d.Get("cpunode_count").(int) + requestBody := gsclient.LocationUpdateRequest{ + Name: d.Get("name").(string), + Labels: &labels, + CPUNodeCount: &cpunodeCount, + } + + ctx, cancel := context.WithTimeout(context.Background(), d.Timeout(schema.TimeoutUpdate)) + defer cancel() + err := client.UpdateLocation(ctx, d.Id(), requestBody) + if err != nil { + return fmt.Errorf("%s error: %v", errorPrefix, err) + } + return resourceGridscaleLocationRead(d, meta) +} + +func resourceGridscaleLocationDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*gsclient.Client) + errorPrefix := fmt.Sprintf("delete location (%s) resource -", d.Id()) + + ctx, cancel := context.WithTimeout(context.Background(), d.Timeout(schema.TimeoutDelete)) + defer cancel() + err := errHandler.RemoveErrorContainsHTTPCodes( + client.DeleteLocation(ctx, d.Id()), + http.StatusNotFound, + ) + if err != nil { + return fmt.Errorf("%s error: %v", errorPrefix, err) + } + return nil +} From 8a484f9d245290f99601253bb058a2d4852c0df8 Mon Sep 17 00:00:00 2001 From: Van Thong Nguyen Date: Tue, 8 Feb 2022 14:25:07 +0100 Subject: [PATCH 2/9] fix wrong type of parent_location_uuid --- gridscale/resource_gridscale_location.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/gridscale/resource_gridscale_location.go b/gridscale/resource_gridscale_location.go index d0bf64afa..7f8dcf27a 100644 --- a/gridscale/resource_gridscale_location.go +++ b/gridscale/resource_gridscale_location.go @@ -77,7 +77,7 @@ func resourceGridscaleLocation() *schema.Resource { Computed: true, }, "parent_location_uuid_change_requested": { - Type: schema.TypeInt, + Type: schema.TypeString, Description: "The location_uuid of an existing public location in which to create the private location.", Computed: true, }, @@ -282,6 +282,16 @@ func resourceGridscaleLocationCreate(d *schema.ResourceData, meta interface{}) e func resourceGridscaleLocationUpdate(d *schema.ResourceData, meta interface{}) error { errorPrefix := fmt.Sprintf("update location (%s) resource -", d.Id()) client := meta.(*gsclient.Client) + + // Check if the location is active (approved) before requesting any updates. + loc, err := client.GetLocation(context.Background(), d.Id()) + if err != nil { + return fmt.Errorf("%s error: %v", errorPrefix, err) + } + if !loc.Properties.Active { + return fmt.Errorf("%s error: %v", errorPrefix, "The location is inactive (not approved). Please wait for the location to be active.") + } + labels := convSOStrings(d.Get("labels").(*schema.Set).List()) cpunodeCount := d.Get("cpunode_count").(int) requestBody := gsclient.LocationUpdateRequest{ @@ -292,7 +302,7 @@ func resourceGridscaleLocationUpdate(d *schema.ResourceData, meta interface{}) e ctx, cancel := context.WithTimeout(context.Background(), d.Timeout(schema.TimeoutUpdate)) defer cancel() - err := client.UpdateLocation(ctx, d.Id(), requestBody) + err = client.UpdateLocation(ctx, d.Id(), requestBody) if err != nil { return fmt.Errorf("%s error: %v", errorPrefix, err) } From f85e9b781d307b5ba1f9c137c02951072e12490a Mon Sep 17 00:00:00 2001 From: Van Thong Nguyen Date: Tue, 8 Feb 2022 14:25:18 +0100 Subject: [PATCH 3/9] add acc test of location rs --- gridscale/resource_gridscale_location_test.go | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 gridscale/resource_gridscale_location_test.go diff --git a/gridscale/resource_gridscale_location_test.go b/gridscale/resource_gridscale_location_test.go new file mode 100644 index 000000000..cbd54aaa2 --- /dev/null +++ b/gridscale/resource_gridscale_location_test.go @@ -0,0 +1,87 @@ +package gridscale + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/gridscale/gsclient-go/v3" +) + +func TestAccResourceGridscaleLocation_Basic(t *testing.T) { + var object gsclient.Location + name := fmt.Sprintf("Test-TF-Location-%s", acctest.RandString(10)) + parentLocationUUID := "45ed677b-3702-4b36-be2a-a2eab9827950" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGridscaleLocationDestroyCheck, + Steps: []resource.TestStep{ + { + Config: testAccCheckResourceGridscaleLocationConfig_basic(name, parentLocationUUID), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourceGridscaleLocationExists("gridscale_location.foo", &object), + resource.TestCheckResourceAttr( + "gridscale_location.foo", "name", name), + resource.TestCheckResourceAttr( + "gridscale_location.foo", "parent_location_uuid", parentLocationUUID), + resource.TestCheckResourceAttr( + "gridscale_location.foo", "cpunode_count", "10"), + resource.TestCheckResourceAttr( + "gridscale_location.foo", "product_no", "1500001"), + ), + }, + }, + }) +} + +func testAccCheckResourceGridscaleLocationExists(n string, object *gsclient.Location) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No object UUID is set") + } + + client := testAccProvider.Meta().(*gsclient.Client) + + id := rs.Primary.ID + + foundObject, err := client.GetLocation(context.Background(), id) + + if err != nil { + return err + } + + if foundObject.Properties.ObjectUUID != id { + return fmt.Errorf("Object not found") + } + + *object = foundObject + + return nil + } +} + +func testAccCheckGridscaleLocationDestroyCheck(s *terraform.State) error { + return nil +} + +func testAccCheckResourceGridscaleLocationConfig_basic(name, parentLocationUUID string) string { + return fmt.Sprintf(` +resource "gridscale_location" "foo" { + name = "%s" + parent_location_uuid = "%s" + product_no = 1500001 + cpunode_count = 10 +} +`, name, parentLocationUUID) +} From c8cbb0cd83e8fba7ab175ffe015455d2c9e665dd Mon Sep 17 00:00:00 2001 From: Van Thong Nguyen Date: Tue, 8 Feb 2022 15:02:44 +0100 Subject: [PATCH 4/9] add location rs docs --- website/docs/r/location.html.md | 79 +++++++++++++++++++++++++++++++++ website/gridscale.erb | 3 ++ 2 files changed, 82 insertions(+) create mode 100644 website/docs/r/location.html.md diff --git a/website/docs/r/location.html.md b/website/docs/r/location.html.md new file mode 100644 index 000000000..ab15c9f8f --- /dev/null +++ b/website/docs/r/location.html.md @@ -0,0 +1,79 @@ +--- +layout: "gridscale" +page_title: "gridscale: location" +sidebar_current: "docs-gridscale-resource-location" +description: |- + Manages a location in gridscale. +--- + +# gridscale_location + +Provides a location resource. This can be used to create, modify, and delete location. + +## Example Usage + +The following example shows how one might use this resource to add a location to gridscale: + +```terraform +resource "gridscale_location" "foo" { + name = "my-location" + parent_location_uuid = "%s" + product_no = 1500001 + cpunode_count = 20 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The exact name of the location as show in [the expert panel of gridscale](https://my.gridscale.io/Expert/Template). + +* `parent_location_uuid` - (Required, ForceNew) The location_uuid of an existing public location in which to create the private location. + +* `cpunode_count` - (Required) The number of dedicated cpunodes to assigne to the private location. + +* `product_no` - (Required, ForceNew) The product number of a valid and available dedicated cpunode article. + +* `labels` - (Optional) List of labels. + +## Timeouts + +Timeouts configuration options (in seconds): +More info: [terraform.io/docs/configuration/resources.html#operation-timeouts](https://www.terraform.io/docs/configuration/resources.html#operation-timeouts) + +* `create` - (Default value is "5m" - 5 minutes) Used for creating a resource. +* `update` - (Default value is "5m" - 5 minutes) Used for updating a resource. +* `delete` - (Default value is "5m" - 5 minutes) Used for deleting a resource. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The UUID of the location. +* `name` - The name of the location. +* `parent_location_uuid` - See Argument Reference above. +* `cpunode_count` - See Argument Reference above. +* `product_no` - See Argument Reference above. +* `iata` - Uses IATA airport code, which works as a location identifier. +* `country` - The human-readable name of the location. It supports the full UTF-8 character set, with a maximum of 64 characters. +* `labels` - List of labels. +* `active` - True if the location is active. +* `cpunode_count_change_requested` - The requested number of dedicated cpunodes. +* `product_no_change_requested` - The product number of a valid and available dedicated cpunode article. +* `parent_location_uuid_change_requested` - The location_uuid of an existing public location in which to create the private location. +* `public` - True if this location is publicly available or a private location. +* `certification_list` - List of certifications. +* `city` - The human-readable name of the location. It supports the full UTF-8 character set, with a maximum of 64 characters. +* `data_protection_agreement` - Data protection agreement. +* `geo_location` - Geo location. +* `green_energy` - Green energy. +* `operator_certification_list` - List of operator certifications. +* `owner` - The human-readable name of the owner. +* `owner_website` - The website of the owner. +* `site_name` - The human-readable name of the website. +* `hardware_profiles` - List of supported hardware profiles. +* `has_rocket_storage` - TRUE if the location supports rocket storage. +* `has_server_provisioning` - TRUE if the location supports server provisioning. +* `object_storage_region` - The region of the object storage. +* `backup_center_location_uuid` - The location_uuid of a backup location. diff --git a/website/gridscale.erb b/website/gridscale.erb index a257b74b5..575d4ac71 100644 --- a/website/gridscale.erb +++ b/website/gridscale.erb @@ -161,6 +161,9 @@ > gridscale_ssl_certificate + > + gridscale_location + From bc933b1e4fe608237f39643a12a96b2643fbcccb Mon Sep 17 00:00:00 2001 From: Van Thong Nguyen Date: Tue, 8 Feb 2022 15:20:33 +0100 Subject: [PATCH 5/9] add location datasource --- gridscale/datasource_gridscale_location.go | 253 +++++++++++++++++++++ gridscale/provider.go | 1 + 2 files changed, 254 insertions(+) create mode 100644 gridscale/datasource_gridscale_location.go diff --git a/gridscale/datasource_gridscale_location.go b/gridscale/datasource_gridscale_location.go new file mode 100644 index 000000000..4ccfc7750 --- /dev/null +++ b/gridscale/datasource_gridscale_location.go @@ -0,0 +1,253 @@ +package gridscale + +import ( + "context" + "fmt" + "time" + + "github.com/gridscale/gsclient-go/v3" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func dataSourceGridscaleLocation() *schema.Resource { + return &schema.Resource{ + Read: dataSourceGridscaleLocationRead, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "resource_id": { + Type: schema.TypeString, + Required: true, + Description: "ID of a resource", + ValidateFunc: validation.NoZeroValues, + }, + "name": { + Type: schema.TypeString, + Description: "The human-readable name of the object. It supports the full UTF-8 character set, with a maximum of 64 characters.", + Computed: true, + }, + "labels": { + Type: schema.TypeSet, + Description: "List of labels.", + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "parent_location_uuid": { + Type: schema.TypeString, + Description: "The location_uuid of an existing public location in which to create the private location.", + Computed: true, + }, + "cpunode_count": { + Type: schema.TypeInt, + Description: "The number of dedicated cpunodes to assigne to the private location.", + Computed: true, + }, + "product_no": { + Type: schema.TypeInt, + Description: "The product number of a valid and available dedicated cpunode article.", + Computed: true, + }, + "iata": { + Type: schema.TypeString, + Description: "IATA airport code, which works as a location identifier.", + Computed: true, + }, + "country": { + Type: schema.TypeString, + Description: "The human-readable name of the location. It supports the full UTF-8 character set, with a maximum of 64 characters.", + Computed: true, + }, + "active": { + Type: schema.TypeBool, + Description: "True if the location is active.", + Computed: true, + }, + "cpunode_count_change_requested": { + Type: schema.TypeInt, + Description: "The requested number of dedicated cpunodes.", + Computed: true, + }, + "product_no_change_requested": { + Type: schema.TypeInt, + Description: "The product number of a valid and available dedicated cpunode article.", + Computed: true, + }, + "parent_location_uuid_change_requested": { + Type: schema.TypeString, + Description: "The location_uuid of an existing public location in which to create the private location.", + Computed: true, + }, + "public": { + Type: schema.TypeBool, + Description: "True if this location is publicly available or a private location.", + Computed: true, + }, + "certification_list": { + Type: schema.TypeString, + Description: "List of certifications.", + Computed: true, + }, + "city": { + Type: schema.TypeString, + Description: "The human-readable name of the location. It supports the full UTF-8 character set, with a maximum of 64 characters.", + Computed: true, + }, + "data_protection_agreement": { + Type: schema.TypeString, + Description: "Data protection agreement.", + Computed: true, + }, + "geo_location": { + Type: schema.TypeString, + Description: "Geo location.", + Computed: true, + }, + "green_energy": { + Type: schema.TypeString, + Description: "Green energy.", + Computed: true, + }, + "operator_certification_list": { + Type: schema.TypeString, + Description: "List of operator certifications.", + Computed: true, + }, + "owner": { + Type: schema.TypeString, + Description: "The human-readable name of the owner.", + Computed: true, + }, + "owner_website": { + Type: schema.TypeString, + Description: "The website of the owner.", + Computed: true, + }, + "site_name": { + Type: schema.TypeString, + Description: "The human-readable name of the website.", + Computed: true, + }, + "hardware_profiles": { + Type: schema.TypeString, + Description: "List of supported hardware profiles.", + Computed: true, + }, + "has_rocket_storage": { + Type: schema.TypeString, + Description: "TRUE if the location supports rocket storage.", + Computed: true, + }, + "has_server_provisioning": { + Type: schema.TypeString, + Description: "TRUE if the location supports server provisioning.", + Computed: true, + }, + "object_storage_region": { + Type: schema.TypeString, + Description: "The region of the object storage.", + Computed: true, + }, + "backup_center_location_uuid": { + Type: schema.TypeString, + Description: "The location_uuid of a backup location.", + Computed: true, + }, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(5 * time.Minute), + }, + } +} + +func dataSourceGridscaleLocationRead(d *schema.ResourceData, meta interface{}) error { + id := d.Get("resource_id").(string) + errorPrefix := fmt.Sprintf("read location (%s) dataSource -", id) + client := meta.(*gsclient.Client) + loc, err := client.GetLocation(context.Background(), id) + if err != nil { + return fmt.Errorf("%s error: %v", errorPrefix, err) + } + locProp := loc.Properties + d.SetId(locProp.ObjectUUID) + if err = d.Set("name", locProp.Name); err != nil { + return fmt.Errorf("%s error setting name: %v", errorPrefix, err) + } + if err = d.Set("iata", locProp.Iata); err != nil { + return fmt.Errorf("%s error setting iata: %v", errorPrefix, err) + } + if err = d.Set("country", locProp.Country); err != nil { + return fmt.Errorf("%s error setting country: %v", errorPrefix, err) + } + if err = d.Set("active", locProp.Active); err != nil { + return fmt.Errorf("%s error setting active: %v", errorPrefix, err) + } + if err = d.Set("cpunode_count_change_requested", locProp.ChangeRequested.CPUNodeCount); err != nil { + return fmt.Errorf("%s error setting cpunode_count_change_requested: %v", errorPrefix, err) + } + if err = d.Set("product_no_change_requested", locProp.ChangeRequested.ProductNo); err != nil { + return fmt.Errorf("%s error setting product_no_change_requested: %v", errorPrefix, err) + } + if err = d.Set("parent_location_uuid_change_requested", locProp.ChangeRequested.ParentLocationUUID); err != nil { + return fmt.Errorf("%s error setting parent_location_uuid_change_requested: %v", errorPrefix, err) + } + if err = d.Set("cpunode_count", locProp.CPUNodeCount); err != nil { + return fmt.Errorf("%s error setting cpunode_count: %v", errorPrefix, err) + } + if err = d.Set("public", locProp.Public); err != nil { + return fmt.Errorf("%s error setting public: %v", errorPrefix, err) + } + if err = d.Set("product_no", locProp.ProductNo); err != nil { + return fmt.Errorf("%s error setting product_no: %v", errorPrefix, err) + } + if err = d.Set("certification_list", locProp.LocationInformation.CertificationList); err != nil { + return fmt.Errorf("%s error setting certification_list: %v", errorPrefix, err) + } + if err = d.Set("city", locProp.LocationInformation.City); err != nil { + return fmt.Errorf("%s error setting city: %v", errorPrefix, err) + } + if err = d.Set("data_protection_agreement", locProp.LocationInformation.DataProtectionAgreement); err != nil { + return fmt.Errorf("%s error setting data_protection_agreement: %v", errorPrefix, err) + } + if err = d.Set("geo_location", locProp.LocationInformation.GeoLocation); err != nil { + return fmt.Errorf("%s error setting geo_location: %v", errorPrefix, err) + } + if err = d.Set("green_energy", locProp.LocationInformation.GreenEnergy); err != nil { + return fmt.Errorf("%s error setting green_energy: %v", errorPrefix, err) + } + if err = d.Set("operator_certification_list", locProp.LocationInformation.OperatorCertificationList); err != nil { + return fmt.Errorf("%s error setting operator_certification_list: %v", errorPrefix, err) + } + if err = d.Set("owner", locProp.LocationInformation.Owner); err != nil { + return fmt.Errorf("%s error setting owner: %v", errorPrefix, err) + } + if err = d.Set("owner_website", locProp.LocationInformation.OwnerWebsite); err != nil { + return fmt.Errorf("%s error setting owner_website: %v", errorPrefix, err) + } + if err = d.Set("site_name", locProp.LocationInformation.SiteName); err != nil { + return fmt.Errorf("%s error setting site_name: %v", errorPrefix, err) + } + if err = d.Set("hardware_profiles", locProp.Features.HardwareProfiles); err != nil { + return fmt.Errorf("%s error setting hardware_profiles: %v", errorPrefix, err) + } + if err = d.Set("has_rocket_storage", locProp.Features.HasRocketStorage); err != nil { + return fmt.Errorf("%s error setting has_rocket_storage: %v", errorPrefix, err) + } + if err = d.Set("has_server_provisioning", locProp.Features.HasServerProvisioning); err != nil { + return fmt.Errorf("%s error setting has_server_provisioning: %v", errorPrefix, err) + } + if err = d.Set("object_storage_region", locProp.Features.ObjectStorageRegion); err != nil { + return fmt.Errorf("%s error setting object_storage_region: %v", errorPrefix, err) + } + if err = d.Set("backup_center_location_uuid", locProp.Features.BackupCenterLocationUUID); err != nil { + return fmt.Errorf("%s error setting backup_center_location_uuid: %v", errorPrefix, err) + } + if err = d.Set("labels", locProp.Labels); err != nil { + return fmt.Errorf("%s error setting labels: %v", errorPrefix, err) + } + return nil +} diff --git a/gridscale/provider.go b/gridscale/provider.go index d41ee7c9b..d17ee52ca 100644 --- a/gridscale/provider.go +++ b/gridscale/provider.go @@ -74,6 +74,7 @@ func Provider() *schema.Provider { "gridscale_firewall": dataSourceGridscaleFirewall(), "gridscale_marketplace_application": dataSourceGridscaleMarketplaceApplication(), "gridscale_ssl_certificate": dataSourceGridscaleSSLCert(), + "gridscale_location": dataSourceGridscaleLocation(), }, ResourcesMap: map[string]*schema.Resource{ "gridscale_server": resourceGridscaleServer(), From a193703e15cba4c14cd03d003d90c89484eabc08 Mon Sep 17 00:00:00 2001 From: Van Thong Nguyen Date: Tue, 8 Feb 2022 15:20:46 +0100 Subject: [PATCH 6/9] add location ds acctest --- .../datasource_gridscale_location_test.go | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 gridscale/datasource_gridscale_location_test.go diff --git a/gridscale/datasource_gridscale_location_test.go b/gridscale/datasource_gridscale_location_test.go new file mode 100644 index 000000000..03c04fddd --- /dev/null +++ b/gridscale/datasource_gridscale_location_test.go @@ -0,0 +1,36 @@ +package gridscale + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccdataSourceGridscaleLocation_basic(t *testing.T) { + locUUID := "45ed677b-3702-4b36-be2a-a2eab9827950" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + + Config: testAccCheckDataSourceLocationConfig_basic(locUUID), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.gridscale_location.foo", "id", locUUID), + resource.TestCheckResourceAttrSet("data.gridscale_location.foo", "name"), + resource.TestCheckResourceAttrSet("data.gridscale_location.foo", "cpunode_count"), + ), + }, + }, + }) + +} + +func testAccCheckDataSourceLocationConfig_basic(locUUID string) string { + return fmt.Sprintf(` +data "gridscale_location" "foo" { + resource_id = "%s" +}`, locUUID) +} From 67ac8bda5aab7a442e1af7fd36864ae34c51998f Mon Sep 17 00:00:00 2001 From: Van Thong Nguyen Date: Tue, 8 Feb 2022 15:26:22 +0100 Subject: [PATCH 7/9] remove dummy code (for datasource) --- gridscale/datasource_gridscale_location.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/gridscale/datasource_gridscale_location.go b/gridscale/datasource_gridscale_location.go index 4ccfc7750..796f0da9e 100644 --- a/gridscale/datasource_gridscale_location.go +++ b/gridscale/datasource_gridscale_location.go @@ -3,7 +3,6 @@ package gridscale import ( "context" "fmt" - "time" "github.com/gridscale/gsclient-go/v3" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -13,9 +12,6 @@ import ( func dataSourceGridscaleLocation() *schema.Resource { return &schema.Resource{ Read: dataSourceGridscaleLocationRead, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, Schema: map[string]*schema.Schema{ "resource_id": { @@ -156,11 +152,6 @@ func dataSourceGridscaleLocation() *schema.Resource { Computed: true, }, }, - Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(5 * time.Minute), - Update: schema.DefaultTimeout(5 * time.Minute), - Delete: schema.DefaultTimeout(5 * time.Minute), - }, } } From 20e1493367926ddba2a09c55fb39c876048511fd Mon Sep 17 00:00:00 2001 From: Van Thong Nguyen Date: Tue, 8 Feb 2022 15:26:36 +0100 Subject: [PATCH 8/9] add location ds docs --- website/docs/d/location.html.md | 58 +++++++++++++++++++++++++++++++++ website/gridscale.erb | 3 ++ 2 files changed, 61 insertions(+) create mode 100644 website/docs/d/location.html.md diff --git a/website/docs/d/location.html.md b/website/docs/d/location.html.md new file mode 100644 index 000000000..f63805345 --- /dev/null +++ b/website/docs/d/location.html.md @@ -0,0 +1,58 @@ +--- +layout: "gridscale" +page_title: "gridscale: location" +sidebar_current: "docs-gridscale-datasource-location" +description: |- + Get the data of a location in gridscale. +--- + +# gridscale_location + + Get the data of a location in gridscale. + +## Example Usage + +```terraform +data "gridscale_location" "foo" { + resource_id = "45ed677b-3702-4b36-be2a-a2eab9827950" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `resource_id` - (Required) The UUID of the location. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The UUID of the location. +* `name` - The name of the location. +* `parent_location_uuid` - The location_uuid of an existing public location in which to create the private location. +* `cpunode_count` - The number of dedicated cpunodes to assigne to the private location. +* `product_no` - The product number of a valid and available dedicated cpunode article. +* `iata` - Uses IATA airport code, which works as a location identifier. +* `country` - The human-readable name of the location. It supports the full UTF-8 character set, with a maximum of 64 characters. +* `labels` - List of labels. +* `active` - True if the location is active. +* `cpunode_count_change_requested` - The requested number of dedicated cpunodes. +* `product_no_change_requested` - The product number of a valid and available dedicated cpunode article. +* `parent_location_uuid_change_requested` - The location_uuid of an existing public location in which to create the private location. +* `public` - True if this location is publicly available or a private location. +* `certification_list` - List of certifications. +* `city` - The human-readable name of the location. It supports the full UTF-8 character set, with a maximum of 64 characters. +* `data_protection_agreement` - Data protection agreement. +* `geo_location` - Geo location. +* `green_energy` - Green energy. +* `operator_certification_list` - List of operator certifications. +* `owner` - The human-readable name of the owner. +* `owner_website` - The website of the owner. +* `site_name` - The human-readable name of the website. +* `hardware_profiles` - List of supported hardware profiles. +* `has_rocket_storage` - TRUE if the location supports rocket storage. +* `has_server_provisioning` - TRUE if the location supports server provisioning. +* `object_storage_region` - The region of the object storage. +* `backup_center_location_uuid` - The location_uuid of a backup location. diff --git a/website/gridscale.erb b/website/gridscale.erb index 575d4ac71..d2388c502 100644 --- a/website/gridscale.erb +++ b/website/gridscale.erb @@ -65,6 +65,9 @@ > gridscale_ssl_certificate + > + gridscale_location + From a886b6a0effcad543964df2c6d4d883720bf9ef0 Mon Sep 17 00:00:00 2001 From: Van Thong Nguyen Date: Tue, 8 Feb 2022 15:29:52 +0100 Subject: [PATCH 9/9] add location gh actions workflow --- .github/workflows/location.yml | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/location.yml diff --git a/.github/workflows/location.yml b/.github/workflows/location.yml new file mode 100644 index 000000000..905776344 --- /dev/null +++ b/.github/workflows/location.yml @@ -0,0 +1,47 @@ +name: Test location ds/rs + +on: + workflow_dispatch: + push: + branches: + - master + paths: + - "**.go" + - ".github/workflows/location.yml" + pull_request: + branches: + - master + paths: + - "**gridscale_location**" + - "gridscale/location-utils/**" + - "gridscale/error-handler/**" + - "gridscale/common.go" + - "gridscale/config.go" + - "gridscale/provider.go" + - "gridscale/provider_test.go" + - ".github/workflows/location.yml" + +jobs: + build: + name: Location AccTest + runs-on: ubuntu-latest + env: + GOPATH: /home/runner/go + GRIDSCALE_UUID: ${{ secrets.CI_USER_UUID }} + GRIDSCALE_TOKEN: ${{ secrets.CI_API_TOKEN }} + GRIDSCALE_URL: ${{ secrets.CI_API_URL }} + steps: + - name: Set up Go 1.16 + uses: actions/setup-go@v2 + with: + go-version: ^1.16 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Run TestAccdataSourceGridscaleLocation_basic + run: make testacc TEST=./gridscale TESTARGS='-run=TestAccdataSourceGridscaleLocation_basic' + + - name: Run TestAccResourceGridscaleLocation_Basic + run: make testacc TEST=./gridscale TESTARGS='-run=TestAccResourceGridscaleLocation_Basic'