diff --git a/.changelog/12548.txt b/.changelog/12548.txt new file mode 100644 index 00000000000..cbfc071e725 --- /dev/null +++ b/.changelog/12548.txt @@ -0,0 +1,6 @@ +```release-note:new-resource +`google_redis_cluster_user_created_connections` +``` +```release-note:enhancement +redis: added `psc_service_attachments` field to `google_redis_cluster` resource, to enable use of the fine-grained resource `google_redis_cluster_user_created_connections` +``` \ No newline at end of file diff --git a/google/provider/provider_mmv1_resources.go b/google/provider/provider_mmv1_resources.go index 9b337b49088..bc7d1e8c9fe 100644 --- a/google/provider/provider_mmv1_resources.go +++ b/google/provider/provider_mmv1_resources.go @@ -471,9 +471,9 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{ } // Resources -// Generated resources: 511 +// Generated resources: 512 // Generated IAM resources: 270 -// Total generated resources: 781 +// Total generated resources: 782 var generatedResources = map[string]*schema.Resource{ "google_folder_access_approval_settings": accessapproval.ResourceAccessApprovalFolderSettings(), "google_organization_access_approval_settings": accessapproval.ResourceAccessApprovalOrganizationSettings(), @@ -1113,6 +1113,7 @@ var generatedResources = map[string]*schema.Resource{ "google_pubsub_lite_subscription": pubsublite.ResourcePubsubLiteSubscription(), "google_pubsub_lite_topic": pubsublite.ResourcePubsubLiteTopic(), "google_redis_cluster": redis.ResourceRedisCluster(), + "google_redis_cluster_user_created_connections": redis.ResourceRedisClusterUserCreatedConnections(), "google_redis_instance": redis.ResourceRedisInstance(), "google_resource_manager_lien": resourcemanager.ResourceResourceManagerLien(), "google_secret_manager_secret": secretmanager.ResourceSecretManagerSecret(), diff --git a/google/services/redis/resource_redis_cluster.go b/google/services/redis/resource_redis_cluster.go index c36e9568ec8..0fc1edcb441 100644 --- a/google/services/redis/resource_redis_cluster.go +++ b/google/services/redis/resource_redis_cluster.go @@ -65,24 +65,6 @@ func ResourceRedisCluster() *schema.Resource { Description: `Unique name of the resource in this scope including project and location using the form: projects/{projectId}/locations/{locationId}/clusters/{clusterId}`, }, - "psc_configs": { - Type: schema.TypeList, - Required: true, - Description: `Required. Each PscConfig configures the consumer network where two -network addresses will be designated to the cluster for client access. -Currently, only one PscConfig is supported.`, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "network": { - Type: schema.TypeString, - Required: true, - Description: `Required. The consumer network where the network address of -the discovery endpoint will be reserved, in the form of -projects/{network_project_id_or_number}/global/networks/{network_id}.`, - }, - }, - }, - }, "shard_count": { Type: schema.TypeInt, Required: true, @@ -398,6 +380,24 @@ If not provided, the current time will be used.`, }, }, }, + "psc_configs": { + Type: schema.TypeList, + Optional: true, + Description: `Required. Each PscConfig configures the consumer network where two +network addresses will be designated to the cluster for client access. +Currently, only one PscConfig is supported.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "network": { + Type: schema.TypeString, + Required: true, + Description: `Required. The consumer network where the network address of +the discovery endpoint will be reserved, in the form of +projects/{network_project_id_or_number}/global/networks/{network_id}.`, + }, + }, + }, + }, "redis_configs": { Type: schema.TypeMap, Optional: true, @@ -429,6 +429,7 @@ If not provided, encryption is disabled for the cluster. Default value: "TRANSIT }, "zone_distribution_config": { Type: schema.TypeList, + Computed: true, Optional: true, ForceNew: true, Description: `Immutable. Zone distribution config for Memorystore Redis cluster.`, @@ -567,6 +568,25 @@ resolution and up to nine fractional digits.`, }, }, }, + "psc_service_attachments": { + Type: schema.TypeList, + Computed: true, + Description: `Service attachment details to configure Psc connections.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "connection_type": { + Type: schema.TypeString, + Computed: true, + Description: `Type of a PSC connection targeting this service attachment.`, + }, + "service_attachment": { + Type: schema.TypeString, + Computed: true, + Description: `Service attachment URI which your self-created PscConnection should use as`, + }, + }, + }, + }, "size_gb": { Type: schema.TypeInt, Computed: true, @@ -869,6 +889,9 @@ func resourceRedisClusterRead(d *schema.ResourceData, meta interface{}) error { if err := d.Set("cross_cluster_replication_config", flattenRedisClusterCrossClusterReplicationConfig(res["crossClusterReplicationConfig"], d, config)); err != nil { return fmt.Errorf("Error reading Cluster: %s", err) } + if err := d.Set("psc_service_attachments", flattenRedisClusterPscServiceAttachments(res["pscServiceAttachments"], d, config)); err != nil { + return fmt.Errorf("Error reading Cluster: %s", err) + } return nil } @@ -1741,6 +1764,33 @@ func flattenRedisClusterCrossClusterReplicationConfigUpdateTime(v interface{}, d return v } +func flattenRedisClusterPscServiceAttachments(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{}{ + "service_attachment": flattenRedisClusterPscServiceAttachmentsServiceAttachment(original["serviceAttachment"], d, config), + "connection_type": flattenRedisClusterPscServiceAttachmentsConnectionType(original["connectionType"], d, config), + }) + } + return transformed +} +func flattenRedisClusterPscServiceAttachmentsServiceAttachment(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenRedisClusterPscServiceAttachmentsConnectionType(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + func expandRedisClusterAuthorizationMode(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { return v, nil } diff --git a/google/services/redis/resource_redis_cluster_user_created_connections.go b/google/services/redis/resource_redis_cluster_user_created_connections.go new file mode 100644 index 00000000000..1ae403e1828 --- /dev/null +++ b/google/services/redis/resource_redis_cluster_user_created_connections.go @@ -0,0 +1,688 @@ +// 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 redis + +import ( + "fmt" + "log" + "net/http" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "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" +) + +func ResourceRedisClusterUserCreatedConnections() *schema.Resource { + return &schema.Resource{ + Create: resourceRedisClusterUserCreatedConnectionsCreate, + Read: resourceRedisClusterUserCreatedConnectionsRead, + Update: resourceRedisClusterUserCreatedConnectionsUpdate, + Delete: resourceRedisClusterUserCreatedConnectionsDelete, + + Importer: &schema.ResourceImporter{ + State: resourceRedisClusterUserCreatedConnectionsImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(60 * time.Minute), + Update: schema.DefaultTimeout(120 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + CustomizeDiff: customdiff.All( + tpgresource.DefaultProviderProject, + ), + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: `The name of the Redis cluster these endpoints should be added to.`, + }, + "region": { + Type: schema.TypeString, + Required: true, + Description: `The name of the region of the Redis cluster these endpoints should be added to.`, + }, + "cluster_endpoints": { + Type: schema.TypeList, + Optional: true, + Description: `A list of cluster endpoints`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "connections": { + Type: schema.TypeList, + Optional: true, + Description: ``, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "psc_connection": { + Type: schema.TypeList, + Optional: true, + Description: `Detailed information of a PSC connection that is created by the customer +who owns the cluster.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": { + Type: schema.TypeString, + Required: true, + Description: `The IP allocated on the consumer network for the PSC forwarding rule.`, + }, + "forwarding_rule": { + Type: schema.TypeString, + Required: true, + Description: `The URI of the consumer side forwarding rule. +Format: +projects/{project}/regions/{region}/forwardingRules/{forwarding_rule}`, + }, + "network": { + Type: schema.TypeString, + Required: true, + Description: `The consumer network where the IP address resides, in the form of +projects/{project_id}/global/networks/{network_id}.`, + }, + "psc_connection_id": { + Type: schema.TypeString, + Required: true, + Description: `The PSC connection id of the forwarding rule connected to the +service attachment.`, + }, + "service_attachment": { + Type: schema.TypeString, + Required: true, + Description: `The service attachment which is the target of the PSC connection, in the form of projects/{project-id}/regions/{region}/serviceAttachments/{service-attachment-id}.`, + }, + "project_id": { + Type: schema.TypeString, + Computed: true, + Optional: true, + Description: `The consumer project_id where the forwarding rule is created from.`, + }, + "connection_type": { + Type: schema.TypeString, + Computed: true, + Description: `Output Only. Type of a PSC Connection. + Possible values: + CONNECTION_TYPE_DISCOVERY + CONNECTION_TYPE_PRIMARY + CONNECTION_TYPE_READER`, + }, + "psc_connection_status": { + Type: schema.TypeString, + Computed: true, + Description: `Output Only. The status of the PSC connection: whether a connection exists and ACTIVE or it no longer exists. + Possible values: + ACTIVE + NOT_FOUND`, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceRedisClusterUserCreatedConnectionsCreate(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{}) + clusterEndpointsProp, err := expandRedisClusterUserCreatedConnectionsClusterEndpoints(d.Get("cluster_endpoints"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("cluster_endpoints"); !tpgresource.IsEmptyValue(reflect.ValueOf(clusterEndpointsProp)) && (ok || !reflect.DeepEqual(v, clusterEndpointsProp)) { + obj["clusterEndpoints"] = clusterEndpointsProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{RedisBasePath}}projects/{{project}}/locations/{{region}}/clusters/{{name}}?updateMask=cluster_endpoints") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new ClusterUserCreatedConnections: %#v", obj) + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for ClusterUserCreatedConnections: %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 + } + + headers := make(http.Header) + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PATCH", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutCreate), + Headers: headers, + }) + if err != nil { + return fmt.Errorf("Error creating ClusterUserCreatedConnections: %s", err) + } + + // Store the ID now + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{region}}/clusters/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + err = RedisOperationWaitTime( + config, res, project, "Creating ClusterUserCreatedConnections", userAgent, + d.Timeout(schema.TimeoutCreate)) + + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create ClusterUserCreatedConnections: %s", err) + } + + log.Printf("[DEBUG] Finished creating ClusterUserCreatedConnections %q: %#v", d.Id(), res) + + return resourceRedisClusterUserCreatedConnectionsRead(d, meta) +} + +func resourceRedisClusterUserCreatedConnectionsRead(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, "{{RedisBasePath}}projects/{{project}}/locations/{{region}}/clusters/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for ClusterUserCreatedConnections: %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 + } + + 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("RedisClusterUserCreatedConnections %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading ClusterUserCreatedConnections: %s", err) + } + + if err := d.Set("cluster_endpoints", flattenRedisClusterUserCreatedConnectionsClusterEndpoints(res["clusterEndpoints"], d, config)); err != nil { + return fmt.Errorf("Error reading ClusterUserCreatedConnections: %s", err) + } + + return nil +} + +func resourceRedisClusterUserCreatedConnectionsUpdate(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 ClusterUserCreatedConnections: %s", err) + } + billingProject = project + + obj := make(map[string]interface{}) + clusterEndpointsProp, err := expandRedisClusterUserCreatedConnectionsClusterEndpoints(d.Get("cluster_endpoints"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("cluster_endpoints"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, clusterEndpointsProp)) { + obj["clusterEndpoints"] = clusterEndpointsProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{RedisBasePath}}projects/{{project}}/locations/{{region}}/clusters/{{name}}?updateMask=cluster_endpoints") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating ClusterUserCreatedConnections %q: %#v", d.Id(), obj) + headers := make(http.Header) + updateMask := []string{} + + if d.HasChange("cluster_endpoints") { + updateMask = append(updateMask, "clusterEndpoints") + } + // 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 + } + + // 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 ClusterUserCreatedConnections %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating ClusterUserCreatedConnections %q: %#v", d.Id(), res) + } + + err = RedisOperationWaitTime( + config, res, project, "Updating ClusterUserCreatedConnections", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + } + + return resourceRedisClusterUserCreatedConnectionsRead(d, meta) +} + +func resourceRedisClusterUserCreatedConnectionsDelete(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 ClusterUserCreatedConnections: %s", err) + } + billingProject = project + + obj := make(map[string]interface{}) + // not setting clusterEndpoints in obj + + url, err := tpgresource.ReplaceVars(d, config, "{{RedisBasePath}}projects/{{project}}/locations/{{region}}/clusters/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating ClusterUserCreatedConnections %q: %#v", d.Id(), obj) + headers := make(http.Header) + + updateMask := []string{} + updateMask = append(updateMask, "clusterEndpoints") + // 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 + } + + obj["async_cluster_endpoints_deletion_enabled"] = true + + // 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 ClusterUserCreatedConnections %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating ClusterUserCreatedConnections %q: %#v", d.Id(), res) + } + + err = RedisOperationWaitTime( + config, res, project, "Updating ClusterUserCreatedConnections", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + } + + return resourceRedisClusterUserCreatedConnectionsRead(d, meta) +} + +func resourceRedisClusterUserCreatedConnectionsImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*transport_tpg.Config) + if err := tpgresource.ParseImportId([]string{ + "^projects/(?P[^/]+)/locations/(?P[^/]+)/clusters/(?P[^/]+)$", + "^(?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/{{region}}/clusters/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenRedisClusterUserCreatedConnectionsClusterEndpoints(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{}{ + "connections": flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnections(original["connections"], d, config), + }) + } + return transformed +} +func flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnections(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{}{ + "psc_connection": flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnection(original["pscConnection"], d, config), + }) + } + return transformed +} +func flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnection(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["psc_connection_id"] = + flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionPscConnectionId(original["pscConnectionId"], d, config) + transformed["address"] = + flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionAddress(original["address"], d, config) + transformed["forwarding_rule"] = + flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionForwardingRule(original["forwardingRule"], d, config) + transformed["project_id"] = + flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionProjectId(original["projectId"], d, config) + transformed["network"] = + flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionNetwork(original["network"], d, config) + transformed["service_attachment"] = + flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionServiceAttachment(original["serviceAttachment"], d, config) + transformed["psc_connection_status"] = + flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionPscConnectionStatus(original["pscConnectionStatus"], d, config) + transformed["connection_type"] = + flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionConnectionType(original["connectionType"], d, config) + return []interface{}{transformed} +} +func flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionPscConnectionId(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionAddress(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionForwardingRule(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionProjectId(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionNetwork(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionServiceAttachment(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionPscConnectionStatus(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionConnectionType(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func expandRedisClusterUserCreatedConnectionsClusterEndpoints(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{}) + + transformedConnections, err := expandRedisClusterUserCreatedConnectionsClusterEndpointsConnections(original["connections"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedConnections); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["connections"] = transformedConnections + } + + req = append(req, transformed) + } + return req, nil +} + +func expandRedisClusterUserCreatedConnectionsClusterEndpointsConnections(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{}) + + transformedPscConnection, err := expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnection(original["psc_connection"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPscConnection); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["pscConnection"] = transformedPscConnection + } + + req = append(req, transformed) + } + return req, nil +} + +func expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnection(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{}) + + transformedPscConnectionId, err := expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionPscConnectionId(original["psc_connection_id"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPscConnectionId); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["pscConnectionId"] = transformedPscConnectionId + } + + transformedAddress, err := expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionAddress(original["address"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAddress); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["address"] = transformedAddress + } + + transformedForwardingRule, err := expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionForwardingRule(original["forwarding_rule"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedForwardingRule); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["forwardingRule"] = transformedForwardingRule + } + + transformedProjectId, err := expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionProjectId(original["project_id"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedProjectId); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["projectId"] = transformedProjectId + } + + transformedNetwork, err := expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionNetwork(original["network"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNetwork); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["network"] = transformedNetwork + } + + transformedServiceAttachment, err := expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionServiceAttachment(original["service_attachment"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedServiceAttachment); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["serviceAttachment"] = transformedServiceAttachment + } + + transformedPscConnectionStatus, err := expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionPscConnectionStatus(original["psc_connection_status"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPscConnectionStatus); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["pscConnectionStatus"] = transformedPscConnectionStatus + } + + transformedConnectionType, err := expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionConnectionType(original["connection_type"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedConnectionType); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["connectionType"] = transformedConnectionType + } + + return transformed, nil +} + +func expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionPscConnectionId(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionAddress(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionForwardingRule(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionProjectId(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionNetwork(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionServiceAttachment(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionPscConnectionStatus(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandRedisClusterUserCreatedConnectionsClusterEndpointsConnectionsPscConnectionConnectionType(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} diff --git a/google/services/redis/resource_redis_cluster_user_created_connections_generated_meta.yaml b/google/services/redis/resource_redis_cluster_user_created_connections_generated_meta.yaml new file mode 100644 index 00000000000..48ef109fb8a --- /dev/null +++ b/google/services/redis/resource_redis_cluster_user_created_connections_generated_meta.yaml @@ -0,0 +1,5 @@ +resource: 'google_redis_cluster_user_created_connections' +generation_type: 'mmv1' +api_service_name: 'redis.googleapis.com' +api_version: 'v1' +api_resource_type_kind: 'Cluster' diff --git a/google/services/redis/resource_redis_cluster_user_created_connections_generated_test.go b/google/services/redis/resource_redis_cluster_user_created_connections_generated_test.go new file mode 100644 index 00000000000..84e7449988e --- /dev/null +++ b/google/services/redis/resource_redis_cluster_user_created_connections_generated_test.go @@ -0,0 +1,387 @@ +// 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 redis_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func TestAccRedisClusterUserCreatedConnections_redisClusterUserCreatedConnectionsExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckRedisClusterUserCreatedConnectionsDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccRedisClusterUserCreatedConnections_redisClusterUserCreatedConnectionsExample(context), + }, + { + ResourceName: "google_redis_cluster_user_created_connections.cluster-user-conn", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "region"}, + }, + }, + }) +} + +func testAccRedisClusterUserCreatedConnections_redisClusterUserCreatedConnectionsExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_redis_cluster_user_created_connections" "cluster-user-conn" { + name = "tf-test-cluster-user-conn%{random_suffix}" + region = "us-central1" + cluster_endpoints { + connections { + psc_connection { + psc_connection_id = google_compute_forwarding_rule.forwarding_rule1_network1.psc_connection_id + address = google_compute_address.ip1_network1.address + forwarding_rule = google_compute_forwarding_rule.forwarding_rule1_network1.id + network = google_compute_network.network1.id + project_id = data.google_project.project.project_id + service_attachment = google_redis_cluster.cluster-user-conn.psc_service_attachments[0].service_attachment + } + } + connections { + psc_connection { + psc_connection_id = google_compute_forwarding_rule.forwarding_rule2_network1.psc_connection_id + address = google_compute_address.ip2_network1.address + forwarding_rule = google_compute_forwarding_rule.forwarding_rule2_network1.id + network = google_compute_network.network1.id + service_attachment = google_redis_cluster.cluster-user-conn.psc_service_attachments[1].service_attachment + } + } + } + cluster_endpoints { + connections { + psc_connection { + psc_connection_id = google_compute_forwarding_rule.forwarding_rule1_network2.psc_connection_id + address = google_compute_address.ip1_network2.address + forwarding_rule = google_compute_forwarding_rule.forwarding_rule1_network2.id + network = google_compute_network.network2.id + service_attachment = google_redis_cluster.cluster-user-conn.psc_service_attachments[0].service_attachment + } + } + connections { + psc_connection { + psc_connection_id = google_compute_forwarding_rule.forwarding_rule2_network2.psc_connection_id + address = google_compute_address.ip2_network2.address + forwarding_rule = google_compute_forwarding_rule.forwarding_rule2_network2.id + network = google_compute_network.network2.id + service_attachment = google_redis_cluster.cluster-user-conn.psc_service_attachments[1].service_attachment + } + } + } +} + +resource "google_compute_forwarding_rule" "forwarding_rule1_network1" { + name = "tf-test-fwd1-net1%{random_suffix}" + region = "us-central1" + ip_address = google_compute_address.ip1_network1.id + load_balancing_scheme = "" + network = google_compute_network.network1.id + target = google_redis_cluster.cluster-user-conn.psc_service_attachments[0].service_attachment +} + +resource "google_compute_forwarding_rule" "forwarding_rule2_network1" { + name = "tf-test-fwd2-net1%{random_suffix}" + region = "us-central1" + ip_address = google_compute_address.ip2_network1.id + load_balancing_scheme = "" + network = google_compute_network.network1.id + target = google_redis_cluster.cluster-user-conn.psc_service_attachments[1].service_attachment +} + +resource "google_compute_address" "ip1_network1" { + name = "tf-test-ip1-net1%{random_suffix}" + region = "us-central1" + subnetwork = google_compute_subnetwork.subnet_network1.id + address_type = "INTERNAL" + purpose = "GCE_ENDPOINT" +} + +resource "google_compute_address" "ip2_network1" { + name = "tf-test-ip2-net1%{random_suffix}" + region = "us-central1" + subnetwork = google_compute_subnetwork.subnet_network1.id + address_type = "INTERNAL" + purpose = "GCE_ENDPOINT" +} + +resource "google_compute_subnetwork" "subnet_network1" { + name = "tf-test-subnet-net1%{random_suffix}" + ip_cidr_range = "10.0.0.248/29" + region = "us-central1" + network = google_compute_network.network1.id +} + +resource "google_compute_network" "network1" { + name = "net1%{random_suffix}" + auto_create_subnetworks = false +} + +resource "google_compute_forwarding_rule" "forwarding_rule1_network2" { + name = "tf-test-fwd1-net2%{random_suffix}" + region = "us-central1" + ip_address = google_compute_address.ip1_network2.id + load_balancing_scheme = "" + network = google_compute_network.network2.id + target = google_redis_cluster.cluster-user-conn.psc_service_attachments[0].service_attachment +} + +resource "google_compute_forwarding_rule" "forwarding_rule2_network2" { + name = "tf-test-fwd2-net2%{random_suffix}" + region = "us-central1" + ip_address = google_compute_address.ip2_network2.id + load_balancing_scheme = "" + network = google_compute_network.network2.id + target = google_redis_cluster.cluster-user-conn.psc_service_attachments[1].service_attachment +} + +resource "google_compute_address" "ip1_network2" { + name = "tf-test-ip1-net2%{random_suffix}" + region = "us-central1" + subnetwork = google_compute_subnetwork.subnet_network2.id + address_type = "INTERNAL" + purpose = "GCE_ENDPOINT" +} + +resource "google_compute_address" "ip2_network2" { + name = "tf-test-ip2-net2%{random_suffix}" + region = "us-central1" + subnetwork = google_compute_subnetwork.subnet_network2.id + address_type = "INTERNAL" + purpose = "GCE_ENDPOINT" +} + +resource "google_compute_subnetwork" "subnet_network2" { + name = "tf-test-subnet-net2%{random_suffix}" + ip_cidr_range = "10.0.0.248/29" + region = "us-central1" + network = google_compute_network.network2.id +} + +resource "google_compute_network" "network2" { + name = "network2%{random_suffix}" + auto_create_subnetworks = false +} + +// redis cluster without endpoint +resource "google_redis_cluster" "cluster-user-conn" { + name = "tf-test-cluster-user-conn%{random_suffix}" + shard_count = 3 + region = "us-central1" + replica_count = 0 + deletion_protection_enabled = false +} + +data "google_project" "project" { +} +`, context) +} + +func TestAccRedisClusterUserCreatedConnections_redisClusterUserAndAutoCreatedConnectionsExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckRedisClusterUserCreatedConnectionsDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccRedisClusterUserCreatedConnections_redisClusterUserAndAutoCreatedConnectionsExample(context), + }, + { + ResourceName: "google_redis_cluster_user_created_connections.cluster-user-auto-conn", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "region"}, + }, + }, + }) +} + +func testAccRedisClusterUserCreatedConnections_redisClusterUserAndAutoCreatedConnectionsExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_redis_cluster_user_created_connections" "cluster-user-auto-conn" { + name = "tf-test-cluster-user-auto-conn%{random_suffix}" + region = "us-central1" + cluster_endpoints { + connections { + psc_connection { + psc_connection_id = google_compute_forwarding_rule.forwarding_rule1_network2.psc_connection_id + address = google_compute_address.ip1_network2.address + forwarding_rule = google_compute_forwarding_rule.forwarding_rule1_network2.id + network = google_compute_network.network2.id + service_attachment = google_redis_cluster.cluster-user-auto-conn.psc_service_attachments[0].service_attachment + } + } + connections { + psc_connection { + psc_connection_id = google_compute_forwarding_rule.forwarding_rule2_network2.psc_connection_id + address = google_compute_address.ip2_network2.address + forwarding_rule = google_compute_forwarding_rule.forwarding_rule2_network2.id + network = google_compute_network.network2.id + service_attachment = google_redis_cluster.cluster-user-auto-conn.psc_service_attachments[1].service_attachment + } + } + } +} + +resource "google_compute_forwarding_rule" "forwarding_rule1_network2" { + name = "tf-test-fwd1-net2%{random_suffix}" + region = "us-central1" + ip_address = google_compute_address.ip1_network2.id + load_balancing_scheme = "" + network = google_compute_network.network2.id + target = google_redis_cluster.cluster-user-auto-conn.psc_service_attachments[0].service_attachment +} + +resource "google_compute_forwarding_rule" "forwarding_rule2_network2" { + name = "tf-test-fwd2-net2%{random_suffix}" + region = "us-central1" + ip_address = google_compute_address.ip2_network2.id + load_balancing_scheme = "" + network = google_compute_network.network2.id + target = google_redis_cluster.cluster-user-auto-conn.psc_service_attachments[1].service_attachment +} + +resource "google_compute_address" "ip1_network2" { + name = "tf-test-ip1-net2%{random_suffix}" + region = "us-central1" + subnetwork = google_compute_subnetwork.subnet_network2.id + address_type = "INTERNAL" + purpose = "GCE_ENDPOINT" +} + +resource "google_compute_address" "ip2_network2" { + name = "tf-test-ip2-net2%{random_suffix}" + region = "us-central1" + subnetwork = google_compute_subnetwork.subnet_network2.id + address_type = "INTERNAL" + purpose = "GCE_ENDPOINT" +} + +resource "google_compute_subnetwork" "subnet_network2" { + name = "tf-test-subnet-net2%{random_suffix}" + ip_cidr_range = "10.0.0.248/29" + region = "us-central1" + network = google_compute_network.network2.id +} + +resource "google_compute_network" "network2" { + name = "network2%{random_suffix}" + auto_create_subnetworks = false +} + +// redis cluster without endpoint +resource "google_redis_cluster" "cluster-user-auto-conn" { + name = "tf-test-cluster-user-auto-conn%{random_suffix}" + shard_count = 3 + region = "us-central1" + replica_count = 0 + deletion_protection_enabled = false + psc_configs { + network = google_compute_network.network1.id + } + depends_on = [ + google_network_connectivity_service_connection_policy.default + ] +} + +resource "google_network_connectivity_service_connection_policy" "default" { + name = "scpolicy%{random_suffix}" + location = "us-central1" + service_class = "gcp-memorystore-redis" + description = "my basic service connection policy" + network = google_compute_network.network1.id + psc_config { + subnetworks = [google_compute_subnetwork.subnet_network1.id] + } +} + +resource "google_compute_subnetwork" "subnet_network1" { + name = "tf-test-subnet-net1%{random_suffix}" + ip_cidr_range = "10.0.0.248/29" + region = "us-central1" + network = google_compute_network.network1.id +} + +resource "google_compute_network" "network1" { + name = "net1%{random_suffix}" + auto_create_subnetworks = false +} +`, context) +} + +func testAccCheckRedisClusterUserCreatedConnectionsDestroyProducer(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_redis_cluster_user_created_connections" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := acctest.GoogleProviderConfig(t) + + url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{RedisBasePath}}projects/{{project}}/locations/{{region}}/clusters/{{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("RedisClusterUserCreatedConnections still exists at %s", url) + } + } + + return nil + } +} diff --git a/website/docs/r/redis_cluster.html.markdown b/website/docs/r/redis_cluster.html.markdown index 198ea30ffcf..ced742b0787 100644 --- a/website/docs/r/redis_cluster.html.markdown +++ b/website/docs/r/redis_cluster.html.markdown @@ -28,7 +28,11 @@ To get more information about Cluster, see: * How-to Guides * [Official Documentation](https://cloud.google.com/memorystore/docs/cluster/) -~> **Note:** For [Cross Region Replication](https://cloud.google.com/memorystore/docs/cluster/about-cross-region-replication), please follow the instructions below for performing certain update and failover (switchover and detach) operations +~> **Note:** For [Multiple VPC Networking](https://cloud.google.com/memorystore/docs/cluster/about-multiple-vpc-networking) if you want to use +[User-registered PSC Connections](https://cloud.google.com/memorystore/docs/cluster/about-multiple-vpc-networking#psc_connection_types), +then please use `google_redis_cluster_user_created_connections` resource. + +For [Cross Region Replication](https://cloud.google.com/memorystore/docs/cluster/about-cross-region-replication), please follow the instructions below for performing certain update and failover (switchover and detach) operations **Cross Region Replication** @@ -543,13 +547,6 @@ resource "google_compute_network" "producer_net" { The following arguments are supported: -* `psc_configs` - - (Required) - Required. Each PscConfig configures the consumer network where two - network addresses will be designated to the cluster for client access. - Currently, only one PscConfig is supported. - Structure is [documented below](#nested_psc_configs). - * `shard_count` - (Required) Required. Number of shards for the Redis cluster. @@ -560,14 +557,6 @@ The following arguments are supported: projects/{projectId}/locations/{locationId}/clusters/{clusterId} -The `psc_configs` block supports: - -* `network` - - (Required) - Required. The consumer network where the network address of - the discovery endpoint will be reserved, in the form of - projects/{network_project_id_or_number}/global/networks/{network_id}. - - - - @@ -595,6 +584,13 @@ The following arguments are supported: Immutable. Zone distribution config for Memorystore Redis cluster. Structure is [documented below](#nested_zone_distribution_config). +* `psc_configs` - + (Optional) + Required. Each PscConfig configures the consumer network where two + network addresses will be designated to the cluster for client access. + Currently, only one PscConfig is supported. + Structure is [documented below](#nested_psc_configs). + * `replica_count` - (Optional) Optional. The number of replica nodes per shard. @@ -646,6 +642,14 @@ The following arguments are supported: (Optional) Immutable. The zone for single zone Memorystore Redis cluster. +The `psc_configs` block supports: + +* `network` - + (Required) + Required. The consumer network where the network address of + the discovery endpoint will be reserved, in the form of + projects/{network_project_id_or_number}/global/networks/{network_id}. + The `persistence_config` block supports: * `mode` - @@ -889,6 +893,10 @@ In addition to the arguments listed above, the following computed attributes are Upcoming maintenance schedule. Structure is [documented below](#nested_maintenance_schedule). +* `psc_service_attachments` - + Service attachment details to configure Psc connections. + Structure is [documented below](#nested_psc_service_attachments). + The `discovery_endpoints` block contains: @@ -976,6 +984,16 @@ In addition to the arguments listed above, the following computed attributes are A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. +The `psc_service_attachments` block contains: + +* `service_attachment` - + (Output) + Service attachment URI which your self-created PscConnection should use as + +* `connection_type` - + (Output) + Type of a PSC connection targeting this service attachment. + ## Timeouts This resource provides the following diff --git a/website/docs/r/redis_cluster_user_created_connections.html.markdown b/website/docs/r/redis_cluster_user_created_connections.html.markdown new file mode 100644 index 00000000000..40165641129 --- /dev/null +++ b/website/docs/r/redis_cluster_user_created_connections.html.markdown @@ -0,0 +1,445 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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: "Memorystore (Redis)" +description: |- + Manages user created connections for Redis cluster +--- + +# google_redis_cluster_user_created_connections + +Manages user created connections for Redis cluster + + +To get more information about ClusterUserCreatedConnections, see: + +* [API documentation](https://cloud.google.com/memorystore/docs/cluster/reference/rest/v1/projects.locations.clusters) + +~> **Note:** If you remove a connections item from the resource, the corresponding forwarding rule will no longer be functioning. +If the corresponding forwarding rule is represented in your terraform configuration it is recommended to delete that +`google_compute_forwarding_rule` resource at the same time. + + +## Example Usage - Redis Cluster User Created Connections + + +```hcl +resource "google_redis_cluster_user_created_connections" "cluster-user-conn" { + name = "cluster-user-conn" + region = "us-central1" + cluster_endpoints { + connections { + psc_connection { + psc_connection_id = google_compute_forwarding_rule.forwarding_rule1_network1.psc_connection_id + address = google_compute_address.ip1_network1.address + forwarding_rule = google_compute_forwarding_rule.forwarding_rule1_network1.id + network = google_compute_network.network1.id + project_id = data.google_project.project.project_id + service_attachment = google_redis_cluster.cluster-user-conn.psc_service_attachments[0].service_attachment + } + } + connections { + psc_connection { + psc_connection_id = google_compute_forwarding_rule.forwarding_rule2_network1.psc_connection_id + address = google_compute_address.ip2_network1.address + forwarding_rule = google_compute_forwarding_rule.forwarding_rule2_network1.id + network = google_compute_network.network1.id + service_attachment = google_redis_cluster.cluster-user-conn.psc_service_attachments[1].service_attachment + } + } + } + cluster_endpoints { + connections { + psc_connection { + psc_connection_id = google_compute_forwarding_rule.forwarding_rule1_network2.psc_connection_id + address = google_compute_address.ip1_network2.address + forwarding_rule = google_compute_forwarding_rule.forwarding_rule1_network2.id + network = google_compute_network.network2.id + service_attachment = google_redis_cluster.cluster-user-conn.psc_service_attachments[0].service_attachment + } + } + connections { + psc_connection { + psc_connection_id = google_compute_forwarding_rule.forwarding_rule2_network2.psc_connection_id + address = google_compute_address.ip2_network2.address + forwarding_rule = google_compute_forwarding_rule.forwarding_rule2_network2.id + network = google_compute_network.network2.id + service_attachment = google_redis_cluster.cluster-user-conn.psc_service_attachments[1].service_attachment + } + } + } +} + +resource "google_compute_forwarding_rule" "forwarding_rule1_network1" { + name = "fwd1-net1" + region = "us-central1" + ip_address = google_compute_address.ip1_network1.id + load_balancing_scheme = "" + network = google_compute_network.network1.id + target = google_redis_cluster.cluster-user-conn.psc_service_attachments[0].service_attachment +} + +resource "google_compute_forwarding_rule" "forwarding_rule2_network1" { + name = "fwd2-net1" + region = "us-central1" + ip_address = google_compute_address.ip2_network1.id + load_balancing_scheme = "" + network = google_compute_network.network1.id + target = google_redis_cluster.cluster-user-conn.psc_service_attachments[1].service_attachment +} + +resource "google_compute_address" "ip1_network1" { + name = "ip1-net1" + region = "us-central1" + subnetwork = google_compute_subnetwork.subnet_network1.id + address_type = "INTERNAL" + purpose = "GCE_ENDPOINT" +} + +resource "google_compute_address" "ip2_network1" { + name = "ip2-net1" + region = "us-central1" + subnetwork = google_compute_subnetwork.subnet_network1.id + address_type = "INTERNAL" + purpose = "GCE_ENDPOINT" +} + +resource "google_compute_subnetwork" "subnet_network1" { + name = "subnet-net1" + ip_cidr_range = "10.0.0.248/29" + region = "us-central1" + network = google_compute_network.network1.id +} + +resource "google_compute_network" "network1" { + name = "net1" + auto_create_subnetworks = false +} + +resource "google_compute_forwarding_rule" "forwarding_rule1_network2" { + name = "fwd1-net2" + region = "us-central1" + ip_address = google_compute_address.ip1_network2.id + load_balancing_scheme = "" + network = google_compute_network.network2.id + target = google_redis_cluster.cluster-user-conn.psc_service_attachments[0].service_attachment +} + +resource "google_compute_forwarding_rule" "forwarding_rule2_network2" { + name = "fwd2-net2" + region = "us-central1" + ip_address = google_compute_address.ip2_network2.id + load_balancing_scheme = "" + network = google_compute_network.network2.id + target = google_redis_cluster.cluster-user-conn.psc_service_attachments[1].service_attachment +} + +resource "google_compute_address" "ip1_network2" { + name = "ip1-net2" + region = "us-central1" + subnetwork = google_compute_subnetwork.subnet_network2.id + address_type = "INTERNAL" + purpose = "GCE_ENDPOINT" +} + +resource "google_compute_address" "ip2_network2" { + name = "ip2-net2" + region = "us-central1" + subnetwork = google_compute_subnetwork.subnet_network2.id + address_type = "INTERNAL" + purpose = "GCE_ENDPOINT" +} + +resource "google_compute_subnetwork" "subnet_network2" { + name = "subnet-net2" + ip_cidr_range = "10.0.0.248/29" + region = "us-central1" + network = google_compute_network.network2.id +} + +resource "google_compute_network" "network2" { + name = "network2" + auto_create_subnetworks = false +} + +// redis cluster without endpoint +resource "google_redis_cluster" "cluster-user-conn" { + name = "cluster-user-conn" + shard_count = 3 + region = "us-central1" + replica_count = 0 + deletion_protection_enabled = false +} + +data "google_project" "project" { +} +``` + +## Example Usage - Redis Cluster User And Auto Created Connections + + +```hcl +resource "google_redis_cluster_user_created_connections" "cluster-user-auto-conn" { + name = "cluster-user-auto-conn" + region = "us-central1" + cluster_endpoints { + connections { + psc_connection { + psc_connection_id = google_compute_forwarding_rule.forwarding_rule1_network2.psc_connection_id + address = google_compute_address.ip1_network2.address + forwarding_rule = google_compute_forwarding_rule.forwarding_rule1_network2.id + network = google_compute_network.network2.id + service_attachment = google_redis_cluster.cluster-user-auto-conn.psc_service_attachments[0].service_attachment + } + } + connections { + psc_connection { + psc_connection_id = google_compute_forwarding_rule.forwarding_rule2_network2.psc_connection_id + address = google_compute_address.ip2_network2.address + forwarding_rule = google_compute_forwarding_rule.forwarding_rule2_network2.id + network = google_compute_network.network2.id + service_attachment = google_redis_cluster.cluster-user-auto-conn.psc_service_attachments[1].service_attachment + } + } + } +} + +resource "google_compute_forwarding_rule" "forwarding_rule1_network2" { + name = "fwd1-net2" + region = "us-central1" + ip_address = google_compute_address.ip1_network2.id + load_balancing_scheme = "" + network = google_compute_network.network2.id + target = google_redis_cluster.cluster-user-auto-conn.psc_service_attachments[0].service_attachment +} + +resource "google_compute_forwarding_rule" "forwarding_rule2_network2" { + name = "fwd2-net2" + region = "us-central1" + ip_address = google_compute_address.ip2_network2.id + load_balancing_scheme = "" + network = google_compute_network.network2.id + target = google_redis_cluster.cluster-user-auto-conn.psc_service_attachments[1].service_attachment +} + +resource "google_compute_address" "ip1_network2" { + name = "ip1-net2" + region = "us-central1" + subnetwork = google_compute_subnetwork.subnet_network2.id + address_type = "INTERNAL" + purpose = "GCE_ENDPOINT" +} + +resource "google_compute_address" "ip2_network2" { + name = "ip2-net2" + region = "us-central1" + subnetwork = google_compute_subnetwork.subnet_network2.id + address_type = "INTERNAL" + purpose = "GCE_ENDPOINT" +} + +resource "google_compute_subnetwork" "subnet_network2" { + name = "subnet-net2" + ip_cidr_range = "10.0.0.248/29" + region = "us-central1" + network = google_compute_network.network2.id +} + +resource "google_compute_network" "network2" { + name = "network2" + auto_create_subnetworks = false +} + +// redis cluster without endpoint +resource "google_redis_cluster" "cluster-user-auto-conn" { + name = "cluster-user-auto-conn" + shard_count = 3 + region = "us-central1" + replica_count = 0 + deletion_protection_enabled = false + psc_configs { + network = google_compute_network.network1.id + } + depends_on = [ + google_network_connectivity_service_connection_policy.default + ] +} + +resource "google_network_connectivity_service_connection_policy" "default" { + name = "scpolicy" + location = "us-central1" + service_class = "gcp-memorystore-redis" + description = "my basic service connection policy" + network = google_compute_network.network1.id + psc_config { + subnetworks = [google_compute_subnetwork.subnet_network1.id] + } +} + +resource "google_compute_subnetwork" "subnet_network1" { + name = "subnet-net1" + ip_cidr_range = "10.0.0.248/29" + region = "us-central1" + network = google_compute_network.network1.id +} + +resource "google_compute_network" "network1" { + name = "net1" + auto_create_subnetworks = false +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + The name of the Redis cluster these endpoints should be added to. + +* `region` - + (Required) + The name of the region of the Redis cluster these endpoints should be added to. + + +- - - + + +* `cluster_endpoints` - + (Optional) + A list of cluster endpoints + Structure is [documented below](#nested_cluster_endpoints). + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +The `cluster_endpoints` block supports: + +* `connections` - + (Optional) + Structure is [documented below](#nested_cluster_endpoints_cluster_endpoints_connections). + + +The `connections` block supports: + +* `psc_connection` - + (Optional) + Detailed information of a PSC connection that is created by the customer + who owns the cluster. + Structure is [documented below](#nested_cluster_endpoints_cluster_endpoints_connections_connections_psc_connection). + + +The `psc_connection` block supports: + +* `psc_connection_id` - + (Required) + The PSC connection id of the forwarding rule connected to the + service attachment. + +* `address` - + (Required) + The IP allocated on the consumer network for the PSC forwarding rule. + +* `forwarding_rule` - + (Required) + The URI of the consumer side forwarding rule. + Format: + projects/{project}/regions/{region}/forwardingRules/{forwarding_rule} + +* `project_id` - + (Optional) + The consumer project_id where the forwarding rule is created from. + +* `network` - + (Required) + The consumer network where the IP address resides, in the form of + projects/{project_id}/global/networks/{network_id}. + +* `service_attachment` - + (Required) + The service attachment which is the target of the PSC connection, in the form of projects/{project-id}/regions/{region}/serviceAttachments/{service-attachment-id}. + +* `psc_connection_status` - + (Output) + Output Only. The status of the PSC connection: whether a connection exists and ACTIVE or it no longer exists. + Possible values: + ACTIVE + NOT_FOUND + +* `connection_type` - + (Output) + Output Only. Type of a PSC Connection. + Possible values: + CONNECTION_TYPE_DISCOVERY + CONNECTION_TYPE_PRIMARY + CONNECTION_TYPE_READER + +## 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/{{region}}/clusters/{{name}}` + + +## Timeouts + +This resource provides the following +[Timeouts](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts) configuration options: + +- `create` - Default is 60 minutes. +- `update` - Default is 120 minutes. +- `delete` - Default is 30 minutes. + +## Import + + +ClusterUserCreatedConnections can be imported using any of these accepted formats: + +* `projects/{{project}}/locations/{{region}}/clusters/{{name}}` +* `{{project}}/{{region}}/{{name}}` +* `{{region}}/{{name}}` +* `{{name}}` + + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import ClusterUserCreatedConnections using one of the formats above. For example: + +```tf +import { + id = "projects/{{project}}/locations/{{region}}/clusters/{{name}}" + to = google_redis_cluster_user_created_connections.default +} +``` + +When using the [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import), ClusterUserCreatedConnections can be imported using one of the formats above. For example: + +``` +$ terraform import google_redis_cluster_user_created_connections.default projects/{{project}}/locations/{{region}}/clusters/{{name}} +$ terraform import google_redis_cluster_user_created_connections.default {{project}}/{{region}}/{{name}} +$ terraform import google_redis_cluster_user_created_connections.default {{region}}/{{name}} +$ terraform import google_redis_cluster_user_created_connections.default {{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).