Firebase Scheduled Functions Not Fully Deploying - node.js

I have been trying to deploy the following function for days. This is the code and the errors. It appears on my google cloud platform but it is not working properly.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const database = admin.firestore();
// prettier-ignore
exports.schFunc = functions.region("us-east1").pubsub
.schedule("every 1 minutes")
.onRun((context) => {
database
.doc("timers/timer1")
.update({time: admin.firestore.Timestamp.now()});
return console.log("succesful timer update");
});
These are the errors.
<<< HTTP RESPONSE BODY {"error":{"code":400,"message":"Any resource that needs App Engine can only be created/updated in the App Engine region. Location must equal us-east1 because the App Engine app that is associated with this project is located in us-east1.","status":"INVALID_ARGUMENT"}}
<<< HTTP RESPONSE BODY {"error":{"code":404,"message":"Job not found.","status":"NOT_FOUND"}}
And this is what is showing when I deploy.
Error: Failed to upsert schedule function schFunc in region us-east1
Error: There was an error deploying functions

Related

how to upload firebase images to web using local emulator?

I'm trying to upload some images to a firebase storage bucket,
and trying to run the code locally in the firebase emulator.
With most examples I'll get an error like:
"Unhandled error FirebaseError: Firebase: Need to provide options, when not being deployed to hosting via source.
(app/no-options). at initializeApp
I tried this: https://firebase.google.com/docs/admin/setup
const defaultApp = initializeApp({ credential: applicationDefault() })
Which may or not be working.
I think this creates an authed app (maybe?) but if I later use
const storage = getStorage() I get the same auth error.
So next, I tried using the defaultApp for next steps, like:
const storage = defaultApp.storage();
that doesn't error, but it returns something different from getStorage, that I can't seem to use for other needed things like getting a ref
as per examples https://firebase.google.com/docs/storage/web/upload-files
So it's not clear how to upload files to firebase from the local simulator.
my full function looks like this:
import cuid from "cuid";
import {
getStorage,
ref,
uploadBytes,
uploadBytesResumable,
getDownloadURL
} from "firebase/storage";
import { admin, defaultApp } from '../utils/firebaseInit'
export function saveToBucket(url: string): string {
const normalStorage = getStorage() // Gets a FirebaseStorage instance for the given Firebase app. but NOT AUTHED
const defaultStorage = defaultApp.storage(); // Gets the default FirebaseStorage instance.
const storageRef = ref(normalStorage)
const bucketPath = `personas-out/img-${cuid()}.jpg`
const bucket = defaultStorage.bucket();
// const bucketRef = bucket.ref(); // Property 'ref' does not exist on type 'Bucket'.ts(2339)
const metadata = {
contentType: 'image/jpeg'
};
const imageRef = ref(storageRef, bucketPath);
const file = new File([url], bucketPath, {
type: "image/jpeg",
});
// will happen in the background?
uploadBytesResumable(imageRef, file, metadata)
.then((snapshot: any) => {
console.log('Uploaded', snapshot);
});
return bucketPath
}
You're not supposed to use the Firebase client SDK (firebase/storage) in nodejs code. The client SDKs are meant to work in a browser only. For nodejs code that runs on a backend (which is what you're doing when you deploy to Cloud Functions), you should use the Firebase Admin SDK. The API it exposes for Storage is essentially a thin wrapper around the Google Cloud Storage nodejs library.

Exporting data from Firestore to GCS

I was trying to export data from firestore to google cloud storage using this code snippet
const functions = require('firebase-functions');
const firestore = require('#google-cloud/firestore');
const client = new firestore.v1.FirestoreAdminClient();
const bucket = 'gs://BUCKET_NAME';
exports.scheduledFirestoreExport = functions.pubsub.schedule('every 24 hours').onRun(async() => {
const projectId = process.env.GCP_PROJECT || process.env.GCLOUD_PROJECT;
const databaseName = client.databasePath(projectId, '(default)');
const response = await client.exportDocuments({
name: databaseName,
outputUriPrefix: bucket,
collectionIds: [],
});
console.log(`Backup Successful :${response}`, {response});
//here I am trying to import the data to bigquery
});
The problem I am facing is the client.exportDocuments completes a few milliseconds before the time of file creation in Google Cloud Storage Bucket. So when I am trying to access it for importing it says no such file exist.URL is wrong.
Any suggestions on this?
Here's the underlying method
databases.export documents.
The response is an Operation which is a potentially long-running process on GCP.
You'll need to poll (I think there's no way to subscribe to) the Operation endpoint until the job succeeds or fails.
If it completes, you could then begin the BigQuery job.
See: Managing Export and Import operations
However, this will likely exceed the timeout on the Cloud Function and should probably not be attempted during a single function invocation.
You may want to consider creating another process that's triggered once the export completes. I've not done this. It's possible that you could create a background function that's triggered by the GCS event. I don't know.

How to configure credentials on Google Cloud Task

