How can I deploy a scheduled function to Google Firebase Functions?
I have created a Service Account named
cron-notifications
My code for the function
exports.notification_email = functions
.region('europe-west1')
.pubsub
.schedule('0 * * * *')
.timeZone('Europe/Stockholm')
.onRun(async (event) => { ... }
When I try to deploy using
$ firebase deploy --only functions:notification_email
I get the error below.
Error: HTTP Error: 400, Location must equal europe-west1 because the App Engine app that is associated with this project is located in europe-west1
I can deploy other functions that are not scheduled to "europe-west1" . I think my project is in europe-west1 (how can I verify?)
I have also tried creating a function named notification_email, located in europe-west1, directly from the GCP Cloud Functions Web UI and then deploying my function - overwriting it - with the same result.
I also tried setting the region using
gcloud config set functions/region europe-west1
as stated in the documentation, with the same result.
My $ gclod config list
[core]
account = myAccount
disable_usage_reporting = True
project = myProject
[functions]
region = europe-west1
Please help me out here! :)
When you are using scheduled functions in Firebase Functions, an App Engine instance is created that is needed for Cloud Scheduler to work. You can read about it here.
When those resources are launched for the scheduler to work, they use the location that has been set by default for resources.
Cloud Functions for Firebase (scheduled functions only) — If you run scheduled functions, Cloud Scheduler requires a Google App Engine app; during its setup you're prompted to select your project's default Google Cloud Platform (GCP) resource location (if it wasn't already selected when setting up another service).
I think that you are getting that error because there is a difference between the default GCP resource location you specified and the function one.
More background:
Important: Cloud Scheduler requires that your project have a Google App Engine (GAE) app; during its setup you're prompted to select your project's default Google Cloud Platform (GCP) resource location. This location will be used for GCP services in your project that require a location setting, specifically, your default Cloud Storage bucket and your Cloud Firestore database.
When setting your project's default GCP resource location, primarily consider where you want to store your data and files and, secondly, where you want to run any triggered functions for Cloud Firestore and Cloud Storage. After you set your project's default GCP resource location, you cannot change it.
Link
If you click on the cogwheel next to project-overview in Firebase you can see where your resources are located.
Hope this helps.
Related
When i use firebase deploy --only functions to deploy cloud functions for firebase, i discover, that this functions are deployed with the authentication flag allUsers.
How can i deploy firebase cloud function with private by default as mentioned here ?
There is no way to set this access control level of Cloud Functions through the Firebase CLI. It currently indeed always sets the access to allow all users. It sounds like a reasonable request to allow control of this though, so I'd file a feature request and possibly a PR on the repo.
For now: if you want to set this access level, you will have to do so in the Cloud console as explained in the Google Cloud documentation on controlling access on a function.
This page explains both:
Obtaining and providing service account credentials manually for developing local, deploying on-premises, or deploying to another public cloud.
Obtaining credentials on Compute Engine, Kubernetes Engine, App Engine flexible environment, and Cloud Functions
But there is no mention of obtaining credentials on Cloud Run. I'd appreciate it if you give instructions for obtaining credentials and setting firebase-admin initializeApp and firebase initializeApp for authentication on Cloud Run.
The documentation suggests that you can use the default service account just like other Google Cloud products as described here. The Firebase Admin SDK should use that account when initialized with no parameters.
There are also steps described if you want to use a non-default service account, which you can simply configure in the console or provide with gcloud.
If you must provide a file that's readable at runtime, you will have to deploy an image with that file added to the image. There is no short set of steps to add that file - you will have to make your docker build include it in a readable location, and your code will know where to look for it in order to load it.
I am always grateful for your help.
I want to write code admin.initializeApp(); both locally and in production.
When I deploy functions to production with no auguments, it works.
But locally, it requires me to write it like below:
const serviceAccount = require("/home/yhirochick/development/ServiceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://xxxx.firebaseio.com/"
});
In the official documentation it says that configuration is applied automatically when you initialize the Firebase Admin SDK with no arguments
But when I execute the command firebase serve --only functions locally and some calls some requests by postman produce the error below:
[2019-07-22T06:45:26.227Z] #firebase/database: FIREBASE WARNING: Provided
authentication credentials for the app named "[DEFAULT]" are invalid. This
usually indicates your app was not initialized correctly. Make sure the
"credential" property provided to initializeApp() is authorized to access the
specified "databaseURL" and is from the correct project.
I want to know How can I "admin.initializeApp();" no arguments locally.
I have grappled with this also and I don't think the local testing scenario currently is explained very well in the official documentation. But here is a solution:
For your local environment you need to download the firebase projects firebase service account json file (found in firebase console under project settings -> service account) and set an environment variable GOOGLE_APPLICATION_CREDENTIALS to point to the file:
# Linux/MACOS version
export GOOGLE_APPLICATION_CREDENTIALS="[PATH_TO_YOUR_SERVICE_ACCOUNT_FILE]"
Read more here, also on how to do this on Windows
Now you will be able to use admin.initializeApp() (with no arguments) locally.
A possible downside of this approach is that you have to set the environment variable each time you fire up a terminal before you start the firebase emulator, because the variable gets deleted when you end the session.
Automate it...
You could automate the export ... command by bundling it together with the command that fires up the emulator. You could do this by adding an entry to the scripts section of your package.json, e.g.:
"local": "export GOOGLE_APPLICATION_CREDENTIALS='[PATH_TO_YOUR_SERVICE_ACCOUNT_FILE]' && firebase emulators:start --only functions"
Then, in this example, you would only need to type npm run local.
Alternative: provide explicit credentials in local environment only
Look at this example: https://stackoverflow.com/a/47517466/1269280.
It basically use a runtime node environment variable to separate between local and production and then use the explicit way of providing credentials in the local environment only.
This is my preferred way of doing things, as I think it is more portable. It allows me to put the service account file inside my codebase and not deal with its absolute file path.
If you do something like this then remember to to exclude the service account file from your repo! (it contains sensitive info).
Background: difference between production and local service account discovery
The reason that admin.initializeApp() (with no arguments) works out-of-the-box in production is that when you deploy to production, i.e. Firebase Functions, the code ends up in a 'Google managed environment'. In Google managed environments like Cloud Functions, Cloud Run, App Engine.. etc, the admin SDK has access to your applications default service account (the one you downloaded above) and will use that when no credentials are specified.
This is part of Google Clouds Application Default Credentials (ADC) strategy which also applies to firebase functions.
Now, your local environment is not a 'google managed environment' so it doesn't have access to the default service account credentials. To google cloud, your local box is just an external server trying to access your private Firebase ressources. So you need to provide your service account credentials in one of the ways described above.
Before I knew this, I thought that because I was already logged in to firebase via my terminal i.e. firebase login and were able to deploy code to firebase, the firebase emulator would also have the necessary credentials for the firebase admin sdk, but this is not the case.
I am working on a project in which we have pre-existing cloud functions in use with Firebase. We are adding a small React SPA using firebase cloud hosting, and this SPA will interact with some of the existing public cloud functions.
The way we have been doing things so far, we have a dev project, and a production project in Firebase. For cloud functions, this works fine, we have environment specific config set up with firebase functions:config:set for differentiations between prod and dev servers.
The problem comes with the hosted SPA contacting the cloud functions. I've seen a lot of questions on how to access the environment config in the hosted code, eg this one: How to reference Firebase Functions config variables from a Firebase-hosted application? where the answer seems to be to have firebase functions that return the values of the environment variables, but for me this just moves the problem further back on step. I fully understand that having the environment variables accessible to this code would be a massive security problem as the SPA is run in the browser.
The only environment specific config I really need for the hosted SPA is the base address for the cloud functions.
eg if in my cloud functions I have
const functions = require('firebase-functions');
const express = require('express');
const test = express();
test.on('/hello/:target', (req, res) => {
res.send(`Hello ${req.params.target}`);
})
exports.test = functions.https.onRequest(test);
then having deployed, this cloud function is available both at https://us-central1-DEV-PROJECT-NAME.cloudfunctions.net/test/hello/world and https://us-central1-PROD-PROJECT-NAME.cloudfunctions.net/test/hello/world . How would I best get the appropriate root url (https://us-central1-DEV-PROJECT-NAME.cloudfunctions.net or https://us-central1-PROD-PROJECT-NAME.cloudfunctions.net) for the project that the SPA is deployed to?
eg. is there some global I can access in the frontend js code where I could do something like:
const url = `${__FIREBASE_GLOBALS__.cloudFunctions.baseUrl}/test/hello/${input}`;
And have the url be correctly defined based on which project the hosted app is deployed to?
I'm assuming here that you're not using Firebase in any other way in your SPA other than to call Cloud Functions (since you didn't say otherwise).
Read the Firebase web setup docs for Firebase Hosting, especially the section on SDK imports and implicit initialization. When you host a site with Firebase Hosting, there are some special URLs that give you the configurations for that project. There are some special script includes that give you access to Firebase products. In particular, note the relative path URI /__/firebase/init.js will yield JavaScript that initializes the Firebase JavaScript SDK with the default settings for your project. Go ahead and access that in a browser pointing to your web app. You're probably interested in the projectId property of the config.
If you want to get a hold of that value, you can use the Firebase SDK, which would be initialized by the script includes from the first link above. Minimally, you could add:
<script src="/__/firebase/5.8.2/firebase-app.js"></script>
<script src="/__/firebase/init.js"></script>
Then later on (see API docs):
firebase.app().options.projectId
to get the ID of the project where Firebase Hosting is serving the content. You can use that to build the URL to your functions.
It might also be convenient for you to port your HTTP functions to callable functions and invoke them from the web site with the Firebase SDK to invoke kthem. Or not.
I was able to get the region and appId from the environment variables.
eg:
console.log(process.env);
Check your firebase logs
{ ...
ENTRY_POINT: 'server',
X_GOOGLE_FUNCTION_TRIGGER_TYPE: 'HTTP_TRIGGER',
FIREBASE_CONFIG: '{"projectId":"pid","databaseURL":"https://pid.firebaseio.com","storageBucket":"pid.appspot.com","locationId":"europe-west"}',
X_GOOGLE_FUNCTION_NAME: 'server',
FUNCTION_TRIGGER_TYPE: 'HTTP_TRIGGER',
X_GOOGLE_GCLOUD_PROJECT: 'pid',
FUNCTION_NAME: 'server',
X_GOOGLE_GCP_PROJECT: 'pid',
X_GOOGLE_FUNCTION_REGION: 'us-central1',
FUNCTION_REGION: 'us-central1',
X_GOOGLE_ENTRY_POINT: 'server',
GCLOUD_PROJECT: 'pid',
GCP_PROJECT: 'pid',
... ommited
}
Out of these GCP_PROJECT, GCLOUD_PROJECT, FUNCTION_REGION, FUNCTION_NAME should work. So for eg. process.env.FUNCTION_REGION
Not sure how reliable this will be.
My Firebase project uses several different storage buckets used by cloud function storage triggers. Although I can change the default bucket for a particular cloud storage function in the console or using CLI after deploying it, I was wondering if there was a way to configure these settings (bucket, memory limit, zip-deployment location) in firebase.json and pick them up automatically during deploy. Sometimes deployment of the entire project resets the cloud function settings, which causes issues for our users. Thanks!
If you want to configure values to consume in your functions, use environment configuration.
From the CLI:
firebase functions:config:set someservice.key="THE API KEY" someservice.id="THE CLIENT ID"
Then in a function:
functions.config().someservice.key