diff --git a/.changelog/11094.txt b/.changelog/11094.txt new file mode 100644 index 00000000000..c41381699b1 --- /dev/null +++ b/.changelog/11094.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +privilegedaccessmanager: promoted `google_privileged_access_manager_entitlement` resource from beta to ga +``` \ No newline at end of file diff --git a/google/fwmodels/provider_model.go b/google/fwmodels/provider_model.go index 4118b94315d..886d9b2791a 100644 --- a/google/fwmodels/provider_model.go +++ b/google/fwmodels/provider_model.go @@ -120,6 +120,7 @@ type ProviderModel struct { OSConfigCustomEndpoint types.String `tfsdk:"os_config_custom_endpoint"` OSLoginCustomEndpoint types.String `tfsdk:"os_login_custom_endpoint"` PrivatecaCustomEndpoint types.String `tfsdk:"privateca_custom_endpoint"` + PrivilegedAccessManagerCustomEndpoint types.String `tfsdk:"privileged_access_manager_custom_endpoint"` PublicCACustomEndpoint types.String `tfsdk:"public_ca_custom_endpoint"` PubsubCustomEndpoint types.String `tfsdk:"pubsub_custom_endpoint"` PubsubLiteCustomEndpoint types.String `tfsdk:"pubsub_lite_custom_endpoint"` diff --git a/google/fwprovider/framework_provider.go b/google/fwprovider/framework_provider.go index d54ae812900..026d78a2122 100644 --- a/google/fwprovider/framework_provider.go +++ b/google/fwprovider/framework_provider.go @@ -696,6 +696,12 @@ func (p *FrameworkProvider) Schema(_ context.Context, _ provider.SchemaRequest, transport_tpg.CustomEndpointValidator(), }, }, + "privileged_access_manager_custom_endpoint": &schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + transport_tpg.CustomEndpointValidator(), + }, + }, "public_ca_custom_endpoint": &schema.StringAttribute{ Optional: true, Validators: []validator.String{ diff --git a/google/fwtransport/framework_config.go b/google/fwtransport/framework_config.go index 24bbcdfb6d4..e5ff8390134 100644 --- a/google/fwtransport/framework_config.go +++ b/google/fwtransport/framework_config.go @@ -143,6 +143,7 @@ type FrameworkProviderConfig struct { OSConfigBasePath string OSLoginBasePath string PrivatecaBasePath string + PrivilegedAccessManagerBasePath string PublicCABasePath string PubsubBasePath string PubsubLiteBasePath string @@ -301,6 +302,7 @@ func (p *FrameworkProviderConfig) LoadAndValidateFramework(ctx context.Context, p.OSConfigBasePath = data.OSConfigCustomEndpoint.ValueString() p.OSLoginBasePath = data.OSLoginCustomEndpoint.ValueString() p.PrivatecaBasePath = data.PrivatecaCustomEndpoint.ValueString() + p.PrivilegedAccessManagerBasePath = data.PrivilegedAccessManagerCustomEndpoint.ValueString() p.PublicCABasePath = data.PublicCACustomEndpoint.ValueString() p.PubsubBasePath = data.PubsubCustomEndpoint.ValueString() p.PubsubLiteBasePath = data.PubsubLiteCustomEndpoint.ValueString() @@ -1191,6 +1193,14 @@ func (p *FrameworkProviderConfig) HandleDefaults(ctx context.Context, data *fwmo data.PrivatecaCustomEndpoint = types.StringValue(customEndpoint.(string)) } } + if data.PrivilegedAccessManagerCustomEndpoint.IsNull() { + customEndpoint := transport_tpg.MultiEnvDefault([]string{ + "GOOGLE_PRIVILEGED_ACCESS_MANAGER_CUSTOM_ENDPOINT", + }, transport_tpg.DefaultBasePaths[transport_tpg.PrivilegedAccessManagerBasePathKey]) + if customEndpoint != nil { + data.PrivilegedAccessManagerCustomEndpoint = types.StringValue(customEndpoint.(string)) + } + } if data.PublicCACustomEndpoint.IsNull() { customEndpoint := transport_tpg.MultiEnvDefault([]string{ "GOOGLE_PUBLIC_CA_CUSTOM_ENDPOINT", diff --git a/google/provider/provider.go b/google/provider/provider.go index 5e3f70fea31..1e651bda625 100644 --- a/google/provider/provider.go +++ b/google/provider/provider.go @@ -604,6 +604,11 @@ func Provider() *schema.Provider { Optional: true, ValidateFunc: transport_tpg.ValidateCustomEndpoint, }, + "privileged_access_manager_custom_endpoint": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: transport_tpg.ValidateCustomEndpoint, + }, "public_ca_custom_endpoint": { Type: schema.TypeString, Optional: true, @@ -1023,6 +1028,7 @@ func ProviderConfigure(ctx context.Context, d *schema.ResourceData, p *schema.Pr config.OSConfigBasePath = d.Get("os_config_custom_endpoint").(string) config.OSLoginBasePath = d.Get("os_login_custom_endpoint").(string) config.PrivatecaBasePath = d.Get("privateca_custom_endpoint").(string) + config.PrivilegedAccessManagerBasePath = d.Get("privileged_access_manager_custom_endpoint").(string) config.PublicCABasePath = d.Get("public_ca_custom_endpoint").(string) config.PubsubBasePath = d.Get("pubsub_custom_endpoint").(string) config.PubsubLiteBasePath = d.Get("pubsub_lite_custom_endpoint").(string) diff --git a/google/provider/provider_mmv1_resources.go b/google/provider/provider_mmv1_resources.go index 3f77fb8346d..ccf5fa70cd3 100644 --- a/google/provider/provider_mmv1_resources.go +++ b/google/provider/provider_mmv1_resources.go @@ -96,6 +96,7 @@ import ( "github.com/hashicorp/terraform-provider-google/google/services/osconfig" "github.com/hashicorp/terraform-provider-google/google/services/oslogin" "github.com/hashicorp/terraform-provider-google/google/services/privateca" + "github.com/hashicorp/terraform-provider-google/google/services/privilegedaccessmanager" "github.com/hashicorp/terraform-provider-google/google/services/publicca" "github.com/hashicorp/terraform-provider-google/google/services/pubsub" "github.com/hashicorp/terraform-provider-google/google/services/pubsublite" @@ -420,9 +421,9 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{ } // Resources -// Generated resources: 433 +// Generated resources: 434 // Generated IAM resources: 252 -// Total generated resources: 685 +// Total generated resources: 686 var generatedResources = map[string]*schema.Resource{ "google_folder_access_approval_settings": accessapproval.ResourceAccessApprovalFolderSettings(), "google_organization_access_approval_settings": accessapproval.ResourceAccessApprovalOrganizationSettings(), @@ -978,6 +979,7 @@ var generatedResources = map[string]*schema.Resource{ "google_privateca_certificate_template_iam_binding": tpgiamresource.ResourceIamBinding(privateca.PrivatecaCertificateTemplateIamSchema, privateca.PrivatecaCertificateTemplateIamUpdaterProducer, privateca.PrivatecaCertificateTemplateIdParseFunc), "google_privateca_certificate_template_iam_member": tpgiamresource.ResourceIamMember(privateca.PrivatecaCertificateTemplateIamSchema, privateca.PrivatecaCertificateTemplateIamUpdaterProducer, privateca.PrivatecaCertificateTemplateIdParseFunc), "google_privateca_certificate_template_iam_policy": tpgiamresource.ResourceIamPolicy(privateca.PrivatecaCertificateTemplateIamSchema, privateca.PrivatecaCertificateTemplateIamUpdaterProducer, privateca.PrivatecaCertificateTemplateIdParseFunc), + "google_privileged_access_manager_entitlement": privilegedaccessmanager.ResourcePrivilegedAccessManagerEntitlement(), "google_public_ca_external_account_key": publicca.ResourcePublicCAExternalAccountKey(), "google_pubsub_schema": pubsub.ResourcePubsubSchema(), "google_pubsub_schema_iam_binding": tpgiamresource.ResourceIamBinding(pubsub.PubsubSchemaIamSchema, pubsub.PubsubSchemaIamUpdaterProducer, pubsub.PubsubSchemaIdParseFunc), diff --git a/google/services/privilegedaccessmanager/privileged_access_manager_operation.go b/google/services/privilegedaccessmanager/privileged_access_manager_operation.go new file mode 100644 index 00000000000..a7aeff3a4a0 --- /dev/null +++ b/google/services/privilegedaccessmanager/privileged_access_manager_operation.go @@ -0,0 +1,89 @@ +// 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 privilegedaccessmanager + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +type PrivilegedAccessManagerOperationWaiter struct { + Config *transport_tpg.Config + UserAgent string + tpgresource.CommonOperationWaiter +} + +func (w *PrivilegedAccessManagerOperationWaiter) QueryOp() (interface{}, error) { + if w == nil { + return nil, fmt.Errorf("Cannot query operation, it's unset or nil.") + } + // Returns the proper get. + url := fmt.Sprintf("%s%s", w.Config.PrivilegedAccessManagerBasePath, w.CommonOperationWaiter.Op.Name) + + return transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: w.Config, + Method: "GET", + RawURL: url, + UserAgent: w.UserAgent, + }) +} + +func createPrivilegedAccessManagerWaiter(config *transport_tpg.Config, op map[string]interface{}, activity, userAgent string) (*PrivilegedAccessManagerOperationWaiter, error) { + w := &PrivilegedAccessManagerOperationWaiter{ + Config: config, + UserAgent: userAgent, + } + if err := w.CommonOperationWaiter.SetOp(op); err != nil { + return nil, err + } + return w, nil +} + +// nolint: deadcode,unused +func PrivilegedAccessManagerOperationWaitTimeWithResponse(config *transport_tpg.Config, op map[string]interface{}, response *map[string]interface{}, activity, userAgent string, timeout time.Duration) error { + w, err := createPrivilegedAccessManagerWaiter(config, op, activity, userAgent) + if err != nil { + return err + } + if err := tpgresource.OperationWait(w, activity, timeout, config.PollInterval); err != nil { + return err + } + rawResponse := []byte(w.CommonOperationWaiter.Op.Response) + if len(rawResponse) == 0 { + return errors.New("`resource` not set in operation response") + } + return json.Unmarshal(rawResponse, response) +} + +func PrivilegedAccessManagerOperationWaitTime(config *transport_tpg.Config, op map[string]interface{}, activity, userAgent string, timeout time.Duration) error { + if val, ok := op["name"]; !ok || val == "" { + // This was a synchronous call - there is no operation to wait for. + return nil + } + w, err := createPrivilegedAccessManagerWaiter(config, op, activity, userAgent) + if err != nil { + // If w is nil, the op was synchronous. + return err + } + return tpgresource.OperationWait(w, activity, timeout, config.PollInterval) +} diff --git a/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement.go b/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement.go new file mode 100644 index 00000000000..f75b38b1860 --- /dev/null +++ b/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement.go @@ -0,0 +1,1355 @@ +// 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 privilegedaccessmanager + +import ( + "fmt" + "log" + "net/http" + "reflect" + "regexp" + "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" +) + +const deletedRegexp = `^deleted:` + +func validateDeletedPrincipals(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if regexp.MustCompile(deletedRegexp).MatchString(value) { + errors = append(errors, fmt.Errorf( + "Terraform does not support IAM policies for deleted principals: %s", k)) + } + + return +} + +const entitlementIdRegexp = `^[a-z][a-z0-9-]{3,62}$` + +func validateEntitlementId(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if !regexp.MustCompile(entitlementIdRegexp).MatchString(value) { + errors = append(errors, fmt.Errorf( + "Entitlement Id should be 4-63 characters, and valid characters are '[a-z]', '[0-9]', and '-'. The first character should be from [a-z]. : %s", k)) + } + + return +} + +func ResourcePrivilegedAccessManagerEntitlement() *schema.Resource { + return &schema.Resource{ + Create: resourcePrivilegedAccessManagerEntitlementCreate, + Read: resourcePrivilegedAccessManagerEntitlementRead, + Update: resourcePrivilegedAccessManagerEntitlementUpdate, + Delete: resourcePrivilegedAccessManagerEntitlementDelete, + + Importer: &schema.ResourceImporter{ + State: resourcePrivilegedAccessManagerEntitlementImport, + }, + + 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{ + "eligible_users": { + Type: schema.TypeList, + Required: true, + Description: `Who can create Grants using Entitlement. This list should contain at most one entry`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "principals": { + Type: schema.TypeSet, + Required: true, + Description: `Users who are being allowed for the operation. Each entry should be a valid v1 IAM Principal Identifier. Format for these is documented at "https://cloud.google.com/iam/docs/principal-identifiers#v1"`, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateDeletedPrincipals, + }, + Set: schema.HashString, + }, + }, + }, + }, + "entitlement_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateEntitlementId, + Description: `The ID to use for this Entitlement. This will become the last part of the resource name. +This value should be 4-63 characters, and valid characters are "[a-z]", "[0-9]", and "-". The first character should be from [a-z]. +This value should be unique among all other Entitlements under the specified 'parent'.`, + }, + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The region of the Entitlement resource.`, + }, + "max_request_duration": { + Type: schema.TypeString, + Required: true, + Description: `The maximum amount of time for which access would be granted for a request. +A requester can choose to ask for access for less than this duration but never more. +Format: calculate the time in seconds and concatenate it with 's' i.e. 2 hours = "7200s", 45 minutes = "2700s"`, + }, + "parent": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Format: projects/{project-id|project-number} or organizations/{organization-number} or folders/{folder-number}`, + }, + "privileged_access": { + Type: schema.TypeList, + Required: true, + Description: `Privileged access that this service can be used to gate.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "gcp_iam_access": { + Type: schema.TypeList, + Required: true, + Description: `GcpIamAccess represents IAM based access control on a GCP resource. Refer to https://cloud.google.com/iam/docs to understand more about IAM.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource": { + Type: schema.TypeString, + Required: true, + Description: `Name of the resource.`, + }, + "resource_type": { + Type: schema.TypeString, + Required: true, + Description: `The type of this resource.`, + }, + "role_bindings": { + Type: schema.TypeList, + Required: true, + Description: `Role bindings to be created on successful grant.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "role": { + Type: schema.TypeString, + Required: true, + Description: `IAM role to be granted. https://cloud.google.com/iam/docs/roles-overview.`, + }, + "condition_expression": { + Type: schema.TypeString, + Optional: true, + Description: `The expression field of the IAM condition to be associated with the role. If specified, a user with an active grant for this entitlement would be able to access the resource only if this condition evaluates to true for their request. +https://cloud.google.com/iam/docs/conditions-overview#attributes.`, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "requester_justification_config": { + Type: schema.TypeList, + Required: true, + Description: `Defines the ways in which a requester should provide the justification while requesting for access.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "not_mandatory": { + Type: schema.TypeList, + Optional: true, + Description: `The justification is not mandatory but can be provided in any of the supported formats.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{}, + }, + ConflictsWith: []string{}, + }, + "unstructured": { + Type: schema.TypeList, + Optional: true, + Description: `The requester has to provide a justification in the form of free flowing text.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{}, + }, + ConflictsWith: []string{}, + }, + }, + }, + }, + "additional_notification_targets": { + Type: schema.TypeList, + Optional: true, + Description: `AdditionalNotificationTargets includes email addresses to be notified.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "admin_email_recipients": { + Type: schema.TypeSet, + Optional: true, + Description: `Optional. Additional email addresses to be notified when a principal(requester) is granted access.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + }, + "requester_email_recipients": { + Type: schema.TypeSet, + Optional: true, + Description: `Optional. Additional email address to be notified about an eligible entitlement.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + }, + }, + }, + }, + "approval_workflow": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `The approvals needed before access will be granted to a requester. +No approvals will be needed if this field is null. Different types of approval workflows that can be used to gate privileged access granting.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "manual_approvals": { + Type: schema.TypeList, + Required: true, + Description: `A manual approval workflow where users who are designated as approvers need to call the ApproveGrant/DenyGrant APIs for an Grant. +The workflow can consist of multiple serial steps where each step defines who can act as Approver in that step and how many of those users should approve before the workflow moves to the next step. +This can be used to create approval workflows such as +* Require an approval from any user in a group G. +* Require an approval from any k number of users from a Group G. +* Require an approval from any user in a group G and then from a user U. etc. +A single user might be part of 'approvers' ACL for multiple steps in this workflow but they can only approve once and that approval will only be considered to satisfy the approval step at which it was granted.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "steps": { + Type: schema.TypeList, + Required: true, + Description: `List of approval steps in this workflow. These steps would be followed in the specified order sequentially. 1 step is supported for now.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "approvers": { + Type: schema.TypeList, + Required: true, + Description: `The potential set of approvers in this step. This list should contain at only one entry.`, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "principals": { + Type: schema.TypeSet, + Required: true, + Description: `Users who are being allowed for the operation. Each entry should be a valid v1 IAM Principal Identifier. Format for these is documented at: https://cloud.google.com/iam/docs/principal-identifiers#v1`, + MinItems: 1, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateDeletedPrincipals, + }, + Set: schema.HashString, + }, + }, + }, + }, + "approvals_needed": { + Type: schema.TypeInt, + Optional: true, + Description: `How many users from the above list need to approve. +If there are not enough distinct users in the list above then the workflow +will indefinitely block. Should always be greater than 0. Currently 1 is the only +supported value.`, + }, + "approver_email_recipients": { + Type: schema.TypeSet, + Optional: true, + Description: `Optional. Additional email addresses to be notified when a grant is pending approval.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + }, + }, + }, + }, + "require_approver_justification": { + Type: schema.TypeBool, + Optional: true, + Description: `Optional. Do the approvers need to provide a justification for their actions?`, + }, + }, + }, + }, + }, + }, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. Create time stamp. A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. +Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z"`, + }, + "etag": { + Type: schema.TypeString, + Computed: true, + Description: `For Resource freshness validation (https://google.aip.dev/154)`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `Output Only. The entitlement's name follows a hierarchical structure, comprising the organization, folder, or project, alongside the region and a unique entitlement ID. +Formats: organizations/{organization-number}/locations/{region}/entitlements/{entitlement-id}, folders/{folder-number}/locations/{region}/entitlements/{entitlement-id}, and projects/{project-id|project-number}/locations/{region}/entitlements/{entitlement-id}.`, + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. The current state of the Entitlement.`, + }, + "update_time": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. Update time stamp. A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. +Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".`, + }, + }, + UseJSONNumber: true, + } +} + +func resourcePrivilegedAccessManagerEntitlementCreate(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{}) + eligibleUsersProp, err := expandPrivilegedAccessManagerEntitlementEligibleUsers(d.Get("eligible_users"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("eligible_users"); !tpgresource.IsEmptyValue(reflect.ValueOf(eligibleUsersProp)) && (ok || !reflect.DeepEqual(v, eligibleUsersProp)) { + obj["eligibleUsers"] = eligibleUsersProp + } + approvalWorkflowProp, err := expandPrivilegedAccessManagerEntitlementApprovalWorkflow(d.Get("approval_workflow"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("approval_workflow"); !tpgresource.IsEmptyValue(reflect.ValueOf(approvalWorkflowProp)) && (ok || !reflect.DeepEqual(v, approvalWorkflowProp)) { + obj["approvalWorkflow"] = approvalWorkflowProp + } + privilegedAccessProp, err := expandPrivilegedAccessManagerEntitlementPrivilegedAccess(d.Get("privileged_access"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("privileged_access"); !tpgresource.IsEmptyValue(reflect.ValueOf(privilegedAccessProp)) && (ok || !reflect.DeepEqual(v, privilegedAccessProp)) { + obj["privilegedAccess"] = privilegedAccessProp + } + maxRequestDurationProp, err := expandPrivilegedAccessManagerEntitlementMaxRequestDuration(d.Get("max_request_duration"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("max_request_duration"); !tpgresource.IsEmptyValue(reflect.ValueOf(maxRequestDurationProp)) && (ok || !reflect.DeepEqual(v, maxRequestDurationProp)) { + obj["maxRequestDuration"] = maxRequestDurationProp + } + etagProp, err := expandPrivilegedAccessManagerEntitlementEtag(d.Get("etag"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("etag"); !tpgresource.IsEmptyValue(reflect.ValueOf(etagProp)) && (ok || !reflect.DeepEqual(v, etagProp)) { + obj["etag"] = etagProp + } + requesterJustificationConfigProp, err := expandPrivilegedAccessManagerEntitlementRequesterJustificationConfig(d.Get("requester_justification_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("requester_justification_config"); ok || !reflect.DeepEqual(v, requesterJustificationConfigProp) { + obj["requesterJustificationConfig"] = requesterJustificationConfigProp + } + additionalNotificationTargetsProp, err := expandPrivilegedAccessManagerEntitlementAdditionalNotificationTargets(d.Get("additional_notification_targets"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("additional_notification_targets"); ok || !reflect.DeepEqual(v, additionalNotificationTargetsProp) { + obj["additionalNotificationTargets"] = additionalNotificationTargetsProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{PrivilegedAccessManagerBasePath}}{{parent}}/locations/{{location}}/entitlements?entitlementId={{entitlement_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Entitlement: %#v", obj) + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + headers := make(http.Header) + 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), + Headers: headers, + }) + if err != nil { + return fmt.Errorf("Error creating Entitlement: %s", err) + } + + // Store the ID now + id, err := tpgresource.ReplaceVars(d, config, "{{parent}}/locations/{{location}}/entitlements/{{entitlement_id}}") + 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 = PrivilegedAccessManagerOperationWaitTimeWithResponse( + config, res, &opRes, "Creating Entitlement", userAgent, + d.Timeout(schema.TimeoutCreate)) + if err != nil { + // The resource didn't actually create + d.SetId("") + + return fmt.Errorf("Error waiting to create Entitlement: %s", err) + } + + if err := d.Set("name", flattenPrivilegedAccessManagerEntitlementName(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, "{{parent}}/locations/{{location}}/entitlements/{{entitlement_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating Entitlement %q: %#v", d.Id(), res) + + return resourcePrivilegedAccessManagerEntitlementRead(d, meta) +} + +func resourcePrivilegedAccessManagerEntitlementRead(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, "{{PrivilegedAccessManagerBasePath}}{{parent}}/locations/{{location}}/entitlements/{{entitlement_id}}") + if err != nil { + return err + } + + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + headers := make(http.Header) + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Headers: headers, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("PrivilegedAccessManagerEntitlement %q", d.Id())) + } + + if err := d.Set("name", flattenPrivilegedAccessManagerEntitlementName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading Entitlement: %s", err) + } + if err := d.Set("create_time", flattenPrivilegedAccessManagerEntitlementCreateTime(res["createTime"], d, config)); err != nil { + return fmt.Errorf("Error reading Entitlement: %s", err) + } + if err := d.Set("update_time", flattenPrivilegedAccessManagerEntitlementUpdateTime(res["updateTime"], d, config)); err != nil { + return fmt.Errorf("Error reading Entitlement: %s", err) + } + if err := d.Set("eligible_users", flattenPrivilegedAccessManagerEntitlementEligibleUsers(res["eligibleUsers"], d, config)); err != nil { + return fmt.Errorf("Error reading Entitlement: %s", err) + } + if err := d.Set("approval_workflow", flattenPrivilegedAccessManagerEntitlementApprovalWorkflow(res["approvalWorkflow"], d, config)); err != nil { + return fmt.Errorf("Error reading Entitlement: %s", err) + } + if err := d.Set("privileged_access", flattenPrivilegedAccessManagerEntitlementPrivilegedAccess(res["privilegedAccess"], d, config)); err != nil { + return fmt.Errorf("Error reading Entitlement: %s", err) + } + if err := d.Set("max_request_duration", flattenPrivilegedAccessManagerEntitlementMaxRequestDuration(res["maxRequestDuration"], d, config)); err != nil { + return fmt.Errorf("Error reading Entitlement: %s", err) + } + if err := d.Set("state", flattenPrivilegedAccessManagerEntitlementState(res["state"], d, config)); err != nil { + return fmt.Errorf("Error reading Entitlement: %s", err) + } + if err := d.Set("etag", flattenPrivilegedAccessManagerEntitlementEtag(res["etag"], d, config)); err != nil { + return fmt.Errorf("Error reading Entitlement: %s", err) + } + if err := d.Set("requester_justification_config", flattenPrivilegedAccessManagerEntitlementRequesterJustificationConfig(res["requesterJustificationConfig"], d, config)); err != nil { + return fmt.Errorf("Error reading Entitlement: %s", err) + } + if err := d.Set("additional_notification_targets", flattenPrivilegedAccessManagerEntitlementAdditionalNotificationTargets(res["additionalNotificationTargets"], d, config)); err != nil { + return fmt.Errorf("Error reading Entitlement: %s", err) + } + + return nil +} + +func resourcePrivilegedAccessManagerEntitlementUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + obj := make(map[string]interface{}) + eligibleUsersProp, err := expandPrivilegedAccessManagerEntitlementEligibleUsers(d.Get("eligible_users"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("eligible_users"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, eligibleUsersProp)) { + obj["eligibleUsers"] = eligibleUsersProp + } + privilegedAccessProp, err := expandPrivilegedAccessManagerEntitlementPrivilegedAccess(d.Get("privileged_access"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("privileged_access"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, privilegedAccessProp)) { + obj["privilegedAccess"] = privilegedAccessProp + } + maxRequestDurationProp, err := expandPrivilegedAccessManagerEntitlementMaxRequestDuration(d.Get("max_request_duration"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("max_request_duration"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, maxRequestDurationProp)) { + obj["maxRequestDuration"] = maxRequestDurationProp + } + etagProp, err := expandPrivilegedAccessManagerEntitlementEtag(d.Get("etag"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("etag"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, etagProp)) { + obj["etag"] = etagProp + } + requesterJustificationConfigProp, err := expandPrivilegedAccessManagerEntitlementRequesterJustificationConfig(d.Get("requester_justification_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("requester_justification_config"); ok || !reflect.DeepEqual(v, requesterJustificationConfigProp) { + obj["requesterJustificationConfig"] = requesterJustificationConfigProp + } + additionalNotificationTargetsProp, err := expandPrivilegedAccessManagerEntitlementAdditionalNotificationTargets(d.Get("additional_notification_targets"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("additional_notification_targets"); ok || !reflect.DeepEqual(v, additionalNotificationTargetsProp) { + obj["additionalNotificationTargets"] = additionalNotificationTargetsProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{PrivilegedAccessManagerBasePath}}{{parent}}/locations/{{location}}/entitlements/{{entitlement_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating Entitlement %q: %#v", d.Id(), obj) + headers := make(http.Header) + updateMask := []string{} + + if d.HasChange("eligible_users") { + updateMask = append(updateMask, "eligibleUsers") + } + + if d.HasChange("privileged_access") { + updateMask = append(updateMask, "privilegedAccess") + } + + if d.HasChange("max_request_duration") { + updateMask = append(updateMask, "maxRequestDuration") + } + + if d.HasChange("etag") { + updateMask = append(updateMask, "etag") + } + + if d.HasChange("requester_justification_config") { + updateMask = append(updateMask, "requesterJustificationConfig") + } + + if d.HasChange("additional_notification_targets") { + updateMask = append(updateMask, "additionalNotificationTargets") + } + // 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 + } + approvalWorkflowProp, err := expandPrivilegedAccessManagerEntitlementApprovalWorkflow(d.Get("approval_workflow"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("approval_workflow"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, approvalWorkflowProp)) { + obj["approvalWorkflow"] = approvalWorkflowProp + } + if d.HasChange("approval_workflow") { + updateMask = append(updateMask, "approvalWorkflow") + } + 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 + } + + // if updateMask is empty we are not updating anything so skip the post + if len(updateMask) > 0 { + 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), + Headers: headers, + }) + + if err != nil { + return fmt.Errorf("Error updating Entitlement %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating Entitlement %q: %#v", d.Id(), res) + } + + err = PrivilegedAccessManagerOperationWaitTime( + config, res, "Updating Entitlement", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + } + + return resourcePrivilegedAccessManagerEntitlementRead(d, meta) +} + +func resourcePrivilegedAccessManagerEntitlementDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + url, err := tpgresource.ReplaceVars(d, config, "{{PrivilegedAccessManagerBasePath}}{{parent}}/locations/{{location}}/entitlements/{{entitlement_id}}?force=true") + if err != nil { + return err + } + + var obj map[string]interface{} + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + headers := make(http.Header) + + log.Printf("[DEBUG] Deleting Entitlement %q", d.Id()) + 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), + Headers: headers, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, "Entitlement") + } + + err = PrivilegedAccessManagerOperationWaitTime( + config, res, "Deleting Entitlement", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting Entitlement %q: %#v", d.Id(), res) + return nil +} + +func resourcePrivilegedAccessManagerEntitlementImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*transport_tpg.Config) + if err := tpgresource.ParseImportId([]string{ + "^(?P.+)/locations/(?P[^/]+)/entitlements/(?P[^/]+)$", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := tpgresource.ReplaceVars(d, config, "{{parent}}/locations/{{location}}/entitlements/{{entitlement_id}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenPrivilegedAccessManagerEntitlementName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenPrivilegedAccessManagerEntitlementCreateTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenPrivilegedAccessManagerEntitlementUpdateTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenPrivilegedAccessManagerEntitlementEligibleUsers(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{}{ + "principals": flattenPrivilegedAccessManagerEntitlementEligibleUsersPrincipals(original["principals"], d, config), + }) + } + return transformed +} +func flattenPrivilegedAccessManagerEntitlementEligibleUsersPrincipals(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return schema.NewSet(schema.HashString, v.([]interface{})) +} + +func flattenPrivilegedAccessManagerEntitlementApprovalWorkflow(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["manual_approvals"] = + flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovals(original["manualApprovals"], d, config) + return []interface{}{transformed} +} +func flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovals(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["require_approver_justification"] = + flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsRequireApproverJustification(original["requireApproverJustification"], d, config) + transformed["steps"] = + flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsSteps(original["steps"], d, config) + return []interface{}{transformed} +} +func flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsRequireApproverJustification(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsSteps(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{}{ + "approvers": flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApprovers(original["approvers"], d, config), + "approvals_needed": flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApprovalsNeeded(original["approvalsNeeded"], d, config), + "approver_email_recipients": flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApproverEmailRecipients(original["approverEmailRecipients"], d, config), + }) + } + return transformed +} +func flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApprovers(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{}{ + "principals": flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApproversPrincipals(original["principals"], d, config), + }) + } + return transformed +} +func flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApproversPrincipals(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return schema.NewSet(schema.HashString, v.([]interface{})) +} + +func flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApprovalsNeeded(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := tpgresource.StringToFixed64(strVal); err == nil { + return intVal + } + } + + // number values are represented as float64 + if floatVal, ok := v.(float64); ok { + intVal := int(floatVal) + return intVal + } + + return v // let terraform core handle it otherwise +} + +func flattenPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApproverEmailRecipients(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return schema.NewSet(schema.HashString, v.([]interface{})) +} + +func flattenPrivilegedAccessManagerEntitlementPrivilegedAccess(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["gcp_iam_access"] = + flattenPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccess(original["gcpIamAccess"], d, config) + return []interface{}{transformed} +} +func flattenPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccess(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["resource_type"] = + flattenPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessResourceType(original["resourceType"], d, config) + transformed["resource"] = + flattenPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessResource(original["resource"], d, config) + transformed["role_bindings"] = + flattenPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessRoleBindings(original["roleBindings"], d, config) + return []interface{}{transformed} +} +func flattenPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessResourceType(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessResource(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessRoleBindings(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{}{ + "role": flattenPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessRoleBindingsRole(original["role"], d, config), + "condition_expression": flattenPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessRoleBindingsConditionExpression(original["conditionExpression"], d, config), + }) + } + return transformed +} +func flattenPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessRoleBindingsRole(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessRoleBindingsConditionExpression(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenPrivilegedAccessManagerEntitlementMaxRequestDuration(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenPrivilegedAccessManagerEntitlementState(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenPrivilegedAccessManagerEntitlementEtag(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenPrivilegedAccessManagerEntitlementRequesterJustificationConfig(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + transformed := make(map[string]interface{}) + transformed["not_mandatory"] = + flattenPrivilegedAccessManagerEntitlementRequesterJustificationConfigNotMandatory(original["notMandatory"], d, config) + transformed["unstructured"] = + flattenPrivilegedAccessManagerEntitlementRequesterJustificationConfigUnstructured(original["unstructured"], d, config) + return []interface{}{transformed} +} +func flattenPrivilegedAccessManagerEntitlementRequesterJustificationConfigNotMandatory(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + transformed := make(map[string]interface{}) + return []interface{}{transformed} +} + +func flattenPrivilegedAccessManagerEntitlementRequesterJustificationConfigUnstructured(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + transformed := make(map[string]interface{}) + return []interface{}{transformed} +} + +func flattenPrivilegedAccessManagerEntitlementAdditionalNotificationTargets(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + transformed := make(map[string]interface{}) + transformed["admin_email_recipients"] = + flattenPrivilegedAccessManagerEntitlementAdditionalNotificationTargetsAdminEmailRecipients(original["adminEmailRecipients"], d, config) + transformed["requester_email_recipients"] = + flattenPrivilegedAccessManagerEntitlementAdditionalNotificationTargetsRequesterEmailRecipients(original["requesterEmailRecipients"], d, config) + return []interface{}{transformed} +} +func flattenPrivilegedAccessManagerEntitlementAdditionalNotificationTargetsAdminEmailRecipients(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return schema.NewSet(schema.HashString, v.([]interface{})) +} + +func flattenPrivilegedAccessManagerEntitlementAdditionalNotificationTargetsRequesterEmailRecipients(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return schema.NewSet(schema.HashString, v.([]interface{})) +} + +func expandPrivilegedAccessManagerEntitlementEligibleUsers(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{}) + + transformedPrincipals, err := expandPrivilegedAccessManagerEntitlementEligibleUsersPrincipals(original["principals"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPrincipals); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["principals"] = transformedPrincipals + } + + req = append(req, transformed) + } + return req, nil +} + +func expandPrivilegedAccessManagerEntitlementEligibleUsersPrincipals(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + v = v.(*schema.Set).List() + return v, nil +} + +func expandPrivilegedAccessManagerEntitlementApprovalWorkflow(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{}) + + transformedManualApprovals, err := expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovals(original["manual_approvals"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedManualApprovals); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["manualApprovals"] = transformedManualApprovals + } + + return transformed, nil +} + +func expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovals(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{}) + + transformedRequireApproverJustification, err := expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsRequireApproverJustification(original["require_approver_justification"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRequireApproverJustification); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["requireApproverJustification"] = transformedRequireApproverJustification + } + + transformedSteps, err := expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsSteps(original["steps"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSteps); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["steps"] = transformedSteps + } + + return transformed, nil +} + +func expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsRequireApproverJustification(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsSteps(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{}) + + transformedApprovers, err := expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApprovers(original["approvers"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedApprovers); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["approvers"] = transformedApprovers + } + + transformedApprovalsNeeded, err := expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApprovalsNeeded(original["approvals_needed"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedApprovalsNeeded); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["approvalsNeeded"] = transformedApprovalsNeeded + } + + transformedApproverEmailRecipients, err := expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApproverEmailRecipients(original["approver_email_recipients"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedApproverEmailRecipients); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["approverEmailRecipients"] = transformedApproverEmailRecipients + } + + req = append(req, transformed) + } + return req, nil +} + +func expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApprovers(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{}) + + transformedPrincipals, err := expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApproversPrincipals(original["principals"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPrincipals); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["principals"] = transformedPrincipals + } + + req = append(req, transformed) + } + return req, nil +} + +func expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApproversPrincipals(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + v = v.(*schema.Set).List() + return v, nil +} + +func expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApprovalsNeeded(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandPrivilegedAccessManagerEntitlementApprovalWorkflowManualApprovalsStepsApproverEmailRecipients(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + v = v.(*schema.Set).List() + return v, nil +} + +func expandPrivilegedAccessManagerEntitlementPrivilegedAccess(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{}) + + transformedGcpIamAccess, err := expandPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccess(original["gcp_iam_access"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedGcpIamAccess); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["gcpIamAccess"] = transformedGcpIamAccess + } + + return transformed, nil +} + +func expandPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccess(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{}) + + transformedResourceType, err := expandPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessResourceType(original["resource_type"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceType); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["resourceType"] = transformedResourceType + } + + transformedResource, err := expandPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessResource(original["resource"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResource); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["resource"] = transformedResource + } + + transformedRoleBindings, err := expandPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessRoleBindings(original["role_bindings"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRoleBindings); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["roleBindings"] = transformedRoleBindings + } + + return transformed, nil +} + +func expandPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessResourceType(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessResource(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessRoleBindings(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{}) + + transformedRole, err := expandPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessRoleBindingsRole(original["role"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRole); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["role"] = transformedRole + } + + transformedConditionExpression, err := expandPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessRoleBindingsConditionExpression(original["condition_expression"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedConditionExpression); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["conditionExpression"] = transformedConditionExpression + } + + req = append(req, transformed) + } + return req, nil +} + +func expandPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessRoleBindingsRole(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandPrivilegedAccessManagerEntitlementPrivilegedAccessGcpIamAccessRoleBindingsConditionExpression(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandPrivilegedAccessManagerEntitlementMaxRequestDuration(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandPrivilegedAccessManagerEntitlementEtag(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandPrivilegedAccessManagerEntitlementRequesterJustificationConfig(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 { + return nil, nil + } + + if l[0] == nil { + transformed := make(map[string]interface{}) + return transformed, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedNotMandatory, err := expandPrivilegedAccessManagerEntitlementRequesterJustificationConfigNotMandatory(original["not_mandatory"], d, config) + if err != nil { + return nil, err + } else { + transformed["notMandatory"] = transformedNotMandatory + } + + transformedUnstructured, err := expandPrivilegedAccessManagerEntitlementRequesterJustificationConfigUnstructured(original["unstructured"], d, config) + if err != nil { + return nil, err + } else { + transformed["unstructured"] = transformedUnstructured + } + + return transformed, nil +} + +func expandPrivilegedAccessManagerEntitlementRequesterJustificationConfigNotMandatory(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 { + return nil, nil + } + + if l[0] == nil { + transformed := make(map[string]interface{}) + return transformed, nil + } + transformed := make(map[string]interface{}) + + return transformed, nil +} + +func expandPrivilegedAccessManagerEntitlementRequesterJustificationConfigUnstructured(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 { + return nil, nil + } + + if l[0] == nil { + transformed := make(map[string]interface{}) + return transformed, nil + } + transformed := make(map[string]interface{}) + + return transformed, nil +} + +func expandPrivilegedAccessManagerEntitlementAdditionalNotificationTargets(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 { + return nil, nil + } + + if l[0] == nil { + transformed := make(map[string]interface{}) + return transformed, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedAdminEmailRecipients, err := expandPrivilegedAccessManagerEntitlementAdditionalNotificationTargetsAdminEmailRecipients(original["admin_email_recipients"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAdminEmailRecipients); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["adminEmailRecipients"] = transformedAdminEmailRecipients + } + + transformedRequesterEmailRecipients, err := expandPrivilegedAccessManagerEntitlementAdditionalNotificationTargetsRequesterEmailRecipients(original["requester_email_recipients"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRequesterEmailRecipients); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["requesterEmailRecipients"] = transformedRequesterEmailRecipients + } + + return transformed, nil +} + +func expandPrivilegedAccessManagerEntitlementAdditionalNotificationTargetsAdminEmailRecipients(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + v = v.(*schema.Set).List() + return v, nil +} + +func expandPrivilegedAccessManagerEntitlementAdditionalNotificationTargetsRequesterEmailRecipients(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + v = v.(*schema.Set).List() + return v, nil +} diff --git a/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement_generated_test.go b/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement_generated_test.go new file mode 100644 index 00000000000..45f207dd8f5 --- /dev/null +++ b/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement_generated_test.go @@ -0,0 +1,150 @@ +// 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 privilegedaccessmanager_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 TestAccPrivilegedAccessManagerEntitlement_privilegedAccessManagerEntitlementBasicExample(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: testAccCheckPrivilegedAccessManagerEntitlementDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccPrivilegedAccessManagerEntitlement_privilegedAccessManagerEntitlementBasicExample(context), + }, + { + ResourceName: "google_privileged_access_manager_entitlement.tfentitlement", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"entitlement_id", "location", "parent"}, + }, + }, + }) +} + +func testAccPrivilegedAccessManagerEntitlement_privilegedAccessManagerEntitlementBasicExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_privileged_access_manager_entitlement" "tfentitlement" { + entitlement_id = "tf-test-example-entitlement%{random_suffix}" + location = "global" + max_request_duration = "43200s" + parent = "projects/%{project}" + requester_justification_config { + unstructured{} + } + eligible_users { + principals = [ + "group:test@google.com" + ] + } + privileged_access{ + gcp_iam_access{ + role_bindings{ + role = "roles/storage.admin" + condition_expression = "request.time < timestamp(\"2024-04-23T18:30:00.000Z\")" + } + resource = "//cloudresourcemanager.googleapis.com/projects/%{project}" + resource_type = "cloudresourcemanager.googleapis.com/Project" + } + } + additional_notification_targets { + admin_email_recipients = [ + "user@example.com", + ] + requester_email_recipients = [ + "user@example.com" + ] + } + approval_workflow { + manual_approvals { + require_approver_justification = true + steps { + approvals_needed = 1 + approver_email_recipients = [ + "user@example.com" + ] + approvers { + principals = [ + "group:test@google.com" + ] + } + } + } + } +} +`, context) +} + +func testAccCheckPrivilegedAccessManagerEntitlementDestroyProducer(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_privileged_access_manager_entitlement" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := acctest.GoogleProviderConfig(t) + + url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{PrivilegedAccessManagerBasePath}}{{parent}}/locations/{{location}}/entitlements/{{entitlement_id}}") + 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("PrivilegedAccessManagerEntitlement still exists at %s", url) + } + } + + return nil + } +} diff --git a/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement_sweeper.go b/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement_sweeper.go new file mode 100644 index 00000000000..551c78be9e2 --- /dev/null +++ b/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement_sweeper.go @@ -0,0 +1,143 @@ +// 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 privilegedaccessmanager + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/sweeper" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func init() { + sweeper.AddTestSweepers("PrivilegedAccessManagerEntitlement", testSweepPrivilegedAccessManagerEntitlement) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepPrivilegedAccessManagerEntitlement(region string) error { + resourceName := "PrivilegedAccessManagerEntitlement" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sweeper.SharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + t := &testing.T{} + billingId := envvar.GetTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &tpgresource.ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://privilegedaccessmanager.googleapis.com/v1/{{parent}}/locations/{{location}}/entitlements", "?")[0] + listUrl, err := tpgresource.ReplaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: config.Project, + RawURL: listUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["entitlements"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + var name string + // Id detected in the delete URL, attempt to use id. + if obj["id"] != nil { + name = tpgresource.GetResourceNameFromSelfLink(obj["id"].(string)) + } else if obj["name"] != nil { + name = tpgresource.GetResourceNameFromSelfLink(obj["name"].(string)) + } else { + log.Printf("[INFO][SWEEPER_LOG] %s resource name and id were nil", resourceName) + return nil + } + // Skip resources that shouldn't be sweeped + if !sweeper.IsSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://privilegedaccessmanager.googleapis.com/v1/{{parent}}/locations/{{location}}/entitlements/{{entitlement_id}}?force=true" + deleteUrl, err := tpgresource.ReplaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: config.Project, + RawURL: deleteUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + + return nil +} diff --git a/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement_test.go b/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement_test.go index bd5f4bfbd10..5fb60bfce07 100644 --- a/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement_test.go +++ b/google/services/privilegedaccessmanager/resource_privileged_access_manager_entitlement_test.go @@ -1,3 +1,133 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package privilegedaccessmanager_test + +import ( + "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 TestAccPrivilegedAccessManagerEntitlement_privilegedAccessManagerEntitlementProjectExample_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "project_name": envvar.GetTestProjectFromEnv(), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckPrivilegedAccessManagerEntitlementDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccPrivilegedAccessManagerEntitlement_privilegedAccessManagerEntitlementBasicExample_basic(context), + }, + { + ResourceName: "google_privileged_access_manager_entitlement.tfentitlement", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "entitlement_id", "parent"}, + }, + { + Config: testAccPrivilegedAccessManagerEntitlement_privilegedAccessManagerEntitlementBasicExample_update(context), + }, + { + ResourceName: "google_privileged_access_manager_entitlement.tfentitlement", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "entitlement_id", "parent"}, + }, + }, + }) +} + +func testAccPrivilegedAccessManagerEntitlement_privilegedAccessManagerEntitlementBasicExample_basic(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_privileged_access_manager_entitlement" "tfentitlement" { + entitlement_id = "tf-test-example-entitlement%{random_suffix}" + location = "global" + max_request_duration = "43200s" + parent = "projects/%{project_name}" + requester_justification_config { + unstructured{} + } + eligible_users { + principals = ["group:test@google.com"] + } + privileged_access{ + gcp_iam_access{ + role_bindings{ + role = "roles/storage.admin" + condition_expression = "request.time < timestamp(\"2024-04-23T18:30:00.000Z\")" + } + resource = "//cloudresourcemanager.googleapis.com/projects/%{project_name}" + resource_type = "cloudresourcemanager.googleapis.com/Project" + } + } + additional_notification_targets { + admin_email_recipients = ["user@example.com"] + requester_email_recipients = ["user@example.com"] + } + approval_workflow { + manual_approvals { + require_approver_justification = true + steps { + approvals_needed = 1 + approver_email_recipients = ["user@example.com"] + approvers { + principals = ["group:test@google.com"] + } + } + } + } +} +`, context) +} + +func testAccPrivilegedAccessManagerEntitlement_privilegedAccessManagerEntitlementBasicExample_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_privileged_access_manager_entitlement" "tfentitlement" { + entitlement_id = "tf-test-example-entitlement%{random_suffix}" + location = "global" + max_request_duration = "4300s" + parent = "projects/%{project_name}" + requester_justification_config { + not_mandatory{} + } + eligible_users { + principals = ["group:test@google.com"] + } + privileged_access{ + gcp_iam_access{ + role_bindings{ + role = "roles/storage.admin" + condition_expression = "request.time < timestamp(\"2024-04-23T18:30:00.000Z\")" + } + resource = "//cloudresourcemanager.googleapis.com/projects/%{project_name}" + resource_type = "cloudresourcemanager.googleapis.com/Project" + } + } + additional_notification_targets { + admin_email_recipients = ["user1@example.com"] + requester_email_recipients = ["user2@example.com"] + } + approval_workflow { + manual_approvals { + require_approver_justification = false + steps { + approvals_needed = 1 + approver_email_recipients = ["user3@example.com"] + approvers { + principals = ["group:test@google.com"] + } + } + } + } +} +`, context) +} diff --git a/google/sweeper/gcp_sweeper_test.go b/google/sweeper/gcp_sweeper_test.go index cf6ec9fae30..91a14669494 100644 --- a/google/sweeper/gcp_sweeper_test.go +++ b/google/sweeper/gcp_sweeper_test.go @@ -99,6 +99,7 @@ import ( _ "github.com/hashicorp/terraform-provider-google/google/services/osconfig" _ "github.com/hashicorp/terraform-provider-google/google/services/oslogin" _ "github.com/hashicorp/terraform-provider-google/google/services/privateca" + _ "github.com/hashicorp/terraform-provider-google/google/services/privilegedaccessmanager" _ "github.com/hashicorp/terraform-provider-google/google/services/publicca" _ "github.com/hashicorp/terraform-provider-google/google/services/pubsub" _ "github.com/hashicorp/terraform-provider-google/google/services/pubsublite" diff --git a/google/transport/config.go b/google/transport/config.go index c6423664439..fcda6a244ee 100644 --- a/google/transport/config.go +++ b/google/transport/config.go @@ -279,6 +279,7 @@ type Config struct { OSConfigBasePath string OSLoginBasePath string PrivatecaBasePath string + PrivilegedAccessManagerBasePath string PublicCABasePath string PubsubBasePath string PubsubLiteBasePath string @@ -417,6 +418,7 @@ const OrgPolicyBasePathKey = "OrgPolicy" const OSConfigBasePathKey = "OSConfig" const OSLoginBasePathKey = "OSLogin" const PrivatecaBasePathKey = "Privateca" +const PrivilegedAccessManagerBasePathKey = "PrivilegedAccessManager" const PublicCABasePathKey = "PublicCA" const PubsubBasePathKey = "Pubsub" const PubsubLiteBasePathKey = "PubsubLite" @@ -549,6 +551,7 @@ var DefaultBasePaths = map[string]string{ OSConfigBasePathKey: "https://osconfig.googleapis.com/v1/", OSLoginBasePathKey: "https://oslogin.googleapis.com/v1/", PrivatecaBasePathKey: "https://privateca.googleapis.com/v1/", + PrivilegedAccessManagerBasePathKey: "https://privilegedaccessmanager.googleapis.com/v1/", PublicCABasePathKey: "https://publicca.googleapis.com/v1/", PubsubBasePathKey: "https://pubsub.googleapis.com/v1/", PubsubLiteBasePathKey: "https://{{region}}-pubsublite.googleapis.com/v1/admin/", @@ -1120,6 +1123,11 @@ func SetEndpointDefaults(d *schema.ResourceData) error { "GOOGLE_PRIVATECA_CUSTOM_ENDPOINT", }, DefaultBasePaths[PrivatecaBasePathKey])) } + if d.Get("privileged_access_manager_custom_endpoint") == "" { + d.Set("privileged_access_manager_custom_endpoint", MultiEnvDefault([]string{ + "GOOGLE_PRIVILEGED_ACCESS_MANAGER_CUSTOM_ENDPOINT", + }, DefaultBasePaths[PrivilegedAccessManagerBasePathKey])) + } if d.Get("public_ca_custom_endpoint") == "" { d.Set("public_ca_custom_endpoint", MultiEnvDefault([]string{ "GOOGLE_PUBLIC_CA_CUSTOM_ENDPOINT", @@ -2240,6 +2248,7 @@ func ConfigureBasePaths(c *Config) { c.OSConfigBasePath = DefaultBasePaths[OSConfigBasePathKey] c.OSLoginBasePath = DefaultBasePaths[OSLoginBasePathKey] c.PrivatecaBasePath = DefaultBasePaths[PrivatecaBasePathKey] + c.PrivilegedAccessManagerBasePath = DefaultBasePaths[PrivilegedAccessManagerBasePathKey] c.PublicCABasePath = DefaultBasePaths[PublicCABasePathKey] c.PubsubBasePath = DefaultBasePaths[PubsubBasePathKey] c.PubsubLiteBasePath = DefaultBasePaths[PubsubLiteBasePathKey] diff --git a/website/docs/r/privileged_access_manager_entitlement.html.markdown b/website/docs/r/privileged_access_manager_entitlement.html.markdown index d452bf89f74..f65c4f59336 100644 --- a/website/docs/r/privileged_access_manager_entitlement.html.markdown +++ b/website/docs/r/privileged_access_manager_entitlement.html.markdown @@ -21,8 +21,6 @@ description: |- An Entitlement defines the eligibility of a set of users to obtain a predefined access for some time possibly after going through an approval workflow. -~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. -See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. ## Example Usage - Privileged Access Manager Entitlement Basic @@ -30,7 +28,6 @@ See [Provider Versions](https://terraform.io/docs/providers/google/guides/provid ```hcl resource "google_privileged_access_manager_entitlement" "tfentitlement" { - provider = google-beta entitlement_id = "example-entitlement" location = "global" max_request_duration = "43200s" @@ -39,7 +36,9 @@ resource "google_privileged_access_manager_entitlement" "tfentitlement" { unstructured{} } eligible_users { - principals = ["group:test@google.com"] + principals = [ + "group:test@google.com" + ] } privileged_access{ gcp_iam_access{ @@ -52,17 +51,25 @@ resource "google_privileged_access_manager_entitlement" "tfentitlement" { } } additional_notification_targets { - admin_email_recipients = ["user@example.com"] - requester_email_recipients = ["user@example.com"] + admin_email_recipients = [ + "user@example.com", + ] + requester_email_recipients = [ + "user@example.com" + ] } approval_workflow { manual_approvals { require_approver_justification = true steps { approvals_needed = 1 - approver_email_recipients = ["user@example.com"] + approver_email_recipients = [ + "user@example.com" + ] approvers { - principals = ["group:test@google.com"] + principals = [ + "group:test@google.com" + ] } } }