google-cloud-tasks
Version:
Nodejs package to push tasks to Google Cloud Tasks (beta). Include pushing batches.
198 lines (174 loc) • 7.2 kB
JavaScript
const { fetch, promise: { delay, addTimeout } } = require('../utils')
// doc: https://cloud.google.com/tasks/docs/reference/rest/
const APP_ENG_PUSH_TASK_URL = (projectId, locationId, queueName) => `https://cloudtasks.googleapis.com/v2beta3/projects/${projectId}/locations/${locationId}/queues/${queueName}/tasks`
const _validateRequiredParams = (params={}) => Object.keys(params).forEach(p => {
if (!params[p])
throw new Error(`Parameter '${p}' is required.`)
})
/**
* Returns up to 30,000 tasks
* BEWARE: Google Cloud Task quotas for listing tasks is 10 requests per seconds (https://cloud.google.com/tasks/docs/quotas)
*
* @param {String} projectId [description]
* @param {String} locationId [description]
* @param {String} queueName [description]
* @param {String} token [description]
* @param {String} options.pageToken [description]
* @param {Number} options.page [description]
* @param {Function} options.some [description]
* @param {Function} options.filter [description]
* @return {[type]} [description]
*/
const listTasks = ({ projectId, locationId, queueName, token }, options={}) => Promise.resolve(null).then(() => {
const taskUri = `${APP_ENG_PUSH_TASK_URL(projectId, locationId, queueName)}?pageSize=1000${options.pageToken ? `&pageToken=${options.pageToken}` : ''}`
return fetch.get(taskUri, {
Accept: 'application/json',
Authorization: `Bearer ${token}`
}).then((res={}) => {
const page = options.page || 0
const filter = options.filter && typeof(options.filter) == 'function' ? options.filter : null
const pageToken = (res.data || {}).nextPageToken
const tasks = ((res.data || {}).tasks || []).map(({ name, appEngineHttpRequest, scheduleTime, createTime }) => {
const id = (name || '').split('/').slice(-1)[0]
const method = ((appEngineHttpRequest || {}).httpMethod || 'GET').toUpperCase()
const pathname = (appEngineHttpRequest || {}).relativeUri || '/'
return {
id,
method,
pathname,
schedule: scheduleTime,
created: createTime
}
})
if (page > 30 || !pageToken || tasks.length < 1000)
return tasks.filter(t => filter ? filter(t) : true)
return listTasks({ projectId, locationId, queueName, token }, { pageToken, page: page+1, filter })
.then(data => {
tasks.push(...data)
return tasks.filter(t => filter ? filter(t) : true)
})
})
})
/**
* Scan up to 30,000 tasks to find a specific one
* BEWARE: Google Cloud Task quotas for listing tasks is 10 requests per seconds (https://cloud.google.com/tasks/docs/quotas)
*
* @param {String} projectId [description]
* @param {String} locationId [description]
* @param {String} queueName [description]
* @param {String} token [description]
* @param {String} options.pageToken [description]
* @param {Number} options.page [description]
* @param {Function} options.find [description]
* @param {Function} options.filter [description]
* @return {[type]} [description]
*/
const findTask = ({ projectId, locationId, queueName, token, find }, options={}) => Promise.resolve(null).then(() => {
const taskUri = `${APP_ENG_PUSH_TASK_URL(projectId, locationId, queueName)}?pageSize=1000${options.pageToken ? `&pageToken=${options.pageToken}` : ''}`
return fetch.get(taskUri, {
Accept: 'application/json',
Authorization: `Bearer ${token}`
}).then((res={}) => {
const page = options.page || 0
const filter = options.filter && typeof(options.filter) == 'function' ? options.filter : null
const pageToken = (res.data || {}).nextPageToken
const tasks = ((res.data || {}).tasks || []).map(({ name, appEngineHttpRequest, scheduleTime, createTime }) => {
const id = (name || '').split('/').slice(-1)[0]
const method = ((appEngineHttpRequest || {}).httpMethod || 'GET').toUpperCase()
const pathname = (appEngineHttpRequest || {}).relativeUri || '/'
return {
id,
method,
pathname,
schedule: scheduleTime,
created: createTime
}
})
const matchedTask = tasks.find(find)
if (matchedTask)
return matchedTask
if (page > 30 || !pageToken || tasks.length < 1000)
return null
return findTask({ projectId, locationId, queueName, token, find }, { pageToken, page: page+1, filter })
})
})
const _createTaskId = ({ projectId, locationId, queueName, id }) => `projects/${projectId}/locations/${locationId}/queues/${queueName}/tasks/${id}`
let _devModeTaskIdTracking = { lastUpdated: 0 }
const DEV_TASK_TRACKING_TIMEOUT = 10 * 60 * 1000 // 10 minutes
const pushTask = ({ projectId, locationId, queueName, token, method, pathname, headers, body, id, schedule, serviceUrl }) => Promise.resolve(null).then(() => {
_validateRequiredParams({ projectId, locationId, queueName, token, method })
const m = method.toUpperCase()
const pname = `/${(pathname || '').replace(/^\//, '')}`
const service = serviceUrl ? `${serviceUrl.replace(/\/$/, '')}${pname}` : null
let request = {
headers,
httpMethod: m,
relativeUri: pname
}
if (m != 'GET' && body)
request.body = Buffer.from(JSON.stringify(body)).toString('base64')
let payload = {
task: {
appEngineHttpRequest: request
},
responseView: 'FULL'
}
if (id)
payload.task.name = _createTaskId({ projectId, locationId, queueName, id })
if (schedule)
payload.task.scheduleTime = schedule
if (service) {
// THE IS THE DEV MODE
if (id) {
const taskId = _createTaskId({ projectId, locationId, queueName, id })
const devTaskTrackingExpired = (Date.now() - _devModeTaskIdTracking.lastUpdated) > DEV_TASK_TRACKING_TIMEOUT
if (devTaskTrackingExpired) {
_devModeTaskIdTracking = null // force memory flushing
_devModeTaskIdTracking = { lastUpdated: Date.now() } // reset the cache
_devModeTaskIdTracking[taskId] = true
} else if (!_devModeTaskIdTracking[taskId])
_devModeTaskIdTracking[taskId] = true
else
return { status: 409, data: { error: { message: 'Requested entity already exists' } }, request: { method: m, uri: service } }
}
const devSchedule = schedule
? new Date(schedule).getTime() - Date.now()
: -10
const _pushTask = () => (m == 'GET' ? fetch.get : fetch.post)(
service,
{
Accept: 'application/json',
Authorization: `Bearer ${token}`,
'x-appengine-queuename': queueName || '__dev',
'x-appengine-taskname': '__dev',
},
JSON.stringify(body||{}))
return delay(devSchedule > 0 ? devSchedule : [100, 1000])
.then(() => addTimeout(_pushTask(), 4000)
.catch(err => {
if (err && (err.message || '').indexOf('timeout') >= 0)
return { status: 200, data: null }
else
throw err
}))
.then(res => {
res.request = { method: m, uri: service }
return res
})
} else {
// THE IS THE PROD MODE
const taskUri = APP_ENG_PUSH_TASK_URL(projectId, locationId, queueName)
return fetch.post(taskUri, {
Accept: 'application/json',
Authorization: `Bearer ${token}`
}, JSON.stringify(payload)).then(res => {
res.request = { method: 'POST', uri: taskUri }
return res
})
}
})
module.exports = {
pushTask,
listTasks,
findTask
}