diff --git a/mmv1/products/netapp/VolumeQuotaRule.yaml b/mmv1/products/netapp/VolumeQuotaRule.yaml new file mode 100644 index 000000000000..930ef3c0e7eb --- /dev/null +++ b/mmv1/products/netapp/VolumeQuotaRule.yaml @@ -0,0 +1,125 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +name: 'VolumeQuotaRule' +api_resource_type_kind: QuotaRule +description: | + QuotaRule specifies the maximum capacity a user or group can use within a volume. They can be used for creating default and individual quota rules. +references: + guides: + 'Documentation': https://cloud.google.com/netapp/volumes/docs/configure-and-use/volumes/overview#volume_user_and_group_quotas + api: https://cloud.google.com/netapp/volumes/docs/reference/rest/v1/projects.locations.volumes.quotaRules +docs: +base_url: 'projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/quotaRules' +self_link: 'projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/quotaRules/{{name}}' +create_url: 'projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/quotaRules?quotaRuleId={{name}}' +update_url: 'projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/quotaRules/{{name}}' +update_verb: 'PATCH' +update_mask: true +delete_url: 'projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/quotaRules/{{name}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +autogen_async: true +async: + actions: ['create', 'delete', 'update'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + result: + resource_inside_response: false +custom_code: +# Skipping the sweeper since we need to sweep multiple regions +exclude_sweeper: true +examples: + - name: 'netapp_volume_quota_rule_basic' + primary_resource_id: 'test_quota_rule' + vars: + volume_name: 'test-volume' + pool_name: 'test-pool' + network_name: 'test-network' + quota_rule_name: 'test-volume-quota-rule' + test_vars_overrides: + 'network_name': 'acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog"))' +parameters: + - name: 'location' + type: String + description: | + Loction of the quotaRule. QuotaRules are child resources of volumes and live in the same location. + url_param_only: true + immutable: true + - name: 'volume_name' + type: String + description: | + Name of the volume to create the quotaRule in. + url_param_only: true + required: true + immutable: true + - name: 'name' + type: String + description: + The resource name of the quotaRule. + url_param_only: true + required: true + immutable: true +properties: + - name: 'target' + type: String + description: | + The quota rule applies to the specified user or group. + Valid targets for volumes with NFS protocol enabled: + - UNIX UID for individual user quota + - UNIX GID for individual group quota + Valid targets for volumes with SMB protocol enabled: + - Windows SID for individual user quota + Leave empty for default quotas + - name: 'type' + type: Enum + description: | + Types of Quota Rule. + required: true + enum_values: + - 'INDIVIDUAL_USER_QUOTA' + - 'INDIVIDUAL_GROUP_QUOTA' + - 'DEFAULT_USER_QUOTA' + - 'DEFAULT_GROUP_QUOTA' + - name: 'diskLimitMib' + type: Integer + description: + The maximum allowed capacity in MiB. + required: true + - name: 'state' + type: String + description: | + The state of the quota rule. Possible Values : [STATE_UNSPECIFIED, CREATING, UPDATING, READY, DELETING, ERROR] + output: true + - name: 'stateDetails' + type: String + description: | + State details of the quota rule + output: true + - name: 'createTime' + type: String + description: | + Create time of the quota rule. A timestamp in RFC3339 UTC "Zulu" format. Examples: "2023-06-22T09:13:01.617Z". + output: true + - name: 'description' + type: String + description: | + Description for the quota rule. + - name: 'labels' + type: KeyValueLabels + description: | + Labels as key value pairs of the quota rule. Example: `{ "owner": "Bob", "department": "finance", "purpose": "testing" }`. diff --git a/mmv1/templates/terraform/examples/netapp_volume_quota_rule_basic.tf.tmpl b/mmv1/templates/terraform/examples/netapp_volume_quota_rule_basic.tf.tmpl new file mode 100644 index 000000000000..119b89dcd642 --- /dev/null +++ b/mmv1/templates/terraform/examples/netapp_volume_quota_rule_basic.tf.tmpl @@ -0,0 +1,29 @@ +resource "google_netapp_storage_pool" "default" { + name = "{{index $.Vars "pool_name"}}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "default" { + location = google_netapp_storage_pool.default.location + name = "{{index $.Vars "volume_name"}}" + capacity_gib = 100 + share_name = "{{index $.Vars "volume_name"}}" + storage_pool = google_netapp_storage_pool.default.name + protocols = ["NFSV3"] +} + +resource "google_netapp_volume_quota_rule" "{{$.PrimaryResourceId}}" { + depends_on = [google_netapp_volume.default] + location = google_netapp_volume.default.location + volume_name = google_netapp_volume.default.name + type = "DEFAULT_USER_QUOTA" + disk_limit_mib = 50 + name = "{{index $.Vars "quota_rule_name"}}" +} + +data "google_compute_network" "default" { + name = "{{index $.Vars "network_name"}}" +} \ No newline at end of file diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_quotaRule_sweeper.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_quotaRule_sweeper.go new file mode 100644 index 000000000000..9f8b9abf43e1 --- /dev/null +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_quotaRule_sweeper.go @@ -0,0 +1,127 @@ +package netapp + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/sweeper" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func init() { + sweeper.AddTestSweepers("NetappVolumeQuotaRule", testSweepNetappVolumeQuotaRule) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepNetappVolumeQuotaRule(region string) error { + resourceName := "NetappVolumeQuotaRule" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sweeper.SharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + t := &testing.T{} + billingId := envvar.GetTestBillingAccountFromEnv(t) + + regions := []string{"us-central1", "us-west2", "us-east4"} + for _, r := range regions { + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s in %s", resourceName, r) + + // Setup variables to replace in list template + d := &tpgresource.ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": r, + "location": r, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://netapp.googleapis.com/v1/projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/quotaRules", "?")[0] + listUrl, err := tpgresource.ReplaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + continue + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: config.Project, + RawURL: listUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + continue + } + + resourceList, ok := res["volumeQuotaRules"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + continue + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + continue + } + + name := tpgresource.GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !sweeper.IsSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://netapp.googleapis.com/v1/projects/{{project}}/locations/{{location}}/volumes/{{volume_name}}/quotaRules/{{name}}" + deleteUrl, err := tpgresource.ReplaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + continue + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: config.Project, + RawURL: deleteUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + } + + return nil +} diff --git a/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_quotaRule_test.go b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_quotaRule_test.go new file mode 100644 index 000000000000..804dcb37b292 --- /dev/null +++ b/mmv1/third_party/terraform/services/netapp/resource_netapp_volume_quotaRule_test.go @@ -0,0 +1,225 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package netapp_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +func TestAccNetappVolumeQuotaRule_netappVolumeQuotaRuleBasicExample_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "network_name": acctest.BootstrapSharedServiceNetworkingConnection(t, "gcnv-network-config-1", acctest.ServiceNetworkWithParentService("netapp.servicenetworking.goog")), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckNetappVolumeQuotaRuleDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccNetappVolumeQuotaRule_netappVolumeQuotaRuleFull(context), + }, + { + ResourceName: "google_netapp_volume_quota_rule.test_default_user_quota_rule", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "name", "terraform_labels", "volume_name"}, + }, + { + ResourceName: "google_netapp_volume_quota_rule.test_default_group_quota_rule", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "name", "terraform_labels", "volume_name"}, + }, + { + ResourceName: "google_netapp_volume_quota_rule.test_individual_user_quota_rule", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "name", "terraform_labels", "volume_name"}, + }, + { + ResourceName: "google_netapp_volume_quota_rule.test_individual_group_quota_rule", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "name", "terraform_labels", "volume_name"}, + }, + { + Config: testAccNetappVolumeQuotaRule_netappVolumeQuotaRuleFull_update(context), + }, + { + ResourceName: "google_netapp_volume_quota_rule.test_default_user_quota_rule", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "name", "terraform_labels", "volume_name"}, + }, + { + ResourceName: "google_netapp_volume_quota_rule.test_default_group_quota_rule", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "name", "terraform_labels", "volume_name"}, + }, + { + ResourceName: "google_netapp_volume_quota_rule.test_individual_user_quota_rule", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "name", "terraform_labels", "volume_name"}, + }, + { + ResourceName: "google_netapp_volume_quota_rule.test_individual_group_quota_rule", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "name", "terraform_labels", "volume_name"}, + }, + }, + }) +} + +func testAccNetappVolumeQuotaRule_netappVolumeQuotaRuleFull(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_netapp_storage_pool" "default" { + name = "tf-test-test-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "default" { + location = google_netapp_storage_pool.default.location + name = "tf-test-test-volume%{random_suffix}" + capacity_gib = 100 + share_name = "tf-test-test-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.default.name + protocols = ["NFSV3"] +} + +resource "google_netapp_volume_quota_rule" "test_default_user_quota_rule" { + depends_on = [google_netapp_volume.default] + location = google_netapp_volume.default.location + volume_name = google_netapp_volume.default.name + name = "tf-default-user-quota-rule%{random_suffix}" + description = "This is a test description" + type = "DEFAULT_USER_QUOTA" + disk_limit_mib = 15 +} + +resource "google_netapp_volume_quota_rule" "test_default_group_quota_rule" { + depends_on = [google_netapp_volume.default] + location = google_netapp_volume.default.location + volume_name = google_netapp_volume.default.name + name = "tf-default-group-quota-rule%{random_suffix}" + description = "This is a test description" + labels = { + key = "test" + value = "quota_rule" + } + type = "DEFAULT_GROUP_QUOTA" + disk_limit_mib = 20 +} + +resource "google_netapp_volume_quota_rule" "test_individual_user_quota_rule" { + depends_on = [google_netapp_volume.default] + location = google_netapp_volume.default.location + volume_name = google_netapp_volume.default.name + name = "tf-individual-user-quota-rule%{random_suffix}" + description = "This is a test description" + type = "INDIVIDUAL_USER_QUOTA" + disk_limit_mib = 25 + target = "001" +} + +resource "google_netapp_volume_quota_rule" "test_individual_group_quota_rule" { + depends_on = [google_netapp_volume.default] + location = google_netapp_volume.default.location + volume_name = google_netapp_volume.default.name + name = "tf-individual-group-quota-rule%{random_suffix}" + description = "This is a test description" + type = "INDIVIDUAL_GROUP_QUOTA" + disk_limit_mib = 30 + target = "011" +} + +data "google_compute_network" "default" { + name = "%{network_name}" +} +`, context) +} + +func testAccNetappVolumeQuotaRule_netappVolumeQuotaRuleFull_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_netapp_storage_pool" "default" { + name = "tf-test-test-pool%{random_suffix}" + location = "us-west2" + service_level = "PREMIUM" + capacity_gib = 2048 + network = data.google_compute_network.default.id +} + +resource "google_netapp_volume" "default" { + location = google_netapp_storage_pool.default.location + name = "tf-test-test-volume%{random_suffix}" + capacity_gib = 100 + share_name = "tf-test-test-volume%{random_suffix}" + storage_pool = google_netapp_storage_pool.default.name + protocols = ["NFSV3"] +} + +resource "google_netapp_volume_quota_rule" "test_default_user_quota_rule" { + depends_on = [google_netapp_volume.default] + location = google_netapp_volume.default.location + volume_name = google_netapp_volume.default.name + name = "tf-default-user-quota-rule%{random_suffix}" + description = "This is a test description" + type = "DEFAULT_USER_QUOTA" + disk_limit_mib = 35 +} + +resource "google_netapp_volume_quota_rule" "test_default_group_quota_rule" { + depends_on = [google_netapp_volume.default] + location = google_netapp_volume.default.location + volume_name = google_netapp_volume.default.name + name = "tf-default-group-quota-rule%{random_suffix}" + description = "This is a test description" + labels = { + key = "test" + value = "quota_rule" + } + type = "DEFAULT_GROUP_QUOTA" + disk_limit_mib = 40 +} + +resource "google_netapp_volume_quota_rule" "test_individual_user_quota_rule" { + depends_on = [google_netapp_volume.default] + location = google_netapp_volume.default.location + volume_name = google_netapp_volume.default.name + name = "tf-individual-user-quota-rule%{random_suffix}" + description = "This is a test description" + type = "INDIVIDUAL_USER_QUOTA" + disk_limit_mib = 45 + target = "001" +} + +resource "google_netapp_volume_quota_rule" "test_individual_group_quota_rule" { + depends_on = [google_netapp_volume.default] + location = google_netapp_volume.default.location + volume_name = google_netapp_volume.default.name + name = "tf-individual-group-quota-rule%{random_suffix}" + description = "This is a test description" + type = "INDIVIDUAL_GROUP_QUOTA" + disk_limit_mib = 50 + target = "011" +} + +data "google_compute_network" "default" { + name = "%{network_name}" +} +`, context) +}