I have a function who delivers email-s with payment bills to some subscribers, and because of it we have to queue this function to ensure that this e-mail will be delivered. So we started to use Google Cloud Tasks from GCP to it.
I created the queue like the doc's: GCP Cloud Task Documentation
My problem is that i don't know how to create and "put the function on the queue". I followed the doc's here: HTTP Tasks GCP docs but i never used GCP before.
where do i found these credentials specified on the second link above?
// TODO(developer): Uncomment these lines and replace with your values.
// const project = 'my-project-id';
// const queue = 'my-queue';
// const location = 'us-central1';
// const url = 'https://example.com/taskhandler';
// const payload = 'Hello, World!';
// const inSeconds = 180;

Saving to two different Firestore databases with dialogflow

I'm making an actions on google project that will require adding data to two different Cloud Firestore. For some reason when I trigger the intent, it will only save to the original Cloud Firestore, but not the new one.
For simplicity, I'm going to refer to the original Cloud Firestore as "DB1" and the new one/ second one as "DB2"
Here's what I had tried:
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const admin = require('firebase-admin');
const {google} = require('googleapis');
const {
<Basically All of the libraries / AOG functions>
} = require('actions-on-google');
const defaultAppConfig = {"<FIREBASE CREDENTIALS FOR DB1 >"}
const OtherAppConfig = {"<FIREBASE CREDENTIALS FOR DB2>"}
const defaultApp = admin.initializeApp(defaultAppConfig); // DB1
const otherApp = admin.initializeApp(OtherappConfig, 'Other'); // DB2
const db = admin.firestore(functions.config(defaultApp).firebase); //DB1
const ab = admin.firestore(functions.config(otherApp).firebase); // DB2
const app = dialogflow({
debug: true,
clientId: '<DIALOGFLOW CREDENTIALS>'
});
app.intent('DB2 Write', (conv) =>{
conv.ask('Okay I made the write to DB2');
var data = {
name: 'This is a Test write'
};
var setDoc = ab.collection('Test').doc('Write').set(data);
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
Sorry if some parts are unnecessary, I wanted to include as much information as I could (I might be missing something that someone else sees).
To sum up what I thought would happen, I thought when I triggered the intent 'DB2 Write' that it would write 'This is a Test Write' to DB2, however it just keeps writing the message/data to DB1.
How do I get this working so it will write to my second Cloud Firestore or "DB2" when this intent is triggered?
Thanks for the help!
Note: If it makes a difference, I'm using the dialogflow inline editor for this code.
____________________________________________________________________________
Update: Here is what I have tried/ updated and it still writes to DB1
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
const otherAdmin = require('firebase-admin');
otherAdmin.initializeApp({
credential: otherAdmin.credential.cert(OtherAppConfig)
},'Other');
const ab = otherAdmin.firestore();
and as well:
admin.initializeApp(defaultAppConfig);
var otherApp = admin.initializeApp(OtherAppConfig, 'other');
console.log(admin.app().name); // '[DEFAULT]'
console.log(otherApp.name); // 'other'
// Use the shorthand notation to retrieve the default app's services
var db = admin.firestore(functions.config().firebase);
// Use the otherApp variable to retrieve the other app's services
var ab = otherApp.firestore(functions.config().firebase);
I'd like to note, the credentials I'm using for "OtherAppConfig" and "defaultAppConfig" were taken from the Firebase private key. ie: firebase console > project overview > service accounts > generate private key. Could this be the problem?
I think the problem is thus:
A Dialogflow project and a Firebase project are the same under the hood. This cool, as your Firebase Functions will know intuitively connect with Dialogflow and your database without a lot of manual configuration.
However, if you have two databases from different Cloud Projects, you will need to do some additional configurations to connect securely. I'm not sure what your AppConfigs contain, but they may not be sufficiently setup. As such, the Firebase setup may be pulling the default (current project) app and database when you're grabbing the functions config.
You may want to, for your second project, download the service key. Then you can load it as a file or directly as JSON in your startup routine.
This snippet below should work the way you want.
// Setup 1st db, this project's db
const admin = require('firebase-admin');
admin.initializeApp(); // Initialize with default params as we get it by default
const db = admin.firestore();
// Setup 2nd db
const otherAdmin = require('firebase-admin'); // We can import twice
const myOtherServiceKey = { ... }
otherAdmin.initializeApp({
credential: otherAdmin.credential.cert(myOtherServiceKey)
});
const ab = otherAdmin.firestore(); // This should be the DB of our second project

Firestore admin in Node.js Missing or insufficient permissions

I am trying to access Firestore using Firebase Admin on Node.js v8.4.0 in a Firebase Cloud Function running locally using firebase functions:shell on Windows 10.
firebase -v 4.2.1
"dependencies": {
"firebase-admin": "^6.0.0",
"firebase-functions": "^2.0.5"
}
After attempting to use firebase admin from my apps code, I attempted to run the quick start example in https://github.com/firebase/functions-samples/blob/master/quickstarts/uppercase-firestore/functions/index.js.
This is the actual code run:
'use strict';
// [START all]
// [START import]
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp({})
// [END import]
// [START addMessage]
// Take the text parameter passed to this HTTP endpoint and insert it into the
// Realtime Database under the path /messages/:documentId/original
// [START addMessageTrigger]
exports.addMessage = functions.https.onRequest((req, res) => {
// [END addMessageTrigger]
// Grab the text parameter.
const original = req.query.text;
// [START adminSdkAdd]
// Push the new message into the Realtime Database using the Firebase Admin SDK.
return admin.firestore().collection('messages').add({original: original}).then((writeResult) => {
// Send back a message that we've succesfully written the message
return res.json({result: `Message with ID: ${writeResult.id} added.`});
});
// [END adminSdkAdd]
});
// [END addMessage]
// [START makeUppercase]
// Listens for new messages added to /messages/:documentId/original and creates an
// uppercase version of the message to /messages/:documentId/uppercase
// [START makeUppercaseTrigger]
exports.makeUppercase = functions.firestore.document('/messages/{documentId}')
.onCreate((snap, context) => {
// [END makeUppercaseTrigger]
// [START makeUppercaseBody]
// Grab the current value of what was written to the Realtime Database.
const original = snap.data().original;
console.log('Uppercasing', context.params.documentId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an 'uppercase' sibling in the Realtime Database returns a Promise.
return snap.ref.set({uppercase}, {merge: true});
// [END makeUppercaseBody]
});
// [END makeUppercase]
// [END all]
However, I still get the permission denied error.
This is the output I get:
firebase > makeUppercase({original:'alphabets'},{params:{documentId:'mydoc'}})
'Successfully invoked function.'
firebase > info: User function triggered, starting execution
info: Uppercasing mydoc alphabets
info: Function crashed
info: { Error: 7 PERMISSION_DENIED: Missing or insufficient permissions.
at Object.exports.createStatusError (C:\projects\myproject\functions\node_modules\grpc\src\common.js:87:15)
at Object.onReceiveStatus (C:\projects\myproject\functions\node_modules\grpc\src\client_interceptors.js:1188:28)
at InterceptingListener._callNext (C:\projects\myproject\functions\node_modules\grpc\src\client_interceptors.js:564:42)
at InterceptingListener.onReceiveStatus (C:\projects\myproject\functions\node_modules\grpc\src\client_interceptors.js:614:8)
at callback (C:\projects\myproject\functions\node_modules\grpc\src\client_interceptors.js:841:24)
code: 7,
metadata: Metadata { _internal_repr: {} },
details: 'Missing or insufficient permissions.' }
My security rules are completely open but that did not resolve the error.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}
I also thought that this might be an authentication issue so I have tried the following to initialize the app:
1
admin.initializeApp()
2
admin.initializeApp(functions.config().firebase);
3
var serviceAccount = require('path/to/serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://<DATABASE_NAME>.firebaseio.com'
});
The last one with my own credentials file and project configuration. All of these attempts still give me the missing permissions error.
Update:
I deployed these functions to the cloud and they seem to be working perfectly but when running locally, I'm still getting a Error:7 Permission Denied.
Update 2:
Set application default credentials using gcloud auth application-default login as per suggestion by #Doug Stevenson. Ensured environment variable GOOGLE_APPLICATION_CREDENTIALS is not set. Attempted the code in 1,2 and 3 above as well as 4 below with no success. Encountered the same error.
4
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: "https://myapp-redacted.firebaseio.com"
});
I hope that by now you've already resolved your issue. The same thing just happened to me and I'm sharing what it was in my case with the hope that it will help others.
We manage several firebase projects - productions, dev, staging etc.
we init the admin sdk with this:
let serviceAccount = require("../serviceAccountKey.json");
const databaseUrl = functions.config().environment.databaseurl
const storageBucket = functions.config().environment.storagebucket
const isDev = "true" === functions.config().environment.dev
// if dev environment
if (isDev) {
serviceAccount = require("../serviceAccountKey-dev.json");
}
admin.initializeApp({
projectId: functions.config().environment.projectid,
credential: admin.credential.cert(serviceAccount),
databaseURL: databaseUrl,
storageBucket: storageBucket
});
You know how you need to do
firebase functions:config:get > .runtimeconfig.json
for the emulation to work. Well, my runtimeconfig was containing the wrong configuration. I was loading my serviceAccountKey-dev, but I was trying to access a different project. The second I fixed my runtimeconfig - it worked for me.
I had the same issue and was resolved by going to the cloud console then granting the role Firebase Admin SDK admin service agent to the app engine service account which is in the following format {PROJECT_ID}#appspot.gserviceaccount.com.
Ultimately I just had to run gcloud auth application-default login to make sure I was logged in with the correct Google account.
It is required that the client, the firebase function, to have access to the resource, firebase firestore. Following the least privilege principle you would need to:
Create a role within Google Cloud IAM with the following permissions:
datastore.entities.get, datastore.entities.update.
Also in IAM, create a service account and assign it the recently created role.
Update your firebase function selecting the new service account.
I have not found a way to assign the service account while deploying with firebase-cli. Here a guide for configuring permissions of the functions https://cloud.google.com/functions/docs/securing/function-identity.

Resources