From c1eeb7061eb040560f0818fdb330dff1d1822b4c Mon Sep 17 00:00:00 2001 From: The Magician Date: Tue, 24 Mar 2020 14:01:09 -0700 Subject: [PATCH] Add resource for enabling firebase (#3281) (#1885) * Add resource for enabling firebase * removing whitespace for GA terraform * Change skip_delete to resource level property Signed-off-by: Modular Magician --- .changelog/3281.txt | 3 + google-beta/config.go | 3 + google-beta/firebase_operation.go | 71 ++++++++ google-beta/provider.go | 14 +- google-beta/resource_firebase_project.go | 168 ++++++++++++++++++ ...esource_firebase_project_generated_test.go | 58 ++++++ website/docs/r/firebase_project.html.markdown | 111 ++++++++++++ website/google.erb | 9 + 8 files changed, 435 insertions(+), 2 deletions(-) create mode 100644 .changelog/3281.txt create mode 100644 google-beta/firebase_operation.go create mode 100644 google-beta/resource_firebase_project.go create mode 100644 google-beta/resource_firebase_project_generated_test.go create mode 100644 website/docs/r/firebase_project.html.markdown diff --git a/.changelog/3281.txt b/.changelog/3281.txt new file mode 100644 index 0000000000..1863d8f2fa --- /dev/null +++ b/.changelog/3281.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +firebase: Add the `google_firebase_project` resource which will enable Firebase for a referenced Google project. +``` diff --git a/google-beta/config.go b/google-beta/config.go index f4f2de59e5..ce2a4a9897 100644 --- a/google-beta/config.go +++ b/google-beta/config.go @@ -99,6 +99,7 @@ type Config struct { DialogflowBasePath string DNSBasePath string FilestoreBasePath string + FirebaseBasePath string FirestoreBasePath string GameServicesBasePath string HealthcareBasePath string @@ -242,6 +243,7 @@ var DeploymentManagerDefaultBasePath = "https://www.googleapis.com/deploymentman var DialogflowDefaultBasePath = "https://dialogflow.googleapis.com/v2/" var DNSDefaultBasePath = "https://www.googleapis.com/dns/v1beta2/" var FilestoreDefaultBasePath = "https://file.googleapis.com/v1/" +var FirebaseDefaultBasePath = "https://firebase.googleapis.com/v1beta1/" var FirestoreDefaultBasePath = "https://firestore.googleapis.com/v1/" var GameServicesDefaultBasePath = "https://gameservices.googleapis.com/v1beta/" var HealthcareDefaultBasePath = "https://healthcare.googleapis.com/v1beta1/" @@ -758,6 +760,7 @@ func ConfigureBasePaths(c *Config) { c.DialogflowBasePath = DialogflowDefaultBasePath c.DNSBasePath = DNSDefaultBasePath c.FilestoreBasePath = FilestoreDefaultBasePath + c.FirebaseBasePath = FirebaseDefaultBasePath c.FirestoreBasePath = FirestoreDefaultBasePath c.GameServicesBasePath = GameServicesDefaultBasePath c.HealthcareBasePath = HealthcareDefaultBasePath diff --git a/google-beta/firebase_operation.go b/google-beta/firebase_operation.go new file mode 100644 index 0000000000..026eb9b6ac --- /dev/null +++ b/google-beta/firebase_operation.go @@ -0,0 +1,71 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// 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" +) + +type FirebaseOperationWaiter struct { + Config *Config + Project string + CommonOperationWaiter +} + +func (w *FirebaseOperationWaiter) 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("https://firebase.googleapis.com/v1beta1/%s", w.CommonOperationWaiter.Op.Name) + return sendRequest(w.Config, "GET", w.Project, url, nil) +} + +func createFirebaseWaiter(config *Config, op map[string]interface{}, project, activity string) (*FirebaseOperationWaiter, error) { + if val, ok := op["name"]; !ok || val == "" { + // This was a synchronous call - there is no operation to wait for. + return nil, nil + } + w := &FirebaseOperationWaiter{ + Config: config, + Project: project, + } + if err := w.CommonOperationWaiter.SetOp(op); err != nil { + return nil, err + } + return w, nil +} + +// nolint: deadcode,unused +func firebaseOperationWaitTimeWithResponse(config *Config, op map[string]interface{}, response *map[string]interface{}, project, activity string, timeoutMinutes int) error { + w, err := createFirebaseWaiter(config, op, project, activity) + if err != nil || w == nil { + // If w is nil, the op was synchronous. + return err + } + if err := OperationWait(w, activity, timeoutMinutes, config.PollInterval); err != nil { + return err + } + return json.Unmarshal([]byte(w.CommonOperationWaiter.Op.Response), response) +} + +func firebaseOperationWaitTime(config *Config, op map[string]interface{}, project, activity string, timeoutMinutes int) error { + w, err := createFirebaseWaiter(config, op, project, activity) + if err != nil || w == nil { + // If w is nil, the op was synchronous. + return err + } + return OperationWait(w, activity, timeoutMinutes, config.PollInterval) +} diff --git a/google-beta/provider.go b/google-beta/provider.go index ef711d2715..d426c94bc8 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -285,6 +285,14 @@ func Provider() terraform.ResourceProvider { "GOOGLE_FILESTORE_CUSTOM_ENDPOINT", }, FilestoreDefaultBasePath), }, + "firebase_custom_endpoint": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateCustomEndpoint, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_FIREBASE_CUSTOM_ENDPOINT", + }, FirebaseDefaultBasePath), + }, "firestore_custom_endpoint": { Type: schema.TypeString, Optional: true, @@ -578,9 +586,9 @@ func Provider() terraform.ResourceProvider { return provider } -// Generated resources: 134 +// Generated resources: 135 // Generated IAM resources: 54 -// Total generated resources: 188 +// Total generated resources: 189 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -690,6 +698,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_dns_managed_zone": resourceDNSManagedZone(), "google_dns_policy": resourceDNSPolicy(), "google_filestore_instance": resourceFilestoreInstance(), + "google_firebase_project": resourceFirebaseProject(), "google_firestore_index": resourceFirestoreIndex(), "google_game_services_realm": resourceGameServicesRealm(), "google_game_services_game_server_cluster": resourceGameServicesGameServerCluster(), @@ -960,6 +969,7 @@ func providerConfigure(d *schema.ResourceData, p *schema.Provider, terraformVers config.DialogflowBasePath = d.Get("dialogflow_custom_endpoint").(string) config.DNSBasePath = d.Get("dns_custom_endpoint").(string) config.FilestoreBasePath = d.Get("filestore_custom_endpoint").(string) + config.FirebaseBasePath = d.Get("firebase_custom_endpoint").(string) config.FirestoreBasePath = d.Get("firestore_custom_endpoint").(string) config.GameServicesBasePath = d.Get("game_services_custom_endpoint").(string) config.HealthcareBasePath = d.Get("healthcare_custom_endpoint").(string) diff --git a/google-beta/resource_firebase_project.go b/google-beta/resource_firebase_project.go new file mode 100644 index 0000000000..965b5fe243 --- /dev/null +++ b/google-beta/resource_firebase_project.go @@ -0,0 +1,168 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// 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" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceFirebaseProject() *schema.Resource { + return &schema.Resource{ + Create: resourceFirebaseProjectCreate, + Read: resourceFirebaseProjectRead, + Delete: resourceFirebaseProjectDelete, + + Importer: &schema.ResourceImporter{ + State: resourceFirebaseProjectImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "display_name": { + Type: schema.TypeString, + Computed: true, + Description: `The GCP project display name`, + }, + "project_number": { + Type: schema.TypeString, + Computed: true, + Description: `The number of the google project that firebase is enabled on.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func resourceFirebaseProjectCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + + url, err := replaceVars(d, config, "{{FirebaseBasePath}}projects/{{project}}:addFirebase") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Project: %#v", obj) + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequestWithTimeout(config, "POST", project, url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating Project: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + err = firebaseOperationWaitTime( + config, res, project, "Creating Project", + int(d.Timeout(schema.TimeoutCreate).Minutes())) + + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create Project: %s", err) + } + + log.Printf("[DEBUG] Finished creating Project %q: %#v", d.Id(), res) + + return resourceFirebaseProjectRead(d, meta) +} + +func resourceFirebaseProjectRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{FirebaseBasePath}}projects/{{project}}/{{name}}") + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequest(config, "GET", project, url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("FirebaseProject %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading Project: %s", err) + } + + if err := d.Set("project_number", flattenFirebaseProjectProjectNumber(res["projectNumber"], d, config)); err != nil { + return fmt.Errorf("Error reading Project: %s", err) + } + if err := d.Set("display_name", flattenFirebaseProjectDisplayName(res["displayName"], d, config)); err != nil { + return fmt.Errorf("Error reading Project: %s", err) + } + + return nil +} + +func resourceFirebaseProjectDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[WARNING] Firebase Project resources"+ + " cannot be deleted from GCP. The resource %s will be removed from Terraform"+ + " state, but will still be present on the server.", d.Id()) + d.SetId("") + + return nil +} + +func resourceFirebaseProjectImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)", + "(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenFirebaseProjectProjectNumber(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenFirebaseProjectDisplayName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} diff --git a/google-beta/resource_firebase_project_generated_test.go b/google-beta/resource_firebase_project_generated_test.go new file mode 100644 index 0000000000..66ca0aafc7 --- /dev/null +++ b/google-beta/resource_firebase_project_generated_test.go @@ -0,0 +1,58 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// 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 ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccFirebaseProject_firebaseProjectBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": getTestOrgFromEnv(t), + "random_suffix": acctest.RandString(10), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + Steps: []resource.TestStep{ + { + Config: testAccFirebaseProject_firebaseProjectBasicExample(context), + }, + }, + }) +} + +func testAccFirebaseProject_firebaseProjectBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_project" "default" { + provider = google-beta + + project_id = "tf-test%{random_suffix}" + name = "tf-test%{random_suffix}" + org_id = "%{org_id}" +} + +resource "google_firebase_project" "default" { + provider = google-beta + project = google_project.default.project_id +} +`, context) +} diff --git a/website/docs/r/firebase_project.html.markdown b/website/docs/r/firebase_project.html.markdown new file mode 100644 index 0000000000..8349fcdb74 --- /dev/null +++ b/website/docs/r/firebase_project.html.markdown @@ -0,0 +1,111 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# 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: "Firebase" +layout: "google" +page_title: "Google: google_firebase_project" +sidebar_current: "docs-google-firebase-project" +description: |- + A Google Cloud Firebase instance. +--- + +# google\_firebase\_project + +A Google Cloud Firebase instance. This enables Firebase resources on a given google project. +Since a FirebaseProject is actually also a GCP Project, a FirebaseProject uses underlying GCP +identifiers (most importantly, the projectId) as its own for easy interop with GCP APIs. + +Once Firebase has been added to a Google Project it cannot be removed. + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. + +To get more information about Project, see: + +* [API documentation](https://firebase.google.com/docs/projects/api/reference/rest/v1beta1/projects) +* How-to Guides + * [Official Documentation](https://firebase.google.com/) + + +## Example Usage - Firebase Project Basic + + +```hcl +resource "google_project" "default" { + provider = google-beta + + project_id = "tf-test%{random_suffix}" + name = "tf-test%{random_suffix}" + org_id = "123456789" +} + +resource "google_firebase_project" "default" { + provider = google-beta + project = google_project.default.project_id +} +``` + +## Argument Reference + +The following arguments are supported: + + + +- - - + + +* `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}}/{{name}}` + +* `project_number` - + The number of the google project that firebase is enabled on. + +* `display_name` - + The GCP project display name + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 10 minutes. +- `delete` - Default is 10 minutes. + +## Import + +Project can be imported using any of these accepted formats: + +``` +$ terraform import -provider=google-beta google_firebase_project.default projects/{{project}} +$ terraform import -provider=google-beta google_firebase_project.default {{project}} +``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. + +## 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/google.erb b/website/google.erb index 265b2492aa..f92a3077a1 100644 --- a/website/google.erb +++ b/website/google.erb @@ -1007,6 +1007,15 @@ + > + Google Firebase Resources + + + > Google Filestore Resources