I am trying to run a Google Cloud Tasks task using a Cloud Function, but I'm hitting an error where any region I try to use is wrong.
The code is basic. All is well until it stops because of the following error:
Error: {"code":3,"message":"Location 'europe-west1' is not a valid location. Use ListLocations to list valid locations.","details":[]}
If I attempt to use, for example, us-central1, it will report:
Error: {"code":3,"message":"Location must equal europe-west1 because the App Engine app that is associated with this project is located in europe-west1","details":[]}
I am using the Google Cloud Tasks API with Node.js for creating a new Task:
const client = new CloudTasksClient({ fallback: true });
const parent = client.queuePath(PROJECT, 'europe-west1', QUEUE);
A full example can be found here: https://github.com/googleapis/nodejs-tasks/blob/master/samples/createHttpTaskWithToken.js
The URL called is:
""https://cloudtasks.googleapis.com:443/$rpc/google.cloud.tasks.v2beta3.CloudTasks/CreateTask"
If I run the locations list command, this is the output:
$ gcloud tasks locations list
europe-west1 projects/[project-id]/locations/europe-west1
Edit: Using the REST API (https://cloud.google.com/tasks/docs/reference/rest/v2beta3/projects.locations.queues.tasks/create) with the same configuration works. It may be a bug in the client?
I am really not sure what is wrong with my setup.
Not sure what information would be helpful to debug this, so apologies in advance if there's not enough information.
I realized which the example that you are using is for non App Engine/Cloud functions environments please try the simple example that is in the npm page.
Please check on your package.json that you are defining the latest version of google-cloud/tasks library, at this time it is 1.9.0
You don't need to use a Ouath token within App Engine/ Cloud Functions environments, because are already configured with a service account.
// Imports the Google Cloud Tasks library.
const {CloudTasksClient} = require('#google-cloud/tasks');
// Instantiates a client.
const client = new CloudTasksClient();
// TODO(developer): Uncomment these lines and replace with your values.
// const project = 'my-project-id';
// const queue = 'my-appengine-queue';
// const location = 'us-central1';
// const payload = 'hello';
// Construct the fully qualified queue name.
const parent = client.queuePath(project, location, queue);
const task = {
appEngineHttpRequest: {
httpMethod: 'POST',
relativeUri: '/log_payload',
},
};
if (payload) {
task.appEngineHttpRequest.body = Buffer.from(payload).toString('base64');
}
if (inSeconds) {
task.scheduleTime = {
seconds: inSeconds + Date.now() / 1000,
};
}
const request = {
parent: parent,
task: task,
};
console.log('Sending task:');
console.log(task);
// Send create task request.
const [response] = await client.createTask(request);
const name = response.name;
console.log(`Created task ${name}`);
Related
I am using express to create my firebase functions, and I understand how to create regular callable functions. I am lost however on the exact way to implement trigger functions for the background (i.e. onCreate, onDelete, onUpdate, onWrite), as well as how Reactjs in the frontend is supposed to receive the data.
The scenario I have is a generic chat system that uses react, firebase functions with express and realtime database. I am generally confused on the process of using triggers for when someone sends a message, to update another user's frontend data.
I have had a hard time finding a tutorial or documentation on the combination of these questions. Any links or a basic programmatic examples of the life cycle would be wonderful.
The parts I do understand is the way to write a trigger function:
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
// Grab the current value of what was written to the Realtime Database.
const original = change.after.val();
console.log('Uppercasing', context.params.pushId, 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 change.after.ref.parent.child('uppercase').set(uppercase);
});
But I don't understand how this is being called or how the data from this reaches frontend code.
Background functions cannot return anything to client. They run after a certain event i.e. onWrite() in this case. If you want to update data at /messages/{pushId}/original to other users then you'll have to use Firebase Client SDK to listen to that path:
import { getDatabase, ref, onValue} from "firebase/database";
const db = getDatabase();
const msgRef = ref(db, `/messages/${pushId}/original`);
onValue(msgRef, (snapshot) => {
const data = snapshot.val();
console.log(data)
});
You can also listen to /messages/${pushId} with onChildAdded() to get notified about any new node under that path.
My handler named taskToFireOff has allUsers deleted, the Cloud Tasks Enqueuer principal's name is set to the ServiceAccountEmail, and its role is set to Invoker (not shown)
When a post is created, I use .onCreate to create a task. The task is created but the function that the http request is connected to never fires. I looked here https://console.cloud.google.com/cloudtasks/queue/ and see that there are 2 tasks (I tried twice) just sitting there.
When I go to the Cloud Task page in the console > click the task > Tasks > Method / URL, it says
POST https://us-east1-myProjectId.cloudfunctions.net/taskToFireOff
But when I go to the task > Logs > View > Suggested > Query Details it says
New error group seen in onCreateData: Error: 3 INVALID_ARGUMENT: Any
resource that needs App Engine can only be created/updated in the App
Engine region. Location must equal us-east1 because the App
It's an odd error because the above url begins with us-east1 and in my index.js file I set the location using us-east1
exports.onCreateData = functions.database.ref('/posts/{postId}').onCreate(async (snapshot, context) => {
const payload = {"postId": context.params.postId};
const project = JSON.parse(process.env.FIREBASE_CONFIG!).projectId;
const location = 'us-east1';
const queue = 'ttl-task';
const serviceAccountEmail = 'yaddayadda#myProjectId.iam.gserviceaccount.com';
const queuePath = tasksClient.queuePath(project, location, queue);
const url = `https://${location}-${project}.cloudfunctions.net/taskToFireOff`
const task = {
httpRequest: {
httpMethod: 'POST',
url,
oidcToken: {
serviceAccountEmail
},
body: Buffer.from(JSON.stringify(payload)).toString('base64'), // JSON.parse(JSON.stringify(payloadData));
headers: {
'Content-Type': 'application/json',
},
},
scheduleTime: {
seconds: // ...
}
}
const [response] = await tasksClient.createTask({ parent: queuePath, task });
// ...
});
exports.taskToFireOff = functions.https.onRequest((request, response) => {
// ...
});
Also while inside the console, when I click the Task itself and look at the payload, all the correct information is there.
When I created my RealTimeDatabase I was given us-central1
But when I created my Storage bucket, I selected us-east1. At the bottom of Firestore Database it says
.
When I enter $ gcloud app describe it says that the locationId is us-east1
Could the problem be that the Cloud Task is in us-east1 but the Cloud Functions are in us-central1?
Running $ gcloud functions list lists all of my Cloud Functions in the us-central1 region
Btw, I created another project just to see the selection that I'm given when creating a Storage bucket. For Storage, there isn't a way to select us-central1. There is a default Multi-region nam5(us-central) but there isn't a specific region for us-central1. I selected us-east1 because I'm in NY.
The problem was that the App Engine was in us-east1 but the function handler taskToFireOff was in us-central1. The error was saying that the function associated with App Engine/Cloud Task needs to be in the same region together. In short I needed to change that particular cloud function’s region.
To fix the issue I had to create a new handler which was pretty easy. I followed this answer which led me to this link specifically the Change a function's region or regions section.
1st- in terminal I ran:
$ gcloud app describe
// use whatever appears for the locationId which for me was us-east1
2nd- I added this new function to the index.js file:
// whatever appeared for the locationId I put inside the region param
exports.newTaskToFireOff = functions.region('us-east1').https.onRequest((request, response) => {
// ... in here I c+p all of the code from my initial taskToFireOff function
});
3rd- I ran this in terminal to update gcloud with the new function
$ firebase deploy --only functions:newTaskToFireOff
4th- I ran this in terminal to delete the old function from gcloud:
$ firebase functions: delete taskToFireOff
5th- I went into my index.js file and manually deleted the entire taskToFireOff. You won't need the old function, just the code from inside of it
// I deleted this old function
exports.taskToFireOff = functions.https.onRequest((request, response) => {
// ... I took this code and c+p inside the newTaskToFireOff before I deleted this function
});
7th-
When I go to the console's Cloud Functions page the old taskToFireOff is no longer there and the newTaskToFireOff is there with the region us-east1
After you finish these steps don’t forget that the new function from step 2 you have to go to the Cloud console (use the above link) and change its Permissions. Set the role to Invoker and set its principal role name to use whatever you set for the Cloud Task's ServiceAccountEmail otherwise it still won’t work. You should delete allUsers too.
I’m using next.js to build static HTML webpages.
One of my webpages needs data from a third-party API, which I’d like to fetch at build time and bake into the resulting HTML.
I don’t want this call to ever happen on the client, because:
CORS prevents the request from succeeding anyway
I would have to expose an API key on the client (no thank you)
I thought getInitialProps was the answer, because the fetched data is indeed baked in during the build/export process, but when I navigate away from the page and return from it, getInitialProps gets triggered on the client, breaking everything.
My current code in getInitialProps is something like:
static async getInitialProps(){
// Get Behance posts
const behanceEndpoint = `https://www.behance.net/v2/users/${process.env.BEHANCE_USERNAME}/projects?api_key=${process.env.BEHANCE_API_KEY}`
const behanceRes = await fetch(behanceEndpoint)
let behancePosts = await behanceRes.json()
// Return only the required number of posts
return {
behancePosts: behancePosts
}
}
Any advice or best practice on how to handle this? I know Gatsby.js does it out of the box.
one possibility would be, if you just want to execute this once on the server to check if the req parameter is present in getInitialProps: (Documentation)
req - HTTP request object (server only).
One dirty approach:
static async getInitialProps({ req }){
if (req) {
// only executed on server
// Get Behance posts
const behanceEndpoint = `https://www.behance.net/v2/users/${process.env.BEHANCE_USERNAME}/projects?api_key=${process.env.BEHANCE_API_KEY}`
const behanceRes = await fetch(behanceEndpoint)
let behancePosts = await behanceRes.json()
// Return only the required number of posts
return {
behancePosts: behancePosts
}
} else {
// client context
}
Hope this helps a little bit.
How can I post message as a bot(async) in new hangouts chat without using the Google App Engine. I have gone through the examples, but all of them use App Engine for authentication, but i need to authenticate it without using the same.
Here is a code sample that connects to a chat using an http request and a webhook from Google Hangout Chat with a Python script. Webhooks are the only alternative to using a service account. More info here: https://developers.google.com/hangouts/chat/how-tos/webhooks
`from httplib2 import Http
from json import dumps
#
# Hangouts Chat incoming webhook quickstart
#
def main():
url = '<webhook url here>'
bot_message = {
'text' : 'text go here'}
message_headers = { 'Content-Type': 'application/json; charset=UTF-8'}
http_obj = Http()
response = http_obj.request(
uri=url,
method='POST',
headers=message_headers,
body=dumps(bot_message),
)
print(response)
if __name__ == '__main__':
main()
`
If your bot implementation is with google app script try to do it with google service account and as indicated here an example of async message
// Example bot for Hangouts Chat that demonstrates bot-initiated messages
// by spamming the user every minute.
//
// This bot makes use of the Apps Script OAuth2 library at:
// https://github.com/googlesamples/apps-script-oauth2
//
// Follow the instructions there to add the library to your script.
// When added to a space, we store the space's ID in ScriptProperties.
function onAddToSpace(e) {
PropertiesService.getScriptProperties()
.setProperty(e.space.name, '');
return {
'text': 'Hi! I\'ll post a message here every minute. ' +
'Please remove me after testing or I\'ll keep spamming you!'
};
}
// When removed from a space, we remove the space's ID from ScriptProperties.
function onRemoveFromSpace(e) {
PropertiesService.getScriptProperties()
.deleteProperty(e.space.name);
}
// Add a trigger that invokes this function every minute via the
// "Edit > Current Project's Triggers" menu. When it runs, it will
// post in each space the bot was added to.
function onTrigger() {
var spaceIds = PropertiesService.getScriptProperties()
.getKeys();
var message = { 'text': 'Hi! It\'s now ' + (new Date()) };
for (var i = 0; i < spaceIds.length; ++i) {
postMessage(spaceIds[i], message);
}
}
var SCOPE = 'https://www.googleapis.com/auth/chat.bot';
// The values below are copied from the JSON file downloaded upon
// service account creation.
var SERVICE_ACCOUNT_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n';
var SERVICE_ACCOUNT_EMAIL = 'service-account#project-id.iam.gserviceaccount.com';
// Posts a message into the given space ID via the API, using
// service account authentication.
function postMessage(spaceId, message) {
var service = OAuth2.createService('chat')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setPrivateKey(SERVICE_ACCOUNT_PRIVATE_KEY)
.setClientId(SERVICE_ACCOUNT_EMAIL)
.setPropertyStore(PropertiesService.getUserProperties())
.setScope(SCOPE);
if (!service.hasAccess()) {
Logger.log('Authentication error: %s', service.getLastError());
return;
}
var url = 'https://chat.googleapis.com/v1/' + spaceId + '/messages';
UrlFetchApp.fetch(url, {
method: 'post',
headers: { 'Authorization': 'Bearer ' + service.getAccessToken() },
contentType: 'application/json',
payload: JSON.stringify(message),
});
}
You need to perform some below steps.
Create a service-account in console.developers.google.com and download the private key in JSON format
Use below modules if you code in python.
from oauth2client.service_account import ServiceAccountCredentials
from googleapiclient.discovery import build, build_from_document
from httplib2 import Http
Below snippet will post the message to user via chat.google.
scopes = ['https://www.googleapis.com/auth/chat.bot']
credentials = ServiceAccountCredentials.from_json_keyfile_name('/path/to/json',
scopes)
http = Http()
credentials.authorize(http)
chat = build('chat', 'v1', http=http)
resp = chat.spaces().messages().create(
parent=space,
body={'text': 'HELLO WORLD'}).execute()
You would require a space name where you can post the code. You will get the same from hangout chat response.
It’s possible to do so using JavaScript, python, (possibly more). You can check out examples here: https://github.com/gsuitedevs/hangouts-chat-samples/tree/master/node/basic-cloud-functions-bot
If you’re using cards and JavaScript I would encourage you to checkout my library here: https://github.com/BaReinhard/hangouts-card-helper
I’m also in the process of creating another example for JavaScript that is more async focused that should provide and example that’s a bit easier to reason about the code. Will link when the PR is pushed.
Edit:
I realize that you mentioned REST api. The above answer is more useful for a specific bot that can be accessed #mentions. However, if you can provide us with a bit more information I can better fix my answer to answer your question.
I'm confused as to the appropriate way to access a bunch of images stored in Firebase storage with a react redux firebase web app. In short, I'd love to get a walkthrough of, once a photo has been uploaded to firebase storage, how you'd go about linking it to a firebase db (like what exactly from the snapshot returned you'd store), then access it (if it's not just <img src={data.downloadURL} />), and also how you'd handle (if necessary) updating that link when the photo gets overwritten. If you can answer that, feel free to skip the rest of this...
Two options I came across are either
store the full URL in my firebase DB, or
store something less, like the path within the bucket, then call downloadURL() for every photo... which seems like a lot of unnecessary traffic, no?
My db structure at the moment is like so:
{
<someProjectId>: {
imgs: {
<someAutoGenId>: {
"name":"photo1.jpg",
"url":"https://<bucket, path, etc>token=<token>"
},
...
},
<otherProjectDetails>: "",
...
},
...
}
Going forward with that structure and the first idea listed, I ran into trouble when a photo was overwritten, so I would need to go through the list of images and remove the db record that matches the name (or find it and update its URL). I could do this (at most, there would be two refs with the old token that I would need to replace), but then I saw people doing it via option 2, though not necessarily with my exact situation.
The last thing I did see a few times, were similar questions with generic responses pointing to Cloud Functions, which I will look into right after posting, but I wasn't sure if that was overcomplicating things in my case, so I figured it couldn't hurt too much to ask. I initially saw/read about Cloud Functions and the fact that Firebase's db is "live," but wasn't sure if that played well in a React/Redux environment. Regardless, I'd appreciate any insight, and thank you.
In researching Cloud Functions, I realized that the use of Cloud Functions wasn't an entirely separate option, but rather a way to accomplish the first option I listed above (and probably the second as well). I really tried to make this clear, but I'm pretty confident I failed... so my apologies. Here's my (2-Part) working solution to syncing references in Firebase DB to Firebase Storage urls (in a React Redux Web App, though I think Part One should be applicable regardless):
PART ONE
Follow along here https://firebase.google.com/docs/functions/get-started to get cloud functions enabled.
The part of my database with the info I was storing relating to the images was at /projects/detail/{projectKey}/imgs and had this structure:
{
<autoGenKey1>: {
name: 'image1.jpg',
url: <longURLWithToken>
},
<moreAutoGenKeys>: {
...
}, ...}
My cloud function looked like this:
exports.updateURLToken = functions.database.ref(`/projects/detail/{projectKey}/imgs`)
.onWrite(event => {
const projectKey = event.params.projectKey
const newObjectSet = event.data.val()
const newKeys = Object.keys(newObjectSet)
const oldObjectSet = event.data.previous.val()
const oldKeys = Object.keys(oldObjectSet)
let newObjectKey = null
// If something was removed, none of this is necessary - return
if (oldKeys.length > newKeys.length) {
return null
}
for (let i = 0; i < newKeys.length; ++i) {// Looking for the new object -> will be missing in oldObjectSet
const key = newKeys[i]
if (oldKeys.indexOf(key) === -1) {// Found new object
newObjectKey = key
break
}
}
if (newObjectKey !== null) {// Checking if new object overwrote an existing object (same name)
const newObject = newObjectSet[newObjectKey]
let duplicateKey = null
for (let i = 0; i < oldKeys.length; ++i) {
const oldObject = oldObjectSet[oldKeys[i]]
if (newObject.name === oldObject.name) {// Duplicate found
duplicateKey = oldKeys[i]
break
}
}
if (duplicateKey !== null) {// Remove duplicate
return event.data.ref.child(duplicateKey).remove((error) => error ? 'Error removing duplicate project detail image' : true)
}
}
return null
})
After loading this function, it would run every time anything changed at that location (projects/detail/{projectKey}/imgs). So I uploaded the images, added a new object to my db with the name and url, then this would find the new object that was created, and if it had a duplicate name, that old object with the same name was removed from the db.
PART TWO
So now my database had the correct info, but unless I refreshed the page after every time images were uploaded, adding the new object to my database resulted (locally) in me having all the duplicate refs still, and this is where the realtime database came in to play.
Inside my container, I have:
function mapDispatchToProps (dispatch) {
syncProjectDetailImages(dispatch) // the relavant line -> imported from api.js
return bindActionCreators({
...projectsContentActionCreators,
...themeActionCreators,
...userActionCreators,
}, dispatch)
}
Then my api.js holds that syncProjectDetailImages function:
const SAVING_PROJECT_SUCCESS = 'SAVING_PROJECT_SUCCESS'
export function syncProjectDetailImages (dispatch) {
ref.child(`projects/detail`).on('child_changed', (snapshot) => {
dispatch(projectDetailImagesUpdated(snapshot.key, snapshot.val()))
})
}
function projectDetailImagesUpdated (key, updatedProject) {
return {
type: SAVING_PROJECT_SUCCESS,
group: 'detail',
key,
updatedProject
}
}
And finally, dispatch is figured out in my modules folder (I used the same function I would when saving any part of an updated project with redux - no new code was necessary)