hashicorp / terraform-provider-google

Terraform Provider for Google Cloud Platform
https://registry.terraform.io/providers/hashicorp/google/latest/docs
Mozilla Public License 2.0
2.33k stars 1.73k forks source link

Support Firebase Hosting #12955

Open rainshen49 opened 1 year ago

rainshen49 commented 1 year ago

Community Note

Description

Learn more about Firebase Hosting. The goal is to allow using Terraform to provision a fully functional Firebase Hosting site, using the existing Firebase Hosting REST API behind the scenes.

New or Affected Resource(s)

Firebase Hosting has a number of entities. I plan to start with these, but more may be added by demand (comment below so I can learn more about your use case).

Request from the community:

Potential Terraform Configuration

resource "google_firebase_hosting_site" "default" {
  provider = google-beta
  project  = "project-id"
  site_id = "site_id"
}

resource "google_firebase_hosting_channel" "default" {
  provider = google-beta
  project  = "project-id"
  site_id = google_firebase_hosting_site.default.site_id
  channel_id = "channel_id"
}

resource "google_firebase_hosting_version" "default" {
  provider = google-beta
  site_id  = google_firebase_hosting_site.default.site_id
  config {
    rewrites {
      glob = "/google/*"
      path = "https://www.google.com"
    }
  }
}

resource "google_firebase_hosting_release" "default" {
  provider = google-beta
  site_id = google_firebase_hosting_site.default.site_id
  version_name = google_firebase_hosting_version.default.name
  message = "Test release"
}

References

b/277381452

Splizard commented 1 year ago

Can we add a google_firebase_hosting_domain to this?

rainshen49 commented 1 year ago

Hi @Splizard, thank you for the suggestion! Added to the description. Realistically, since the custom domains support isn't in the Firebase Hosting API yet, Terraform resource will have to wait.

In the meantime, can you tell me a bit more about your use case? Adding a domain is an inherently manual process due to the need to verify ownership. What is an example workflow that you are trying to optimize for?

sytdas commented 1 year ago

Does this support deploying static content?

rainshen49 commented 1 year ago

Deploying static content is not supported at the moment. However, feedback is always welcome to help with prioritization.

@sytdas What does your use case look like? How many sites do you plan to manage with Terraform at at time?

sytdas commented 1 year ago

We are deploying single app with firebase deploy command for different environment like dev, test, prod. The prod and test bundle is built and upload to GCS and need to be deploy manually. We want to integrate this into existing terraform.

sytdas commented 1 year ago

@rainshen49 I came up with a solution using null_resouce to describe my current flow.


resource "google_firebase_project" "default" {
  provider = google-beta
  project = "my-project"
}

resource "null_resource" "download_artifacts" {
  provisioner "local-exec" {
    command = "gcloud storage cp gs://mybucket/public.tar public.tar"
  }
  triggers = {
    "key" = "2"
  }
}

resource "null_resource" "extract_artifacts" {
  provisioner "local-exec" {
    command = "mkdir -p public && tar -xf public.tar -C public"
  }
  depends_on = [
    null_resource.download_artifacts
  ]
  triggers = {
    "key" = null_resource.download_artifacts.triggers.key
  }
}

resource "null_resource" "deploy_firebase" {
  provisioner "local-exec" {
    working_dir = "public"
    command = "deploy"
    interpreter = ["firebase", "--project=${google_firebase_project.default.project}"]
  }
  depends_on = [
    null_resource.extract_artifacts
  ]
  triggers = {
    "key" = null_resource.extract_artifacts.triggers.key
  }
}

resource "null_resource" "cleanup_firebase" {
  provisioner "local-exec" {
    command = "rm -rf public && rm public.tar"
  }
  depends_on = [
    null_resource.deploy_firebase
  ]
  triggers = {
    key = null_resource.deploy_firebase.triggers.key
  }
}

The public.tar file contains firebase.json and index.html

"hosting": {
  "public": ".",
  "ignore": [
    "firebase.json",
    "**/.*",
    "**/node_modules/**"
  ]
}
rainshen49 commented 1 year ago

