Google api SQL database dump via google cloud functions - node.js

With a node.js script via googleapis I done dump of all databases created on my Google SQL instance, the dump generate a single file for all databases which i store in a bucket. My target is to have one file for each database and not one file for all, the main problem is that if I run an export request for database A i can't run another for database B until the first is done.

You may use Async with callback in order to run the exports sequentially, you may use the operations list method that will get you the status of the exports in order to be able to know when the export has finished and when to move to the next step using callback. For more information check this other post

My solution is to use a recursive function like this:
"use strict"
const { google } = require("googleapis");
const { auth } = require("google-auth-library");
const dateFormat = require('date-format');
var sqladmin = google.sql("v1beta4");
const project = "my-project-name";
const instanceName = "my-sql-instance-name";
const dbToDump = [];
exports.dumpDatabase = (_req, res) => {
async function dump() {
let count = 0;
let currentRequestName = '';
const authRes = await auth.getApplicationDefault();
var authClient = authRes.credential;
let databases = await sqladmin.databases.list({
project: project,
instance: instanceName,
auth: authClient
});
for (let i = 0; i < databases.data.items.length; i++) {
const element = databases.data.items[i];
// the system databases will be omitted
if (
element.name != "information_schema" &&
element.name != "sys" &&
element.name != "mysql" &&
element.name != "performance_schema"
) {
dbToDump.push(element.name);
}
}
async function recursiveCall() {
//exit condition
if (count >= dbToDump.length) {
res.status(200).send("Command complete");
return true;
}
// no request running
if (currentRequestName == '') {
// set data for next export call
var request = {
project: project,
instance: instanceName,
resource: {
exportContext: {
kind: "sql#exportContext",
fileType: "SQL",
uri: 'gs://my-gsc-bucket/${dbToDump[count]}-${dateFormat.asString('yyyyMMddhhmm', new Date())}.gz',
databases: [dbToDump[count]]
}
},
auth: authClient
};
let r = await sqladmin.instances.export(request); //dump start
currentRequestName = r.data.name;
}
// call to monitor request status
let requestStatus = await sqladmin.operations.get({ project: project, operation: currentRequestName, auth: authClient });
if (requestStatus.data.status == 'DONE') {
// the current request is completed, prepare for next call
count++;
currentRequestName = '';
recursiveCall();
} else {
// wait 10 seconds before check status
setTimeout(recursiveCall, 10000)
}
}
recoursiveCall();
}
dump();
};
This work for me, the only one more setting is to increase the timeout over the 60s.
Thank's Andres S for the support

Related

How to execute SQL function in Azure function project

I am working on an azure function that is invoking a web service in node.js and it works fine. I have a function GetDetails to make an SQL query to retreive data from sql server database.
const sql = require("mssql");
const dataSQL = {};
const GUID = "";
const navServiceKey = "";
const navUserName = "";
async function GetDetails() {
var email = "yamen#gmail.com";
var password = "password";
try {
console.log("nav service" + navServiceKey);
// make sure that any items are correctly URL encoded in the connection string
await sql.connect(
"Server=tcp:app.windows.net,1433;Database=BHUB_TEST;User Id=AppUser;Password=password;Encrypt=true MultipleActiveResultSets=False;TrustServerCertificate=False;ConnectionTimeout=30;"
);
const result =
await sql.query`select * from users where email = ${email} AND password = ${password} `;
if (result.rowsAffected[0] >= 1) {
dataSQL = result.recordset[0];
navServiceKey = JSON.stringify(dataSQL.navServiceKey);
GUID = JSON.stringify(dataSQL.userGUID);
navUserName = JSON.stringify(dataSQL.navUserName);
} else {
console.log("failed");
}
} catch (err) {
}}
so since this is in node.js if i were to test this sql function only i'd do the following i.e. node index.js - then function will be executed successfully and return result. However, I am calling this function within the azure function like below but when I run the azure function project, then I copy the URL given to test it on postman, the sql function won't return anything !
Any idea of how to execute SQL query function in Azure function if that makes sense ?
module.exports = async function (context, req) {
GetDetails();
const axios = require("axios");
const data = {
email: req.query.email,
password: req.query.password,
};
var cred = "YAMEN" + ":" + "jbdv******";
const encoded = Buffer.from(cred, "utf8").toString("base64");
var credbase64 = "Basic " + encoded;
const headers = {
Authorization: credbase64,
"Content-Type": " application/json",
};
{
try {
const url = `https://tegos/BC19-NUP/QryEnwisAppUser?filter=UserSecurityID eq ${GUID}`;
const response = await axios.get(url, {
headers,
});
console.log(response);
console.log(response.data);
context.res = {
// status: 200, /* Defaults to 200 */
body: response.data,
};
} catch (e) {
// maybe return the error
console.error(e);
}}};
That is not how you connect to a SQL database from an Azure application. You need to use the pyodbc module instead.
Quickstart: Use Python to query a database

