I'm using Cloud Functions for Firebase with three different projects for development, testing and production purposes. Each project has a service-account.json. When I deploy the sources to an environment, the initialization looks like this:
var serviceAccount = require("./service-account-dev.json");
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: "https://nwDEV.firebaseio.com"
});
This is a bit difficult to handle, because I have to change the code everytime I want to deploy to a different environment. Is there a way to have an overall configuration, e.g. in firebase.json or.firebasesrc, which allows to integrate the service-account and decides on deployment which configuration to choose?
Otherwise is there a possibility to detect under which environment the code is running and to load the specific service-account.json and to set the databaseURL-property?
You can use environment variables. https://firebase.google.com/docs/functions/config-env
Select the project (you can use the command firebase projects:list to see them):
firebase use my-project-development
Set an environment variable
firebase functions:config:set app.environment="dev"
In your functions file, apply a conditional to choose the file:
const serviceAccount = functions.config().app.environment === 'dev' ? 'credentials-dev.json' : 'credentials-prod.json';
Then you can use the file depending on the project:
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: "https://nwDEV.firebaseio.com"
});
From what I understand of your question, what you are looking for boils down to a solution to translate the cloud functions you are deploying to the appropriate settings, i.e. production, development, and testing, which I assume means each of these is a unique project, and therefore database, in your Firebase environment.
If the above is true then the following should help.
Firebase Cloud Functions, and CLI more generally, is able to deploy to a specific project in your Firebase environment. To do this execute the following command in the terminal while in the cloud functions directory.
$ firebase use --add
This will allow you to pick your additional project (for instance, development) and assign it an alias (I recommend "development" if it is as such). Then when deploying your functions you can choose which project (and therefore database) to deploy to by using the alias.
$ firebase use default # sets environment to the default alias
$ firebase use development # sets environment to the development alias
For more information please see: https://firebase.googleblog.com/2016/07/deploy-to-multiple-environments-with.html
One thing you may have to do for this to work would be to use the default config settings for Cloud Functions.
$ admin.initializeApp(functions.config().firebase);
Short answer:
the GCLOUD_PROJECT environment variable will be unique to your project, hence you can utilise it like this (sample code is for 2 different projects but you can extend it using switch or any other conditional statement):
const env = process.env.GCLOUD_PROJECT === 'my-app-prod' ? 'prod' : 'dev';
then use that env variable to load intended configuration.
Full example: (TypeScript)
update .firebaserc file
{
"projects": {
"default": "my-app-dev",
"prod": "my-app-prod",
}
}
create and modify your ./somewhere/config.ts file accordingly, let's say you're using AWS services (please ensure to secure your configuration details)
export const config = {
dev: {
awsRegion: 'myDevRegion',
awsAccessKey: 'myDevKey',
awsSecretKey: 'myDevSecretKey'
},
prod: {
awsRegion: 'myProdRegion',
awsAccessKey: 'myProdKey',
awsSecretKey: 'myProdSecretKey'
}
};
now above items can be used in the index.ts file
import { config } from './somewhere/config';
import * as aws from 'aws-sdk';
. . .
const env = process.env.GCLOUD_PROJECT === 'my-app-prod' ? 'prod' : 'dev';
const awsCredentials = {
region: config[env].awsRegion,
accessKeyId: config[env].awsAccessKey,
secretAccessKey: config[env].awsSecretKey
};
aws.config.update(awsCredentials);
. . .
export const myFuncToUseAWS = functions....
Now the deployment
Dev environment deployment: $ firebase deploy --only functions -P default
Prod environment deployment: $ firebase deploy --only functions -P prod
Related
I have a Firebase code project with Functions meant to be deployed to multiple Firebase projects over multiple regions.
I used to set the deployment region like this:
return functions
.region(process.env.REGION)
// ...
and used this command to deploy:
$ REGION=us-central1 firebase deploy --only functions
it worked like a charm until recently. Now it seems to completely ignore REGION=us-central1 even if I export it before I run firebase deploy.
EDIT 2022-06-13 - Possible solution
I changed the code to dump the contents of process.env to a file during deployment. This is what I got:
{
"FIREBASE_CONFIG": "{\"projectId\":\"REDACTED\",\"storageBucket\":\"REDACTED.appspot.com\",\"locationId\":\"us-central\"}",
"GCLOUD_PROJECT": "REDACTED",
"CLOUD_RUNTIME_CONFIG": "{REDACTED}",
"__CF_USER_TEXT_ENCODING": "REDACTED"
}
so definitely is not the same list of variables I have in my local environment.
I could use the locationId from FIREBASE_CONFIG to get the target location, or CLOUD_RUNTIME_CONFIG (it contains the dump of the functions .config() object, so I could set the target there).
I also believe that I could use the .env and .env.{project alias or ID} files and their contents would be available in process.env at deployment time.
As per Osvaldo López's suggestion, here are some other details:
Running on MacOS
No errors are reported
No recent changes to the CLI
Any input would be very welcome! Thanks.
I'm building this cloud function in my local environment and I need to save something to Firestore:
Here's how I'm initializing the firebase-admin app:
myFunction.js
if (!adminHasInitialized) {
console.log("INITIALIZING ADMIN APP"); // THIS IS BEING LOGGED
admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
adminHasInitialized = true;
}
And here's how I'm trying to update my Firestore:
await admin.firestore().collection("myCollection").doc("myDoc").update({
...
});
NOTE: I'm working on MY_PROJECT_1
And this is the error I'm getting:
details: 'No document to update: projects/MY_PROJECT_2/databases/(default)/documents/myCollection/myDoc',
PROBLEM
Somehow firebase-admin is looking inside another database for a different project MY_PROJECT_2 that I have.
When I run: firebase projects:list, this is what I get:
Project Display Name │ Project ID │ Resource Location ID
MY_PROJECT_1 │ MY_PROJECT_1 (current) │ us-central1
MY_PROJECT_2 │ MY_PROJECT_2 │ us-central1
So the current project is correct.
Also I have this file, which I set the default as MY_PROJECT_1
.firebaserc
{
"projects": {
"default": "MY_PROJECT_1"
}
}
I also tried to add the DB URL when I'm initializing the app, as:
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: "https://MY_PROJECT_1.firebaseio.com"
});
But I keep getting the same error.
NOTE: I'm running this function in my local environment using babel-node throught the following command:
npx babel-node functions/src/myFunction.js
QUESTION
How is firebase-admin getting a different default project than the one I'm currently working on?
UPDATE 1
I think that firebase-admin is getting the default configuration from the gcloud SDK that is installed in my PC.
From:
[C:\Users\USER\AppData\Roaming\gcloud\configurations\config_default]
config_default:
[core]
account = my#email.com
project = MY_PROJECT_2 // MAYBE IT IS COMING FROM HERE
UPDATE 2
So far, the only thing I've managed to make it work is with:
admin.initializeApp({
projectId: "MY_PROJECT_1",
databaseURL: "https://MY_PROJECT_1.firebaseio.com"
});
This gives me the correct project when I run this function locally.
Since version 1.0.0 of the Firebase SDK for Cloud Functions you don't need anymore to initialize with admin.initializeApp(functions.config().firebase);, see the doc here, which explains that:
firebase-admin is now initialized without any parameters within the
Cloud Functions runtime.
So please try with:
if (!adminHasInitialized) {
console.log("INITIALIZING ADMIN APP");
admin.initializeApp();
adminHasInitialized = true;
}
It is quite difficult to keep track of the CSP rules in the firebase.json file, therefore I decided to add a script that constructs the proper rules. Now I need to manually update the firebase.json file every time I am changing something in the CSP configuration. Is there any way to dynamically configure firebase? I was hoping to achieve it with a simple renaming as it worked for many services grunt.json -> grunt.js I guess I can use some sort of templating but I wonder if there's a built-in mode to dynamically construct headers, rules etc for firebase.
There isn't a built-in way to generate firebase.json on command. I would recommend creating a script, e.g. npm run deploy that first generates your firebase.json content and then runs firebase deploy. That way you can make sure it's always regenerated before deployment.
// configureFirebase.js
const fs = require('fs');
const config = {
hosting: {
// ...
}
}
fs.writeFileSync(__dirname + '/firebase.json', JSON.stringify(config));
// package.json
{
"scripts": {
"deploy": "node configureFirebase.js && firebase deploy"
}
}
I'm trying to set an environment variable for an API key that I don't want in my code. My source javascript looks something like this :
.get(`http://api-url-and-parameters&api-key=${process.env.API_KEY}`)
I'm using webpack and the package dotenv-webpack https://www.npmjs.com/package/dotenv-webpack to set API_KEY in a gitignored .env file and it's all running fine on my local. I'd like to also be able to set that variable when deploying through Netlify, I've tried adding it through to GUI to the 'build environment variables', and also to set it directly in the build command, but without success.
Any idea what might be the issue ?
WARNING: If this is a secret key, you will not want to expose this environment variable value in any bundle that gets returned to the client. It should only be used by your build scripts to be used to create your content during build.
Issue
dotenv-webpack expects there to be a .env file to load in your variables during the webpack build of your bundle. When the repository is checked out by Netlify, the .env does not exist because for good reason it is in .gitignore.
Solution
Store your API_KEY in the Netlify build environment variables and build the .env using a script prior to running the build command.
scripts/create-env.js
const fs = require('fs')
fs.writeFileSync('./.env', `API_KEY=${process.env.API_KEY}\n`)
Run the script as part of your build
node ./scripts/create-env.js && <your_existing_webpack_build_command>
Caveats & Recommendations
Do not use this method with a public facing repository [open] because any PR or branch deploy could create a simple script into your code to expose the API_KEY
The example script above is for simplicity so, make any script you use be able to error out with a code other than 0 so if the script fails the deploy will fail.
You can set Dotenv-webpack to load system environment variables as well as those you have declared in your .env file by doing the following:
plugins: [
new Dotenv({
systemvars: true
})
]
I.e Setting the systemvars attribute of your webpack dotenv plugin to true.
Note that system environment variables with the same name will overwrite those defined in your .env file.
Source: https://www.npmjs.com/package/dotenv-webpack#properties
if you go to corresponding site's settings in Netlify, under build&deploy you can find a section called environment variables you can easily add your environment variables from there. if you add MY_API_KEY variable to environment variables you will be able to access it inside your project via process.env.MY_API_KEY.
If you're using Nuxt JS there is a more "straight forward" approach.
Just edit the nuxt.config.js like so:
module.exports = {
env: {
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY
},
// ...
Then add the GOOGLE_API_KEY to Netlify through the build environment variables as usual.
Credit goes to yann-linn and his answer on github.
What you can also do is also to define a global constant in Webpack. Netlify environment variables defined in UI will work with it. You don't need dotenv or dotenv-webpack.
webpack.config.js
const webpack = require("webpack");
module.exports = {
plugins: [
new webpack.DefinePlugin({
"process.env.API_KEY": JSON.stringify(process.env.API_KEY)
}),
]
}
However again, of course you shouldn't do it just inputting enviornmental variables in the frontend if your API key is confidential and project public. The API key will appear in the source code of the website and will be easily accessible for everyone visiting it. Lambda function would be a better option.
You can use the Netlify's config file also ...
You can find documentation here.
Also i wanted to have the same ENV variables with with different values per branch/environment.
This workaround worked for me:
Create a netlify.toml file like:
[build]
NUXT_ENV_BASE_API = "/api"
NUXT_ENV_HOST_DOMAIN = "https://your-domain.gr"
[context.branch-deploy]
environment = { NUXT_ENV_BASE_API = "/dev-api", NUXT_ENV_HOST_DOMAIN = "https://dev.your-domain.gr" }
[context.production]
environment = { NUXT_ENV_BASE_API = "/api", NUXT_ENV_HOST_DOMAIN = "https://your-domain.gr" }
And deploy in Netlify ...
I'm using http cloud functions to listen for a request and then return a simple message.
I'm developing cloud functions locally using:
firebase serve --only functions
I've setup some custom environment variables using
firebase functions:config:set
Accessing the custom config variables using the below code works fine when the project is deployed
functions.config()
but it does not work when developing locally. When the function is triggered by hitting: http://localhost:5002/my-project-name/us-central1/functionName I can't access the custom config variables. when using functions.config() locally, I can see the default config, just not my custom config variables
Is there an alternate solution or best practice for environment variables when working locally?
As of now, you have to manually create a .runtimeconfig.json file inside your functions directory by running this command. Then run the serve command.
firebase functions:config:get > .runtimeconfig.json
If you are using Windows Powershell, replace the above with:
firebase functions:config:get | ac .runtimeconfig.json
You can learn more in https://firebase.google.com/docs/functions/local-emulator
For those who want to use the environment variables (process.env), I follow this workaround.
Set the config values before deploying
firebase functions:config:set envs.db_host=$DB_HOST_PROD envs.db_user=$DB_USER_PROD envs.db_password=$DB_PASSWORD_PROD envs.db_name=$DB_NAME_PROD envs.db_use_ssl=false
Read the config and update the env variables first thing under your functions code.
const functions = require('firebase-functions');
const config = functions.config();
// Porting envs from firebase config
for (const key in config.envs) {
process.env[key.toUpperCase()] = config.envs[key];
}
You can keep a file called .env.json and load it when you trigger deploy command
{
"name": "project",
"version": "0.0.0",
"scripts": {
"deploy": "npm run env && firebase deploy --only functions",
"env": "test -f env.json && firebase functions:config:unset env && firebase functions:config:set env=\"$(cat env.json)\" || echo \"Please add the file env.json before deploy.\""
},
"dependencies": {
"firebase-functions": "^3.1.0"
},
"devDependencies": {
"firebase-functions-test": "^0.1.6"
}
}
I've narrowed down the issue to Windows Powershell.
Running firebase functions:config:get > .runtimeconfig.json in powershell generates a broken json I don't know why, which when parsed gives Unexpected token � in JSON at position 0.
I've managed to sort it out by running .runtimeconfig.json generation command in Windows command prompt.
If you are using Nrwl NX, you will have to generate your .runtimeconfig.json inside of the dist/apps/functions directory.
Example package.json:
{
"scripts": {
"firebase:emulators:start": "firebase functions:config:get > dist/apps/functions/.runtimeconfig.json && env-cmd firebase emulators:start --export-on-exit=\".firebase-emulator\" --import=\".firebase-emulator\""
}
}
I am not sure if the top-rated answer works or not but for firebase function on mac (to-serve locally), I do something like this
npm run admin-keys && export dev=true && firebase emulators:start
Where admin keys is
"admin-keys": "export GOOGLE_APPLICATION_CREDENTIALS='./.keys/admin.keys.json'"
This will load configuration from .runtimeconfig.json
For production, you would manually have to set it by doing something like this
firebase functions:config:set facebookCred.secret="something"