diff --git a/.changelog/6801.txt b/.changelog/6801.txt new file mode 100644 index 00000000000..def3fb4a6f4 --- /dev/null +++ b/.changelog/6801.txt @@ -0,0 +1,6 @@ +```release-note:new-resource +`google_beyondcorp_app_connector` +``` +```release-note:new-resource +`google_beyondcorp_app_gateway` +``` diff --git a/google/beyondcorp_operation.go b/google/beyondcorp_operation.go new file mode 100644 index 00000000000..1727b8bf4b2 --- /dev/null +++ b/google/beyondcorp_operation.go @@ -0,0 +1,75 @@ +// ---------------------------------------------------------------------------- +// +// *** 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 ( + "encoding/json" + "fmt" + "time" +) + +type BeyondcorpOperationWaiter struct { + Config *Config + UserAgent string + Project string + CommonOperationWaiter +} + +func (w *BeyondcorpOperationWaiter) 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.BeyondcorpBasePath, w.CommonOperationWaiter.Op.Name) + + return sendRequest(w.Config, "GET", w.Project, url, w.UserAgent, nil) +} + +func createBeyondcorpWaiter(config *Config, op map[string]interface{}, project, activity, userAgent string) (*BeyondcorpOperationWaiter, error) { + w := &BeyondcorpOperationWaiter{ + Config: config, + UserAgent: userAgent, + Project: project, + } + if err := w.CommonOperationWaiter.SetOp(op); err != nil { + return nil, err + } + return w, nil +} + +// nolint: deadcode,unused +func beyondcorpOperationWaitTimeWithResponse(config *Config, op map[string]interface{}, response *map[string]interface{}, project, activity, userAgent string, timeout time.Duration) error { + w, err := createBeyondcorpWaiter(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 beyondcorpOperationWaitTime(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 := createBeyondcorpWaiter(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) +} diff --git a/google/config.go b/google/config.go index b978f5a3013..ff462dee40b 100644 --- a/google/config.go +++ b/google/config.go @@ -177,6 +177,7 @@ type Config struct { ApigeeBasePath string AppEngineBasePath string ArtifactRegistryBasePath string + BeyondcorpBasePath string BigQueryBasePath string BigqueryAnalyticsHubBasePath string BigqueryConnectionBasePath string @@ -274,6 +275,7 @@ const ActiveDirectoryBasePathKey = "ActiveDirectory" const ApigeeBasePathKey = "Apigee" const AppEngineBasePathKey = "AppEngine" const ArtifactRegistryBasePathKey = "ArtifactRegistry" +const BeyondcorpBasePathKey = "Beyondcorp" const BigQueryBasePathKey = "BigQuery" const BigqueryAnalyticsHubBasePathKey = "BigqueryAnalyticsHub" const BigqueryConnectionBasePathKey = "BigqueryConnection" @@ -365,6 +367,7 @@ var DefaultBasePaths = map[string]string{ ApigeeBasePathKey: "https://apigee.googleapis.com/v1/", AppEngineBasePathKey: "https://appengine.googleapis.com/v1/", ArtifactRegistryBasePathKey: "https://artifactregistry.googleapis.com/v1/", + BeyondcorpBasePathKey: "https://beyondcorp.googleapis.com/v1/", BigQueryBasePathKey: "https://bigquery.googleapis.com/bigquery/v2/", BigqueryAnalyticsHubBasePathKey: "https://analyticshub.googleapis.com/v1/", BigqueryConnectionBasePathKey: "https://bigqueryconnection.googleapis.com/v1/", @@ -1218,6 +1221,7 @@ func ConfigureBasePaths(c *Config) { c.ApigeeBasePath = DefaultBasePaths[ApigeeBasePathKey] c.AppEngineBasePath = DefaultBasePaths[AppEngineBasePathKey] c.ArtifactRegistryBasePath = DefaultBasePaths[ArtifactRegistryBasePathKey] + c.BeyondcorpBasePath = DefaultBasePaths[BeyondcorpBasePathKey] c.BigQueryBasePath = DefaultBasePaths[BigQueryBasePathKey] c.BigqueryAnalyticsHubBasePath = DefaultBasePaths[BigqueryAnalyticsHubBasePathKey] c.BigqueryConnectionBasePath = DefaultBasePaths[BigqueryConnectionBasePathKey] diff --git a/google/provider.go b/google/provider.go index 4355d463220..059ff82ee36 100644 --- a/google/provider.go +++ b/google/provider.go @@ -197,6 +197,14 @@ func Provider() *schema.Provider { "GOOGLE_ARTIFACT_REGISTRY_CUSTOM_ENDPOINT", }, DefaultBasePaths[ArtifactRegistryBasePathKey]), }, + "beyondcorp_custom_endpoint": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateCustomEndpoint, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_BEYONDCORP_CUSTOM_ENDPOINT", + }, DefaultBasePaths[BeyondcorpBasePathKey]), + }, "big_query_custom_endpoint": { Type: schema.TypeString, Optional: true, @@ -917,9 +925,9 @@ func Provider() *schema.Provider { return provider } -// Generated resources: 246 +// Generated resources: 248 // Generated IAM resources: 150 -// Total generated resources: 396 +// Total generated resources: 398 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -966,6 +974,8 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_artifact_registry_repository_iam_binding": ResourceIamBinding(ArtifactRegistryRepositoryIamSchema, ArtifactRegistryRepositoryIamUpdaterProducer, ArtifactRegistryRepositoryIdParseFunc), "google_artifact_registry_repository_iam_member": ResourceIamMember(ArtifactRegistryRepositoryIamSchema, ArtifactRegistryRepositoryIamUpdaterProducer, ArtifactRegistryRepositoryIdParseFunc), "google_artifact_registry_repository_iam_policy": ResourceIamPolicy(ArtifactRegistryRepositoryIamSchema, ArtifactRegistryRepositoryIamUpdaterProducer, ArtifactRegistryRepositoryIdParseFunc), + "google_beyondcorp_app_connector": resourceBeyondcorpAppConnector(), + "google_beyondcorp_app_gateway": resourceBeyondcorpAppGateway(), "google_bigquery_dataset": resourceBigQueryDataset(), "google_bigquery_dataset_access": resourceBigQueryDatasetAccess(), "google_bigquery_job": resourceBigQueryJob(), @@ -1541,6 +1551,7 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData, p *schema.Pr config.ApigeeBasePath = d.Get("apigee_custom_endpoint").(string) config.AppEngineBasePath = d.Get("app_engine_custom_endpoint").(string) config.ArtifactRegistryBasePath = d.Get("artifact_registry_custom_endpoint").(string) + config.BeyondcorpBasePath = d.Get("beyondcorp_custom_endpoint").(string) config.BigQueryBasePath = d.Get("big_query_custom_endpoint").(string) config.BigqueryAnalyticsHubBasePath = d.Get("bigquery_analytics_hub_custom_endpoint").(string) config.BigqueryConnectionBasePath = d.Get("bigquery_connection_custom_endpoint").(string) diff --git a/google/resource_beyondcorp_app_connector.go b/google/resource_beyondcorp_app_connector.go new file mode 100644 index 00000000000..e28a8b17ee4 --- /dev/null +++ b/google/resource_beyondcorp_app_connector.go @@ -0,0 +1,490 @@ +// ---------------------------------------------------------------------------- +// +// *** 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 resourceBeyondcorpAppConnector() *schema.Resource { + return &schema.Resource{ + Create: resourceBeyondcorpAppConnectorCreate, + Read: resourceBeyondcorpAppConnectorRead, + Update: resourceBeyondcorpAppConnectorUpdate, + Delete: resourceBeyondcorpAppConnectorDelete, + + Importer: &schema.ResourceImporter{ + State: resourceBeyondcorpAppConnectorImport, + }, + + 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{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `ID of the AppConnector.`, + }, + "principal_info": { + Type: schema.TypeList, + Required: true, + Description: `Principal information about the Identity of the AppConnector.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service_account": { + Type: schema.TypeList, + Required: true, + Description: `ServiceAccount represents a GCP service account.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "email": { + Type: schema.TypeString, + Required: true, + Description: `Email address of the service account.`, + }, + }, + }, + }, + }, + }, + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + Description: `An arbitrary user-provided name for the AppConnector.`, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Description: `Resource labels to represent user provided metadata.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "region": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The region of the AppConnector.`, + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: `Represents the different states of a AppConnector.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceBeyondcorpAppConnectorCreate(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{}) + displayNameProp, err := expandBeyondcorpAppConnectorDisplayName(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 + } + labelsProp, err := expandBeyondcorpAppConnectorLabels(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 + } + principalInfoProp, err := expandBeyondcorpAppConnectorPrincipalInfo(d.Get("principal_info"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("principal_info"); !isEmptyValue(reflect.ValueOf(principalInfoProp)) && (ok || !reflect.DeepEqual(v, principalInfoProp)) { + obj["principalInfo"] = principalInfoProp + } + + url, err := replaceVars(d, config, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{region}}/appConnectors?app_connector_id={{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new AppConnector: %#v", obj) + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for AppConnector: %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 AppConnector: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{region}}/appConnectors/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Use the resource in the operation response to populate + // identity fields and d.Id() before read + var opRes map[string]interface{} + err = beyondcorpOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating AppConnector", userAgent, + d.Timeout(schema.TimeoutCreate)) + if err != nil { + // The resource didn't actually create + d.SetId("") + + return fmt.Errorf("Error waiting to create AppConnector: %s", err) + } + + // This may have caused the ID to update - update it if so. + id, err = replaceVars(d, config, "projects/{{project}}/locations/{{region}}/appConnectors/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating AppConnector %q: %#v", d.Id(), res) + + return resourceBeyondcorpAppConnectorRead(d, meta) +} + +func resourceBeyondcorpAppConnectorRead(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, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{region}}/appConnectors/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for AppConnector: %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("BeyondcorpAppConnector %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading AppConnector: %s", err) + } + + if err := d.Set("display_name", flattenBeyondcorpAppConnectorDisplayName(res["displayName"], d, config)); err != nil { + return fmt.Errorf("Error reading AppConnector: %s", err) + } + if err := d.Set("labels", flattenBeyondcorpAppConnectorLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading AppConnector: %s", err) + } + if err := d.Set("principal_info", flattenBeyondcorpAppConnectorPrincipalInfo(res["principalInfo"], d, config)); err != nil { + return fmt.Errorf("Error reading AppConnector: %s", err) + } + if err := d.Set("state", flattenBeyondcorpAppConnectorState(res["state"], d, config)); err != nil { + return fmt.Errorf("Error reading AppConnector: %s", err) + } + + return nil +} + +func resourceBeyondcorpAppConnectorUpdate(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 AppConnector: %s", err) + } + billingProject = project + + obj := make(map[string]interface{}) + displayNameProp, err := expandBeyondcorpAppConnectorDisplayName(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 + } + labelsProp, err := expandBeyondcorpAppConnectorLabels(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 + } + principalInfoProp, err := expandBeyondcorpAppConnectorPrincipalInfo(d.Get("principal_info"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("principal_info"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, principalInfoProp)) { + obj["principalInfo"] = principalInfoProp + } + + url, err := replaceVars(d, config, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{region}}/appConnectors/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating AppConnector %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("display_name") { + updateMask = append(updateMask, "displayName") + } + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + + if d.HasChange("principal_info") { + updateMask = append(updateMask, "principalInfo") + } + // 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 AppConnector %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating AppConnector %q: %#v", d.Id(), res) + } + + err = beyondcorpOperationWaitTime( + config, res, project, "Updating AppConnector", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + + return resourceBeyondcorpAppConnectorRead(d, meta) +} + +func resourceBeyondcorpAppConnectorDelete(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 AppConnector: %s", err) + } + billingProject = project + + url, err := replaceVars(d, config, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{region}}/appConnectors/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting AppConnector %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, "AppConnector") + } + + err = beyondcorpOperationWaitTime( + config, res, project, "Deleting AppConnector", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting AppConnector %q: %#v", d.Id(), res) + return nil +} + +func resourceBeyondcorpAppConnectorImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/locations/(?P[^/]+)/appConnectors/(?P[^/]+)", + "(?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/{{region}}/appConnectors/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenBeyondcorpAppConnectorDisplayName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenBeyondcorpAppConnectorLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenBeyondcorpAppConnectorPrincipalInfo(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["service_account"] = + flattenBeyondcorpAppConnectorPrincipalInfoServiceAccount(original["serviceAccount"], d, config) + return []interface{}{transformed} +} +func flattenBeyondcorpAppConnectorPrincipalInfoServiceAccount(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["email"] = + flattenBeyondcorpAppConnectorPrincipalInfoServiceAccountEmail(original["email"], d, config) + return []interface{}{transformed} +} +func flattenBeyondcorpAppConnectorPrincipalInfoServiceAccountEmail(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenBeyondcorpAppConnectorState(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandBeyondcorpAppConnectorDisplayName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandBeyondcorpAppConnectorLabels(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 expandBeyondcorpAppConnectorPrincipalInfo(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{}) + + transformedServiceAccount, err := expandBeyondcorpAppConnectorPrincipalInfoServiceAccount(original["service_account"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedServiceAccount); val.IsValid() && !isEmptyValue(val) { + transformed["serviceAccount"] = transformedServiceAccount + } + + return transformed, nil +} + +func expandBeyondcorpAppConnectorPrincipalInfoServiceAccount(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{}) + + transformedEmail, err := expandBeyondcorpAppConnectorPrincipalInfoServiceAccountEmail(original["email"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedEmail); val.IsValid() && !isEmptyValue(val) { + transformed["email"] = transformedEmail + } + + return transformed, nil +} + +func expandBeyondcorpAppConnectorPrincipalInfoServiceAccountEmail(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google/resource_beyondcorp_app_connector_generated_test.go b/google/resource_beyondcorp_app_connector_generated_test.go new file mode 100644 index 00000000000..719255bb3be --- /dev/null +++ b/google/resource_beyondcorp_app_connector_generated_test.go @@ -0,0 +1,149 @@ +// ---------------------------------------------------------------------------- +// +// *** 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 TestAccBeyondcorpAppConnector_beyondcorpAppConnectorBasicExample(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: testAccCheckBeyondcorpAppConnectorDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBeyondcorpAppConnector_beyondcorpAppConnectorBasicExample(context), + }, + { + ResourceName: "google_beyondcorp_app_connector.app_connector", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "region"}, + }, + }, + }) +} + +func testAccBeyondcorpAppConnector_beyondcorpAppConnectorBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_service_account" "service_account" { + account_id = "tf-test-my-account%{random_suffix}" + display_name = "Test Service Account" +} + +resource "google_beyondcorp_app_connector" "app_connector" { + name = "tf-test-my-app-connector%{random_suffix}" + principal_info { + service_account { + email = google_service_account.service_account.email + } + } +} +`, context) +} + +func TestAccBeyondcorpAppConnector_beyondcorpAppConnectorFullExample(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: testAccCheckBeyondcorpAppConnectorDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBeyondcorpAppConnector_beyondcorpAppConnectorFullExample(context), + }, + { + ResourceName: "google_beyondcorp_app_connector.app_connector", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "region"}, + }, + }, + }) +} + +func testAccBeyondcorpAppConnector_beyondcorpAppConnectorFullExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_service_account" "service_account" { + account_id = "tf-test-my-account%{random_suffix}" + display_name = "Test Service Account" +} + +resource "google_beyondcorp_app_connector" "app_connector" { + name = "tf-test-my-app-connector%{random_suffix}" + region = "us-central1" + display_name = "some display name%{random_suffix}" + principal_info { + service_account { + email = google_service_account.service_account.email + } + } + labels = { + foo = "bar" + bar = "baz" + } +} +`, context) +} + +func testAccCheckBeyondcorpAppConnectorDestroyProducer(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_beyondcorp_app_connector" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{region}}/appConnectors/{{name}}") + 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("BeyondcorpAppConnector still exists at %s", url) + } + } + + return nil + } +} diff --git a/google/resource_beyondcorp_app_connector_sweeper_test.go b/google/resource_beyondcorp_app_connector_sweeper_test.go new file mode 100644 index 00000000000..09718e330d9 --- /dev/null +++ b/google/resource_beyondcorp_app_connector_sweeper_test.go @@ -0,0 +1,124 @@ +// ---------------------------------------------------------------------------- +// +// *** 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("BeyondcorpAppConnector", &resource.Sweeper{ + Name: "BeyondcorpAppConnector", + F: testSweepBeyondcorpAppConnector, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepBeyondcorpAppConnector(region string) error { + resourceName := "BeyondcorpAppConnector" + 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://beyondcorp.googleapis.com/v1/projects/{{project}}/locations/{{region}}/appConnectors", "?")[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["appConnectors"] + 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{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !isSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://beyondcorp.googleapis.com/v1/projects/{{project}}/locations/{{region}}/appConnectors/{{name}}" + 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_beyondcorp_app_connector_test.go b/google/resource_beyondcorp_app_connector_test.go new file mode 100644 index 00000000000..786b44cd6e8 --- /dev/null +++ b/google/resource_beyondcorp_app_connector_test.go @@ -0,0 +1,63 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccBeyondcorpAppConnector_beyondcorpAppConnectorUpdateExample(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: testAccCheckBeyondcorpAppConnectorDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBeyondcorpAppConnector_beyondcorpAppConnectorBasicExample(context), + }, + { + ResourceName: "google_beyondcorp_app_connector.app_connector", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "region"}, + }, + { + Config: testAccBeyondcorpAppConnector_beyondcorpAppConnectorUpdateExample(context), + }, + { + ResourceName: "google_beyondcorp_app_connector.app_connector", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "region"}, + }, + { + Config: testAccBeyondcorpAppConnector_beyondcorpAppConnectorBasicExample(context), + }, + }, + }) +} + +func testAccBeyondcorpAppConnector_beyondcorpAppConnectorUpdateExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_service_account" "service_account" { + account_id = "tf-test-my-account%{random_suffix}" + display_name = "Test Service Account" +} + +resource "google_beyondcorp_app_connector" "app_connector" { + name = "tf-test-my-app-connector%{random_suffix}" + principal_info { + service_account { + email = google_service_account.service_account.email + } + } + display_name = "Some display name" +} +`, context) +} diff --git a/google/resource_beyondcorp_app_gateway.go b/google/resource_beyondcorp_app_gateway.go new file mode 100644 index 00000000000..80221c390ee --- /dev/null +++ b/google/resource_beyondcorp_app_gateway.go @@ -0,0 +1,418 @@ +// ---------------------------------------------------------------------------- +// +// *** 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" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceBeyondcorpAppGateway() *schema.Resource { + return &schema.Resource{ + Create: resourceBeyondcorpAppGatewayCreate, + Read: resourceBeyondcorpAppGatewayRead, + Delete: resourceBeyondcorpAppGatewayDelete, + + Importer: &schema.ResourceImporter{ + State: resourceBeyondcorpAppGatewayImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `ID of the AppGateway.`, + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `An arbitrary user-provided name for the AppGateway.`, + }, + "host_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateEnum([]string{"HOST_TYPE_UNSPECIFIED", "GCP_REGIONAL_MIG", ""}), + Description: `The type of hosting used by the AppGateway. Default value: "HOST_TYPE_UNSPECIFIED" Possible values: ["HOST_TYPE_UNSPECIFIED", "GCP_REGIONAL_MIG"]`, + Default: "HOST_TYPE_UNSPECIFIED", + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Description: `Resource labels to represent user provided metadata.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "region": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The region of the AppGateway.`, + }, + "type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateEnum([]string{"TYPE_UNSPECIFIED", "TCP_PROXY", ""}), + Description: `The type of network connectivity used by the AppGateway. Default value: "TYPE_UNSPECIFIED" Possible values: ["TYPE_UNSPECIFIED", "TCP_PROXY"]`, + Default: "TYPE_UNSPECIFIED", + }, + "allocated_connections": { + Type: schema.TypeList, + Computed: true, + Description: `A list of connections allocated for the Gateway.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ingress_port": { + Type: schema.TypeInt, + Optional: true, + Description: `The ingress port of an allocated connection.`, + }, + "psc_uri": { + Type: schema.TypeString, + Optional: true, + Description: `The PSC uri of an allocated connection.`, + }, + }, + }, + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: `Represents the different states of a AppGateway.`, + }, + "uri": { + Type: schema.TypeString, + Computed: true, + Description: `Server-defined URI for this resource.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceBeyondcorpAppGatewayCreate(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{}) + typeProp, err := expandBeyondcorpAppGatewayType(d.Get("type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("type"); !isEmptyValue(reflect.ValueOf(typeProp)) && (ok || !reflect.DeepEqual(v, typeProp)) { + obj["type"] = typeProp + } + hostTypeProp, err := expandBeyondcorpAppGatewayHostType(d.Get("host_type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("host_type"); !isEmptyValue(reflect.ValueOf(hostTypeProp)) && (ok || !reflect.DeepEqual(v, hostTypeProp)) { + obj["hostType"] = hostTypeProp + } + displayNameProp, err := expandBeyondcorpAppGatewayDisplayName(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 + } + labelsProp, err := expandBeyondcorpAppGatewayLabels(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 + } + + url, err := replaceVars(d, config, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{region}}/appGateways?app_gateway_id={{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new AppGateway: %#v", obj) + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for AppGateway: %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 AppGateway: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{region}}/appGateways/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Use the resource in the operation response to populate + // identity fields and d.Id() before read + var opRes map[string]interface{} + err = beyondcorpOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating AppGateway", userAgent, + d.Timeout(schema.TimeoutCreate)) + if err != nil { + // The resource didn't actually create + d.SetId("") + + return fmt.Errorf("Error waiting to create AppGateway: %s", err) + } + + // This may have caused the ID to update - update it if so. + id, err = replaceVars(d, config, "projects/{{project}}/locations/{{region}}/appGateways/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating AppGateway %q: %#v", d.Id(), res) + + return resourceBeyondcorpAppGatewayRead(d, meta) +} + +func resourceBeyondcorpAppGatewayRead(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, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{region}}/appGateways/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for AppGateway: %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("BeyondcorpAppGateway %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading AppGateway: %s", err) + } + + if err := d.Set("type", flattenBeyondcorpAppGatewayType(res["type"], d, config)); err != nil { + return fmt.Errorf("Error reading AppGateway: %s", err) + } + if err := d.Set("host_type", flattenBeyondcorpAppGatewayHostType(res["hostType"], d, config)); err != nil { + return fmt.Errorf("Error reading AppGateway: %s", err) + } + if err := d.Set("display_name", flattenBeyondcorpAppGatewayDisplayName(res["displayName"], d, config)); err != nil { + return fmt.Errorf("Error reading AppGateway: %s", err) + } + if err := d.Set("labels", flattenBeyondcorpAppGatewayLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading AppGateway: %s", err) + } + if err := d.Set("state", flattenBeyondcorpAppGatewayState(res["state"], d, config)); err != nil { + return fmt.Errorf("Error reading AppGateway: %s", err) + } + if err := d.Set("uri", flattenBeyondcorpAppGatewayUri(res["uri"], d, config)); err != nil { + return fmt.Errorf("Error reading AppGateway: %s", err) + } + if err := d.Set("allocated_connections", flattenBeyondcorpAppGatewayAllocatedConnections(res["allocatedConnections"], d, config)); err != nil { + return fmt.Errorf("Error reading AppGateway: %s", err) + } + + return nil +} + +func resourceBeyondcorpAppGatewayDelete(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 AppGateway: %s", err) + } + billingProject = project + + url, err := replaceVars(d, config, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{region}}/appGateways/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting AppGateway %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, "AppGateway") + } + + err = beyondcorpOperationWaitTime( + config, res, project, "Deleting AppGateway", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting AppGateway %q: %#v", d.Id(), res) + return nil +} + +func resourceBeyondcorpAppGatewayImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/locations/(?P[^/]+)/appGateways/(?P[^/]+)", + "(?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/{{region}}/appGateways/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenBeyondcorpAppGatewayType(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenBeyondcorpAppGatewayHostType(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenBeyondcorpAppGatewayDisplayName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenBeyondcorpAppGatewayLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenBeyondcorpAppGatewayState(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenBeyondcorpAppGatewayUri(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenBeyondcorpAppGatewayAllocatedConnections(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["psc_uri"] = + flattenBeyondcorpAppGatewayAllocatedConnectionsPscUri(original["pscUri"], d, config) + transformed["ingress_port"] = + flattenBeyondcorpAppGatewayAllocatedConnectionsIngressPort(original["ingressPort"], d, config) + return []interface{}{transformed} +} +func flattenBeyondcorpAppGatewayAllocatedConnectionsPscUri(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenBeyondcorpAppGatewayAllocatedConnectionsIngressPort(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 expandBeyondcorpAppGatewayType(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandBeyondcorpAppGatewayHostType(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandBeyondcorpAppGatewayDisplayName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandBeyondcorpAppGatewayLabels(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 +} diff --git a/google/resource_beyondcorp_app_gateway_generated_test.go b/google/resource_beyondcorp_app_gateway_generated_test.go new file mode 100644 index 00000000000..8e3692f0161 --- /dev/null +++ b/google/resource_beyondcorp_app_gateway_generated_test.go @@ -0,0 +1,134 @@ +// ---------------------------------------------------------------------------- +// +// *** 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 TestAccBeyondcorpAppGateway_beyondcorpAppGatewayBasicExample(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: testAccCheckBeyondcorpAppGatewayDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBeyondcorpAppGateway_beyondcorpAppGatewayBasicExample(context), + }, + { + ResourceName: "google_beyondcorp_app_gateway.app_gateway", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "region"}, + }, + }, + }) +} + +func testAccBeyondcorpAppGateway_beyondcorpAppGatewayBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_beyondcorp_app_gateway" "app_gateway" { + name = "tf-test-my-app-gateway%{random_suffix}" + type = "TCP_PROXY" + region = "us-central1" + host_type = "GCP_REGIONAL_MIG" +} +`, context) +} + +func TestAccBeyondcorpAppGateway_beyondcorpAppGatewayFullExample(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: testAccCheckBeyondcorpAppGatewayDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBeyondcorpAppGateway_beyondcorpAppGatewayFullExample(context), + }, + { + ResourceName: "google_beyondcorp_app_gateway.app_gateway", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "region"}, + }, + }, + }) +} + +func testAccBeyondcorpAppGateway_beyondcorpAppGatewayFullExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_beyondcorp_app_gateway" "app_gateway" { + name = "tf-test-my-app-gateway%{random_suffix}" + type = "TCP_PROXY" + region = "us-central1" + display_name = "some display name%{random_suffix}" + labels = { + foo = "bar" + bar = "baz" + } + host_type = "GCP_REGIONAL_MIG" +} +`, context) +} + +func testAccCheckBeyondcorpAppGatewayDestroyProducer(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_beyondcorp_app_gateway" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{BeyondcorpBasePath}}projects/{{project}}/locations/{{region}}/appGateways/{{name}}") + 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("BeyondcorpAppGateway still exists at %s", url) + } + } + + return nil + } +} diff --git a/google/resource_beyondcorp_app_gateway_sweeper_test.go b/google/resource_beyondcorp_app_gateway_sweeper_test.go new file mode 100644 index 00000000000..2e790323a01 --- /dev/null +++ b/google/resource_beyondcorp_app_gateway_sweeper_test.go @@ -0,0 +1,124 @@ +// ---------------------------------------------------------------------------- +// +// *** 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("BeyondcorpAppGateway", &resource.Sweeper{ + Name: "BeyondcorpAppGateway", + F: testSweepBeyondcorpAppGateway, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepBeyondcorpAppGateway(region string) error { + resourceName := "BeyondcorpAppGateway" + 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://beyondcorp.googleapis.com/v1/projects/{{project}}/locations/{{region}}/appGateways", "?")[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["appGateways"] + 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{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !isSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://beyondcorp.googleapis.com/v1/projects/{{project}}/locations/{{region}}/appGateways/{{name}}" + 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/website/docs/r/beyondcorp_app_connector.html.markdown b/website/docs/r/beyondcorp_app_connector.html.markdown new file mode 100644 index 00000000000..b19aaf903be --- /dev/null +++ b/website/docs/r/beyondcorp_app_connector.html.markdown @@ -0,0 +1,170 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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: "Google BeyondCorp" +page_title: "Google: google_beyondcorp_app_connector" +description: |- + A BeyondCorp AppConnector resource represents an application facing component deployed proximal to + and with direct access to the application instances. +--- + +# google\_beyondcorp\_app\_connector + +A BeyondCorp AppConnector resource represents an application facing component deployed proximal to +and with direct access to the application instances. It is used to establish connectivity between the +remote enterprise environment and GCP. It initiates connections to the applications and can proxy the +data from users over the connection. + + +To get more information about AppConnector, see: + +* [API documentation](https://cloud.google.com/beyondcorp/docs/reference/rest#rest-resource:-v1.projects.locations.appconnectors) +* How-to Guides + * [Official Documentation](https://cloud.google.com/beyondcorp-enterprise/docs/enable-app-connector) + + +## Example Usage - Beyondcorp App Connector Basic + + +```hcl +resource "google_service_account" "service_account" { + account_id = "my-account" + display_name = "Test Service Account" +} + +resource "google_beyondcorp_app_connector" "app_connector" { + name = "my-app-connector" + principal_info { + service_account { + email = google_service_account.service_account.email + } + } +} +``` + +## Example Usage - Beyondcorp App Connector Full + + +```hcl +resource "google_service_account" "service_account" { + account_id = "my-account" + display_name = "Test Service Account" +} + +resource "google_beyondcorp_app_connector" "app_connector" { + name = "my-app-connector" + region = "us-central1" + display_name = "some display name" + principal_info { + service_account { + email = google_service_account.service_account.email + } + } + labels = { + foo = "bar" + bar = "baz" + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + ID of the AppConnector. + +* `principal_info` - + (Required) + Principal information about the Identity of the AppConnector. + Structure is [documented below](#nested_principal_info). + + +The `principal_info` block supports: + +* `service_account` - + (Required) + ServiceAccount represents a GCP service account. + Structure is [documented below](#nested_service_account). + + +The `service_account` block supports: + +* `email` - + (Required) + Email address of the service account. + +- - - + + +* `region` - + (Optional) + The region of the AppConnector. + +* `display_name` - + (Optional) + An arbitrary user-provided name for the AppConnector. + +* `labels` - + (Optional) + Resource labels to represent user provided metadata. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/locations/{{region}}/appConnectors/{{name}}` + +* `state` - + Represents the different states of a AppConnector. + + +## 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 + + +AppConnector can be imported using any of these accepted formats: + +``` +$ terraform import google_beyondcorp_app_connector.default projects/{{project}}/locations/{{region}}/appConnectors/{{name}} +$ terraform import google_beyondcorp_app_connector.default {{project}}/{{region}}/{{name}} +$ terraform import google_beyondcorp_app_connector.default {{region}}/{{name}} +$ terraform import google_beyondcorp_app_connector.default {{name}} +``` + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override). diff --git a/website/docs/r/beyondcorp_app_gateway.html.markdown b/website/docs/r/beyondcorp_app_gateway.html.markdown new file mode 100644 index 00000000000..39f0cad185a --- /dev/null +++ b/website/docs/r/beyondcorp_app_gateway.html.markdown @@ -0,0 +1,162 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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: "Google BeyondCorp" +page_title: "Google: google_beyondcorp_app_gateway" +description: |- + A BeyondCorp AppGateway resource represents a BeyondCorp protected AppGateway to a remote application. +--- + +# google\_beyondcorp\_app\_gateway + +A BeyondCorp AppGateway resource represents a BeyondCorp protected AppGateway to a remote application. It creates +all the necessary GCP components needed for creating a BeyondCorp protected AppGateway. Multiple connectors can be +authorised for a single AppGateway. + + +To get more information about AppGateway, see: + +* [API documentation](https://cloud.google.com/beyondcorp/docs/reference/rest#rest-resource:-v1.projects.locations.appgateways) +* How-to Guides + * [Official Documentation](https://cloud.google.com/beyondcorp-enterprise/docs/enable-app-connector) + + +## Example Usage - Beyondcorp App Gateway Basic + + +```hcl +resource "google_beyondcorp_app_gateway" "app_gateway" { + name = "my-app-gateway" + type = "TCP_PROXY" + region = "us-central1" + host_type = "GCP_REGIONAL_MIG" +} +``` + +## Example Usage - Beyondcorp App Gateway Full + + +```hcl +resource "google_beyondcorp_app_gateway" "app_gateway" { + name = "my-app-gateway" + type = "TCP_PROXY" + region = "us-central1" + display_name = "some display name" + labels = { + foo = "bar" + bar = "baz" + } + host_type = "GCP_REGIONAL_MIG" +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + ID of the AppGateway. + + +- - - + + +* `region` - + (Optional) + The region of the AppGateway. + +* `type` - + (Optional) + The type of network connectivity used by the AppGateway. + Default value is `TYPE_UNSPECIFIED`. + Possible values are `TYPE_UNSPECIFIED` and `TCP_PROXY`. + +* `host_type` - + (Optional) + The type of hosting used by the AppGateway. + Default value is `HOST_TYPE_UNSPECIFIED`. + Possible values are `HOST_TYPE_UNSPECIFIED` and `GCP_REGIONAL_MIG`. + +* `display_name` - + (Optional) + An arbitrary user-provided name for the AppGateway. + +* `labels` - + (Optional) + Resource labels to represent user provided metadata. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/locations/{{region}}/appGateways/{{name}}` + +* `state` - + Represents the different states of a AppGateway. + +* `uri` - + Server-defined URI for this resource. + +* `allocated_connections` - + A list of connections allocated for the Gateway. + Structure is [documented below](#nested_allocated_connections). + + +The `allocated_connections` block contains: + +* `psc_uri` - + (Optional) + The PSC uri of an allocated connection. + +* `ingress_port` - + (Optional) + The ingress port of an allocated connection. + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +AppGateway can be imported using any of these accepted formats: + +``` +$ terraform import google_beyondcorp_app_gateway.default projects/{{project}}/locations/{{region}}/appGateways/{{name}} +$ terraform import google_beyondcorp_app_gateway.default {{project}}/{{region}}/{{name}} +$ terraform import google_beyondcorp_app_gateway.default {{region}}/{{name}} +$ terraform import google_beyondcorp_app_gateway.default {{name}} +``` + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override).