From fee8d04d7083665ab1992a4bbd7fc74a346e0b06 Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Fri, 15 Sep 2023 03:32:01 +0000 Subject: [PATCH] adding RestorePlan to Backup for GKE (#8803) * adding RestorePlan to Backup for GKE * fixed spacing issue * fixed more spacing issue * adding newline end of file * fixed trailing spaces * fixed test file names * added test gaps * adding description adn labels * fixed tests * fixed protected application name length * fixed protected app test * fix restore all namespaces test names * removed bp prefix from resource names * removed last bp ref Signed-off-by: Modular Magician --- .changelog/8803.txt | 3 + google/provider/provider.go | 11 +- .../gkebackup/iam_gke_backup_restore_plan.go | 245 +++ ..._gke_backup_restore_plan_generated_test.go | 409 ++++ .../resource_gke_backup_restore_plan.go | 1668 +++++++++++++++++ ..._gke_backup_restore_plan_generated_test.go | 559 ++++++ ...ckup_restore_plan_iam_policy.html.markdown | 54 + .../r/gke_backup_restore_plan.html.markdown | 695 +++++++ .../gke_backup_restore_plan_iam.html.markdown | 155 ++ 9 files changed, 3796 insertions(+), 3 deletions(-) create mode 100644 .changelog/8803.txt create mode 100644 google/services/gkebackup/iam_gke_backup_restore_plan.go create mode 100644 google/services/gkebackup/iam_gke_backup_restore_plan_generated_test.go create mode 100644 google/services/gkebackup/resource_gke_backup_restore_plan.go create mode 100644 google/services/gkebackup/resource_gke_backup_restore_plan_generated_test.go create mode 100644 website/docs/d/gke_backup_restore_plan_iam_policy.html.markdown create mode 100644 website/docs/r/gke_backup_restore_plan.html.markdown create mode 100644 website/docs/r/gke_backup_restore_plan_iam.html.markdown diff --git a/.changelog/8803.txt b/.changelog/8803.txt new file mode 100644 index 00000000000..c13658797a3 --- /dev/null +++ b/.changelog/8803.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +`google_gke_backup_restore_plan` +``` diff --git a/google/provider/provider.go b/google/provider/provider.go index c51d0bc760a..85a3ecd8f29 100644 --- a/google/provider/provider.go +++ b/google/provider/provider.go @@ -933,6 +933,7 @@ func DatasourceMapWithErrors() (map[string]*schema.Resource, error) { "google_dataproc_metastore_service_iam_policy": tpgiamresource.DataSourceIamPolicy(dataprocmetastore.DataprocMetastoreServiceIamSchema, dataprocmetastore.DataprocMetastoreServiceIamUpdaterProducer), "google_dns_managed_zone_iam_policy": tpgiamresource.DataSourceIamPolicy(dns.DNSManagedZoneIamSchema, dns.DNSManagedZoneIamUpdaterProducer), "google_gke_backup_backup_plan_iam_policy": tpgiamresource.DataSourceIamPolicy(gkebackup.GKEBackupBackupPlanIamSchema, gkebackup.GKEBackupBackupPlanIamUpdaterProducer), + "google_gke_backup_restore_plan_iam_policy": tpgiamresource.DataSourceIamPolicy(gkebackup.GKEBackupRestorePlanIamSchema, gkebackup.GKEBackupRestorePlanIamUpdaterProducer), "google_gke_hub_membership_iam_policy": tpgiamresource.DataSourceIamPolicy(gkehub.GKEHubMembershipIamSchema, gkehub.GKEHubMembershipIamUpdaterProducer), "google_gke_hub_feature_iam_policy": tpgiamresource.DataSourceIamPolicy(gkehub2.GKEHub2FeatureIamSchema, gkehub2.GKEHub2FeatureIamUpdaterProducer), "google_gke_hub_scope_iam_policy": tpgiamresource.DataSourceIamPolicy(gkehub2.GKEHub2ScopeIamSchema, gkehub2.GKEHub2ScopeIamUpdaterProducer), @@ -986,9 +987,9 @@ func DatasourceMapWithErrors() (map[string]*schema.Resource, error) { }) } -// Generated resources: 321 -// Generated IAM resources: 207 -// Total generated resources: 528 +// Generated resources: 322 +// Generated IAM resources: 210 +// Total generated resources: 532 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -1326,6 +1327,10 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_gke_backup_backup_plan_iam_binding": tpgiamresource.ResourceIamBinding(gkebackup.GKEBackupBackupPlanIamSchema, gkebackup.GKEBackupBackupPlanIamUpdaterProducer, gkebackup.GKEBackupBackupPlanIdParseFunc), "google_gke_backup_backup_plan_iam_member": tpgiamresource.ResourceIamMember(gkebackup.GKEBackupBackupPlanIamSchema, gkebackup.GKEBackupBackupPlanIamUpdaterProducer, gkebackup.GKEBackupBackupPlanIdParseFunc), "google_gke_backup_backup_plan_iam_policy": tpgiamresource.ResourceIamPolicy(gkebackup.GKEBackupBackupPlanIamSchema, gkebackup.GKEBackupBackupPlanIamUpdaterProducer, gkebackup.GKEBackupBackupPlanIdParseFunc), + "google_gke_backup_restore_plan": gkebackup.ResourceGKEBackupRestorePlan(), + "google_gke_backup_restore_plan_iam_binding": tpgiamresource.ResourceIamBinding(gkebackup.GKEBackupRestorePlanIamSchema, gkebackup.GKEBackupRestorePlanIamUpdaterProducer, gkebackup.GKEBackupRestorePlanIdParseFunc), + "google_gke_backup_restore_plan_iam_member": tpgiamresource.ResourceIamMember(gkebackup.GKEBackupRestorePlanIamSchema, gkebackup.GKEBackupRestorePlanIamUpdaterProducer, gkebackup.GKEBackupRestorePlanIdParseFunc), + "google_gke_backup_restore_plan_iam_policy": tpgiamresource.ResourceIamPolicy(gkebackup.GKEBackupRestorePlanIamSchema, gkebackup.GKEBackupRestorePlanIamUpdaterProducer, gkebackup.GKEBackupRestorePlanIdParseFunc), "google_gke_hub_membership": gkehub.ResourceGKEHubMembership(), "google_gke_hub_membership_iam_binding": tpgiamresource.ResourceIamBinding(gkehub.GKEHubMembershipIamSchema, gkehub.GKEHubMembershipIamUpdaterProducer, gkehub.GKEHubMembershipIdParseFunc), "google_gke_hub_membership_iam_member": tpgiamresource.ResourceIamMember(gkehub.GKEHubMembershipIamSchema, gkehub.GKEHubMembershipIamUpdaterProducer, gkehub.GKEHubMembershipIdParseFunc), diff --git a/google/services/gkebackup/iam_gke_backup_restore_plan.go b/google/services/gkebackup/iam_gke_backup_restore_plan.go new file mode 100644 index 00000000000..53c8deb9193 --- /dev/null +++ b/google/services/gkebackup/iam_gke_backup_restore_plan.go @@ -0,0 +1,245 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package gkebackup + +import ( + "fmt" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "google.golang.org/api/cloudresourcemanager/v1" + + "github.com/hashicorp/terraform-provider-google/google/tpgiamresource" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +var GKEBackupRestorePlanIamSchema = map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ForceNew: true, + }, + "location": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, + }, +} + +type GKEBackupRestorePlanIamUpdater struct { + project string + location string + name string + d tpgresource.TerraformResourceData + Config *transport_tpg.Config +} + +func GKEBackupRestorePlanIamUpdaterProducer(d tpgresource.TerraformResourceData, config *transport_tpg.Config) (tpgiamresource.ResourceIamUpdater, error) { + values := make(map[string]string) + + project, _ := tpgresource.GetProject(d, config) + if project != "" { + if err := d.Set("project", project); err != nil { + return nil, fmt.Errorf("Error setting project: %s", err) + } + } + values["project"] = project + location, _ := tpgresource.GetLocation(d, config) + if location != "" { + if err := d.Set("location", location); err != nil { + return nil, fmt.Errorf("Error setting location: %s", err) + } + } + values["location"] = location + if v, ok := d.GetOk("name"); ok { + values["name"] = v.(string) + } + + // We may have gotten either a long or short name, so attempt to parse long name if possible + m, err := tpgresource.GetImportIdQualifiers([]string{"projects/(?P[^/]+)/locations/(?P[^/]+)/restorePlans/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)"}, d, config, d.Get("name").(string)) + if err != nil { + return nil, err + } + + for k, v := range m { + values[k] = v + } + + u := &GKEBackupRestorePlanIamUpdater{ + project: values["project"], + location: values["location"], + name: values["name"], + d: d, + Config: config, + } + + if err := d.Set("project", u.project); err != nil { + return nil, fmt.Errorf("Error setting project: %s", err) + } + if err := d.Set("location", u.location); err != nil { + return nil, fmt.Errorf("Error setting location: %s", err) + } + if err := d.Set("name", u.GetResourceId()); err != nil { + return nil, fmt.Errorf("Error setting name: %s", err) + } + + return u, nil +} + +func GKEBackupRestorePlanIdParseFunc(d *schema.ResourceData, config *transport_tpg.Config) error { + values := make(map[string]string) + + project, _ := tpgresource.GetProject(d, config) + if project != "" { + values["project"] = project + } + + location, _ := tpgresource.GetLocation(d, config) + if location != "" { + values["location"] = location + } + + m, err := tpgresource.GetImportIdQualifiers([]string{"projects/(?P[^/]+)/locations/(?P[^/]+)/restorePlans/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)"}, d, config, d.Id()) + if err != nil { + return err + } + + for k, v := range m { + values[k] = v + } + + u := &GKEBackupRestorePlanIamUpdater{ + project: values["project"], + location: values["location"], + name: values["name"], + d: d, + Config: config, + } + if err := d.Set("name", u.GetResourceId()); err != nil { + return fmt.Errorf("Error setting name: %s", err) + } + d.SetId(u.GetResourceId()) + return nil +} + +func (u *GKEBackupRestorePlanIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) { + url, err := u.qualifyRestorePlanUrl("getIamPolicy") + if err != nil { + return nil, err + } + + project, err := tpgresource.GetProject(u.d, u.Config) + if err != nil { + return nil, err + } + var obj map[string]interface{} + + userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent) + if err != nil { + return nil, err + } + + policy, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: u.Config, + Method: "GET", + Project: project, + RawURL: url, + UserAgent: userAgent, + Body: obj, + }) + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + out := &cloudresourcemanager.Policy{} + err = tpgresource.Convert(policy, out) + if err != nil { + return nil, errwrap.Wrapf("Cannot convert a policy to a resource manager policy: {{err}}", err) + } + + return out, nil +} + +func (u *GKEBackupRestorePlanIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error { + json, err := tpgresource.ConvertToMap(policy) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + obj["policy"] = json + + url, err := u.qualifyRestorePlanUrl("setIamPolicy") + if err != nil { + return err + } + project, err := tpgresource.GetProject(u.d, u.Config) + if err != nil { + return err + } + + userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent) + if err != nil { + return err + } + + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: u.Config, + Method: "POST", + Project: project, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: u.d.Timeout(schema.TimeoutCreate), + }) + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return nil +} + +func (u *GKEBackupRestorePlanIamUpdater) qualifyRestorePlanUrl(methodIdentifier string) (string, error) { + urlTemplate := fmt.Sprintf("{{GKEBackupBasePath}}%s:%s", fmt.Sprintf("projects/%s/locations/%s/restorePlans/%s", u.project, u.location, u.name), methodIdentifier) + url, err := tpgresource.ReplaceVars(u.d, u.Config, urlTemplate) + if err != nil { + return "", err + } + return url, nil +} + +func (u *GKEBackupRestorePlanIamUpdater) GetResourceId() string { + return fmt.Sprintf("projects/%s/locations/%s/restorePlans/%s", u.project, u.location, u.name) +} + +func (u *GKEBackupRestorePlanIamUpdater) GetMutexKey() string { + return fmt.Sprintf("iam-gkebackup-restoreplan-%s", u.GetResourceId()) +} + +func (u *GKEBackupRestorePlanIamUpdater) DescribeResource() string { + return fmt.Sprintf("gkebackup restoreplan %q", u.GetResourceId()) +} diff --git a/google/services/gkebackup/iam_gke_backup_restore_plan_generated_test.go b/google/services/gkebackup/iam_gke_backup_restore_plan_generated_test.go new file mode 100644 index 00000000000..34420b6e317 --- /dev/null +++ b/google/services/gkebackup/iam_gke_backup_restore_plan_generated_test.go @@ -0,0 +1,409 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package gkebackup_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" +) + +func TestAccGKEBackupRestorePlanIamBindingGenerated(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/viewer", + "project": envvar.GetTestProjectFromEnv(), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccGKEBackupRestorePlanIamBinding_basicGenerated(context), + }, + { + ResourceName: "google_gke_backup_restore_plan_iam_binding.foo", + ImportStateId: fmt.Sprintf("projects/%s/locations/%s/restorePlans/%s roles/viewer", envvar.GetTestProjectFromEnv(), envvar.GetTestRegionFromEnv(), fmt.Sprintf("tf-test-restore-all-ns%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Iam Binding update + Config: testAccGKEBackupRestorePlanIamBinding_updateGenerated(context), + }, + { + ResourceName: "google_gke_backup_restore_plan_iam_binding.foo", + ImportStateId: fmt.Sprintf("projects/%s/locations/%s/restorePlans/%s roles/viewer", envvar.GetTestProjectFromEnv(), envvar.GetTestRegionFromEnv(), fmt.Sprintf("tf-test-restore-all-ns%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccGKEBackupRestorePlanIamMemberGenerated(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/viewer", + "project": envvar.GetTestProjectFromEnv(), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + // Test Iam Member creation (no update for member, no need to test) + Config: testAccGKEBackupRestorePlanIamMember_basicGenerated(context), + }, + { + ResourceName: "google_gke_backup_restore_plan_iam_member.foo", + ImportStateId: fmt.Sprintf("projects/%s/locations/%s/restorePlans/%s roles/viewer user:admin@hashicorptest.com", envvar.GetTestProjectFromEnv(), envvar.GetTestRegionFromEnv(), fmt.Sprintf("tf-test-restore-all-ns%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccGKEBackupRestorePlanIamPolicyGenerated(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/viewer", + "project": envvar.GetTestProjectFromEnv(), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccGKEBackupRestorePlanIamPolicy_basicGenerated(context), + Check: resource.TestCheckResourceAttrSet("data.google_gke_backup_restore_plan_iam_policy.foo", "policy_data"), + }, + { + ResourceName: "google_gke_backup_restore_plan_iam_policy.foo", + ImportStateId: fmt.Sprintf("projects/%s/locations/%s/restorePlans/%s", envvar.GetTestProjectFromEnv(), envvar.GetTestRegionFromEnv(), fmt.Sprintf("tf-test-restore-all-ns%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccGKEBackupRestorePlanIamPolicy_emptyBinding(context), + }, + { + ResourceName: "google_gke_backup_restore_plan_iam_policy.foo", + ImportStateId: fmt.Sprintf("projects/%s/locations/%s/restorePlans/%s", envvar.GetTestProjectFromEnv(), envvar.GetTestRegionFromEnv(), fmt.Sprintf("tf-test-restore-all-ns%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccGKEBackupRestorePlanIamMember_basicGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_container_cluster" "primary" { + name = "tf-test-restore-all-ns%{random_suffix}-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "%{project}.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "tf-test-restore-all-ns%{random_suffix}" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "all_ns" { + name = "tf-test-restore-all-ns%{random_suffix}" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + all_namespaces = true + namespaced_resource_restore_mode = "FAIL_ON_CONFLICT" + volume_data_restore_policy = "RESTORE_VOLUME_DATA_FROM_BACKUP" + cluster_resource_restore_scope { + all_group_kinds = true + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + } +} + +resource "google_gke_backup_restore_plan_iam_member" "foo" { + project = google_gke_backup_restore_plan.all_ns.project + location = google_gke_backup_restore_plan.all_ns.location + name = google_gke_backup_restore_plan.all_ns.name + role = "%{role}" + member = "user:admin@hashicorptest.com" +} +`, context) +} + +func testAccGKEBackupRestorePlanIamPolicy_basicGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_container_cluster" "primary" { + name = "tf-test-restore-all-ns%{random_suffix}-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "%{project}.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "tf-test-restore-all-ns%{random_suffix}" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "all_ns" { + name = "tf-test-restore-all-ns%{random_suffix}" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + all_namespaces = true + namespaced_resource_restore_mode = "FAIL_ON_CONFLICT" + volume_data_restore_policy = "RESTORE_VOLUME_DATA_FROM_BACKUP" + cluster_resource_restore_scope { + all_group_kinds = true + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + } +} + +data "google_iam_policy" "foo" { + binding { + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + } +} + +resource "google_gke_backup_restore_plan_iam_policy" "foo" { + project = google_gke_backup_restore_plan.all_ns.project + location = google_gke_backup_restore_plan.all_ns.location + name = google_gke_backup_restore_plan.all_ns.name + policy_data = data.google_iam_policy.foo.policy_data +} + +data "google_gke_backup_restore_plan_iam_policy" "foo" { + project = google_gke_backup_restore_plan.all_ns.project + location = google_gke_backup_restore_plan.all_ns.location + name = google_gke_backup_restore_plan.all_ns.name + depends_on = [ + google_gke_backup_restore_plan_iam_policy.foo + ] +} +`, context) +} + +func testAccGKEBackupRestorePlanIamPolicy_emptyBinding(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_container_cluster" "primary" { + name = "tf-test-restore-all-ns%{random_suffix}-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "%{project}.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "tf-test-restore-all-ns%{random_suffix}" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "all_ns" { + name = "tf-test-restore-all-ns%{random_suffix}" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + all_namespaces = true + namespaced_resource_restore_mode = "FAIL_ON_CONFLICT" + volume_data_restore_policy = "RESTORE_VOLUME_DATA_FROM_BACKUP" + cluster_resource_restore_scope { + all_group_kinds = true + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + } +} + +data "google_iam_policy" "foo" { +} + +resource "google_gke_backup_restore_plan_iam_policy" "foo" { + project = google_gke_backup_restore_plan.all_ns.project + location = google_gke_backup_restore_plan.all_ns.location + name = google_gke_backup_restore_plan.all_ns.name + policy_data = data.google_iam_policy.foo.policy_data +} +`, context) +} + +func testAccGKEBackupRestorePlanIamBinding_basicGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_container_cluster" "primary" { + name = "tf-test-restore-all-ns%{random_suffix}-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "%{project}.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "tf-test-restore-all-ns%{random_suffix}" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "all_ns" { + name = "tf-test-restore-all-ns%{random_suffix}" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + all_namespaces = true + namespaced_resource_restore_mode = "FAIL_ON_CONFLICT" + volume_data_restore_policy = "RESTORE_VOLUME_DATA_FROM_BACKUP" + cluster_resource_restore_scope { + all_group_kinds = true + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + } +} + +resource "google_gke_backup_restore_plan_iam_binding" "foo" { + project = google_gke_backup_restore_plan.all_ns.project + location = google_gke_backup_restore_plan.all_ns.location + name = google_gke_backup_restore_plan.all_ns.name + role = "%{role}" + members = ["user:admin@hashicorptest.com"] +} +`, context) +} + +func testAccGKEBackupRestorePlanIamBinding_updateGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_container_cluster" "primary" { + name = "tf-test-restore-all-ns%{random_suffix}-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "%{project}.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "tf-test-restore-all-ns%{random_suffix}" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "all_ns" { + name = "tf-test-restore-all-ns%{random_suffix}" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + all_namespaces = true + namespaced_resource_restore_mode = "FAIL_ON_CONFLICT" + volume_data_restore_policy = "RESTORE_VOLUME_DATA_FROM_BACKUP" + cluster_resource_restore_scope { + all_group_kinds = true + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + } +} + +resource "google_gke_backup_restore_plan_iam_binding" "foo" { + project = google_gke_backup_restore_plan.all_ns.project + location = google_gke_backup_restore_plan.all_ns.location + name = google_gke_backup_restore_plan.all_ns.name + role = "%{role}" + members = ["user:admin@hashicorptest.com", "user:gterraformtest1@gmail.com"] +} +`, context) +} diff --git a/google/services/gkebackup/resource_gke_backup_restore_plan.go b/google/services/gkebackup/resource_gke_backup_restore_plan.go new file mode 100644 index 00000000000..e2c33b5b12c --- /dev/null +++ b/google/services/gkebackup/resource_gke_backup_restore_plan.go @@ -0,0 +1,1668 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package gkebackup + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" + "github.com/hashicorp/terraform-provider-google/google/verify" +) + +func ResourceGKEBackupRestorePlan() *schema.Resource { + return &schema.Resource{ + Create: resourceGKEBackupRestorePlanCreate, + Read: resourceGKEBackupRestorePlanRead, + Update: resourceGKEBackupRestorePlanUpdate, + Delete: resourceGKEBackupRestorePlanDelete, + + Importer: &schema.ResourceImporter{ + State: resourceGKEBackupRestorePlanImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "backup_plan": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `A reference to the BackupPlan from which Backups may be used +as the source for Restores created via this RestorePlan.`, + }, + "cluster": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The source cluster from which Restores will be created via this RestorePlan.`, + }, + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The region of the Restore Plan.`, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The full name of the BackupPlan Resource.`, + }, + "restore_config": { + Type: schema.TypeList, + Required: true, + Description: `Defines the configuration of Restores created via this RestorePlan.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "all_namespaces": { + Type: schema.TypeBool, + Optional: true, + Description: `If True, restore all namespaced resources in the Backup. +Setting this field to False will result in an error.`, + ExactlyOneOf: []string{"restore_config.0.all_namespaces", "restore_config.0.excluded_namespaces", "restore_config.0.selected_namespaces", "restore_config.0.selected_applications", "restore_config.0.no_namespaces"}, + }, + "cluster_resource_conflict_policy": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: verify.ValidateEnum([]string{"USE_EXISTING_VERSION", "USE_BACKUP_VERSION", ""}), + Description: `Defines the behavior for handling the situation where cluster-scoped resources +being restored already exist in the target cluster. +This MUST be set to a value other than 'CLUSTER_RESOURCE_CONFLICT_POLICY_UNSPECIFIED' +if 'clusterResourceRestoreScope' is anyting other than 'noGroupKinds'. +See https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/reference/rest/v1/RestoreConfig#clusterresourceconflictpolicy +for more information on each policy option. Possible values: ["USE_EXISTING_VERSION", "USE_BACKUP_VERSION"]`, + }, + "cluster_resource_restore_scope": { + Type: schema.TypeList, + Optional: true, + Description: `Identifies the cluster-scoped resources to restore from the Backup.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "all_group_kinds": { + Type: schema.TypeBool, + Optional: true, + Description: `If True, all valid cluster-scoped resources will be restored. +Mutually exclusive to any other field in 'clusterResourceRestoreScope'.`, + ExactlyOneOf: []string{"restore_config.0.cluster_resource_restore_scope.0.all_group_kinds", "restore_config.0.cluster_resource_restore_scope.0.excluded_group_kinds", "restore_config.0.cluster_resource_restore_scope.0.selected_group_kinds", "restore_config.0.cluster_resource_restore_scope.0.no_group_kinds"}, + }, + "excluded_group_kinds": { + Type: schema.TypeList, + Optional: true, + Description: `A list of cluster-scoped resource group kinds to NOT restore from the backup. +If specified, all valid cluster-scoped resources will be restored except +for those specified in the list. +Mutually exclusive to any other field in 'clusterResourceRestoreScope'.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_group": { + Type: schema.TypeString, + Optional: true, + Description: `API Group string of a Kubernetes resource, e.g. +"apiextensions.k8s.io", "storage.k8s.io", etc. +Use empty string for core group.`, + }, + "resource_kind": { + Type: schema.TypeString, + Optional: true, + Description: `Kind of a Kubernetes resource, e.g. +"CustomResourceDefinition", "StorageClass", etc.`, + }, + }, + }, + ExactlyOneOf: []string{"restore_config.0.cluster_resource_restore_scope.0.all_group_kinds", "restore_config.0.cluster_resource_restore_scope.0.excluded_group_kinds", "restore_config.0.cluster_resource_restore_scope.0.selected_group_kinds", "restore_config.0.cluster_resource_restore_scope.0.no_group_kinds"}, + }, + "no_group_kinds": { + Type: schema.TypeBool, + Optional: true, + Description: `If True, no cluster-scoped resources will be restored. +Mutually exclusive to any other field in 'clusterResourceRestoreScope'.`, + ExactlyOneOf: []string{"restore_config.0.cluster_resource_restore_scope.0.all_group_kinds", "restore_config.0.cluster_resource_restore_scope.0.excluded_group_kinds", "restore_config.0.cluster_resource_restore_scope.0.selected_group_kinds", "restore_config.0.cluster_resource_restore_scope.0.no_group_kinds"}, + }, + "selected_group_kinds": { + Type: schema.TypeList, + Optional: true, + Description: `A list of cluster-scoped resource group kinds to restore from the backup. +If specified, only the selected resources will be restored. +Mutually exclusive to any other field in the 'clusterResourceRestoreScope'.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_group": { + Type: schema.TypeString, + Optional: true, + Description: `API Group string of a Kubernetes resource, e.g. +"apiextensions.k8s.io", "storage.k8s.io", etc. +Use empty string for core group.`, + }, + "resource_kind": { + Type: schema.TypeString, + Optional: true, + Description: `Kind of a Kubernetes resource, e.g. +"CustomResourceDefinition", "StorageClass", etc.`, + }, + }, + }, + ExactlyOneOf: []string{"restore_config.0.cluster_resource_restore_scope.0.all_group_kinds", "restore_config.0.cluster_resource_restore_scope.0.excluded_group_kinds", "restore_config.0.cluster_resource_restore_scope.0.selected_group_kinds", "restore_config.0.cluster_resource_restore_scope.0.no_group_kinds"}, + }, + }, + }, + }, + "excluded_namespaces": { + Type: schema.TypeList, + Optional: true, + Description: `A list of selected namespaces excluded from restoration. +All namespaces except those in this list will be restored.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "namespaces": { + Type: schema.TypeList, + Required: true, + Description: `A list of Kubernetes Namespaces.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + ExactlyOneOf: []string{"restore_config.0.all_namespaces", "restore_config.0.excluded_namespaces", "restore_config.0.selected_namespaces", "restore_config.0.selected_applications", "restore_config.0.no_namespaces"}, + }, + "namespaced_resource_restore_mode": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: verify.ValidateEnum([]string{"DELETE_AND_RESTORE", "FAIL_ON_CONFLICT", ""}), + Description: `Defines the behavior for handling the situation where sets of namespaced resources +being restored already exist in the target cluster. +This MUST be set to a value other than 'NAMESPACED_RESOURCE_RESTORE_MODE_UNSPECIFIED' +if the 'namespacedResourceRestoreScope' is anything other than 'noNamespaces'. +See https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/reference/rest/v1/RestoreConfig#namespacedresourcerestoremode +for more information on each mode. Possible values: ["DELETE_AND_RESTORE", "FAIL_ON_CONFLICT"]`, + }, + "no_namespaces": { + Type: schema.TypeBool, + Optional: true, + Description: `Do not restore any namespaced resources if set to "True". +Specifying this field to "False" is not allowed.`, + ExactlyOneOf: []string{"restore_config.0.all_namespaces", "restore_config.0.excluded_namespaces", "restore_config.0.selected_namespaces", "restore_config.0.selected_applications", "restore_config.0.no_namespaces"}, + }, + "selected_applications": { + Type: schema.TypeList, + Optional: true, + Description: `A list of selected ProtectedApplications to restore. +The listed ProtectedApplications and all the resources +to which they refer will be restored.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "namespaced_names": { + Type: schema.TypeList, + Required: true, + Description: `A list of namespaced Kubernetes resources.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: `The name of a Kubernetes Resource.`, + }, + "namespace": { + Type: schema.TypeString, + Required: true, + Description: `The namespace of a Kubernetes Resource.`, + }, + }, + }, + }, + }, + }, + ExactlyOneOf: []string{"restore_config.0.all_namespaces", "restore_config.0.excluded_namespaces", "restore_config.0.selected_namespaces", "restore_config.0.selected_applications", "restore_config.0.no_namespaces"}, + }, + "selected_namespaces": { + Type: schema.TypeList, + Optional: true, + Description: `A list of selected namespaces to restore from the Backup. +The listed Namespaces and all resources contained in them will be restored.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "namespaces": { + Type: schema.TypeList, + Required: true, + Description: `A list of Kubernetes Namespaces.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + ExactlyOneOf: []string{"restore_config.0.all_namespaces", "restore_config.0.excluded_namespaces", "restore_config.0.selected_namespaces", "restore_config.0.selected_applications", "restore_config.0.no_namespaces"}, + }, + "transformation_rules": { + Type: schema.TypeList, + Optional: true, + Description: `A list of transformation rules to be applied against Kubernetes +resources as they are selected for restoration from a Backup. +Rules are executed in order defined - this order matters, +as changes made by a rule may impact the filtering logic of subsequent +rules. An empty list means no transformation will occur.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "field_actions": { + Type: schema.TypeList, + Required: true, + Description: `A list of transformation rule actions to take against candidate +resources. Actions are executed in order defined - this order +matters, as they could potentially interfere with each other and +the first operation could affect the outcome of the second operation.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "op": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidateEnum([]string{"REMOVE", "MOVE", "COPY", "ADD", "TEST", "REPLACE"}), + Description: `Specifies the operation to perform. Possible values: ["REMOVE", "MOVE", "COPY", "ADD", "TEST", "REPLACE"]`, + }, + "from_path": { + Type: schema.TypeString, + Optional: true, + Description: `A string containing a JSON Pointer value that references the +location in the target document to move the value from.`, + }, + "path": { + Type: schema.TypeString, + Optional: true, + Description: `A string containing a JSON-Pointer value that references a +location within the target document where the operation is performed.`, + }, + "value": { + Type: schema.TypeString, + Optional: true, + Description: `A string that specifies the desired value in string format +to use for transformation.`, + }, + }, + }, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `The description is a user specified string description +of the transformation rule.`, + }, + "resource_filter": { + Type: schema.TypeList, + Optional: true, + Description: `This field is used to specify a set of fields that should be used to +determine which resources in backup should be acted upon by the +supplied transformation rule actions, and this will ensure that only +specific resources are affected by transformation rule actions.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "group_kinds": { + Type: schema.TypeList, + Optional: true, + Description: `(Filtering parameter) Any resource subject to transformation must +belong to one of the listed "types". If this field is not provided, +no type filtering will be performed +(all resources of all types matching previous filtering parameters +will be candidates for transformation).`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_group": { + Type: schema.TypeString, + Optional: true, + Description: `API Group string of a Kubernetes resource, e.g. +"apiextensions.k8s.io", "storage.k8s.io", etc. +Use empty string for core group.`, + }, + "resource_kind": { + Type: schema.TypeString, + Optional: true, + Description: `Kind of a Kubernetes resource, e.g. +"CustomResourceDefinition", "StorageClass", etc.`, + }, + }, + }, + }, + "json_path": { + Type: schema.TypeString, + Optional: true, + Description: `This is a JSONPath expression that matches specific fields of +candidate resources and it operates as a filtering parameter +(resources that are not matched with this expression will not +be candidates for transformation).`, + }, + "namespaces": { + Type: schema.TypeList, + Optional: true, + Description: `(Filtering parameter) Any resource subject to transformation must +be contained within one of the listed Kubernetes Namespace in the +Backup. If this field is not provided, no namespace filtering will +be performed (all resources in all Namespaces, including all +cluster-scoped resources, will be candidates for transformation). +To mix cluster-scoped and namespaced resources in the same rule, +use an empty string ("") as one of the target namespaces.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + }, + "volume_data_restore_policy": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: verify.ValidateEnum([]string{"RESTORE_VOLUME_DATA_FROM_BACKUP", "REUSE_VOLUME_HANDLE_FROM_BACKUP", "NO_VOLUME_DATA_RESTORATION", ""}), + Description: `Specifies the mechanism to be used to restore volume data. +This should be set to a value other than 'NAMESPACED_RESOURCE_RESTORE_MODE_UNSPECIFIED' +if the 'namespacedResourceRestoreScope' is anything other than 'noNamespaces'. +If not specified, it will be treated as 'NO_VOLUME_DATA_RESTORATION'. +See https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/reference/rest/v1/RestoreConfig#VolumeDataRestorePolicy +for more information on each policy option. Possible values: ["RESTORE_VOLUME_DATA_FROM_BACKUP", "REUSE_VOLUME_HANDLE_FROM_BACKUP", "NO_VOLUME_DATA_RESTORATION"]`, + }, + }, + }, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `User specified descriptive string for this RestorePlan.`, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Description: `Description: A set of custom labels supplied by the user. +A list of key->value pairs. +Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: `The State of the RestorePlan.`, + }, + "state_reason": { + Type: schema.TypeString, + Computed: true, + Description: `Detailed description of why RestorePlan is in its current state.`, + }, + "uid": { + Type: schema.TypeString, + Computed: true, + Description: `Server generated, unique identifier of UUID format.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceGKEBackupRestorePlanCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + nameProp, err := expandGKEBackupRestorePlanName(d.Get("name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("name"); !tpgresource.IsEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + descriptionProp, err := expandGKEBackupRestorePlanDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !tpgresource.IsEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + labelsProp, err := expandGKEBackupRestorePlanLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !tpgresource.IsEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + backupPlanProp, err := expandGKEBackupRestorePlanBackupPlan(d.Get("backup_plan"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("backup_plan"); !tpgresource.IsEmptyValue(reflect.ValueOf(backupPlanProp)) && (ok || !reflect.DeepEqual(v, backupPlanProp)) { + obj["backupPlan"] = backupPlanProp + } + clusterProp, err := expandGKEBackupRestorePlanCluster(d.Get("cluster"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("cluster"); !tpgresource.IsEmptyValue(reflect.ValueOf(clusterProp)) && (ok || !reflect.DeepEqual(v, clusterProp)) { + obj["cluster"] = clusterProp + } + restoreConfigProp, err := expandGKEBackupRestorePlanRestoreConfig(d.Get("restore_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("restore_config"); !tpgresource.IsEmptyValue(reflect.ValueOf(restoreConfigProp)) && (ok || !reflect.DeepEqual(v, restoreConfigProp)) { + obj["restoreConfig"] = restoreConfigProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{GKEBackupBasePath}}projects/{{project}}/locations/{{location}}/restorePlans?restorePlanId={{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new RestorePlan: %#v", obj) + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for RestorePlan: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutCreate), + }) + if err != nil { + return fmt.Errorf("Error creating RestorePlan: %s", err) + } + + // Store the ID now + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/restorePlans/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Use the resource in the operation response to populate + // identity fields and d.Id() before read + var opRes map[string]interface{} + err = GKEBackupOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating RestorePlan", userAgent, + d.Timeout(schema.TimeoutCreate)) + if err != nil { + // The resource didn't actually create + d.SetId("") + + return fmt.Errorf("Error waiting to create RestorePlan: %s", err) + } + + if err := d.Set("name", flattenGKEBackupRestorePlanName(opRes["name"], d, config)); err != nil { + return err + } + + // This may have caused the ID to update - update it if so. + id, err = tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/restorePlans/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating RestorePlan %q: %#v", d.Id(), res) + + return resourceGKEBackupRestorePlanRead(d, meta) +} + +func resourceGKEBackupRestorePlanRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + url, err := tpgresource.ReplaceVars(d, config, "{{GKEBackupBasePath}}projects/{{project}}/locations/{{location}}/restorePlans/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for RestorePlan: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("GKEBackupRestorePlan %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading RestorePlan: %s", err) + } + + if err := d.Set("name", flattenGKEBackupRestorePlanName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading RestorePlan: %s", err) + } + if err := d.Set("uid", flattenGKEBackupRestorePlanUid(res["uid"], d, config)); err != nil { + return fmt.Errorf("Error reading RestorePlan: %s", err) + } + if err := d.Set("description", flattenGKEBackupRestorePlanDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading RestorePlan: %s", err) + } + if err := d.Set("labels", flattenGKEBackupRestorePlanLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading RestorePlan: %s", err) + } + if err := d.Set("backup_plan", flattenGKEBackupRestorePlanBackupPlan(res["backupPlan"], d, config)); err != nil { + return fmt.Errorf("Error reading RestorePlan: %s", err) + } + if err := d.Set("cluster", flattenGKEBackupRestorePlanCluster(res["cluster"], d, config)); err != nil { + return fmt.Errorf("Error reading RestorePlan: %s", err) + } + if err := d.Set("restore_config", flattenGKEBackupRestorePlanRestoreConfig(res["restoreConfig"], d, config)); err != nil { + return fmt.Errorf("Error reading RestorePlan: %s", err) + } + if err := d.Set("state", flattenGKEBackupRestorePlanState(res["state"], d, config)); err != nil { + return fmt.Errorf("Error reading RestorePlan: %s", err) + } + if err := d.Set("state_reason", flattenGKEBackupRestorePlanStateReason(res["stateReason"], d, config)); err != nil { + return fmt.Errorf("Error reading RestorePlan: %s", err) + } + + return nil +} + +func resourceGKEBackupRestorePlanUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for RestorePlan: %s", err) + } + billingProject = project + + obj := make(map[string]interface{}) + descriptionProp, err := expandGKEBackupRestorePlanDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + labelsProp, err := expandGKEBackupRestorePlanLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + restoreConfigProp, err := expandGKEBackupRestorePlanRestoreConfig(d.Get("restore_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("restore_config"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, restoreConfigProp)) { + obj["restoreConfig"] = restoreConfigProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{GKEBackupBasePath}}projects/{{project}}/locations/{{location}}/restorePlans/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating RestorePlan %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("description") { + updateMask = append(updateMask, "description") + } + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + + if d.HasChange("restore_config") { + updateMask = append(updateMask, "restoreConfig") + } + // updateMask is a URL parameter but not present in the schema, so ReplaceVars + // won't set it + url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PATCH", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutUpdate), + }) + + if err != nil { + return fmt.Errorf("Error updating RestorePlan %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating RestorePlan %q: %#v", d.Id(), res) + } + + err = GKEBackupOperationWaitTime( + config, res, project, "Updating RestorePlan", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + + return resourceGKEBackupRestorePlanRead(d, meta) +} + +func resourceGKEBackupRestorePlanDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for RestorePlan: %s", err) + } + billingProject = project + + url, err := tpgresource.ReplaceVars(d, config, "{{GKEBackupBasePath}}projects/{{project}}/locations/{{location}}/restorePlans/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting RestorePlan %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutDelete), + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, "RestorePlan") + } + + err = GKEBackupOperationWaitTime( + config, res, project, "Deleting RestorePlan", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting RestorePlan %q: %#v", d.Id(), res) + return nil +} + +func resourceGKEBackupRestorePlanImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*transport_tpg.Config) + if err := tpgresource.ParseImportId([]string{ + "projects/(?P[^/]+)/locations/(?P[^/]+)/restorePlans/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/restorePlans/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenGKEBackupRestorePlanName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return tpgresource.NameFromSelfLinkStateFunc(v) +} + +func flattenGKEBackupRestorePlanUid(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanLabels(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanBackupPlan(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanCluster(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfig(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["all_namespaces"] = + flattenGKEBackupRestorePlanRestoreConfigAllNamespaces(original["allNamespaces"], d, config) + transformed["excluded_namespaces"] = + flattenGKEBackupRestorePlanRestoreConfigExcludedNamespaces(original["excludedNamespaces"], d, config) + transformed["selected_namespaces"] = + flattenGKEBackupRestorePlanRestoreConfigSelectedNamespaces(original["selectedNamespaces"], d, config) + transformed["selected_applications"] = + flattenGKEBackupRestorePlanRestoreConfigSelectedApplications(original["selectedApplications"], d, config) + transformed["no_namespaces"] = + flattenGKEBackupRestorePlanRestoreConfigNoNamespaces(original["noNamespaces"], d, config) + transformed["namespaced_resource_restore_mode"] = + flattenGKEBackupRestorePlanRestoreConfigNamespacedResourceRestoreMode(original["namespacedResourceRestoreMode"], d, config) + transformed["volume_data_restore_policy"] = + flattenGKEBackupRestorePlanRestoreConfigVolumeDataRestorePolicy(original["volumeDataRestorePolicy"], d, config) + transformed["cluster_resource_restore_scope"] = + flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScope(original["clusterResourceRestoreScope"], d, config) + transformed["cluster_resource_conflict_policy"] = + flattenGKEBackupRestorePlanRestoreConfigClusterResourceConflictPolicy(original["clusterResourceConflictPolicy"], d, config) + transformed["transformation_rules"] = + flattenGKEBackupRestorePlanRestoreConfigTransformationRules(original["transformationRules"], d, config) + return []interface{}{transformed} +} +func flattenGKEBackupRestorePlanRestoreConfigAllNamespaces(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigExcludedNamespaces(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["namespaces"] = + flattenGKEBackupRestorePlanRestoreConfigExcludedNamespacesNamespaces(original["namespaces"], d, config) + return []interface{}{transformed} +} +func flattenGKEBackupRestorePlanRestoreConfigExcludedNamespacesNamespaces(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigSelectedNamespaces(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["namespaces"] = + flattenGKEBackupRestorePlanRestoreConfigSelectedNamespacesNamespaces(original["namespaces"], d, config) + return []interface{}{transformed} +} +func flattenGKEBackupRestorePlanRestoreConfigSelectedNamespacesNamespaces(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigSelectedApplications(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["namespaced_names"] = + flattenGKEBackupRestorePlanRestoreConfigSelectedApplicationsNamespacedNames(original["namespacedNames"], d, config) + return []interface{}{transformed} +} +func flattenGKEBackupRestorePlanRestoreConfigSelectedApplicationsNamespacedNames(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "namespace": flattenGKEBackupRestorePlanRestoreConfigSelectedApplicationsNamespacedNamesNamespace(original["namespace"], d, config), + "name": flattenGKEBackupRestorePlanRestoreConfigSelectedApplicationsNamespacedNamesName(original["name"], d, config), + }) + } + return transformed +} +func flattenGKEBackupRestorePlanRestoreConfigSelectedApplicationsNamespacedNamesNamespace(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigSelectedApplicationsNamespacedNamesName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigNoNamespaces(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigNamespacedResourceRestoreMode(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigVolumeDataRestorePolicy(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScope(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["all_group_kinds"] = + flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeAllGroupKinds(original["allGroupKinds"], d, config) + transformed["excluded_group_kinds"] = + flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeExcludedGroupKinds(original["excludedGroupKinds"], d, config) + transformed["selected_group_kinds"] = + flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKinds(original["selectedGroupKinds"], d, config) + transformed["no_group_kinds"] = + flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeNoGroupKinds(original["noGroupKinds"], d, config) + return []interface{}{transformed} +} +func flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeAllGroupKinds(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeExcludedGroupKinds(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "resource_group": flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeExcludedGroupKindsResourceGroup(original["resourceGroup"], d, config), + "resource_kind": flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeExcludedGroupKindsResourceKind(original["resourceKind"], d, config), + }) + } + return transformed +} +func flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeExcludedGroupKindsResourceGroup(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeExcludedGroupKindsResourceKind(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKinds(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "resource_group": flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindsResourceGroup(original["resourceGroup"], d, config), + "resource_kind": flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindsResourceKind(original["resourceKind"], d, config), + }) + } + return transformed +} +func flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindsResourceGroup(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindsResourceKind(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeNoGroupKinds(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigClusterResourceConflictPolicy(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigTransformationRules(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "description": flattenGKEBackupRestorePlanRestoreConfigTransformationRulesDescription(original["description"], d, config), + "resource_filter": flattenGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilter(original["resourceFilter"], d, config), + "field_actions": flattenGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActions(original["fieldActions"], d, config), + }) + } + return transformed +} +func flattenGKEBackupRestorePlanRestoreConfigTransformationRulesDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilter(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["namespaces"] = + flattenGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterNamespaces(original["namespaces"], d, config) + transformed["group_kinds"] = + flattenGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterGroupKinds(original["groupKinds"], d, config) + transformed["json_path"] = + flattenGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterJsonPath(original["jsonPath"], d, config) + return []interface{}{transformed} +} +func flattenGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterNamespaces(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterGroupKinds(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "resource_group": flattenGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterGroupKindsResourceGroup(original["resourceGroup"], d, config), + "resource_kind": flattenGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterGroupKindsResourceKind(original["resourceKind"], d, config), + }) + } + return transformed +} +func flattenGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterGroupKindsResourceGroup(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterGroupKindsResourceKind(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterJsonPath(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActions(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "op": flattenGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsOp(original["op"], d, config), + "from_path": flattenGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsFromPath(original["fromPath"], d, config), + "path": flattenGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsPath(original["path"], d, config), + "value": flattenGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsValue(original["value"], d, config), + }) + } + return transformed +} +func flattenGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsOp(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsFromPath(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsPath(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsValue(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanState(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenGKEBackupRestorePlanStateReason(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func expandGKEBackupRestorePlanName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/restorePlans/{{name}}") +} + +func expandGKEBackupRestorePlanDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanLabels(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandGKEBackupRestorePlanBackupPlan(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanCluster(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfig(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedAllNamespaces, err := expandGKEBackupRestorePlanRestoreConfigAllNamespaces(original["all_namespaces"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAllNamespaces); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["allNamespaces"] = transformedAllNamespaces + } + + transformedExcludedNamespaces, err := expandGKEBackupRestorePlanRestoreConfigExcludedNamespaces(original["excluded_namespaces"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedExcludedNamespaces); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["excludedNamespaces"] = transformedExcludedNamespaces + } + + transformedSelectedNamespaces, err := expandGKEBackupRestorePlanRestoreConfigSelectedNamespaces(original["selected_namespaces"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSelectedNamespaces); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["selectedNamespaces"] = transformedSelectedNamespaces + } + + transformedSelectedApplications, err := expandGKEBackupRestorePlanRestoreConfigSelectedApplications(original["selected_applications"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSelectedApplications); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["selectedApplications"] = transformedSelectedApplications + } + + transformedNoNamespaces, err := expandGKEBackupRestorePlanRestoreConfigNoNamespaces(original["no_namespaces"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNoNamespaces); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["noNamespaces"] = transformedNoNamespaces + } + + transformedNamespacedResourceRestoreMode, err := expandGKEBackupRestorePlanRestoreConfigNamespacedResourceRestoreMode(original["namespaced_resource_restore_mode"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespacedResourceRestoreMode); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["namespacedResourceRestoreMode"] = transformedNamespacedResourceRestoreMode + } + + transformedVolumeDataRestorePolicy, err := expandGKEBackupRestorePlanRestoreConfigVolumeDataRestorePolicy(original["volume_data_restore_policy"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedVolumeDataRestorePolicy); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["volumeDataRestorePolicy"] = transformedVolumeDataRestorePolicy + } + + transformedClusterResourceRestoreScope, err := expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScope(original["cluster_resource_restore_scope"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedClusterResourceRestoreScope); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["clusterResourceRestoreScope"] = transformedClusterResourceRestoreScope + } + + transformedClusterResourceConflictPolicy, err := expandGKEBackupRestorePlanRestoreConfigClusterResourceConflictPolicy(original["cluster_resource_conflict_policy"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedClusterResourceConflictPolicy); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["clusterResourceConflictPolicy"] = transformedClusterResourceConflictPolicy + } + + transformedTransformationRules, err := expandGKEBackupRestorePlanRestoreConfigTransformationRules(original["transformation_rules"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTransformationRules); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["transformationRules"] = transformedTransformationRules + } + + return transformed, nil +} + +func expandGKEBackupRestorePlanRestoreConfigAllNamespaces(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigExcludedNamespaces(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedNamespaces, err := expandGKEBackupRestorePlanRestoreConfigExcludedNamespacesNamespaces(original["namespaces"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespaces); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["namespaces"] = transformedNamespaces + } + + return transformed, nil +} + +func expandGKEBackupRestorePlanRestoreConfigExcludedNamespacesNamespaces(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigSelectedNamespaces(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedNamespaces, err := expandGKEBackupRestorePlanRestoreConfigSelectedNamespacesNamespaces(original["namespaces"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespaces); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["namespaces"] = transformedNamespaces + } + + return transformed, nil +} + +func expandGKEBackupRestorePlanRestoreConfigSelectedNamespacesNamespaces(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigSelectedApplications(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedNamespacedNames, err := expandGKEBackupRestorePlanRestoreConfigSelectedApplicationsNamespacedNames(original["namespaced_names"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespacedNames); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["namespacedNames"] = transformedNamespacedNames + } + + return transformed, nil +} + +func expandGKEBackupRestorePlanRestoreConfigSelectedApplicationsNamespacedNames(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedNamespace, err := expandGKEBackupRestorePlanRestoreConfigSelectedApplicationsNamespacedNamesNamespace(original["namespace"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespace); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["namespace"] = transformedNamespace + } + + transformedName, err := expandGKEBackupRestorePlanRestoreConfigSelectedApplicationsNamespacedNamesName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["name"] = transformedName + } + + req = append(req, transformed) + } + return req, nil +} + +func expandGKEBackupRestorePlanRestoreConfigSelectedApplicationsNamespacedNamesNamespace(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigSelectedApplicationsNamespacedNamesName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigNoNamespaces(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigNamespacedResourceRestoreMode(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigVolumeDataRestorePolicy(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScope(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedAllGroupKinds, err := expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeAllGroupKinds(original["all_group_kinds"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAllGroupKinds); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["allGroupKinds"] = transformedAllGroupKinds + } + + transformedExcludedGroupKinds, err := expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeExcludedGroupKinds(original["excluded_group_kinds"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedExcludedGroupKinds); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["excludedGroupKinds"] = transformedExcludedGroupKinds + } + + transformedSelectedGroupKinds, err := expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKinds(original["selected_group_kinds"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSelectedGroupKinds); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["selectedGroupKinds"] = transformedSelectedGroupKinds + } + + transformedNoGroupKinds, err := expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeNoGroupKinds(original["no_group_kinds"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNoGroupKinds); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["noGroupKinds"] = transformedNoGroupKinds + } + + return transformed, nil +} + +func expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeAllGroupKinds(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeExcludedGroupKinds(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedResourceGroup, err := expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeExcludedGroupKindsResourceGroup(original["resource_group"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceGroup); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["resourceGroup"] = transformedResourceGroup + } + + transformedResourceKind, err := expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeExcludedGroupKindsResourceKind(original["resource_kind"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceKind); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["resourceKind"] = transformedResourceKind + } + + req = append(req, transformed) + } + return req, nil +} + +func expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeExcludedGroupKindsResourceGroup(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeExcludedGroupKindsResourceKind(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKinds(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedResourceGroup, err := expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindsResourceGroup(original["resource_group"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceGroup); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["resourceGroup"] = transformedResourceGroup + } + + transformedResourceKind, err := expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindsResourceKind(original["resource_kind"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceKind); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["resourceKind"] = transformedResourceKind + } + + req = append(req, transformed) + } + return req, nil +} + +func expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindsResourceGroup(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindsResourceKind(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigClusterResourceRestoreScopeNoGroupKinds(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigClusterResourceConflictPolicy(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRules(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedDescription, err := expandGKEBackupRestorePlanRestoreConfigTransformationRulesDescription(original["description"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["description"] = transformedDescription + } + + transformedResourceFilter, err := expandGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilter(original["resource_filter"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceFilter); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["resourceFilter"] = transformedResourceFilter + } + + transformedFieldActions, err := expandGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActions(original["field_actions"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedFieldActions); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["fieldActions"] = transformedFieldActions + } + + req = append(req, transformed) + } + return req, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRulesDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilter(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedNamespaces, err := expandGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterNamespaces(original["namespaces"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespaces); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["namespaces"] = transformedNamespaces + } + + transformedGroupKinds, err := expandGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterGroupKinds(original["group_kinds"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedGroupKinds); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["groupKinds"] = transformedGroupKinds + } + + transformedJsonPath, err := expandGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterJsonPath(original["json_path"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedJsonPath); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["jsonPath"] = transformedJsonPath + } + + return transformed, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterNamespaces(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterGroupKinds(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedResourceGroup, err := expandGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterGroupKindsResourceGroup(original["resource_group"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceGroup); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["resourceGroup"] = transformedResourceGroup + } + + transformedResourceKind, err := expandGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterGroupKindsResourceKind(original["resource_kind"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceKind); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["resourceKind"] = transformedResourceKind + } + + req = append(req, transformed) + } + return req, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterGroupKindsResourceGroup(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterGroupKindsResourceKind(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRulesResourceFilterJsonPath(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActions(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedOp, err := expandGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsOp(original["op"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedOp); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["op"] = transformedOp + } + + transformedFromPath, err := expandGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsFromPath(original["from_path"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedFromPath); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["fromPath"] = transformedFromPath + } + + transformedPath, err := expandGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsPath(original["path"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPath); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["path"] = transformedPath + } + + transformedValue, err := expandGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsValue(original["value"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedValue); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["value"] = transformedValue + } + + req = append(req, transformed) + } + return req, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsOp(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsFromPath(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsPath(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandGKEBackupRestorePlanRestoreConfigTransformationRulesFieldActionsValue(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} diff --git a/google/services/gkebackup/resource_gke_backup_restore_plan_generated_test.go b/google/services/gkebackup/resource_gke_backup_restore_plan_generated_test.go new file mode 100644 index 00000000000..bbc989ffe14 --- /dev/null +++ b/google/services/gkebackup/resource_gke_backup_restore_plan_generated_test.go @@ -0,0 +1,559 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package gkebackup_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func TestAccGKEBackupRestorePlan_gkebackupRestoreplanAllNamespacesExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project": envvar.GetTestProjectFromEnv(), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckGKEBackupRestorePlanDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccGKEBackupRestorePlan_gkebackupRestoreplanAllNamespacesExample(context), + }, + { + ResourceName: "google_gke_backup_restore_plan.all_ns", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location"}, + }, + }, + }) +} + +func testAccGKEBackupRestorePlan_gkebackupRestoreplanAllNamespacesExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_container_cluster" "primary" { + name = "tf-test-restore-all-ns%{random_suffix}-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "%{project}.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "tf-test-restore-all-ns%{random_suffix}" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "all_ns" { + name = "tf-test-restore-all-ns%{random_suffix}" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + all_namespaces = true + namespaced_resource_restore_mode = "FAIL_ON_CONFLICT" + volume_data_restore_policy = "RESTORE_VOLUME_DATA_FROM_BACKUP" + cluster_resource_restore_scope { + all_group_kinds = true + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + } +} +`, context) +} + +func TestAccGKEBackupRestorePlan_gkebackupRestoreplanRollbackNamespaceExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project": envvar.GetTestProjectFromEnv(), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckGKEBackupRestorePlanDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccGKEBackupRestorePlan_gkebackupRestoreplanRollbackNamespaceExample(context), + }, + { + ResourceName: "google_gke_backup_restore_plan.rollback_ns", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location"}, + }, + }, + }) +} + +func testAccGKEBackupRestorePlan_gkebackupRestoreplanRollbackNamespaceExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_container_cluster" "primary" { + name = "tf-test-rollback-ns%{random_suffix}-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "%{project}.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "tf-test-rollback-ns%{random_suffix}" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "rollback_ns" { + name = "tf-test-rollback-ns%{random_suffix}-rp" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + selected_namespaces { + namespaces = ["my-ns"] + } + namespaced_resource_restore_mode = "DELETE_AND_RESTORE" + volume_data_restore_policy = "RESTORE_VOLUME_DATA_FROM_BACKUP" + cluster_resource_restore_scope { + selected_group_kinds { + resource_group = "apiextension.k8s.io" + resource_kind = "CustomResourceDefinition" + } + selected_group_kinds { + resource_group = "storage.k8s.io" + resource_kind = "StorageClass" + } + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + } +} +`, context) +} + +func TestAccGKEBackupRestorePlan_gkebackupRestoreplanProtectedApplicationExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project": envvar.GetTestProjectFromEnv(), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckGKEBackupRestorePlanDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccGKEBackupRestorePlan_gkebackupRestoreplanProtectedApplicationExample(context), + }, + { + ResourceName: "google_gke_backup_restore_plan.rollback_app", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location"}, + }, + }, + }) +} + +func testAccGKEBackupRestorePlan_gkebackupRestoreplanProtectedApplicationExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_container_cluster" "primary" { + name = "tf-test-rollback-app%{random_suffix}-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "%{project}.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "tf-test-rollback-app%{random_suffix}" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "rollback_app" { + name = "tf-test-rollback-app%{random_suffix}-rp" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + selected_applications { + namespaced_names { + name = "my-app" + namespace = "my-ns" + } + } + namespaced_resource_restore_mode = "DELETE_AND_RESTORE" + volume_data_restore_policy = "REUSE_VOLUME_HANDLE_FROM_BACKUP" + cluster_resource_restore_scope { + no_group_kinds = true + } + } +} +`, context) +} + +func TestAccGKEBackupRestorePlan_gkebackupRestoreplanAllClusterResourcesExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project": envvar.GetTestProjectFromEnv(), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckGKEBackupRestorePlanDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccGKEBackupRestorePlan_gkebackupRestoreplanAllClusterResourcesExample(context), + }, + { + ResourceName: "google_gke_backup_restore_plan.all_cluster_resources", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location"}, + }, + }, + }) +} + +func testAccGKEBackupRestorePlan_gkebackupRestoreplanAllClusterResourcesExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_container_cluster" "primary" { + name = "tf-test-all-groupkinds%{random_suffix}-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "%{project}.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "tf-test-all-groupkinds%{random_suffix}" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "all_cluster_resources" { + name = "tf-test-all-groupkinds%{random_suffix}-rp" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + no_namespaces = true + namespaced_resource_restore_mode = "FAIL_ON_CONFLICT" + cluster_resource_restore_scope { + all_group_kinds = true + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + } +} +`, context) +} + +func TestAccGKEBackupRestorePlan_gkebackupRestoreplanRenameNamespaceExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project": envvar.GetTestProjectFromEnv(), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckGKEBackupRestorePlanDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccGKEBackupRestorePlan_gkebackupRestoreplanRenameNamespaceExample(context), + }, + { + ResourceName: "google_gke_backup_restore_plan.rename_ns", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location"}, + }, + }, + }) +} + +func testAccGKEBackupRestorePlan_gkebackupRestoreplanRenameNamespaceExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_container_cluster" "primary" { + name = "tf-test-rename-ns%{random_suffix}-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "%{project}.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "tf-test-rename-ns%{random_suffix}" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "rename_ns" { + name = "tf-test-rename-ns%{random_suffix}-rp" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + selected_namespaces { + namespaces = ["ns1"] + } + namespaced_resource_restore_mode = "FAIL_ON_CONFLICT" + volume_data_restore_policy = "REUSE_VOLUME_HANDLE_FROM_BACKUP" + cluster_resource_restore_scope { + no_group_kinds = true + } + transformation_rules { + description = "rename namespace from ns1 to ns2" + resource_filter { + group_kinds { + resource_kind = "Namespace" + } + json_path = ".metadata[?(@.name == 'ns1')]" + } + field_actions { + op = "REPLACE" + path = "/metadata/name" + value = "ns2" + } + } + transformation_rules { + description = "move all resources from ns1 to ns2" + resource_filter { + namespaces = ["ns1"] + } + field_actions { + op = "REPLACE" + path = "/metadata/namespace" + value = "ns2" + } + } + } +} +`, context) +} + +func TestAccGKEBackupRestorePlan_gkebackupRestoreplanSecondTransformationExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project": envvar.GetTestProjectFromEnv(), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckGKEBackupRestorePlanDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccGKEBackupRestorePlan_gkebackupRestoreplanSecondTransformationExample(context), + }, + { + ResourceName: "google_gke_backup_restore_plan.transform_rule", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location"}, + }, + }, + }) +} + +func testAccGKEBackupRestorePlan_gkebackupRestoreplanSecondTransformationExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_container_cluster" "primary" { + name = "tf-test-transform-rule%{random_suffix}-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "%{project}.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "tf-test-transform-rule%{random_suffix}" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "transform_rule" { + name = "tf-test-transform-rule%{random_suffix}-rp" + description = "copy nginx env variables" + labels = { + "app" = "nginx" + } + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + excluded_namespaces { + namespaces = ["my-ns"] + } + namespaced_resource_restore_mode = "DELETE_AND_RESTORE" + volume_data_restore_policy = "RESTORE_VOLUME_DATA_FROM_BACKUP" + cluster_resource_restore_scope { + excluded_group_kinds { + resource_group = "apiextension.k8s.io" + resource_kind = "CustomResourceDefinition" + } + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + transformation_rules { + description = "Copy environment variables from the nginx container to the install init container." + resource_filter { + group_kinds { + resource_kind = "Pod" + resource_group = "" + } + json_path = ".metadata[?(@.name == 'nginx')]" + } + field_actions { + op = "COPY" + path = "/spec/initContainers/0/env" + from_path = "/spec/containers/0/env" + } + } + } +} +`, context) +} + +func testAccCheckGKEBackupRestorePlanDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_gke_backup_restore_plan" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := acctest.GoogleProviderConfig(t) + + url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{GKEBackupBasePath}}projects/{{project}}/locations/{{location}}/restorePlans/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: config.UserAgent, + }) + if err == nil { + return fmt.Errorf("GKEBackupRestorePlan still exists at %s", url) + } + } + + return nil + } +} diff --git a/website/docs/d/gke_backup_restore_plan_iam_policy.html.markdown b/website/docs/d/gke_backup_restore_plan_iam_policy.html.markdown new file mode 100644 index 00000000000..a81346cb6ac --- /dev/null +++ b/website/docs/d/gke_backup_restore_plan_iam_policy.html.markdown @@ -0,0 +1,54 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: MMv1 *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Backup for GKE" +description: |- + A datasource to retrieve the IAM policy state for Backup for GKE RestorePlan +--- + + +# `google_gke_backup_restore_plan_iam_policy` +Retrieves the current IAM policy data for restoreplan + + + +## example + +```hcl +data "google_gke_backup_restore_plan_iam_policy" "policy" { + project = google_gke_backup_restore_plan.all_ns.project + location = google_gke_backup_restore_plan.all_ns.location + name = google_gke_backup_restore_plan.all_ns.name +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Used to find the parent resource to bind the IAM policy to +* `location` - (Required) The region of the Restore Plan. + Used to find the parent resource to bind the IAM policy to + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the project will be parsed from the identifier of the parent resource. If no project is provided in the parent identifier and no project is specified, the provider project is used. + +## Attributes Reference + +The attributes are exported: + +* `etag` - (Computed) The etag of the IAM policy. + +* `policy_data` - (Required only by `google_gke_backup_restore_plan_iam_policy`) The policy data generated by + a `google_iam_policy` data source. diff --git a/website/docs/r/gke_backup_restore_plan.html.markdown b/website/docs/r/gke_backup_restore_plan.html.markdown new file mode 100644 index 00000000000..2ae4fa96241 --- /dev/null +++ b/website/docs/r/gke_backup_restore_plan.html.markdown @@ -0,0 +1,695 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: MMv1 *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Backup for GKE" +description: |- + Represents a Restore Plan instance. +--- + +# google\_gke\_backup\_restore\_plan + +Represents a Restore Plan instance. + + +To get more information about RestorePlan, see: + +* [API documentation](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/reference/rest/v1/projects.locations.restorePlans) +* How-to Guides + * [Official Documentation](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke) + +## Example Usage - Gkebackup Restoreplan All Namespaces + + +```hcl +resource "google_container_cluster" "primary" { + name = "restore-all-ns-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "my-project-name.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "restore-all-ns" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "all_ns" { + name = "restore-all-ns" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + all_namespaces = true + namespaced_resource_restore_mode = "FAIL_ON_CONFLICT" + volume_data_restore_policy = "RESTORE_VOLUME_DATA_FROM_BACKUP" + cluster_resource_restore_scope { + all_group_kinds = true + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + } +} +``` +## Example Usage - Gkebackup Restoreplan Rollback Namespace + + +```hcl +resource "google_container_cluster" "primary" { + name = "rollback-ns-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "my-project-name.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "rollback-ns" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "rollback_ns" { + name = "rollback-ns-rp" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + selected_namespaces { + namespaces = ["my-ns"] + } + namespaced_resource_restore_mode = "DELETE_AND_RESTORE" + volume_data_restore_policy = "RESTORE_VOLUME_DATA_FROM_BACKUP" + cluster_resource_restore_scope { + selected_group_kinds { + resource_group = "apiextension.k8s.io" + resource_kind = "CustomResourceDefinition" + } + selected_group_kinds { + resource_group = "storage.k8s.io" + resource_kind = "StorageClass" + } + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + } +} +``` +## Example Usage - Gkebackup Restoreplan Protected Application + + +```hcl +resource "google_container_cluster" "primary" { + name = "rollback-app-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "my-project-name.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "rollback-app" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "rollback_app" { + name = "rollback-app-rp" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + selected_applications { + namespaced_names { + name = "my-app" + namespace = "my-ns" + } + } + namespaced_resource_restore_mode = "DELETE_AND_RESTORE" + volume_data_restore_policy = "REUSE_VOLUME_HANDLE_FROM_BACKUP" + cluster_resource_restore_scope { + no_group_kinds = true + } + } +} +``` +## Example Usage - Gkebackup Restoreplan All Cluster Resources + + +```hcl +resource "google_container_cluster" "primary" { + name = "all-groupkinds-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "my-project-name.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "all-groupkinds" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "all_cluster_resources" { + name = "all-groupkinds-rp" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + no_namespaces = true + namespaced_resource_restore_mode = "FAIL_ON_CONFLICT" + cluster_resource_restore_scope { + all_group_kinds = true + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + } +} +``` +## Example Usage - Gkebackup Restoreplan Rename Namespace + + +```hcl +resource "google_container_cluster" "primary" { + name = "rename-ns-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "my-project-name.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "rename-ns" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "rename_ns" { + name = "rename-ns-rp" + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + selected_namespaces { + namespaces = ["ns1"] + } + namespaced_resource_restore_mode = "FAIL_ON_CONFLICT" + volume_data_restore_policy = "REUSE_VOLUME_HANDLE_FROM_BACKUP" + cluster_resource_restore_scope { + no_group_kinds = true + } + transformation_rules { + description = "rename namespace from ns1 to ns2" + resource_filter { + group_kinds { + resource_kind = "Namespace" + } + json_path = ".metadata[?(@.name == 'ns1')]" + } + field_actions { + op = "REPLACE" + path = "/metadata/name" + value = "ns2" + } + } + transformation_rules { + description = "move all resources from ns1 to ns2" + resource_filter { + namespaces = ["ns1"] + } + field_actions { + op = "REPLACE" + path = "/metadata/namespace" + value = "ns2" + } + } + } +} +``` +## Example Usage - Gkebackup Restoreplan Second Transformation + + +```hcl +resource "google_container_cluster" "primary" { + name = "transform-rule-cluster" + location = "us-central1" + initial_node_count = 1 + workload_identity_config { + workload_pool = "my-project-name.svc.id.goog" + } + addons_config { + gke_backup_agent_config { + enabled = true + } + } +} + +resource "google_gke_backup_backup_plan" "basic" { + name = "transform-rule" + cluster = google_container_cluster.primary.id + location = "us-central1" + backup_config { + include_volume_data = true + include_secrets = true + all_namespaces = true + } +} + +resource "google_gke_backup_restore_plan" "transform_rule" { + name = "transform-rule-rp" + description = "copy nginx env variables" + labels = { + "app" = "nginx" + } + location = "us-central1" + backup_plan = google_gke_backup_backup_plan.basic.id + cluster = google_container_cluster.primary.id + restore_config { + excluded_namespaces { + namespaces = ["my-ns"] + } + namespaced_resource_restore_mode = "DELETE_AND_RESTORE" + volume_data_restore_policy = "RESTORE_VOLUME_DATA_FROM_BACKUP" + cluster_resource_restore_scope { + excluded_group_kinds { + resource_group = "apiextension.k8s.io" + resource_kind = "CustomResourceDefinition" + } + } + cluster_resource_conflict_policy = "USE_EXISTING_VERSION" + transformation_rules { + description = "Copy environment variables from the nginx container to the install init container." + resource_filter { + group_kinds { + resource_kind = "Pod" + resource_group = "" + } + json_path = ".metadata[?(@.name == 'nginx')]" + } + field_actions { + op = "COPY" + path = "/spec/initContainers/0/env" + from_path = "/spec/containers/0/env" + } + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + The full name of the BackupPlan Resource. + +* `backup_plan` - + (Required) + A reference to the BackupPlan from which Backups may be used + as the source for Restores created via this RestorePlan. + +* `cluster` - + (Required) + The source cluster from which Restores will be created via this RestorePlan. + +* `restore_config` - + (Required) + Defines the configuration of Restores created via this RestorePlan. + Structure is [documented below](#nested_restore_config). + +* `location` - + (Required) + The region of the Restore Plan. + + +The `restore_config` block supports: + +* `all_namespaces` - + (Optional) + If True, restore all namespaced resources in the Backup. + Setting this field to False will result in an error. + +* `excluded_namespaces` - + (Optional) + A list of selected namespaces excluded from restoration. + All namespaces except those in this list will be restored. + Structure is [documented below](#nested_excluded_namespaces). + +* `selected_namespaces` - + (Optional) + A list of selected namespaces to restore from the Backup. + The listed Namespaces and all resources contained in them will be restored. + Structure is [documented below](#nested_selected_namespaces). + +* `selected_applications` - + (Optional) + A list of selected ProtectedApplications to restore. + The listed ProtectedApplications and all the resources + to which they refer will be restored. + Structure is [documented below](#nested_selected_applications). + +* `no_namespaces` - + (Optional) + Do not restore any namespaced resources if set to "True". + Specifying this field to "False" is not allowed. + +* `namespaced_resource_restore_mode` - + (Optional) + Defines the behavior for handling the situation where sets of namespaced resources + being restored already exist in the target cluster. + This MUST be set to a value other than `NAMESPACED_RESOURCE_RESTORE_MODE_UNSPECIFIED` + if the `namespacedResourceRestoreScope` is anything other than `noNamespaces`. + See https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/reference/rest/v1/RestoreConfig#namespacedresourcerestoremode + for more information on each mode. + Possible values are: `DELETE_AND_RESTORE`, `FAIL_ON_CONFLICT`. + +* `volume_data_restore_policy` - + (Optional) + Specifies the mechanism to be used to restore volume data. + This should be set to a value other than `NAMESPACED_RESOURCE_RESTORE_MODE_UNSPECIFIED` + if the `namespacedResourceRestoreScope` is anything other than `noNamespaces`. + If not specified, it will be treated as `NO_VOLUME_DATA_RESTORATION`. + See https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/reference/rest/v1/RestoreConfig#VolumeDataRestorePolicy + for more information on each policy option. + Possible values are: `RESTORE_VOLUME_DATA_FROM_BACKUP`, `REUSE_VOLUME_HANDLE_FROM_BACKUP`, `NO_VOLUME_DATA_RESTORATION`. + +* `cluster_resource_restore_scope` - + (Optional) + Identifies the cluster-scoped resources to restore from the Backup. + Structure is [documented below](#nested_cluster_resource_restore_scope). + +* `cluster_resource_conflict_policy` - + (Optional) + Defines the behavior for handling the situation where cluster-scoped resources + being restored already exist in the target cluster. + This MUST be set to a value other than `CLUSTER_RESOURCE_CONFLICT_POLICY_UNSPECIFIED` + if `clusterResourceRestoreScope` is anyting other than `noGroupKinds`. + See https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/reference/rest/v1/RestoreConfig#clusterresourceconflictpolicy + for more information on each policy option. + Possible values are: `USE_EXISTING_VERSION`, `USE_BACKUP_VERSION`. + +* `transformation_rules` - + (Optional) + A list of transformation rules to be applied against Kubernetes + resources as they are selected for restoration from a Backup. + Rules are executed in order defined - this order matters, + as changes made by a rule may impact the filtering logic of subsequent + rules. An empty list means no transformation will occur. + Structure is [documented below](#nested_transformation_rules). + + +The `excluded_namespaces` block supports: + +* `namespaces` - + (Required) + A list of Kubernetes Namespaces. + +The `selected_namespaces` block supports: + +* `namespaces` - + (Required) + A list of Kubernetes Namespaces. + +The `selected_applications` block supports: + +* `namespaced_names` - + (Required) + A list of namespaced Kubernetes resources. + Structure is [documented below](#nested_namespaced_names). + + +The `namespaced_names` block supports: + +* `namespace` - + (Required) + The namespace of a Kubernetes Resource. + +* `name` - + (Required) + The name of a Kubernetes Resource. + +The `cluster_resource_restore_scope` block supports: + +* `all_group_kinds` - + (Optional) + If True, all valid cluster-scoped resources will be restored. + Mutually exclusive to any other field in `clusterResourceRestoreScope`. + +* `excluded_group_kinds` - + (Optional) + A list of cluster-scoped resource group kinds to NOT restore from the backup. + If specified, all valid cluster-scoped resources will be restored except + for those specified in the list. + Mutually exclusive to any other field in `clusterResourceRestoreScope`. + Structure is [documented below](#nested_excluded_group_kinds). + +* `selected_group_kinds` - + (Optional) + A list of cluster-scoped resource group kinds to restore from the backup. + If specified, only the selected resources will be restored. + Mutually exclusive to any other field in the `clusterResourceRestoreScope`. + Structure is [documented below](#nested_selected_group_kinds). + +* `no_group_kinds` - + (Optional) + If True, no cluster-scoped resources will be restored. + Mutually exclusive to any other field in `clusterResourceRestoreScope`. + + +The `excluded_group_kinds` block supports: + +* `resource_group` - + (Optional) + API Group string of a Kubernetes resource, e.g. + "apiextensions.k8s.io", "storage.k8s.io", etc. + Use empty string for core group. + +* `resource_kind` - + (Optional) + Kind of a Kubernetes resource, e.g. + "CustomResourceDefinition", "StorageClass", etc. + +The `selected_group_kinds` block supports: + +* `resource_group` - + (Optional) + API Group string of a Kubernetes resource, e.g. + "apiextensions.k8s.io", "storage.k8s.io", etc. + Use empty string for core group. + +* `resource_kind` - + (Optional) + Kind of a Kubernetes resource, e.g. + "CustomResourceDefinition", "StorageClass", etc. + +The `transformation_rules` block supports: + +* `description` - + (Optional) + The description is a user specified string description + of the transformation rule. + +* `resource_filter` - + (Optional) + This field is used to specify a set of fields that should be used to + determine which resources in backup should be acted upon by the + supplied transformation rule actions, and this will ensure that only + specific resources are affected by transformation rule actions. + Structure is [documented below](#nested_resource_filter). + +* `field_actions` - + (Required) + A list of transformation rule actions to take against candidate + resources. Actions are executed in order defined - this order + matters, as they could potentially interfere with each other and + the first operation could affect the outcome of the second operation. + Structure is [documented below](#nested_field_actions). + + +The `resource_filter` block supports: + +* `namespaces` - + (Optional) + (Filtering parameter) Any resource subject to transformation must + be contained within one of the listed Kubernetes Namespace in the + Backup. If this field is not provided, no namespace filtering will + be performed (all resources in all Namespaces, including all + cluster-scoped resources, will be candidates for transformation). + To mix cluster-scoped and namespaced resources in the same rule, + use an empty string ("") as one of the target namespaces. + +* `group_kinds` - + (Optional) + (Filtering parameter) Any resource subject to transformation must + belong to one of the listed "types". If this field is not provided, + no type filtering will be performed + (all resources of all types matching previous filtering parameters + will be candidates for transformation). + Structure is [documented below](#nested_group_kinds). + +* `json_path` - + (Optional) + This is a JSONPath expression that matches specific fields of + candidate resources and it operates as a filtering parameter + (resources that are not matched with this expression will not + be candidates for transformation). + + +The `group_kinds` block supports: + +* `resource_group` - + (Optional) + API Group string of a Kubernetes resource, e.g. + "apiextensions.k8s.io", "storage.k8s.io", etc. + Use empty string for core group. + +* `resource_kind` - + (Optional) + Kind of a Kubernetes resource, e.g. + "CustomResourceDefinition", "StorageClass", etc. + +The `field_actions` block supports: + +* `op` - + (Required) + Specifies the operation to perform. + Possible values are: `REMOVE`, `MOVE`, `COPY`, `ADD`, `TEST`, `REPLACE`. + +* `from_path` - + (Optional) + A string containing a JSON Pointer value that references the + location in the target document to move the value from. + +* `path` - + (Optional) + A string containing a JSON-Pointer value that references a + location within the target document where the operation is performed. + +* `value` - + (Optional) + A string that specifies the desired value in string format + to use for transformation. + +- - - + + +* `description` - + (Optional) + User specified descriptive string for this RestorePlan. + +* `labels` - + (Optional) + Description: A set of custom labels supplied by the user. + A list of key->value pairs. + Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/locations/{{location}}/restorePlans/{{name}}` + +* `uid` - + Server generated, unique identifier of UUID format. + +* `state` - + The State of the RestorePlan. + +* `state_reason` - + Detailed description of why RestorePlan is in its current state. + + +## Timeouts + +This resource provides the following +[Timeouts](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `update` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +RestorePlan can be imported using any of these accepted formats: + +``` +$ terraform import google_gke_backup_restore_plan.default projects/{{project}}/locations/{{location}}/restorePlans/{{name}} +$ terraform import google_gke_backup_restore_plan.default {{project}}/{{location}}/{{name}} +$ terraform import google_gke_backup_restore_plan.default {{location}}/{{name}} +``` + +## User Project Overrides + +This resource supports [User Project Overrides](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#user_project_override). diff --git a/website/docs/r/gke_backup_restore_plan_iam.html.markdown b/website/docs/r/gke_backup_restore_plan_iam.html.markdown new file mode 100644 index 00000000000..bd1d89023ae --- /dev/null +++ b/website/docs/r/gke_backup_restore_plan_iam.html.markdown @@ -0,0 +1,155 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: MMv1 *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Backup for GKE" +description: |- + Collection of resources to manage IAM policy for Backup for GKE RestorePlan +--- + +# IAM policy for Backup for GKE RestorePlan +Three different resources help you manage your IAM policy for Backup for GKE RestorePlan. Each of these resources serves a different use case: + +* `google_gke_backup_restore_plan_iam_policy`: Authoritative. Sets the IAM policy for the restoreplan and replaces any existing policy already attached. +* `google_gke_backup_restore_plan_iam_binding`: Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the restoreplan are preserved. +* `google_gke_backup_restore_plan_iam_member`: Non-authoritative. Updates the IAM policy to grant a role to a new member. Other members for the role for the restoreplan are preserved. + +A data source can be used to retrieve policy data in advent you do not need creation + +* `google_gke_backup_restore_plan_iam_policy`: Retrieves the IAM policy for the restoreplan + +~> **Note:** `google_gke_backup_restore_plan_iam_policy` **cannot** be used in conjunction with `google_gke_backup_restore_plan_iam_binding` and `google_gke_backup_restore_plan_iam_member` or they will fight over what your policy should be. + +~> **Note:** `google_gke_backup_restore_plan_iam_binding` resources **can be** used in conjunction with `google_gke_backup_restore_plan_iam_member` resources **only if** they do not grant privilege to the same role. + + + + +## google\_gke\_backup\_restore\_plan\_iam\_policy + +```hcl +data "google_iam_policy" "admin" { + binding { + role = "roles/viewer" + members = [ + "user:jane@example.com", + ] + } +} + +resource "google_gke_backup_restore_plan_iam_policy" "policy" { + project = google_gke_backup_restore_plan.all_ns.project + location = google_gke_backup_restore_plan.all_ns.location + name = google_gke_backup_restore_plan.all_ns.name + policy_data = data.google_iam_policy.admin.policy_data +} +``` + +## google\_gke\_backup\_restore\_plan\_iam\_binding + +```hcl +resource "google_gke_backup_restore_plan_iam_binding" "binding" { + project = google_gke_backup_restore_plan.all_ns.project + location = google_gke_backup_restore_plan.all_ns.location + name = google_gke_backup_restore_plan.all_ns.name + role = "roles/viewer" + members = [ + "user:jane@example.com", + ] +} +``` + +## google\_gke\_backup\_restore\_plan\_iam\_member + +```hcl +resource "google_gke_backup_restore_plan_iam_member" "member" { + project = google_gke_backup_restore_plan.all_ns.project + location = google_gke_backup_restore_plan.all_ns.location + name = google_gke_backup_restore_plan.all_ns.name + role = "roles/viewer" + member = "user:jane@example.com" +} +``` + + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Used to find the parent resource to bind the IAM policy to +* `location` - (Required) The region of the Restore Plan. + Used to find the parent resource to bind the IAM policy to + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the project will be parsed from the identifier of the parent resource. If no project is provided in the parent identifier and no project is specified, the provider project is used. + +* `member/members` - (Required) Identities that will be granted the privilege in `role`. + Each entry can have one of the following values: + * **allUsers**: A special identifier that represents anyone who is on the internet; with or without a Google account. + * **allAuthenticatedUsers**: A special identifier that represents anyone who is authenticated with a Google account or a service account. + * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. + * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. + * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. + * **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com. + * **projectOwner:projectid**: Owners of the given project. For example, "projectOwner:my-example-project" + * **projectEditor:projectid**: Editors of the given project. For example, "projectEditor:my-example-project" + * **projectViewer:projectid**: Viewers of the given project. For example, "projectViewer:my-example-project" + +* `role` - (Required) The role that should be applied. Only one + `google_gke_backup_restore_plan_iam_binding` can be used per role. Note that custom roles must be of the format + `[projects|organizations]/{parent-name}/roles/{role-name}`. + +* `policy_data` - (Required only by `google_gke_backup_restore_plan_iam_policy`) The policy data generated by + a `google_iam_policy` data source. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `etag` - (Computed) The etag of the IAM policy. + +## Import + +For all import syntaxes, the "resource in question" can take any of the following forms: + +* projects/{{project}}/locations/{{location}}/restorePlans/{{name}} +* {{project}}/{{location}}/{{name}} +* {{location}}/{{name}} +* {{name}} + +Any variables not passed in the import command will be taken from the provider configuration. + +Backup for GKE restoreplan IAM resources can be imported using the resource identifiers, role, and member. + +IAM member imports use space-delimited identifiers: the resource in question, the role, and the member identity, e.g. +``` +$ terraform import google_gke_backup_restore_plan_iam_member.editor "projects/{{project}}/locations/{{location}}/restorePlans/{{restore_plan}} roles/viewer user:jane@example.com" +``` + +IAM binding imports use space-delimited identifiers: the resource in question and the role, e.g. +``` +$ terraform import google_gke_backup_restore_plan_iam_binding.editor "projects/{{project}}/locations/{{location}}/restorePlans/{{restore_plan}} roles/viewer" +``` + +IAM policy imports use the identifier of the resource in question, e.g. +``` +$ terraform import google_gke_backup_restore_plan_iam_policy.editor projects/{{project}}/locations/{{location}}/restorePlans/{{restore_plan}} +``` + +-> **Custom Roles**: If you're importing a IAM resource with a custom role, make sure to use the + full name of the custom role, e.g. `[projects/my-project|organizations/my-org]/roles/my-custom-role`. + +## User Project Overrides + +This resource supports [User Project Overrides](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#user_project_override).