diff --git a/.changelog/6479.txt b/.changelog/6479.txt new file mode 100644 index 00000000000..10ee9f08c47 --- /dev/null +++ b/.changelog/6479.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +google_datastream_connection_profile +``` diff --git a/google/config.go b/google/config.go index 46b3cd7ba1a..05efc2ff776 100644 --- a/google/config.go +++ b/google/config.go @@ -192,6 +192,7 @@ type Config struct { DataprocBasePath string DataprocMetastoreBasePath string DatastoreBasePath string + DatastreamBasePath string DeploymentManagerBasePath string DialogflowBasePath string DialogflowCXBasePath string @@ -286,6 +287,7 @@ const DataLossPreventionBasePathKey = "DataLossPrevention" const DataprocBasePathKey = "Dataproc" const DataprocMetastoreBasePathKey = "DataprocMetastore" const DatastoreBasePathKey = "Datastore" +const DatastreamBasePathKey = "Datastream" const DeploymentManagerBasePathKey = "DeploymentManager" const DialogflowBasePathKey = "Dialogflow" const DialogflowCXBasePathKey = "DialogflowCX" @@ -374,6 +376,7 @@ var DefaultBasePaths = map[string]string{ DataprocBasePathKey: "https://dataproc.googleapis.com/v1/", DataprocMetastoreBasePathKey: "https://metastore.googleapis.com/v1/", DatastoreBasePathKey: "https://datastore.googleapis.com/v1/", + DatastreamBasePathKey: "https://datastream.googleapis.com/v1/", DeploymentManagerBasePathKey: "https://www.googleapis.com/deploymentmanager/v2/", DialogflowBasePathKey: "https://dialogflow.googleapis.com/v2/", DialogflowCXBasePathKey: "https://{{location}}-dialogflow.googleapis.com/v3/", @@ -1224,6 +1227,7 @@ func ConfigureBasePaths(c *Config) { c.DataprocBasePath = DefaultBasePaths[DataprocBasePathKey] c.DataprocMetastoreBasePath = DefaultBasePaths[DataprocMetastoreBasePathKey] c.DatastoreBasePath = DefaultBasePaths[DatastoreBasePathKey] + c.DatastreamBasePath = DefaultBasePaths[DatastreamBasePathKey] c.DeploymentManagerBasePath = DefaultBasePaths[DeploymentManagerBasePathKey] c.DialogflowBasePath = DefaultBasePaths[DialogflowBasePathKey] c.DialogflowCXBasePath = DefaultBasePaths[DialogflowCXBasePathKey] diff --git a/google/datastream_operation.go b/google/datastream_operation.go new file mode 100644 index 00000000000..0078bd8fb19 --- /dev/null +++ b/google/datastream_operation.go @@ -0,0 +1,85 @@ +package google + +import ( + "bytes" + "encoding/json" + "fmt" + datastream "google.golang.org/api/datastream/v1" + "time" +) + +type DatastreamOperationWaiter struct { + Config *Config + UserAgent string + Project string + CommonOperationWaiter +} + +func (w *DatastreamOperationWaiter) 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.DatastreamBasePath, w.CommonOperationWaiter.Op.Name) + + return sendRequest(w.Config, "GET", w.Project, url, w.UserAgent, nil) +} + +func (w *DatastreamOperationWaiter) Error() error { + if w != nil && w.Op.Error != nil { + return DatastreamError(*w.Op.Error) + } + return nil +} + +func createDatastreamWaiter(config *Config, op map[string]interface{}, project, activity, userAgent string) (*DatastreamOperationWaiter, error) { + w := &DatastreamOperationWaiter{ + Config: config, + UserAgent: userAgent, + Project: project, + } + if err := w.CommonOperationWaiter.SetOp(op); err != nil { + return nil, err + } + return w, nil +} + +// nolint: deadcode,unused +func datastreamOperationWaitTimeWithResponse(config *Config, op map[string]interface{}, response *map[string]interface{}, project, activity, userAgent string, timeout time.Duration) error { + w, err := createDatastreamWaiter(config, op, project, activity, userAgent) + if err != nil { + return err + } + if err := OperationWait(w, activity, timeout, config.PollInterval); err != nil { + return err + } + return json.Unmarshal([]byte(w.CommonOperationWaiter.Op.Response), response) +} + +func datastreamOperationWaitTime(config *Config, op map[string]interface{}, project, 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 := createDatastreamWaiter(config, op, project, activity, userAgent) + if err != nil { + // If w is nil, the op was synchronous. + return err + } + return OperationWait(w, activity, timeout, config.PollInterval) +} + +// DatastreamError wraps datastream.Status and implements the +// error interface so it can be returned. +type DatastreamError datastream.Status + +func (e DatastreamError) Error() string { + var buf bytes.Buffer + + for _, err := range e.Details { + buf.Write(err) + buf.WriteString("\n") + } + + return buf.String() +} diff --git a/google/provider.go b/google/provider.go index 0d0edbd0c0b..ad63b04d869 100644 --- a/google/provider.go +++ b/google/provider.go @@ -397,6 +397,14 @@ func Provider() *schema.Provider { "GOOGLE_DATASTORE_CUSTOM_ENDPOINT", }, DefaultBasePaths[DatastoreBasePathKey]), }, + "datastream_custom_endpoint": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateCustomEndpoint, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_DATASTREAM_CUSTOM_ENDPOINT", + }, DefaultBasePaths[DatastreamBasePathKey]), + }, "deployment_manager_custom_endpoint": { Type: schema.TypeString, Optional: true, @@ -880,9 +888,9 @@ func Provider() *schema.Provider { return provider } -// Generated resources: 229 +// Generated resources: 230 // Generated IAM resources: 138 -// Total generated resources: 367 +// Total generated resources: 368 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -1086,6 +1094,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_dataproc_metastore_service_iam_member": ResourceIamMember(DataprocMetastoreServiceIamSchema, DataprocMetastoreServiceIamUpdaterProducer, DataprocMetastoreServiceIdParseFunc), "google_dataproc_metastore_service_iam_policy": ResourceIamPolicy(DataprocMetastoreServiceIamSchema, DataprocMetastoreServiceIamUpdaterProducer, DataprocMetastoreServiceIdParseFunc), "google_datastore_index": resourceDatastoreIndex(), + "google_datastream_connection_profile": resourceDatastreamConnectionProfile(), "google_deployment_manager_deployment": resourceDeploymentManagerDeployment(), "google_dialogflow_agent": resourceDialogflowAgent(), "google_dialogflow_intent": resourceDialogflowIntent(), @@ -1503,6 +1512,7 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData, p *schema.Pr config.DataprocBasePath = d.Get("dataproc_custom_endpoint").(string) config.DataprocMetastoreBasePath = d.Get("dataproc_metastore_custom_endpoint").(string) config.DatastoreBasePath = d.Get("datastore_custom_endpoint").(string) + config.DatastreamBasePath = d.Get("datastream_custom_endpoint").(string) config.DeploymentManagerBasePath = d.Get("deployment_manager_custom_endpoint").(string) config.DialogflowBasePath = d.Get("dialogflow_custom_endpoint").(string) config.DialogflowCXBasePath = d.Get("dialogflow_cx_custom_endpoint").(string) diff --git a/google/resource_datastream_connection_profile.go b/google/resource_datastream_connection_profile.go new file mode 100644 index 00000000000..13acd76e6c6 --- /dev/null +++ b/google/resource_datastream_connection_profile.go @@ -0,0 +1,1385 @@ +// ---------------------------------------------------------------------------- +// +// *** 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 google + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceDatastreamConnectionProfile() *schema.Resource { + return &schema.Resource{ + Create: resourceDatastreamConnectionProfileCreate, + Read: resourceDatastreamConnectionProfileRead, + Update: resourceDatastreamConnectionProfileUpdate, + Delete: resourceDatastreamConnectionProfileDelete, + + Importer: &schema.ResourceImporter{ + State: resourceDatastreamConnectionProfileImport, + }, + + 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{ + "connection_profile_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The connection profile identifier.`, + }, + "display_name": { + Type: schema.TypeString, + Required: true, + Description: `Display name.`, + }, + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The name of the location this repository is located in.`, + }, + "forward_ssh_connectivity": { + Type: schema.TypeList, + Optional: true, + Description: `Forward SSH tunnel connectivity.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "hostname": { + Type: schema.TypeString, + Required: true, + Description: `Hostname for the SSH tunnel.`, + }, + "username": { + Type: schema.TypeString, + Required: true, + Description: `Username for the SSH tunnel.`, + }, + "password": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `SSH password.`, + Sensitive: true, + ConflictsWith: []string{}, + }, + "port": { + Type: schema.TypeInt, + Optional: true, + Description: `Port for the SSH tunnel.`, + Default: 22, + }, + "private_key": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `SSH private key.`, + Sensitive: true, + ConflictsWith: []string{}, + }, + }, + }, + }, + "gcs_profile": { + Type: schema.TypeList, + Optional: true, + Description: `Cloud Storage bucket profile.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bucket": { + Type: schema.TypeString, + Required: true, + Description: `The Cloud Storage bucket name.`, + }, + "root_path": { + Type: schema.TypeString, + Optional: true, + Description: `The root path inside the Cloud Storage bucket.`, + }, + }, + }, + ExactlyOneOf: []string{"oracle_profile", "gcs_profile", "mysql_profile", "postgresql_profile"}, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Description: `Labels.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "mysql_profile": { + Type: schema.TypeList, + Optional: true, + Description: `MySQL database profile.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "hostname": { + Type: schema.TypeString, + Required: true, + Description: `Hostname for the MySQL connection.`, + }, + "password": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Password for the MySQL connection.`, + Sensitive: true, + }, + "username": { + Type: schema.TypeString, + Required: true, + Description: `Username for the MySQL connection.`, + }, + "port": { + Type: schema.TypeInt, + Optional: true, + Description: `Port for the MySQL connection.`, + Default: 3306, + }, + "ssl_config": { + Type: schema.TypeList, + Optional: true, + Description: `SSL configuration for the MySQL connection.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ca_certificate": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `PEM-encoded certificate of the CA that signed the source database +server's certificate.`, + Sensitive: true, + }, + "client_certificate": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `PEM-encoded certificate that will be used by the replica to +authenticate against the source database server. If this field +is used then the 'clientKey' and the 'caCertificate' fields are +mandatory.`, + Sensitive: true, + }, + "client_key": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `PEM-encoded private key associated with the Client Certificate. +If this field is used then the 'client_certificate' and the +'ca_certificate' fields are mandatory.`, + Sensitive: true, + }, + "ca_certificate_set": { + Type: schema.TypeBool, + Computed: true, + Description: `Indicates whether the clientKey field is set.`, + }, + "client_certificate_set": { + Type: schema.TypeBool, + Computed: true, + Description: `Indicates whether the clientCertificate field is set.`, + }, + "client_key_set": { + Type: schema.TypeBool, + Computed: true, + Description: `Indicates whether the clientKey field is set.`, + }, + }, + }, + }, + }, + }, + ExactlyOneOf: []string{"oracle_profile", "gcs_profile", "mysql_profile", "postgresql_profile"}, + }, + "oracle_profile": { + Type: schema.TypeList, + Optional: true, + Description: `Oracle database profile.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database_service": { + Type: schema.TypeString, + Required: true, + Description: `Database for the Oracle connection.`, + }, + "hostname": { + Type: schema.TypeString, + Required: true, + Description: `Hostname for the Oracle connection.`, + }, + "password": { + Type: schema.TypeString, + Required: true, + Description: `Password for the Oracle connection.`, + Sensitive: true, + }, + "username": { + Type: schema.TypeString, + Required: true, + Description: `Username for the Oracle connection.`, + }, + "connection_attributes": { + Type: schema.TypeMap, + Optional: true, + Description: `Connection string attributes`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "port": { + Type: schema.TypeInt, + Optional: true, + Description: `Port for the Oracle connection.`, + Default: 1521, + }, + }, + }, + ExactlyOneOf: []string{"oracle_profile", "gcs_profile", "mysql_profile", "postgresql_profile"}, + }, + "postgresql_profile": { + Type: schema.TypeList, + Optional: true, + Description: `PostgreSQL database profile.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + Description: `Database for the PostgreSQL connection.`, + }, + "hostname": { + Type: schema.TypeString, + Required: true, + Description: `Hostname for the PostgreSQL connection.`, + }, + "password": { + Type: schema.TypeString, + Required: true, + Description: `Password for the PostgreSQL connection.`, + Sensitive: true, + }, + "username": { + Type: schema.TypeString, + Required: true, + Description: `Username for the PostgreSQL connection.`, + }, + "port": { + Type: schema.TypeInt, + Optional: true, + Description: `Port for the PostgreSQL connection.`, + Default: 5432, + }, + }, + }, + ExactlyOneOf: []string{"oracle_profile", "gcs_profile", "mysql_profile", "postgresql_profile"}, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The resource's name.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceDatastreamConnectionProfileCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + labelsProp, err := expandDatastreamConnectionProfileLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + displayNameProp, err := expandDatastreamConnectionProfileDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !isEmptyValue(reflect.ValueOf(displayNameProp)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + oracleProfileProp, err := expandDatastreamConnectionProfileOracleProfile(d.Get("oracle_profile"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("oracle_profile"); !isEmptyValue(reflect.ValueOf(oracleProfileProp)) && (ok || !reflect.DeepEqual(v, oracleProfileProp)) { + obj["oracleProfile"] = oracleProfileProp + } + gcsProfileProp, err := expandDatastreamConnectionProfileGcsProfile(d.Get("gcs_profile"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("gcs_profile"); !isEmptyValue(reflect.ValueOf(gcsProfileProp)) && (ok || !reflect.DeepEqual(v, gcsProfileProp)) { + obj["gcsProfile"] = gcsProfileProp + } + mysqlProfileProp, err := expandDatastreamConnectionProfileMysqlProfile(d.Get("mysql_profile"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("mysql_profile"); !isEmptyValue(reflect.ValueOf(mysqlProfileProp)) && (ok || !reflect.DeepEqual(v, mysqlProfileProp)) { + obj["mysqlProfile"] = mysqlProfileProp + } + postgresqlProfileProp, err := expandDatastreamConnectionProfilePostgresqlProfile(d.Get("postgresql_profile"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("postgresql_profile"); !isEmptyValue(reflect.ValueOf(postgresqlProfileProp)) && (ok || !reflect.DeepEqual(v, postgresqlProfileProp)) { + obj["postgresqlProfile"] = postgresqlProfileProp + } + forwardSshConnectivityProp, err := expandDatastreamConnectionProfileForwardSshConnectivity(d.Get("forward_ssh_connectivity"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("forward_ssh_connectivity"); !isEmptyValue(reflect.ValueOf(forwardSshConnectivityProp)) && (ok || !reflect.DeepEqual(v, forwardSshConnectivityProp)) { + obj["forwardSshConnectivity"] = forwardSshConnectivityProp + } + + url, err := replaceVars(d, config, "{{DatastreamBasePath}}projects/{{project}}/locations/{{location}}/connectionProfiles?connectionProfileId={{connection_profile_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new ConnectionProfile: %#v", obj) + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for ConnectionProfile: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "POST", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating ConnectionProfile: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/connectionProfiles/{{connection_profile_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 = datastreamOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating ConnectionProfile", userAgent, + d.Timeout(schema.TimeoutCreate)) + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create ConnectionProfile: %s", err) + } + + if err := d.Set("name", flattenDatastreamConnectionProfileName(opRes["name"], d, config)); err != nil { + return err + } + + // This may have caused the ID to update - update it if so. + id, err = replaceVars(d, config, "projects/{{project}}/locations/{{location}}/connectionProfiles/{{connection_profile_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating ConnectionProfile %q: %#v", d.Id(), res) + + return resourceDatastreamConnectionProfileRead(d, meta) +} + +func resourceDatastreamConnectionProfileRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{DatastreamBasePath}}projects/{{project}}/locations/{{location}}/connectionProfiles/{{connection_profile_id}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for ConnectionProfile: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("DatastreamConnectionProfile %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading ConnectionProfile: %s", err) + } + + if err := d.Set("name", flattenDatastreamConnectionProfileName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading ConnectionProfile: %s", err) + } + if err := d.Set("labels", flattenDatastreamConnectionProfileLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading ConnectionProfile: %s", err) + } + if err := d.Set("display_name", flattenDatastreamConnectionProfileDisplayName(res["displayName"], d, config)); err != nil { + return fmt.Errorf("Error reading ConnectionProfile: %s", err) + } + if err := d.Set("oracle_profile", flattenDatastreamConnectionProfileOracleProfile(res["oracleProfile"], d, config)); err != nil { + return fmt.Errorf("Error reading ConnectionProfile: %s", err) + } + if err := d.Set("gcs_profile", flattenDatastreamConnectionProfileGcsProfile(res["gcsProfile"], d, config)); err != nil { + return fmt.Errorf("Error reading ConnectionProfile: %s", err) + } + if err := d.Set("mysql_profile", flattenDatastreamConnectionProfileMysqlProfile(res["mysqlProfile"], d, config)); err != nil { + return fmt.Errorf("Error reading ConnectionProfile: %s", err) + } + if err := d.Set("postgresql_profile", flattenDatastreamConnectionProfilePostgresqlProfile(res["postgresqlProfile"], d, config)); err != nil { + return fmt.Errorf("Error reading ConnectionProfile: %s", err) + } + if err := d.Set("forward_ssh_connectivity", flattenDatastreamConnectionProfileForwardSshConnectivity(res["forwardSshConnectivity"], d, config)); err != nil { + return fmt.Errorf("Error reading ConnectionProfile: %s", err) + } + + return nil +} + +func resourceDatastreamConnectionProfileUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for ConnectionProfile: %s", err) + } + billingProject = project + + obj := make(map[string]interface{}) + labelsProp, err := expandDatastreamConnectionProfileLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + displayNameProp, err := expandDatastreamConnectionProfileDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + oracleProfileProp, err := expandDatastreamConnectionProfileOracleProfile(d.Get("oracle_profile"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("oracle_profile"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, oracleProfileProp)) { + obj["oracleProfile"] = oracleProfileProp + } + gcsProfileProp, err := expandDatastreamConnectionProfileGcsProfile(d.Get("gcs_profile"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("gcs_profile"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, gcsProfileProp)) { + obj["gcsProfile"] = gcsProfileProp + } + mysqlProfileProp, err := expandDatastreamConnectionProfileMysqlProfile(d.Get("mysql_profile"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("mysql_profile"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, mysqlProfileProp)) { + obj["mysqlProfile"] = mysqlProfileProp + } + postgresqlProfileProp, err := expandDatastreamConnectionProfilePostgresqlProfile(d.Get("postgresql_profile"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("postgresql_profile"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, postgresqlProfileProp)) { + obj["postgresqlProfile"] = postgresqlProfileProp + } + forwardSshConnectivityProp, err := expandDatastreamConnectionProfileForwardSshConnectivity(d.Get("forward_ssh_connectivity"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("forward_ssh_connectivity"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, forwardSshConnectivityProp)) { + obj["forwardSshConnectivity"] = forwardSshConnectivityProp + } + + url, err := replaceVars(d, config, "{{DatastreamBasePath}}projects/{{project}}/locations/{{location}}/connectionProfiles/{{connection_profile_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating ConnectionProfile %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + + if d.HasChange("display_name") { + updateMask = append(updateMask, "displayName") + } + + if d.HasChange("oracle_profile") { + updateMask = append(updateMask, "oracleProfile") + } + + if d.HasChange("gcs_profile") { + updateMask = append(updateMask, "gcsProfile") + } + + if d.HasChange("mysql_profile") { + updateMask = append(updateMask, "mysqlProfile") + } + + if d.HasChange("postgresql_profile") { + updateMask = append(updateMask, "postgresqlProfile") + } + + if d.HasChange("forward_ssh_connectivity") { + updateMask = append(updateMask, "forwardSshConnectivity") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = 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 := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating ConnectionProfile %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating ConnectionProfile %q: %#v", d.Id(), res) + } + + err = datastreamOperationWaitTime( + config, res, project, "Updating ConnectionProfile", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + + return resourceDatastreamConnectionProfileRead(d, meta) +} + +func resourceDatastreamConnectionProfileDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for ConnectionProfile: %s", err) + } + billingProject = project + + url, err := replaceVars(d, config, "{{DatastreamBasePath}}projects/{{project}}/locations/{{location}}/connectionProfiles/{{connection_profile_id}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting ConnectionProfile %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "DELETE", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "ConnectionProfile") + } + + err = datastreamOperationWaitTime( + config, res, project, "Deleting ConnectionProfile", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting ConnectionProfile %q: %#v", d.Id(), res) + return nil +} + +func resourceDatastreamConnectionProfileImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/locations/(?P[^/]+)/connectionProfiles/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/connectionProfiles/{{connection_profile_id}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenDatastreamConnectionProfileName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileDisplayName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileOracleProfile(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["hostname"] = + flattenDatastreamConnectionProfileOracleProfileHostname(original["hostname"], d, config) + transformed["port"] = + flattenDatastreamConnectionProfileOracleProfilePort(original["port"], d, config) + transformed["username"] = + flattenDatastreamConnectionProfileOracleProfileUsername(original["username"], d, config) + transformed["password"] = + flattenDatastreamConnectionProfileOracleProfilePassword(original["password"], d, config) + transformed["database_service"] = + flattenDatastreamConnectionProfileOracleProfileDatabaseService(original["databaseService"], d, config) + transformed["connection_attributes"] = + flattenDatastreamConnectionProfileOracleProfileConnectionAttributes(original["connectionAttributes"], d, config) + return []interface{}{transformed} +} +func flattenDatastreamConnectionProfileOracleProfileHostname(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileOracleProfilePort(v interface{}, d *schema.ResourceData, config *Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := 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 flattenDatastreamConnectionProfileOracleProfileUsername(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileOracleProfilePassword(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return d.Get("oracle_profile.0.password") +} + +func flattenDatastreamConnectionProfileOracleProfileDatabaseService(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileOracleProfileConnectionAttributes(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileGcsProfile(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["bucket"] = + flattenDatastreamConnectionProfileGcsProfileBucket(original["bucket"], d, config) + transformed["root_path"] = + flattenDatastreamConnectionProfileGcsProfileRootPath(original["rootPath"], d, config) + return []interface{}{transformed} +} +func flattenDatastreamConnectionProfileGcsProfileBucket(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileGcsProfileRootPath(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileMysqlProfile(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["hostname"] = + flattenDatastreamConnectionProfileMysqlProfileHostname(original["hostname"], d, config) + transformed["port"] = + flattenDatastreamConnectionProfileMysqlProfilePort(original["port"], d, config) + transformed["username"] = + flattenDatastreamConnectionProfileMysqlProfileUsername(original["username"], d, config) + transformed["password"] = + flattenDatastreamConnectionProfileMysqlProfilePassword(original["password"], d, config) + transformed["ssl_config"] = + flattenDatastreamConnectionProfileMysqlProfileSslConfig(original["sslConfig"], d, config) + return []interface{}{transformed} +} +func flattenDatastreamConnectionProfileMysqlProfileHostname(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileMysqlProfilePort(v interface{}, d *schema.ResourceData, config *Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := 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 flattenDatastreamConnectionProfileMysqlProfileUsername(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileMysqlProfilePassword(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return d.Get("mysql_profile.0.password") +} + +func flattenDatastreamConnectionProfileMysqlProfileSslConfig(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["client_key"] = + flattenDatastreamConnectionProfileMysqlProfileSslConfigClientKey(original["clientKey"], d, config) + transformed["client_key_set"] = + flattenDatastreamConnectionProfileMysqlProfileSslConfigClientKeySet(original["clientKeySet"], d, config) + transformed["client_certificate"] = + flattenDatastreamConnectionProfileMysqlProfileSslConfigClientCertificate(original["clientCertificate"], d, config) + transformed["client_certificate_set"] = + flattenDatastreamConnectionProfileMysqlProfileSslConfigClientCertificateSet(original["clientCertificateSet"], d, config) + transformed["ca_certificate"] = + flattenDatastreamConnectionProfileMysqlProfileSslConfigCaCertificate(original["caCertificate"], d, config) + transformed["ca_certificate_set"] = + flattenDatastreamConnectionProfileMysqlProfileSslConfigCaCertificateSet(original["caCertificateSet"], d, config) + return []interface{}{transformed} +} + +func flattenDatastreamConnectionProfileMysqlProfileSslConfigClientKey(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return d.Get("mysql_profile.0.ssl_config.0.client_key") +} + +func flattenDatastreamConnectionProfileMysqlProfileSslConfigClientKeySet(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileMysqlProfileSslConfigClientCertificate(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return d.Get("mysql_profile.0.ssl_config.0.client_certificate") +} + +func flattenDatastreamConnectionProfileMysqlProfileSslConfigClientCertificateSet(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileMysqlProfileSslConfigCaCertificate(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return d.Get("mysql_profile.0.ssl_config.0.ca_certificate") +} + +func flattenDatastreamConnectionProfileMysqlProfileSslConfigCaCertificateSet(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfilePostgresqlProfile(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["hostname"] = + flattenDatastreamConnectionProfilePostgresqlProfileHostname(original["hostname"], d, config) + transformed["port"] = + flattenDatastreamConnectionProfilePostgresqlProfilePort(original["port"], d, config) + transformed["username"] = + flattenDatastreamConnectionProfilePostgresqlProfileUsername(original["username"], d, config) + transformed["password"] = + flattenDatastreamConnectionProfilePostgresqlProfilePassword(original["password"], d, config) + transformed["database"] = + flattenDatastreamConnectionProfilePostgresqlProfileDatabase(original["database"], d, config) + return []interface{}{transformed} +} +func flattenDatastreamConnectionProfilePostgresqlProfileHostname(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfilePostgresqlProfilePort(v interface{}, d *schema.ResourceData, config *Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := 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 flattenDatastreamConnectionProfilePostgresqlProfileUsername(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfilePostgresqlProfilePassword(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return d.Get("postgresql_profile.0.password") +} + +func flattenDatastreamConnectionProfilePostgresqlProfileDatabase(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileForwardSshConnectivity(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["hostname"] = + flattenDatastreamConnectionProfileForwardSshConnectivityHostname(original["hostname"], d, config) + transformed["username"] = + flattenDatastreamConnectionProfileForwardSshConnectivityUsername(original["username"], d, config) + transformed["port"] = + flattenDatastreamConnectionProfileForwardSshConnectivityPort(original["port"], d, config) + transformed["password"] = + flattenDatastreamConnectionProfileForwardSshConnectivityPassword(original["password"], d, config) + transformed["private_key"] = + flattenDatastreamConnectionProfileForwardSshConnectivityPrivateKey(original["privateKey"], d, config) + return []interface{}{transformed} +} +func flattenDatastreamConnectionProfileForwardSshConnectivityHostname(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileForwardSshConnectivityUsername(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamConnectionProfileForwardSshConnectivityPort(v interface{}, d *schema.ResourceData, config *Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := 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 flattenDatastreamConnectionProfileForwardSshConnectivityPassword(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return d.Get("forward_ssh_connectivity.0.password") +} + +func flattenDatastreamConnectionProfileForwardSshConnectivityPrivateKey(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return d.Get("forward_ssh_connectivity.0.private_key") +} + +func expandDatastreamConnectionProfileLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandDatastreamConnectionProfileDisplayName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileOracleProfile(v interface{}, d TerraformResourceData, config *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{}) + + transformedHostname, err := expandDatastreamConnectionProfileOracleProfileHostname(original["hostname"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedHostname); val.IsValid() && !isEmptyValue(val) { + transformed["hostname"] = transformedHostname + } + + transformedPort, err := expandDatastreamConnectionProfileOracleProfilePort(original["port"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPort); val.IsValid() && !isEmptyValue(val) { + transformed["port"] = transformedPort + } + + transformedUsername, err := expandDatastreamConnectionProfileOracleProfileUsername(original["username"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedUsername); val.IsValid() && !isEmptyValue(val) { + transformed["username"] = transformedUsername + } + + transformedPassword, err := expandDatastreamConnectionProfileOracleProfilePassword(original["password"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPassword); val.IsValid() && !isEmptyValue(val) { + transformed["password"] = transformedPassword + } + + transformedDatabaseService, err := expandDatastreamConnectionProfileOracleProfileDatabaseService(original["database_service"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDatabaseService); val.IsValid() && !isEmptyValue(val) { + transformed["databaseService"] = transformedDatabaseService + } + + transformedConnectionAttributes, err := expandDatastreamConnectionProfileOracleProfileConnectionAttributes(original["connection_attributes"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedConnectionAttributes); val.IsValid() && !isEmptyValue(val) { + transformed["connectionAttributes"] = transformedConnectionAttributes + } + + return transformed, nil +} + +func expandDatastreamConnectionProfileOracleProfileHostname(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileOracleProfilePort(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileOracleProfileUsername(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileOracleProfilePassword(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileOracleProfileDatabaseService(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileOracleProfileConnectionAttributes(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandDatastreamConnectionProfileGcsProfile(v interface{}, d TerraformResourceData, config *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{}) + + transformedBucket, err := expandDatastreamConnectionProfileGcsProfileBucket(original["bucket"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedBucket); val.IsValid() && !isEmptyValue(val) { + transformed["bucket"] = transformedBucket + } + + transformedRootPath, err := expandDatastreamConnectionProfileGcsProfileRootPath(original["root_path"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRootPath); val.IsValid() && !isEmptyValue(val) { + transformed["rootPath"] = transformedRootPath + } + + return transformed, nil +} + +func expandDatastreamConnectionProfileGcsProfileBucket(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileGcsProfileRootPath(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileMysqlProfile(v interface{}, d TerraformResourceData, config *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{}) + + transformedHostname, err := expandDatastreamConnectionProfileMysqlProfileHostname(original["hostname"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedHostname); val.IsValid() && !isEmptyValue(val) { + transformed["hostname"] = transformedHostname + } + + transformedPort, err := expandDatastreamConnectionProfileMysqlProfilePort(original["port"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPort); val.IsValid() && !isEmptyValue(val) { + transformed["port"] = transformedPort + } + + transformedUsername, err := expandDatastreamConnectionProfileMysqlProfileUsername(original["username"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedUsername); val.IsValid() && !isEmptyValue(val) { + transformed["username"] = transformedUsername + } + + transformedPassword, err := expandDatastreamConnectionProfileMysqlProfilePassword(original["password"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPassword); val.IsValid() && !isEmptyValue(val) { + transformed["password"] = transformedPassword + } + + transformedSslConfig, err := expandDatastreamConnectionProfileMysqlProfileSslConfig(original["ssl_config"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSslConfig); val.IsValid() && !isEmptyValue(val) { + transformed["sslConfig"] = transformedSslConfig + } + + return transformed, nil +} + +func expandDatastreamConnectionProfileMysqlProfileHostname(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileMysqlProfilePort(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileMysqlProfileUsername(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileMysqlProfilePassword(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileMysqlProfileSslConfig(v interface{}, d TerraformResourceData, config *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{}) + + transformedClientKey, err := expandDatastreamConnectionProfileMysqlProfileSslConfigClientKey(original["client_key"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedClientKey); val.IsValid() && !isEmptyValue(val) { + transformed["clientKey"] = transformedClientKey + } + + transformedClientKeySet, err := expandDatastreamConnectionProfileMysqlProfileSslConfigClientKeySet(original["client_key_set"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedClientKeySet); val.IsValid() && !isEmptyValue(val) { + transformed["clientKeySet"] = transformedClientKeySet + } + + transformedClientCertificate, err := expandDatastreamConnectionProfileMysqlProfileSslConfigClientCertificate(original["client_certificate"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedClientCertificate); val.IsValid() && !isEmptyValue(val) { + transformed["clientCertificate"] = transformedClientCertificate + } + + transformedClientCertificateSet, err := expandDatastreamConnectionProfileMysqlProfileSslConfigClientCertificateSet(original["client_certificate_set"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedClientCertificateSet); val.IsValid() && !isEmptyValue(val) { + transformed["clientCertificateSet"] = transformedClientCertificateSet + } + + transformedCaCertificate, err := expandDatastreamConnectionProfileMysqlProfileSslConfigCaCertificate(original["ca_certificate"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCaCertificate); val.IsValid() && !isEmptyValue(val) { + transformed["caCertificate"] = transformedCaCertificate + } + + transformedCaCertificateSet, err := expandDatastreamConnectionProfileMysqlProfileSslConfigCaCertificateSet(original["ca_certificate_set"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCaCertificateSet); val.IsValid() && !isEmptyValue(val) { + transformed["caCertificateSet"] = transformedCaCertificateSet + } + + return transformed, nil +} + +func expandDatastreamConnectionProfileMysqlProfileSslConfigClientKey(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileMysqlProfileSslConfigClientKeySet(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileMysqlProfileSslConfigClientCertificate(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileMysqlProfileSslConfigClientCertificateSet(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileMysqlProfileSslConfigCaCertificate(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileMysqlProfileSslConfigCaCertificateSet(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfilePostgresqlProfile(v interface{}, d TerraformResourceData, config *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{}) + + transformedHostname, err := expandDatastreamConnectionProfilePostgresqlProfileHostname(original["hostname"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedHostname); val.IsValid() && !isEmptyValue(val) { + transformed["hostname"] = transformedHostname + } + + transformedPort, err := expandDatastreamConnectionProfilePostgresqlProfilePort(original["port"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPort); val.IsValid() && !isEmptyValue(val) { + transformed["port"] = transformedPort + } + + transformedUsername, err := expandDatastreamConnectionProfilePostgresqlProfileUsername(original["username"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedUsername); val.IsValid() && !isEmptyValue(val) { + transformed["username"] = transformedUsername + } + + transformedPassword, err := expandDatastreamConnectionProfilePostgresqlProfilePassword(original["password"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPassword); val.IsValid() && !isEmptyValue(val) { + transformed["password"] = transformedPassword + } + + transformedDatabase, err := expandDatastreamConnectionProfilePostgresqlProfileDatabase(original["database"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDatabase); val.IsValid() && !isEmptyValue(val) { + transformed["database"] = transformedDatabase + } + + return transformed, nil +} + +func expandDatastreamConnectionProfilePostgresqlProfileHostname(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfilePostgresqlProfilePort(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfilePostgresqlProfileUsername(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfilePostgresqlProfilePassword(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfilePostgresqlProfileDatabase(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileForwardSshConnectivity(v interface{}, d TerraformResourceData, config *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{}) + + transformedHostname, err := expandDatastreamConnectionProfileForwardSshConnectivityHostname(original["hostname"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedHostname); val.IsValid() && !isEmptyValue(val) { + transformed["hostname"] = transformedHostname + } + + transformedUsername, err := expandDatastreamConnectionProfileForwardSshConnectivityUsername(original["username"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedUsername); val.IsValid() && !isEmptyValue(val) { + transformed["username"] = transformedUsername + } + + transformedPort, err := expandDatastreamConnectionProfileForwardSshConnectivityPort(original["port"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPort); val.IsValid() && !isEmptyValue(val) { + transformed["port"] = transformedPort + } + + transformedPassword, err := expandDatastreamConnectionProfileForwardSshConnectivityPassword(original["password"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPassword); val.IsValid() && !isEmptyValue(val) { + transformed["password"] = transformedPassword + } + + transformedPrivateKey, err := expandDatastreamConnectionProfileForwardSshConnectivityPrivateKey(original["private_key"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPrivateKey); val.IsValid() && !isEmptyValue(val) { + transformed["privateKey"] = transformedPrivateKey + } + + return transformed, nil +} + +func expandDatastreamConnectionProfileForwardSshConnectivityHostname(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileForwardSshConnectivityUsername(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileForwardSshConnectivityPort(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileForwardSshConnectivityPassword(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandDatastreamConnectionProfileForwardSshConnectivityPrivateKey(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google/resource_datastream_connection_profile_generated_test.go b/google/resource_datastream_connection_profile_generated_test.go new file mode 100644 index 00000000000..5c05e443a84 --- /dev/null +++ b/google/resource_datastream_connection_profile_generated_test.go @@ -0,0 +1,144 @@ +// ---------------------------------------------------------------------------- +// +// *** 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 google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccDatastreamConnectionProfile_datastreamConnectionProfileBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDatastreamConnectionProfileDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDatastreamConnectionProfile_datastreamConnectionProfileBasicExample(context), + }, + { + ResourceName: "google_datastream_connection_profile.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"connection_profile_id", "location"}, + }, + }, + }) +} + +func testAccDatastreamConnectionProfile_datastreamConnectionProfileBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_datastream_connection_profile" "default" { + display_name = "Connection profile" + location = "us-central1" + connection_profile_id = "tf-test-my-profile%{random_suffix}" + + gcs_profile { + bucket = "my-bucket" + root_path = "/path" + } +} +`, context) +} + +func TestAccDatastreamConnectionProfile_datastreamConnectionProfileFullExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDatastreamConnectionProfileDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDatastreamConnectionProfile_datastreamConnectionProfileFullExample(context), + }, + { + ResourceName: "google_datastream_connection_profile.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"connection_profile_id", "location", "forward_ssh_connectivity.0.password"}, + }, + }, + }) +} + +func testAccDatastreamConnectionProfile_datastreamConnectionProfileFullExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_datastream_connection_profile" "default" { + display_name = "Connection profile" + location = "us-central1" + connection_profile_id = "tf-test-my-profile%{random_suffix}" + + gcs_profile { + bucket = "my-bucket" + root_path = "/path" + } + + forward_ssh_connectivity { + hostname = "google.com" + username = "my-user" + port = 8022 + password = "swordfish" + } +} +`, context) +} + +func testAccCheckDatastreamConnectionProfileDestroyProducer(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_datastream_connection_profile" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{DatastreamBasePath}}projects/{{project}}/locations/{{location}}/connectionProfiles/{{connection_profile_id}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = sendRequest(config, "GET", billingProject, url, config.userAgent, nil) + if err == nil { + return fmt.Errorf("DatastreamConnectionProfile still exists at %s", url) + } + } + + return nil + } +} diff --git a/google/resource_datastream_connection_profile_sweeper_test.go b/google/resource_datastream_connection_profile_sweeper_test.go new file mode 100644 index 00000000000..ca27bd0af5d --- /dev/null +++ b/google/resource_datastream_connection_profile_sweeper_test.go @@ -0,0 +1,128 @@ +// ---------------------------------------------------------------------------- +// +// *** 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 google + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func init() { + resource.AddTestSweepers("DatastreamConnectionProfile", &resource.Sweeper{ + Name: "DatastreamConnectionProfile", + F: testSweepDatastreamConnectionProfile, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepDatastreamConnectionProfile(region string) error { + resourceName := "DatastreamConnectionProfile" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := 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 := getTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://datastream.googleapis.com/v1/projects/{{project}}/locations/{{location}}/connectionProfiles", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, config.userAgent, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["connectionProfiles"] + 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 = GetResourceNameFromSelfLink(obj["id"].(string)) + } else if obj["name"] != nil { + name = 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 !isSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://datastream.googleapis.com/v1/projects/{{project}}/locations/{{location}}/connectionProfiles/{{connection_profile_id}}" + deleteUrl, err := 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 = sendRequest(config, "DELETE", config.Project, deleteUrl, config.userAgent, nil) + 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/resource_datastream_connection_profile_test.go b/google/resource_datastream_connection_profile_test.go new file mode 100644 index 00000000000..ed0424bcefe --- /dev/null +++ b/google/resource_datastream_connection_profile_test.go @@ -0,0 +1,144 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDatastreamConnectionProfile_update(t *testing.T) { + // this test uses the random provider + skipIfVcr(t) + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + ExternalProviders: map[string]resource.ExternalProvider{ + "random": {}, + }, + CheckDestroy: testAccCheckDatastreamConnectionProfileDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDatastreamConnectionProfile_update(context), + }, + { + ResourceName: "google_datastream_connection_profile.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"connection_profile_id", "location"}, + }, + { + Config: testAccDatastreamConnectionProfile_update2(context, true), + }, + { + ResourceName: "google_datastream_connection_profile.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"connection_profile_id", "location", "postgresql_profile.0.password"}, + }, + { + // Disable prevent_destroy + Config: testAccDatastreamConnectionProfile_update2(context, false), + }, + }, + }) +} + +func testAccDatastreamConnectionProfile_update(context map[string]interface{}) string { + return Nprintf(` +resource "google_datastream_connection_profile" "default" { + display_name = "Connection profile" + location = "us-central1" + connection_profile_id = "tf-test-my-profile%{random_suffix}" + + gcs_profile { + bucket = "my-bucket" + root_path = "/path" + } + lifecycle { + prevent_destroy = true + } +} +`, context) +} + +func testAccDatastreamConnectionProfile_update2(context map[string]interface{}, preventDestroy bool) string { + context["lifecycle_block"] = "" + if preventDestroy { + context["lifecycle_block"] = ` + lifecycle { + prevent_destroy = true + }` + } + return Nprintf(` +resource "google_sql_database_instance" "instance" { + name = "tf-test-my-database-instance%{random_suffix}" + database_version = "POSTGRES_14" + region = "us-central1" + settings { + tier = "db-f1-micro" + + ip_configuration { + + // Datastream IPs will vary by region. + authorized_networks { + value = "34.71.242.81" + } + + authorized_networks { + value = "34.72.28.29" + } + + authorized_networks { + value = "34.67.6.157" + } + + authorized_networks { + value = "34.67.234.134" + } + + authorized_networks { + value = "34.72.239.218" + } + } + } + + deletion_protection = "false" +} + +resource "google_sql_database" "db" { + instance = google_sql_database_instance.instance.name + name = "db" +} + +resource "random_password" "pwd" { + length = 16 + special = false +} + +resource "google_sql_user" "user" { + name = "user" + instance = google_sql_database_instance.instance.name + password = random_password.pwd.result +} + +resource "google_datastream_connection_profile" "default" { + display_name = "Connection profile" + location = "us-central1" + connection_profile_id = "tf-test-my-profile%{random_suffix}" + + postgresql_profile { + hostname = google_sql_database_instance.instance.public_ip_address + username = google_sql_user.user.name + password = google_sql_user.user.password + database = google_sql_database.db.name + } + %{lifecycle_block} +} +`, context) +} diff --git a/website/docs/r/datastream_connection_profile.html.markdown b/website/docs/r/datastream_connection_profile.html.markdown new file mode 100644 index 00000000000..059f9dfcd47 --- /dev/null +++ b/website/docs/r/datastream_connection_profile.html.markdown @@ -0,0 +1,378 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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: "Datastream" +page_title: "Google: google_datastream_connection_profile" +description: |- + A set of reusable connection configurations to be used as a source or destination for a stream. +--- + +# google\_datastream\_connection\_profile + +A set of reusable connection configurations to be used as a source or destination for a stream. + + +To get more information about ConnectionProfile, see: + +* [API documentation](https://cloud.google.com/datastream/docs/reference/rest/v1/projects.locations.connectionProfiles) +* How-to Guides + * [Official Documentation](https://cloud.google.com/datastream/docs/create-connection-profiles) + +~> **Warning:** All arguments including `oracle_profile.password`, `mysql_profile.password`, `mysql_profile.ssl_config.client_key`, `mysql_profile.ssl_config.client_certificate`, `mysql_profile.ssl_config.ca_certificate`, `postgresql_profile.password`, `forward_ssh_connectivity.password`, and `forward_ssh_connectivity.private_key` will be stored in the raw +state as plain-text. [Read more about sensitive data in state](https://www.terraform.io/language/state/sensitive-data). + + +## Example Usage - Datastream Connection Profile Basic + + +```hcl +resource "google_datastream_connection_profile" "default" { + display_name = "Connection profile" + location = "us-central1" + connection_profile_id = "my-profile" + + gcs_profile { + bucket = "my-bucket" + root_path = "/path" + } +} +``` + +## Example Usage - Datastream Connection Profile Full + + +```hcl +resource "google_datastream_connection_profile" "default" { + display_name = "Connection profile" + location = "us-central1" + connection_profile_id = "my-profile" + + gcs_profile { + bucket = "my-bucket" + root_path = "/path" + } + + forward_ssh_connectivity { + hostname = "google.com" + username = "my-user" + port = 8022 + password = "swordfish" + } +} +``` +## Example Usage - Datastream Connection Profile Postgres + + +```hcl +resource "google_sql_database_instance" "instance" { + name = "my-instance" + database_version = "POSTGRES_14" + region = "us-central1" + settings { + tier = "db-f1-micro" + + ip_configuration { + + // Datastream IPs will vary by region. + authorized_networks { + value = "34.71.242.81" + } + + authorized_networks { + value = "34.72.28.29" + } + + authorized_networks { + value = "34.67.6.157" + } + + authorized_networks { + value = "34.67.234.134" + } + + authorized_networks { + value = "34.72.239.218" + } + } + } + + deletion_protection = "true" +} + +resource "google_sql_database" "db" { + instance = google_sql_database_instance.instance.name + name = "db" +} + +resource "random_password" "pwd" { + length = 16 + special = false +} + +resource "google_sql_user" "user" { + name = "user" + instance = google_sql_database_instance.instance.name + password = random_password.pwd.result +} + +resource "google_datastream_connection_profile" "default" { + display_name = "Connection profile" + location = "us-central1" + connection_profile_id = "my-profile" + + postgresql_profile { + hostname = google_sql_database_instance.instance.public_ip_address + username = google_sql_user.user.name + password = google_sql_user.user.password + database = google_sql_database.db.name + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `display_name` - + (Required) + Display name. + +* `connection_profile_id` - + (Required) + The connection profile identifier. + +* `location` - + (Required) + The name of the location this repository is located in. + + +- - - + + +* `labels` - + (Optional) + Labels. + +* `oracle_profile` - + (Optional) + Oracle database profile. + Structure is [documented below](#nested_oracle_profile). + +* `gcs_profile` - + (Optional) + Cloud Storage bucket profile. + Structure is [documented below](#nested_gcs_profile). + +* `mysql_profile` - + (Optional) + MySQL database profile. + Structure is [documented below](#nested_mysql_profile). + +* `postgresql_profile` - + (Optional) + PostgreSQL database profile. + Structure is [documented below](#nested_postgresql_profile). + +* `forward_ssh_connectivity` - + (Optional) + Forward SSH tunnel connectivity. + Structure is [documented below](#nested_forward_ssh_connectivity). + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +The `oracle_profile` block supports: + +* `hostname` - + (Required) + Hostname for the Oracle connection. + +* `port` - + (Optional) + Port for the Oracle connection. + +* `username` - + (Required) + Username for the Oracle connection. + +* `password` - + (Required) + Password for the Oracle connection. + **Note**: This property is sensitive and will not be displayed in the plan. + +* `database_service` - + (Required) + Database for the Oracle connection. + +* `connection_attributes` - + (Optional) + Connection string attributes + +The `gcs_profile` block supports: + +* `bucket` - + (Required) + The Cloud Storage bucket name. + +* `root_path` - + (Optional) + The root path inside the Cloud Storage bucket. + +The `mysql_profile` block supports: + +* `hostname` - + (Required) + Hostname for the MySQL connection. + +* `port` - + (Optional) + Port for the MySQL connection. + +* `username` - + (Required) + Username for the MySQL connection. + +* `password` - + (Required) + Password for the MySQL connection. + **Note**: This property is sensitive and will not be displayed in the plan. + +* `ssl_config` - + (Optional) + SSL configuration for the MySQL connection. + Structure is [documented below](#nested_ssl_config). + + +The `ssl_config` block supports: + +* `client_key` - + (Optional) + PEM-encoded private key associated with the Client Certificate. + If this field is used then the 'client_certificate' and the + 'ca_certificate' fields are mandatory. + **Note**: This property is sensitive and will not be displayed in the plan. + +* `client_key_set` - + Indicates whether the clientKey field is set. + +* `client_certificate` - + (Optional) + PEM-encoded certificate that will be used by the replica to + authenticate against the source database server. If this field + is used then the 'clientKey' and the 'caCertificate' fields are + mandatory. + **Note**: This property is sensitive and will not be displayed in the plan. + +* `client_certificate_set` - + Indicates whether the clientCertificate field is set. + +* `ca_certificate` - + (Optional) + PEM-encoded certificate of the CA that signed the source database + server's certificate. + **Note**: This property is sensitive and will not be displayed in the plan. + +* `ca_certificate_set` - + Indicates whether the clientKey field is set. + +The `postgresql_profile` block supports: + +* `hostname` - + (Required) + Hostname for the PostgreSQL connection. + +* `port` - + (Optional) + Port for the PostgreSQL connection. + +* `username` - + (Required) + Username for the PostgreSQL connection. + +* `password` - + (Required) + Password for the PostgreSQL connection. + **Note**: This property is sensitive and will not be displayed in the plan. + +* `database` - + (Required) + Database for the PostgreSQL connection. + +The `forward_ssh_connectivity` block supports: + +* `hostname` - + (Required) + Hostname for the SSH tunnel. + +* `username` - + (Required) + Username for the SSH tunnel. + +* `port` - + (Optional) + Port for the SSH tunnel. + +* `password` - + (Optional) + SSH password. + **Note**: This property is sensitive and will not be displayed in the plan. + +* `private_key` - + (Optional) + SSH private key. + **Note**: This property is sensitive and will not be displayed in the plan. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/locations/{{location}}/connectionProfiles/{{connection_profile_id}}` + +* `name` - + The resource's name. + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `update` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +ConnectionProfile can be imported using any of these accepted formats: + +``` +$ terraform import google_datastream_connection_profile.default projects/{{project}}/locations/{{location}}/connectionProfiles/{{connection_profile_id}} +$ terraform import google_datastream_connection_profile.default {{project}}/{{location}}/{{connection_profile_id}} +$ terraform import google_datastream_connection_profile.default {{location}}/{{connection_profile_id}} +``` + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override).