@sytdas Thanks for the details. If I understand your workflow correctly:

  1. Pre-build the web app into public.tar
  2. Extract public.tar into a temporary public directory
  3. Deploy to hosting directly from the public directory (no additional build steps)
  4. Cleanup

And your request is to make step 3 into a terraform resource, right?

Do you have any suggestion on how this should look? Perhaps, we could add a field e.g. files to the google_firebase_hosting_version:

resource "google_firebase_hosting_version" "default" {
  provider = google-beta
  site_id  = google_firebase_hosting_site.default.site_id
  config {
    ...
  }
  files = [
    for f in fileset("path/to/public", "**"): 
      {name = f, hash = filesha256(f)}
  ]
}

The reason I added a SHA256 hash is such that any changes in a file can be picked up by Terraform. Otherwise, the only information Terraform has is the file path.

sytranvn commented 1 year ago

@rainshen49 If I understand correctly, the config parameter is similar to hosting field in firebase.json file.

config - (Optional) The configuration for the behavior of the site. This configuration exists in the firebase.json file. Structure is documented below.

https://firebase.google.com/docs/hosting/full-config#firebase-json_example

So I think allowing config.public field in google_firebase_hosting_version is the most friendly API design.

rainshen49 commented 1 year ago

Interesting, I've never thought of it that way. I can see where you are coming from. However, it feels odd to make public in config because public is only relevant at build time. Once deployed, the web app doesn't care about which directory it comes from. The other fields in config such as rewrites and redirects are relevant at run time. I imagine firebase.json has public as a sibling of other configurations for convenience. The Firebase Hosting API doesn't have it. This is just my opinion though. I'll talk with the rest of the team to see what they think.

In the meantime, thanks for this feature request! I'll pass this on to the team for prioritization. Feel free to add to this thread if there is any update on your end.

intotecho commented 1 year ago

The ability to only keep the latest N versions would be a good feature. https://firebase.google.com/docs/hosting/manage-hosting-resources#release-storage-settings https://support.google.com/firebase/answer/9242086

alethenorio commented 1 year ago

Looks like the Custom Domain API is out :raised_hands:

kryptoblack commented 10 months ago

Is there a way to connect the Default Hosting Site to the Web App?

alethenorio commented 10 months ago

Is there a way to connect the Default Hosting Site to the Web App?

I second that. Some data sources or a way to reference the default hosting sites would be nice.

Also I noticed that when the firebase project is deleted, it does not disable the hosting site which is a bit confusing and requires the user to go digging in documentation and find out how to disable the hosting site through the CLI

rainshen49 commented 10 months ago

@kryptoblack Yes, if you are using Terraform 1.5 or above, you can use an import block. https://registry.terraform.io/providers/hashicorp/google-beta/latest/docs/resources/firebase_hosting_site

import {
  id = "projects/project-id/sites/project-id"
  to = google_firebase_hosting_site.default
}

resource "google_firebase_hosting_site" "default" {
  project = "project-id"
  site_id = "project-id"
  app_id  = google_firebase_web_app.hosting.app_id
}

resource "google_firebase_web_app" "hosting" {
  project      = "project-id"
  display_name = "This web app will associate with the default site"
}

Note that this is because the default hosting site is usually the same as project id.

rainshen49 commented 10 months ago

@alethenorio regarding the default Hosting site not being disabled on project deletion, this behavior is intended to protect users from accidentally taking down their web app.

A workaround if you want to automate disabling Hosting sites is to create a google_firebase_hosting_release of type SITE_DISABLE.

alethenorio commented 10 months ago

@alethenorio regarding the default Hosting site not being disabled on project deletion, this behavior is intended to protect users from accidentally taking down their web app.

A workaround if you want to automate disabling Hosting sites is to create a google_firebase_hosting_release of type SITE_DISABLE.

Thanks for the tip. It feels like such a protection should be built instead in the API like many other Google resources such as databases. My expectation is that terraform resources will generally go through source code reviews with a plan to look at which already offer a layer of protection anyway so having it built in the Terraform resources is just confusion to Terraform users.