Serializing Azure long running operation for later reuse

I'm trying to use Azure SDK for javascript (#azure/arm-sql version 8.0.0) to copy SQL database but I don't want to wait until the operation is done. Instead, I'd like to exit once the request is created and later (let's say each minute) check whether the operation has finished. The SDK seems to support my use case through functions:
getPollState()
Get an LROPollState object that can be used to poll this LRO in a different context (such as on a different process or a different machine). If the LRO couldn't produce an LRO polling strategy, then this will return undefined.
and restoreLROPoller()
Restore an LROPoller from the provided LROPollState. This method can be used to recreate an LROPoller on a different process or machine.
However, the documentation doesn't specify how the state should be serialized/transferred over the wire. I naively tried to serialize it into JSON but when I run the snippet below, I get the following error:
TypeError: operationSpec.serializer.deserialize is not a function occurred in deserializing the responseBody - {"name":"b9952e45-85ff-41f8-b01c-83050c9d9a2c","status":"InProgress","startTime":"2021-10-14T15:38:01.59Z"}
Here is a simplified code snippet:
import { SqlManagementClient } from "#azure/arm-sql";
import { DefaultAzureCredential } from "#azure/identity";
import { LROPoller } from "#azure/ms-rest-azure-js";
const subscription = "<subscription ID>";
const rg = "myResourceGroup";
const server = "mySqlServer";
const dbName = "myDb";
const credentials = new DefaultAzureCredential();
const sqlClient = new SqlManagementClient(credentials, subscription);
const originalDb = await sqlClient.databases.get(rg, server, dbName);
const operation: LROPoller = await sqlClient.databases.beginCreateOrUpdate(rg, server, dbName + "_copy", {
location: "westeurope",
createMode: "Copy",
sourceDatabaseId: originalDb.id
});
const operationState = operation.getPollState()!;
const serializedState = JSON.stringify(operationState);
// The program would save the state somewhere and exit now, but let's make it simple.
const deserializedState = JSON.parse(serializedState);
const restoredOperation: LROPoller = sqlClient.restoreLROPoller(deserializedState);
// Following line throws the exception
// TypeError: operationSpec.serializer.deserialize is not a function occurred in deserializing the responseBody…
await restoredOperation.poll();
So my question is how can I save the operation state in a way that I can later reuse it.
For those who might want to achieve something similar, here is the workaround. However, I still want to get rid of extra code and use SDK functionality itself, so if anyone can answer the original question, I'd be more than happy.
Here is a file AzureOperations.ts with helper functions
import { TokenCredential } from "#azure/core-auth";
import { LROPoller } from "#azure/ms-rest-azure-js";
import fetch from "node-fetch";
export interface AzureOperationReference {
statusUrl: string
}
export interface AzureOperation {
status: "InProgress" | "Succeeded" | "Failed" | "Canceled"
error?: {
code: string,
message: string
}
}
export const createAzureOperationReference = (operation: LROPoller): AzureOperationReference => {
const asyncOperationHeader = "Azure-AsyncOperation";
const headers = operation.getInitialResponse().headers;
if (!headers.contains(asyncOperationHeader)) {
throw new Error(`Given operation is currently not supported because it does not contain header '${asyncOperationHeader}'. If you want to track this operation, implement logic that uses header 'Location' first.`);
}
return {
statusUrl: headers.get(asyncOperationHeader)!
};
};
export const createAzureOperationChecker = (operationReference: AzureOperationReference, credentials: TokenCredential) => {
let token: string = null!;
let tokenExpiration = 0;
let previousOperation: AzureOperation = null!;
let retryAfter = 0;
return async () => {
const now = new Date().getTime();
if (now < retryAfter) {
return previousOperation;
}
if (tokenExpiration < now) {
const newToken = await credentials.getToken("https://management.azure.com/.default");
if (newToken === null) {
throw new Error("Cannot obtain new Azure access token.");
}
tokenExpiration = newToken.expiresOnTimestamp;
token = newToken.token;
}
const response = await fetch(operationReference.statusUrl, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`
}
});
const retryLimitInMiliseconds = Number(response.headers.get("Retry-After")) * 1000;
retryAfter = new Date().getTime() + retryLimitInMiliseconds;
return previousOperation = await response.json() as AzureOperation;
}
}
Then you can import and use them to track pending operations:
// unimportant code removed for brevity
import { createAzureOperationReference, createAzureOperationChecker } from "./AzureOperations.js";
const credentials = new DefaultAzureCredential();
const operation: LROPoller = await sqlClient.databases.beginCreateOrUpdate(…);
// You can serialize this reference as json and store it wherever you want
const reference = createAzureOperationReference(operation);
// You can deserialize it later and use it to fetch the operation status
const checkOperation = createAzureOperationChecker(reference, credentials);
const operationStatus = await checkOperation();
console.log(operationStatus.status);

AWS Sdk response not showing in Lambda Function

I am working on lambda function and creating a method for AWS-SDK historical metric report using node, js. The method is running successful but in response showing nothing. Have a look at the response.
Here is my code
function getKeyByValue(object, value) {
return Object.keys(object).find(key =>
object[key] === value);
}
exports.handler = async (event) => {
const AWS = require('aws-sdk');
var connect = new AWS.Connect({ apiVersion: '2017-08-08' });
let queueARN = event.queueARN || null;
const connectInstanceId = process.env.instanceID;
let flag =0, nextToken = null;
let queueARNsObject = {}, queueARNsArray=[], queueTypeObject={},listQueuesResult;
console.log('At line 12 entring do while loop....')
do{
console.log('How many times do I stay here???')
let listQueuesParams = {
InstanceId: connectInstanceId, /* required */
QueueTypes: [
"STANDARD",
],
NextToken: nextToken,
};
let listQueuesPromise = connect.listQueues(listQueuesParams).promise();
listQueuesResult = await listQueuesPromise;
// console.log(listQueuesResult);
listQueuesResult.QueueSummaryList.forEach(queue => {
if(queueARN != null){
if (queue.Arn == queueARN){
queueARNsArray = [queue.Arn];
queueARNsObject[queue.Name]= queue.Arn;
queueTypeObject[queue.QueueType]= queue.Arn;
flag = 1;
return;
}
}else{
queueARNsObject[queue.Name]= queue.Arn;
queueTypeObject[queue.QueueType]= queue.Arn;
queueARNsArray.push(queue.Arn);
nextToken = listQueuesResult.NextToken;
}
});
}while (flag=0 && nextToken != null);
const HistoricalMetrics = [
{
Name : "CONTACTS_HANDLED",
Unit : "COUNT",
Statistic : "SUM"
},
{
Name : "CONTACTS_ABANDONED",
Unit : "COUNT",
Statistic : "SUM"
},
];
// Metrics params
var getHistoricalMetricsParams = {
InstanceId: connectInstanceId,
StartTime: 1593099900,
EndTime: 1593129300,
Filters: {
Channels: ["VOICE"],
Queues: queueARNsArray
},
HistoricalMetrics: HistoricalMetrics,
Groupings: ["QUEUE"]
};
// console.log(getHistoricalMetricsParams);
// get current metrics by queues
var getHistoricalMetricsPromise = connect
.getMetricData(getHistoricalMetricsParams)
.promise();
var getHistoricalMetricsResult = await getHistoricalMetricsPromise;
console.log("historical metrics",getHistoricalMetricsResult);
// console.log("current |||||||| 1 metrics:", JSON.stringify(getCurrentMetricsResult));
let queueMetricsArray = [];
if(getHistoricalMetricsResult.MetricResults.length){
getHistoricalMetricsResult.MetricResults.forEach(queue => {
let queueMetrics = {
"Queue_Name" : getKeyByValue(queueARNsObject ,queue.Dimensions.Queue.Arn),
"CallsHandled": queue.Collections[0].Value,
"CallsAbanoded": queue.Collections[1].Value,
}
queueMetricsArray.push(queueMetrics);
console.log("TYPE||||", getKeyByValue(queueTypeObject ,queue.Dimensions.Queue.Arn))
});
}
const response = {
responseCode: 200,
metricResults: queueMetricsArray
};
return response;
};
I don't have any idea why it is not showing anything. if anyone of you knows please help me to fix it Thanks. I don't know what is Missing I've almost checked everything but I didn't get anything.
There are a few general areas you can look at:
Specify the region.
AWS.Connect({ apiVersion: '2017-08-08', region:'xxxxx' });
use Await directly with listQueues method
let listQueuesPromise = await connect.listQueues(listQueuesParams).promise();
Check Permissions - make sure there is sufficient authority
Lambda Configuration - increase timeout and memory size
PS: What did console log listQueuesPromise return?

Scheduling Node.js script on GCP using Cloud Functions

I've created a script that scrapes information from a webpage and writes it to a Google Sheet. This is working great on my local machine, but I'd like to schedule this on GCP.
It sounds like Cloud Functions are the way to go, but when I deploy my function I'm getting this error:
Function failed on loading user code. Error message: Node.js module defined by file working.js is expected to export function named run
I'm not sure what I should be using as the "Function to execute". Here's the function I've uploaded:
const puppeteer = require('puppeteer');
const jsonexport = require('jsonexport');
const GoogleSpreadsheet = require('google-spreadsheet');
const creds = require('./google-generated-creds.json');
const fs = require('fs');
var doc = new GoogleSpreadsheet('1qaFi0xnhaCZEduylUvGXWpyMJv00Rz6Y9qqyFR1E9oI');
function run() {
return new Promise(async (resolve, reject) => {
try {
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
const page = await browser.newPage();
const urls = [
"https://www.marksandspencer.com/pure-cotton-long-sleeve-jumpsuit/p/p60258655?image=SD_01_T42_6701_XB_X_EC_90&color=INDIGO&prevPage=plp",
"https://www.marksandspencer.com/cotton-rich-striped-3-4-sleeve-t-shirt/p/p60210598?prevPage=plp",
"https://www.marksandspencer.com/high-neck-long-sleeve-blouse/p/p60260040?image=SD_01_T43_5168_HD_X_EC_90&color=LIGHTDENIM&prevPage=plp",
"https://www.marksandspencer.com/pure-cotton-printed-short-sleeve-t-shirt/p/p60263529?image=SD_01_T41_8030Z_Z4_X_EC_90&color=WHITEMIX&prevPage=plp",
"https://www.marksandspencer.com/pure-cotton-button-detailed-denim-mini-skirt/p/p60260145?image=SD_01_T57_4004_QP_X_EC_90&color=DARKINDIGO&prevPage=plp",
"https://www.marksandspencer.com/pure-cotton-long-sleeve-shirt-midi-dress/p/p60258654?image=SD_01_T42_6703_HP_X_EC_90&color=DENIM&prevPage=plp",
"https://www.marksandspencer.com/mid-rise-skinny-leg-ankle-grazer-jeans/p/p60220155?prevPage=plp",
"https://www.marksandspencer.com/pure-cotton-long-sleeve-shirt/p/p60260208?image=SD_01_T43_5181_HP_X_EC_90&color=DENIM&prevPage=plp",
"https://www.marksandspencer.com/long-sleeve-shirt-mini-dress/p/p60258652?image=SD_01_T42_6704_HP_X_EC_90&color=DENIM&prevPage=plp",
"https://www.marksandspencer.com/wide-fit-suede-lace-up-trainers/p/p60216277?prevPage=plp",
"https://www.marksandspencer.com/suede-ankle-boots/p/p60226911?prevPage=plp",
"https://www.marksandspencer.com/leather-buckle-hip-belt/p/p60186701?prevPage=plp",
"https://www.marksandspencer.com/cross-body-bag/p/p60215352?prevPage=plp"
];
const productsList = [];
for (let i = 0; i < urls.length; i++) {
const url = urls[i];
await page.goto(url);
let products = await page.evaluate(() => {
let product = document.querySelector('h1[itemprop=name]').innerText;
let results = [];
let items = document.querySelectorAll('[data-ttip-id=sizeGridTooltip] tbody tr td label');
items.forEach((element) => {
let size = element.getAttribute('for');
let stockLevel = "";
let nearest_td = element.closest('td');
if (nearest_td.classList.contains('low-stock')) {
stockLevel = "Low stock"
} else if (nearest_td.classList.contains('out-of-stock')) {
stockLevel = "Out of stock"
} else {
stockLevel = "In stock"
}
results.push({
product: product,
size: size,
stock: stockLevel
})
});
return results
})
productsList.push(products)
}
browser.close();
function flatten(arr) {
return arr.reduce(function(flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
var flatProducts = flatten(productsList)
flatProducts.forEach(function(row) {
// Authenticate with the Google Spreadsheets API.
doc.useServiceAccountAuth(creds, function(err) {
// Get all of the rows from the spreadsheet.
doc.addRow(1, row, function(err, rows) {
console.log(row);
});
});
});
} catch (e) {
return reject(e);
}
})
}
run().then(console.log).catch(console.error);
I've never used Cloud Functions before so unsure how much I'd need to modify my script.
You can't just upload any script to run. You have to define a function using either the Cloud tools (via gcloud) or the Firebase tools and SDK. You will also have to figure out how you want to trigger it. When the function is triggered, then you can arrange to have your code executed.
I would say that it's mostly non-trivial to just port an existing script to Cloud Functions. You will have to take time to learn about how the system works in order to make effective use of it.
What that errors is referring to is that Cloud Functions can't find a function to run in that file (working.js) because you haven't exported one. For example, if you create a Cloud Function named run, then you must export a function in the script by assigning it to exports.run in your module:
exports.run = (event, callback) => {
callback(null, `Hello ${event.data.name || 'World'}!`);
};
There's more examples in the documentation, but it's likely that other changes will be necessary in your script for authentication, etc, to work from GCP.

Cloud Functions for Firebase BigQuery sync error

We're working on a cloud function that allows us to keep our bigquery and firebase database in sync. The function triggers when a place is created/updated/deleted.
Based on the trigger action (create/update/delete) we add a property called big_query_active to signal if the object exists or not. Same goes for the date.
Our current problem is that the call to big query sometimes returns an error. So that would mean that the data is not in sync anymore. How can this be prevented?
'use strict';
// Default imports.
const functions = require('firebase-functions');
const bigQuery = require('#google-cloud/bigquery');
// If you want to change the nodes to listen to REMEMBER TO change the constants below.
// The 'id' field is AUTOMATICALLY added to the values, so you CANNOT add it.
const ROOT_NODE = 'places';
const VALUES = [
'country_id',
'category_id',
'name',
'active',
'archived'
];
// This function listens to the supplied root node, but on child added/removed/changed.
// When an object is inserted/deleted/updated the appropriate action will be taken.
exports.children = functions.database.ref(ROOT_NODE + '/{id}').onWrite(event => {
const query = bigQuery();
const dataset = query.dataset('stampwallet');
const table = dataset.table(ROOT_NODE);
if (!event.data.exists() && !event.data.previous.exists()) {
return;
}
const item = event.data.exists() ? event.data.val() : event.data.previous.val();
const data = {};
data['id'] = event.params.id;
for (let index = 0; index < VALUES.length; index++) {
const key = VALUES[index];
data[key] = item[key] !== undefined ? item[key] : null;
}
data['big_query_date'] = new Date().getTime() / 1000;
data['big_query_active'] = event.data.exists();
return table.insert(data).then(() => {
return true;
}).catch((error) => {
if (error.name === 'PartialFailureError') {
console.log('A PartialFailureError happened while uploading to BigQuery...');
} else {
console.log(JSON.stringify(error));
console.log('Random error happened while uploading to BigQuery...');
}
});
});
This is the error that we (sometimes) receive
{"code":"ECONNRESET","errno":"ECONNRESET","syscall":"read"}
How could it be prevented that the data goes out of sync? Or is there a way to retry so that it always succeeds?

Resources