rainshen49 commented 10 months ago

@alethenorio I understand. Firebase Hosting has been around for quite a while, so some seemingly odd behaviors today have to be preserved for backward-compatibility reasons. Since the Terraform initiative is fairly new, we work around those edges. However, we are always open to feedback. Since this is the Terraform repo, for Firebase specific requests, could you go to https://firebase.uservoice.com/forums/948424-general and explain a bit how you would imagine the Hosting API to have the built-in protection? This will inform our product roadmap.

alethenorio commented 10 months ago

@rainshen49 much appreciated. I'll give it a thought use the right channels for feedback.

kryptoblack commented 10 months ago

@kryptoblack Yes, if you are using Terraform 1.5 or above, you can use an import block. https://registry.terraform.io/providers/hashicorp/google-beta/latest/docs/resources/firebase_hosting_site

import {
  id = "projects/project-id/sites/project-id"
  to = google_firebase_hosting_site.default
}

resource "google_firebase_hosting_site" "default" {
  project = "project-id"
  site_id = "project-id"
  app_id  = google_firebase_web_app.hosting.app_id
}

resource "google_firebase_web_app" "hosting" {
  project      = "project-id"
  display_name = "This web app will associate with the default site"
}

Note that this is because the default hosting site is usually the same as project id.

@rainshen49 The above solution is viable only if the project and the default hosting site already exist. But in the case of project being created within the same configuration, import doesn't work as it resolves during plan stage.

The workaround I am thinking for this is splitting the configuration into two stages:

Stage 1: Project and APIs Initialization
Stage 2: Resource Creation

And then share the state of Stage 1 with Stage 2 using terraform_remote_state.

  1. I would like to know if you might have a better way to handle such case.
  2. Since this is just a workaround I believe there is a need for a scalable solution. I suggest
    1. Using a data source (as mentioned by @alethenorio) with a dependency to the google_firebase_project
    2. Adding a new resource for google_firebase_web_app and google_firebase_hosting_site binding.
rainshen49 commented 10 months ago

@kryptoblack I see your point now. Yes, this becomes a two-step process. I'll discuss this with the team.

BenJackGill commented 8 months ago

Is there any update on setting the default site without using a two step process?

rainshen49 commented 8 months ago

Hi @BenJackGill, I appreciate the ping. There are a few changes coming to default Hosting sites which will impact how we solve this problem. Therefore, I'm still looking for a solution that will work reliably after the change, but stay tuned!

RuBiCK commented 4 months ago

@kryptoblack I have a similar issue.

Due to legacy reasons, we are creating static websites with google_firebase_web_app which is creating a Firebase Hosting Site under the hood.

I want to add a google_firebase_hosting_custom_domain for that hosting site but google_firebase_web_app is not exposing attributes related to the hosting site.

As you commented in https://github.com/hashicorp/terraform-provider-google/issues/12955#issuecomment-1872479584 we could import the hosting site but it will fail in the new projects creation as we have those resources in a module and when we create new projects, the hosting site won't exist when trying to import.

Did you solve how to use a custom domain for a google_firebase_web_app ?

kryptoblack commented 4 months ago

Did you solve how to use a custom domain for a google_firebase_web_app ?

I just went ahead with breaking it into two parts and using terraform_remote_state as stated above. It seemed to be the most simplest workaround.

BenJackGill commented 2 months ago

I can see there were multiple merges in relation to this. Can someone smarter then me confirm if this means the issue regarding the default hosting site is fixed?

If so, how do we take advantage of it. Just upgrade to the latest google provider version? Or do we need to change our config somehow?

rainshen49 commented 2 months ago

@BenJackGill Yes, if you upgrade to at least https://github.com/hashicorp/terraform-provider-google-beta/releases/tag/v5.36.0, the following block won't fail any more and instead will reuse the default hosting site

resource "google_firebase_hosting_site" "default" {
  project = "project-id"
  site_id = "project-id"
  app_id  = google_firebase_web_app.hosting.app_id
}