Merge branch 'master' into security/yarn-audit
This commit is contained in:
commit
45edddb32d
|
@ -76,6 +76,18 @@ jobs:
|
||||||
yarn check:types
|
yarn check:types
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
helm-lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Use Node.js 18.x
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
- run: cd charts/budibase && helm lint .
|
||||||
|
|
||||||
test-libraries:
|
test-libraries:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -227,6 +227,14 @@ spec:
|
||||||
resources:
|
resources:
|
||||||
{{- toYaml . | nindent 10 }}
|
{{- toYaml . | nindent 10 }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .Values.services.apps.command }}
|
||||||
|
command:
|
||||||
|
{{- toYaml .Values.services.apps.command | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.services.apps.args }}
|
||||||
|
args:
|
||||||
|
{{- toYaml .Values.services.apps.args | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
{{- with .Values.affinity }}
|
{{- with .Values.affinity }}
|
||||||
affinity:
|
affinity:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
|
|
|
@ -227,6 +227,13 @@ spec:
|
||||||
resources:
|
resources:
|
||||||
{{- toYaml . | nindent 10 }}
|
{{- toYaml . | nindent 10 }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
command:
|
||||||
|
{{- toYaml .Values.services.automationWorkers.command | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.services.automationWorkers.args }}
|
||||||
|
args:
|
||||||
|
{{- toYaml .Values.services.automationWorkers.args | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
{{- with .Values.affinity }}
|
{{- with .Values.affinity }}
|
||||||
affinity:
|
affinity:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
|
@ -244,5 +251,6 @@ spec:
|
||||||
{{ end }}
|
{{ end }}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
serviceAccountName: ""
|
serviceAccountName: ""
|
||||||
|
{{ if .Values.services.automationWorkers.command }}}
|
||||||
status: {}
|
status: {}
|
||||||
{{- end }}
|
{{- end }}
|
|
@ -100,5 +100,13 @@ spec:
|
||||||
{{ end }}
|
{{ end }}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
serviceAccountName: ""
|
serviceAccountName: ""
|
||||||
|
{{ if .Values.services.proxy.command }}
|
||||||
|
command:
|
||||||
|
{{- toYaml .Values.services.proxy.command | nindent 8 }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.services.proxy.args }}
|
||||||
|
args:
|
||||||
|
{{- toYaml .Values.services.proxy.args | nindent 8 }}
|
||||||
|
{{ end }}
|
||||||
volumes:
|
volumes:
|
||||||
status: {}
|
status: {}
|
||||||
|
|
|
@ -213,6 +213,14 @@ spec:
|
||||||
resources:
|
resources:
|
||||||
{{- toYaml . | nindent 10 }}
|
{{- toYaml . | nindent 10 }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .Values.services.worker.command }}
|
||||||
|
command:
|
||||||
|
{{- toYaml .Values.services.worker.command | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.services.worker.args }}
|
||||||
|
args:
|
||||||
|
{{- toYaml .Values.services.worker.args | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
{{- with .Values.affinity }}
|
{{- with .Values.affinity }}
|
||||||
affinity:
|
affinity:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
|
|
|
@ -7,7 +7,7 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME
|
||||||
[[ -z "${BUDIBASE_ENVIRONMENT}" ]] && export BUDIBASE_ENVIRONMENT=PRODUCTION
|
[[ -z "${BUDIBASE_ENVIRONMENT}" ]] && export BUDIBASE_ENVIRONMENT=PRODUCTION
|
||||||
[[ -z "${CLUSTER_PORT}" ]] && export CLUSTER_PORT=80
|
[[ -z "${CLUSTER_PORT}" ]] && export CLUSTER_PORT=80
|
||||||
[[ -z "${DEPLOYMENT_ENVIRONMENT}" ]] && export DEPLOYMENT_ENVIRONMENT=docker
|
[[ -z "${DEPLOYMENT_ENVIRONMENT}" ]] && export DEPLOYMENT_ENVIRONMENT=docker
|
||||||
[[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://127.0.0.1:9000
|
[[ -z "${MINIO_URL}" ]] && [[ -z "${USE_S3}" ]] && export MINIO_URL=http://127.0.0.1:9000
|
||||||
[[ -z "${NODE_ENV}" ]] && export NODE_ENV=production
|
[[ -z "${NODE_ENV}" ]] && export NODE_ENV=production
|
||||||
[[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
[[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
||||||
[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"
|
[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"
|
||||||
|
@ -77,7 +77,12 @@ mkdir -p ${DATA_DIR}/minio
|
||||||
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
||||||
redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 &
|
redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 &
|
||||||
/bbcouch-runner.sh &
|
/bbcouch-runner.sh &
|
||||||
/minio/minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 &
|
|
||||||
|
# only start minio if use s3 isn't passed
|
||||||
|
if [[ -z "${USE_S3}"]]; then
|
||||||
|
/minio/minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 &
|
||||||
|
fi
|
||||||
|
|
||||||
/etc/init.d/nginx restart
|
/etc/init.d/nginx restart
|
||||||
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
||||||
# Add monthly cron job to renew certbot certificate
|
# Add monthly cron job to renew certbot certificate
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.13.46",
|
"version": "2.13.48",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 09dae295e3ba6149c4e1d7fe567870c3a38bd277
|
Subproject commit abf2d7804c940b011328bb0979ebc6261420fe85
|
|
@ -335,3 +335,11 @@ export function isScim(): boolean {
|
||||||
const scimCall = context?.isScim
|
const scimCall = context?.isScim
|
||||||
return !!scimCall
|
return !!scimCall
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCurrentContext(): ContextMap | undefined {
|
||||||
|
try {
|
||||||
|
return Context.get()
|
||||||
|
} catch (e) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { IdentityContext } from "@budibase/types"
|
import { IdentityContext } from "@budibase/types"
|
||||||
|
import { ExecutionTimeTracker } from "../timers"
|
||||||
|
|
||||||
// keep this out of Budibase types, don't want to expose context info
|
// keep this out of Budibase types, don't want to expose context info
|
||||||
export type ContextMap = {
|
export type ContextMap = {
|
||||||
|
@ -9,4 +10,5 @@ export type ContextMap = {
|
||||||
isScim?: boolean
|
isScim?: boolean
|
||||||
automationId?: string
|
automationId?: string
|
||||||
isMigrating?: boolean
|
isMigrating?: boolean
|
||||||
|
jsExecutionTracker?: ExecutionTimeTracker
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ export function DatabaseWithConnection(
|
||||||
opts?: DatabaseOpts
|
opts?: DatabaseOpts
|
||||||
) {
|
) {
|
||||||
const db = new DatabaseImpl(dbName, opts, connection)
|
const db = new DatabaseImpl(dbName, opts, connection)
|
||||||
return new DDInstrumentedDatabase(db, "couchdb")
|
return new DDInstrumentedDatabase(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DatabaseImpl implements Database {
|
export class DatabaseImpl implements Database {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { CouchFindOptions, Database, DatabaseOpts } from "@budibase/types"
|
||||||
import { DDInstrumentedDatabase } from "./instrumentation"
|
import { DDInstrumentedDatabase } from "./instrumentation"
|
||||||
|
|
||||||
export function getDB(dbName: string, opts?: DatabaseOpts): Database {
|
export function getDB(dbName: string, opts?: DatabaseOpts): Database {
|
||||||
return new DDInstrumentedDatabase(new DatabaseImpl(dbName, opts), "couchdb")
|
return new DDInstrumentedDatabase(new DatabaseImpl(dbName, opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
// we have to use a callback for this so that we can close
|
// we have to use a callback for this so that we can close
|
||||||
|
|
|
@ -18,31 +18,28 @@ import tracer from "dd-trace"
|
||||||
import { Writable } from "stream"
|
import { Writable } from "stream"
|
||||||
|
|
||||||
export class DDInstrumentedDatabase implements Database {
|
export class DDInstrumentedDatabase implements Database {
|
||||||
constructor(
|
constructor(private readonly db: Database) {}
|
||||||
private readonly db: Database,
|
|
||||||
private readonly resource: string
|
|
||||||
) {}
|
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return this.db.name
|
return this.db.name
|
||||||
}
|
}
|
||||||
|
|
||||||
exists(): Promise<boolean> {
|
exists(): Promise<boolean> {
|
||||||
return tracer.trace("exists", { resource: this.resource }, span => {
|
return tracer.trace("db.exists", span => {
|
||||||
span?.addTags({ db_name: this.name })
|
span?.addTags({ db_name: this.name })
|
||||||
return this.db.exists()
|
return this.db.exists()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
checkSetup(): Promise<DocumentScope<any>> {
|
checkSetup(): Promise<DocumentScope<any>> {
|
||||||
return tracer.trace("checkSetup", { resource: this.resource }, span => {
|
return tracer.trace("db.checkSetup", span => {
|
||||||
span?.addTags({ db_name: this.name })
|
span?.addTags({ db_name: this.name })
|
||||||
return this.db.checkSetup()
|
return this.db.checkSetup()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
get<T extends Document>(id?: string | undefined): Promise<T> {
|
get<T extends Document>(id?: string | undefined): Promise<T> {
|
||||||
return tracer.trace("get", { resource: this.resource }, span => {
|
return tracer.trace("db.get", span => {
|
||||||
span?.addTags({ db_name: this.name, doc_id: id })
|
span?.addTags({ db_name: this.name, doc_id: id })
|
||||||
return this.db.get(id)
|
return this.db.get(id)
|
||||||
})
|
})
|
||||||
|
@ -52,7 +49,7 @@ export class DDInstrumentedDatabase implements Database {
|
||||||
ids: string[],
|
ids: string[],
|
||||||
opts?: { allowMissing?: boolean | undefined } | undefined
|
opts?: { allowMissing?: boolean | undefined } | undefined
|
||||||
): Promise<T[]> {
|
): Promise<T[]> {
|
||||||
return tracer.trace("getMultiple", { resource: this.resource }, span => {
|
return tracer.trace("db.getMultiple", span => {
|
||||||
span?.addTags({
|
span?.addTags({
|
||||||
db_name: this.name,
|
db_name: this.name,
|
||||||
num_docs: ids.length,
|
num_docs: ids.length,
|
||||||
|
@ -66,7 +63,7 @@ export class DDInstrumentedDatabase implements Database {
|
||||||
id: string | Document,
|
id: string | Document,
|
||||||
rev?: string | undefined
|
rev?: string | undefined
|
||||||
): Promise<DocumentDestroyResponse> {
|
): Promise<DocumentDestroyResponse> {
|
||||||
return tracer.trace("remove", { resource: this.resource }, span => {
|
return tracer.trace("db.remove", span => {
|
||||||
span?.addTags({ db_name: this.name, doc_id: id })
|
span?.addTags({ db_name: this.name, doc_id: id })
|
||||||
return this.db.remove(id, rev)
|
return this.db.remove(id, rev)
|
||||||
})
|
})
|
||||||
|
@ -76,14 +73,14 @@ export class DDInstrumentedDatabase implements Database {
|
||||||
document: AnyDocument,
|
document: AnyDocument,
|
||||||
opts?: DatabasePutOpts | undefined
|
opts?: DatabasePutOpts | undefined
|
||||||
): Promise<DocumentInsertResponse> {
|
): Promise<DocumentInsertResponse> {
|
||||||
return tracer.trace("put", { resource: this.resource }, span => {
|
return tracer.trace("db.put", span => {
|
||||||
span?.addTags({ db_name: this.name, doc_id: document._id })
|
span?.addTags({ db_name: this.name, doc_id: document._id })
|
||||||
return this.db.put(document, opts)
|
return this.db.put(document, opts)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
bulkDocs(documents: AnyDocument[]): Promise<DocumentBulkResponse[]> {
|
bulkDocs(documents: AnyDocument[]): Promise<DocumentBulkResponse[]> {
|
||||||
return tracer.trace("bulkDocs", { resource: this.resource }, span => {
|
return tracer.trace("db.bulkDocs", span => {
|
||||||
span?.addTags({ db_name: this.name, num_docs: documents.length })
|
span?.addTags({ db_name: this.name, num_docs: documents.length })
|
||||||
return this.db.bulkDocs(documents)
|
return this.db.bulkDocs(documents)
|
||||||
})
|
})
|
||||||
|
@ -92,7 +89,7 @@ export class DDInstrumentedDatabase implements Database {
|
||||||
allDocs<T extends Document>(
|
allDocs<T extends Document>(
|
||||||
params: DatabaseQueryOpts
|
params: DatabaseQueryOpts
|
||||||
): Promise<AllDocsResponse<T>> {
|
): Promise<AllDocsResponse<T>> {
|
||||||
return tracer.trace("allDocs", { resource: this.resource }, span => {
|
return tracer.trace("db.allDocs", span => {
|
||||||
span?.addTags({ db_name: this.name })
|
span?.addTags({ db_name: this.name })
|
||||||
return this.db.allDocs(params)
|
return this.db.allDocs(params)
|
||||||
})
|
})
|
||||||
|
@ -102,56 +99,56 @@ export class DDInstrumentedDatabase implements Database {
|
||||||
viewName: string,
|
viewName: string,
|
||||||
params: DatabaseQueryOpts
|
params: DatabaseQueryOpts
|
||||||
): Promise<AllDocsResponse<T>> {
|
): Promise<AllDocsResponse<T>> {
|
||||||
return tracer.trace("query", { resource: this.resource }, span => {
|
return tracer.trace("db.query", span => {
|
||||||
span?.addTags({ db_name: this.name, view_name: viewName })
|
span?.addTags({ db_name: this.name, view_name: viewName })
|
||||||
return this.db.query(viewName, params)
|
return this.db.query(viewName, params)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): Promise<void | OkResponse> {
|
destroy(): Promise<void | OkResponse> {
|
||||||
return tracer.trace("destroy", { resource: this.resource }, span => {
|
return tracer.trace("db.destroy", span => {
|
||||||
span?.addTags({ db_name: this.name })
|
span?.addTags({ db_name: this.name })
|
||||||
return this.db.destroy()
|
return this.db.destroy()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
compact(): Promise<void | OkResponse> {
|
compact(): Promise<void | OkResponse> {
|
||||||
return tracer.trace("compact", { resource: this.resource }, span => {
|
return tracer.trace("db.compact", span => {
|
||||||
span?.addTags({ db_name: this.name })
|
span?.addTags({ db_name: this.name })
|
||||||
return this.db.compact()
|
return this.db.compact()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
dump(stream: Writable, opts?: DatabaseDumpOpts | undefined): Promise<any> {
|
dump(stream: Writable, opts?: DatabaseDumpOpts | undefined): Promise<any> {
|
||||||
return tracer.trace("dump", { resource: this.resource }, span => {
|
return tracer.trace("db.dump", span => {
|
||||||
span?.addTags({ db_name: this.name })
|
span?.addTags({ db_name: this.name })
|
||||||
return this.db.dump(stream, opts)
|
return this.db.dump(stream, opts)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
load(...args: any[]): Promise<any> {
|
load(...args: any[]): Promise<any> {
|
||||||
return tracer.trace("load", { resource: this.resource }, span => {
|
return tracer.trace("db.load", span => {
|
||||||
span?.addTags({ db_name: this.name })
|
span?.addTags({ db_name: this.name })
|
||||||
return this.db.load(...args)
|
return this.db.load(...args)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
createIndex(...args: any[]): Promise<any> {
|
createIndex(...args: any[]): Promise<any> {
|
||||||
return tracer.trace("createIndex", { resource: this.resource }, span => {
|
return tracer.trace("db.createIndex", span => {
|
||||||
span?.addTags({ db_name: this.name })
|
span?.addTags({ db_name: this.name })
|
||||||
return this.db.createIndex(...args)
|
return this.db.createIndex(...args)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteIndex(...args: any[]): Promise<any> {
|
deleteIndex(...args: any[]): Promise<any> {
|
||||||
return tracer.trace("deleteIndex", { resource: this.resource }, span => {
|
return tracer.trace("db.deleteIndex", span => {
|
||||||
span?.addTags({ db_name: this.name })
|
span?.addTags({ db_name: this.name })
|
||||||
return this.db.deleteIndex(...args)
|
return this.db.deleteIndex(...args)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getIndexes(...args: any[]): Promise<any> {
|
getIndexes(...args: any[]): Promise<any> {
|
||||||
return tracer.trace("getIndexes", { resource: this.resource }, span => {
|
return tracer.trace("db.getIndexes", span => {
|
||||||
span?.addTags({ db_name: this.name })
|
span?.addTags({ db_name: this.name })
|
||||||
return this.db.getIndexes(...args)
|
return this.db.getIndexes(...args)
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,6 +15,7 @@ function newJob(queue: string, message: any) {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
queue: queue,
|
queue: queue,
|
||||||
data: message,
|
data: message,
|
||||||
|
opts: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,3 +20,41 @@ export function cleanup() {
|
||||||
}
|
}
|
||||||
intervals = []
|
intervals = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ExecutionTimeoutError extends Error {
|
||||||
|
public readonly name = "ExecutionTimeoutError"
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExecutionTimeTracker {
|
||||||
|
static withLimit(limitMs: number) {
|
||||||
|
return new ExecutionTimeTracker(limitMs)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(readonly limitMs: number) {}
|
||||||
|
|
||||||
|
private totalTimeMs = 0
|
||||||
|
|
||||||
|
track<T>(f: () => T): T {
|
||||||
|
this.checkLimit()
|
||||||
|
const start = process.hrtime.bigint()
|
||||||
|
try {
|
||||||
|
return f()
|
||||||
|
} finally {
|
||||||
|
const end = process.hrtime.bigint()
|
||||||
|
this.totalTimeMs += Number(end - start) / 1e6
|
||||||
|
this.checkLimit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get elapsedMS() {
|
||||||
|
return this.totalTimeMs
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkLimit() {
|
||||||
|
if (this.totalTimeMs > this.limitMs) {
|
||||||
|
throw new ExecutionTimeoutError(
|
||||||
|
`Execution time limit of ${this.limitMs}ms exceeded: ${this.totalTimeMs}ms`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -49,4 +49,8 @@ export class Duration {
|
||||||
static fromDays(duration: number) {
|
static fromDays(duration: number) {
|
||||||
return Duration.from(DurationType.DAYS, duration)
|
return Duration.from(DurationType.DAYS, duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromMilliseconds(duration: number) {
|
||||||
|
return Duration.from(DurationType.MILLISECONDS, duration)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2086,4 +2086,112 @@ describe.each([
|
||||||
expect(row.formula).toBe(relatedRow.name)
|
expect(row.formula).toBe(relatedRow.name)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("Formula JS protection", () => {
|
||||||
|
it("should time out JS execution if a single cell takes too long", async () => {
|
||||||
|
await config.withEnv({ JS_PER_EXECUTION_TIME_LIMIT_MS: 20 }, async () => {
|
||||||
|
const js = Buffer.from(
|
||||||
|
`
|
||||||
|
let i = 0;
|
||||||
|
while (true) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
`
|
||||||
|
).toString("base64")
|
||||||
|
|
||||||
|
const table = await config.createTable({
|
||||||
|
name: "table",
|
||||||
|
type: "table",
|
||||||
|
schema: {
|
||||||
|
text: {
|
||||||
|
name: "text",
|
||||||
|
type: FieldType.STRING,
|
||||||
|
},
|
||||||
|
formula: {
|
||||||
|
name: "formula",
|
||||||
|
type: FieldType.FORMULA,
|
||||||
|
formula: `{{ js "${js}"}}`,
|
||||||
|
formulaType: FormulaTypes.DYNAMIC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await config.api.row.save(table._id!, { text: "foo" })
|
||||||
|
const { rows } = await config.api.row.search(table._id!)
|
||||||
|
expect(rows).toHaveLength(1)
|
||||||
|
const row = rows[0]
|
||||||
|
expect(row.text).toBe("foo")
|
||||||
|
expect(row.formula).toBe("Timed out while executing JS")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should time out JS execution if a multiple cells take too long", async () => {
|
||||||
|
await config.withEnv(
|
||||||
|
{
|
||||||
|
JS_PER_EXECUTION_TIME_LIMIT_MS: 20,
|
||||||
|
JS_PER_REQUEST_TIME_LIMIT_MS: 40,
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
const js = Buffer.from(
|
||||||
|
`
|
||||||
|
let i = 0;
|
||||||
|
while (true) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
`
|
||||||
|
).toString("base64")
|
||||||
|
|
||||||
|
const table = await config.createTable({
|
||||||
|
name: "table",
|
||||||
|
type: "table",
|
||||||
|
schema: {
|
||||||
|
text: {
|
||||||
|
name: "text",
|
||||||
|
type: FieldType.STRING,
|
||||||
|
},
|
||||||
|
formula: {
|
||||||
|
name: "formula",
|
||||||
|
type: FieldType.FORMULA,
|
||||||
|
formula: `{{ js "${js}"}}`,
|
||||||
|
formulaType: FormulaTypes.DYNAMIC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
await config.api.row.save(table._id!, { text: "foo" })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run this test 3 times to make sure that there's no cross-request
|
||||||
|
// pollution of the execution time tracking.
|
||||||
|
for (let reqs = 0; reqs < 3; reqs++) {
|
||||||
|
const { rows } = await config.api.row.search(table._id!)
|
||||||
|
expect(rows).toHaveLength(10)
|
||||||
|
|
||||||
|
let i = 0
|
||||||
|
for (; i < 10; i++) {
|
||||||
|
const row = rows[i]
|
||||||
|
if (row.formula !== "Timed out while executing JS") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the execution times are not deterministic, we can't be sure
|
||||||
|
// of the exact number of rows that were executed before the timeout
|
||||||
|
// but it should absolutely be at least 1.
|
||||||
|
expect(i).toBeGreaterThan(0)
|
||||||
|
expect(i).toBeLessThan(5)
|
||||||
|
|
||||||
|
for (; i < 10; i++) {
|
||||||
|
const row = rows[i]
|
||||||
|
expect(row.text).toBe("foo")
|
||||||
|
expect(row.formula).toBe("Request JS execution limit hit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../sdk"
|
import sdk from "../sdk"
|
||||||
import { automationsEnabled } from "../features"
|
import { automationsEnabled } from "../features"
|
||||||
|
import tracer from "dd-trace"
|
||||||
|
|
||||||
const REBOOT_CRON = "@reboot"
|
const REBOOT_CRON = "@reboot"
|
||||||
const WH_STEP_ID = definitions.WEBHOOK.stepId
|
const WH_STEP_ID = definitions.WEBHOOK.stepId
|
||||||
|
@ -39,8 +40,37 @@ function loggingArgs(job: AutomationJob) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function processEvent(job: AutomationJob) {
|
export async function processEvent(job: AutomationJob) {
|
||||||
|
return tracer.trace(
|
||||||
|
"processEvent",
|
||||||
|
{ resource: "automation" },
|
||||||
|
async span => {
|
||||||
const appId = job.data.event.appId!
|
const appId = job.data.event.appId!
|
||||||
const automationId = job.data.automation._id!
|
const automationId = job.data.automation._id!
|
||||||
|
|
||||||
|
span?.addTags({
|
||||||
|
appId,
|
||||||
|
automationId,
|
||||||
|
job: {
|
||||||
|
id: job.id,
|
||||||
|
name: job.name,
|
||||||
|
attemptsMade: job.attemptsMade,
|
||||||
|
opts: {
|
||||||
|
attempts: job.opts.attempts,
|
||||||
|
priority: job.opts.priority,
|
||||||
|
delay: job.opts.delay,
|
||||||
|
repeat: job.opts.repeat,
|
||||||
|
backoff: job.opts.backoff,
|
||||||
|
lifo: job.opts.lifo,
|
||||||
|
timeout: job.opts.timeout,
|
||||||
|
jobId: job.opts.jobId,
|
||||||
|
removeOnComplete: job.opts.removeOnComplete,
|
||||||
|
removeOnFail: job.opts.removeOnFail,
|
||||||
|
stackTraceLimit: job.opts.stackTraceLimit,
|
||||||
|
preventParsingData: job.opts.preventParsingData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const task = async () => {
|
const task = async () => {
|
||||||
try {
|
try {
|
||||||
// need to actually await these so that an error can be captured properly
|
// need to actually await these so that an error can be captured properly
|
||||||
|
@ -53,12 +83,19 @@ export async function processEvent(job: AutomationJob) {
|
||||||
console.log("automation completed", ...loggingArgs(job))
|
console.log("automation completed", ...loggingArgs(job))
|
||||||
return result
|
return result
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`automation was unable to run`, err, ...loggingArgs(job))
|
span?.addTags({ error: true })
|
||||||
|
console.error(
|
||||||
|
`automation was unable to run`,
|
||||||
|
err,
|
||||||
|
...loggingArgs(job)
|
||||||
|
)
|
||||||
return { err }
|
return { err }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await context.doInAutomationContext({ appId, automationId, task })
|
return await context.doInAutomationContext({ appId, automationId, task })
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateTestHistory(
|
export async function updateTestHistory(
|
||||||
|
|
|
@ -70,6 +70,11 @@ const environment = {
|
||||||
SELF_HOSTED: process.env.SELF_HOSTED,
|
SELF_HOSTED: process.env.SELF_HOSTED,
|
||||||
HTTP_MB_LIMIT: process.env.HTTP_MB_LIMIT,
|
HTTP_MB_LIMIT: process.env.HTTP_MB_LIMIT,
|
||||||
FORKED_PROCESS_NAME: process.env.FORKED_PROCESS_NAME || "main",
|
FORKED_PROCESS_NAME: process.env.FORKED_PROCESS_NAME || "main",
|
||||||
|
JS_PER_EXECUTION_TIME_LIMIT_MS:
|
||||||
|
parseIntSafe(process.env.JS_PER_EXECUTION_TIME_LIMIT_MS) || 1000,
|
||||||
|
JS_PER_REQUEST_TIME_LIMIT_MS: parseIntSafe(
|
||||||
|
process.env.JS_PER_REQUEST_TIME_LIMIT_MS
|
||||||
|
),
|
||||||
// old
|
// old
|
||||||
CLIENT_ID: process.env.CLIENT_ID,
|
CLIENT_ID: process.env.CLIENT_ID,
|
||||||
_set(key: string, value: any) {
|
_set(key: string, value: any) {
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import vm from "vm"
|
||||||
|
import env from "./environment"
|
||||||
|
import { setJSRunner } from "@budibase/string-templates"
|
||||||
|
import { context, timers } from "@budibase/backend-core"
|
||||||
|
import tracer from "dd-trace"
|
||||||
|
|
||||||
|
type TrackerFn = <T>(f: () => T) => T
|
||||||
|
|
||||||
|
export function init() {
|
||||||
|
setJSRunner((js: string, ctx: vm.Context) => {
|
||||||
|
return tracer.trace("runJS", {}, span => {
|
||||||
|
const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS
|
||||||
|
let track: TrackerFn = f => f()
|
||||||
|
if (perRequestLimit) {
|
||||||
|
const bbCtx = context.getCurrentContext()
|
||||||
|
if (bbCtx) {
|
||||||
|
if (!bbCtx.jsExecutionTracker) {
|
||||||
|
bbCtx.jsExecutionTracker =
|
||||||
|
timers.ExecutionTimeTracker.withLimit(perRequestLimit)
|
||||||
|
}
|
||||||
|
track = bbCtx.jsExecutionTracker.track.bind(bbCtx.jsExecutionTracker)
|
||||||
|
span?.addTags({
|
||||||
|
js: {
|
||||||
|
limitMS: bbCtx.jsExecutionTracker.limitMs,
|
||||||
|
elapsedMS: bbCtx.jsExecutionTracker.elapsedMS,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = {
|
||||||
|
...ctx,
|
||||||
|
alert: undefined,
|
||||||
|
setInterval: undefined,
|
||||||
|
setTimeout: undefined,
|
||||||
|
}
|
||||||
|
vm.createContext(ctx)
|
||||||
|
return track(() =>
|
||||||
|
vm.runInNewContext(js, ctx, {
|
||||||
|
timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ export default async (ctx: UserCtx, next: any) => {
|
||||||
|
|
||||||
if (requestAppId) {
|
if (requestAppId) {
|
||||||
const span = tracer.scope().active()
|
const span = tracer.scope().active()
|
||||||
span?.addTags({ app_id: requestAppId })
|
span?.setTag("appId", requestAppId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deny access to application preview
|
// deny access to application preview
|
||||||
|
@ -76,6 +76,14 @@ export default async (ctx: UserCtx, next: any) => {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctx.user) {
|
||||||
|
const span = tracer.scope().active()
|
||||||
|
if (ctx.user._id) {
|
||||||
|
span?.setTag("userId", ctx.user._id)
|
||||||
|
}
|
||||||
|
span?.setTag("tenantId", ctx.user.tenantId)
|
||||||
|
}
|
||||||
|
|
||||||
const userId = ctx.user ? generateUserMetadataID(ctx.user._id!) : undefined
|
const userId = ctx.user ? generateUserMetadataID(ctx.user._id!) : undefined
|
||||||
|
|
||||||
// if the user is not in the right tenant then make sure to wipe their cookie
|
// if the user is not in the right tenant then make sure to wipe their cookie
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { automationsEnabled, printFeatures } from "./features"
|
||||||
import Koa from "koa"
|
import Koa from "koa"
|
||||||
import { Server } from "http"
|
import { Server } from "http"
|
||||||
import { AddressInfo } from "net"
|
import { AddressInfo } from "net"
|
||||||
|
import * as jsRunner from "./jsRunner"
|
||||||
|
|
||||||
let STARTUP_RAN = false
|
let STARTUP_RAN = false
|
||||||
|
|
||||||
|
@ -152,4 +153,6 @@ export async function startup(app?: Koa, server?: Server) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jsRunner.init()
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { cloneDeep } from "lodash/fp"
|
||||||
import { performance } from "perf_hooks"
|
import { performance } from "perf_hooks"
|
||||||
import * as sdkUtils from "../sdk/utils"
|
import * as sdkUtils from "../sdk/utils"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
|
import tracer from "dd-trace"
|
||||||
|
|
||||||
threadUtils.threadSetup()
|
threadUtils.threadSetup()
|
||||||
const FILTER_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.FILTER.stepId
|
const FILTER_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.FILTER.stepId
|
||||||
|
@ -242,6 +243,15 @@ class Orchestrator {
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(): Promise<any> {
|
async execute(): Promise<any> {
|
||||||
|
return tracer.trace(
|
||||||
|
"Orchestrator.execute",
|
||||||
|
{ resource: "automation" },
|
||||||
|
async span => {
|
||||||
|
span?.addTags({
|
||||||
|
appId: this._appId,
|
||||||
|
automationId: this._automation._id,
|
||||||
|
})
|
||||||
|
|
||||||
// this will retrieve from context created at start of thread
|
// this will retrieve from context created at start of thread
|
||||||
this._context.env = await sdkUtils.getEnvironmentVariables()
|
this._context.env = await sdkUtils.getEnvironmentVariables()
|
||||||
let automation = this._automation
|
let automation = this._automation
|
||||||
|
@ -257,15 +267,39 @@ class Orchestrator {
|
||||||
let timeout = this._job.data.event.timeout
|
let timeout = this._job.data.event.timeout
|
||||||
// check if this is a recurring automation,
|
// check if this is a recurring automation,
|
||||||
if (isProdAppID(this._appId) && isRecurring(automation)) {
|
if (isProdAppID(this._appId) && isRecurring(automation)) {
|
||||||
|
span?.addTags({ recurring: true })
|
||||||
metadata = await this.getMetadata()
|
metadata = await this.getMetadata()
|
||||||
const shouldStop = await this.checkIfShouldStop(metadata)
|
const shouldStop = await this.checkIfShouldStop(metadata)
|
||||||
if (shouldStop) {
|
if (shouldStop) {
|
||||||
|
span?.addTags({ shouldStop: true })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const start = performance.now()
|
const start = performance.now()
|
||||||
for (let step of automation.definition.steps) {
|
for (let step of automation.definition.steps) {
|
||||||
|
const stepSpan = tracer.startSpan("Orchestrator.execute.step", {
|
||||||
|
childOf: span,
|
||||||
|
})
|
||||||
|
stepSpan.addTags({
|
||||||
|
resource: "automation",
|
||||||
|
step: {
|
||||||
|
stepId: step.stepId,
|
||||||
|
id: step.id,
|
||||||
|
name: step.name,
|
||||||
|
type: step.type,
|
||||||
|
title: step.stepTitle,
|
||||||
|
internal: step.internal,
|
||||||
|
deprecated: step.deprecated,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let input: any,
|
||||||
|
iterations = 1,
|
||||||
|
iterationCount = 0
|
||||||
|
|
||||||
|
try {
|
||||||
if (timeoutFlag) {
|
if (timeoutFlag) {
|
||||||
|
span?.addTags({ timedOut: true })
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,10 +310,6 @@ class Orchestrator {
|
||||||
}
|
}
|
||||||
|
|
||||||
stepCount++
|
stepCount++
|
||||||
let input: any,
|
|
||||||
iterations = 1,
|
|
||||||
iterationCount = 0
|
|
||||||
|
|
||||||
if (step.stepId === LOOP_STEP_ID) {
|
if (step.stepId === LOOP_STEP_ID) {
|
||||||
loopStep = step
|
loopStep = step
|
||||||
loopStepNumber = stepCount
|
loopStepNumber = stepCount
|
||||||
|
@ -289,22 +319,31 @@ class Orchestrator {
|
||||||
if (loopStep) {
|
if (loopStep) {
|
||||||
input = await processObject(loopStep.inputs, this._context)
|
input = await processObject(loopStep.inputs, this._context)
|
||||||
iterations = getLoopIterations(loopStep as LoopStep)
|
iterations = getLoopIterations(loopStep as LoopStep)
|
||||||
|
stepSpan?.addTags({ step: { iterations } })
|
||||||
}
|
}
|
||||||
for (let index = 0; index < iterations; index++) {
|
for (let index = 0; index < iterations; index++) {
|
||||||
let originalStepInput = cloneDeep(step.inputs)
|
let originalStepInput = cloneDeep(step.inputs)
|
||||||
// Handle if the user has set a max iteration count or if it reaches the max limit set by us
|
// Handle if the user has set a max iteration count or if it reaches the max limit set by us
|
||||||
if (loopStep && input.binding) {
|
if (loopStep && input.binding) {
|
||||||
let tempOutput = { items: loopSteps, iterations: iterationCount }
|
let tempOutput = {
|
||||||
|
items: loopSteps,
|
||||||
|
iterations: iterationCount,
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
loopStep.inputs.binding = automationUtils.typecastForLooping(
|
loopStep.inputs.binding = automationUtils.typecastForLooping(
|
||||||
loopStep as LoopStep,
|
loopStep as LoopStep,
|
||||||
loopStep.inputs as LoopInput
|
loopStep.inputs as LoopInput
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
|
this.updateContextAndOutput(
|
||||||
|
loopStepNumber,
|
||||||
|
step,
|
||||||
|
tempOutput,
|
||||||
|
{
|
||||||
status: AutomationErrors.INCORRECT_TYPE,
|
status: AutomationErrors.INCORRECT_TYPE,
|
||||||
success: false,
|
success: false,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
loopSteps = undefined
|
loopSteps = undefined
|
||||||
loopStep = undefined
|
loopStep = undefined
|
||||||
break
|
break
|
||||||
|
@ -349,7 +388,8 @@ class Orchestrator {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
originalStepInput[key] = automationUtils.substituteLoopStep(
|
originalStepInput[key] =
|
||||||
|
automationUtils.substituteLoopStep(
|
||||||
value,
|
value,
|
||||||
`steps.${loopStepNumber}`
|
`steps.${loopStepNumber}`
|
||||||
)
|
)
|
||||||
|
@ -361,30 +401,42 @@ class Orchestrator {
|
||||||
index === env.AUTOMATION_MAX_ITERATIONS ||
|
index === env.AUTOMATION_MAX_ITERATIONS ||
|
||||||
index === parseInt(loopStep.inputs.iterations)
|
index === parseInt(loopStep.inputs.iterations)
|
||||||
) {
|
) {
|
||||||
this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
|
this.updateContextAndOutput(
|
||||||
|
loopStepNumber,
|
||||||
|
step,
|
||||||
|
tempOutput,
|
||||||
|
{
|
||||||
status: AutomationErrors.MAX_ITERATIONS,
|
status: AutomationErrors.MAX_ITERATIONS,
|
||||||
success: true,
|
success: true,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
loopSteps = undefined
|
loopSteps = undefined
|
||||||
loopStep = undefined
|
loopStep = undefined
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
let isFailure = false
|
let isFailure = false
|
||||||
const currentItem = this._context.steps[loopStepNumber]?.currentItem
|
const currentItem =
|
||||||
|
this._context.steps[loopStepNumber]?.currentItem
|
||||||
if (currentItem && typeof currentItem === "object") {
|
if (currentItem && typeof currentItem === "object") {
|
||||||
isFailure = Object.keys(currentItem).some(value => {
|
isFailure = Object.keys(currentItem).some(value => {
|
||||||
return currentItem[value] === loopStep?.inputs.failure
|
return currentItem[value] === loopStep?.inputs.failure
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
isFailure = currentItem && currentItem === loopStep.inputs.failure
|
isFailure =
|
||||||
|
currentItem && currentItem === loopStep.inputs.failure
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFailure) {
|
if (isFailure) {
|
||||||
this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
|
this.updateContextAndOutput(
|
||||||
|
loopStepNumber,
|
||||||
|
step,
|
||||||
|
tempOutput,
|
||||||
|
{
|
||||||
status: AutomationErrors.FAILURE_CONDITION,
|
status: AutomationErrors.FAILURE_CONDITION,
|
||||||
success: false,
|
success: false,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
loopSteps = undefined
|
loopSteps = undefined
|
||||||
loopStep = undefined
|
loopStep = undefined
|
||||||
break
|
break
|
||||||
|
@ -393,14 +445,22 @@ class Orchestrator {
|
||||||
|
|
||||||
// execution stopped, record state for that
|
// execution stopped, record state for that
|
||||||
if (stopped) {
|
if (stopped) {
|
||||||
this.updateExecutionOutput(step.id, step.stepId, {}, STOPPED_STATUS)
|
this.updateExecutionOutput(
|
||||||
|
step.id,
|
||||||
|
step.stepId,
|
||||||
|
{},
|
||||||
|
STOPPED_STATUS
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a loop step, we need to manually add the bindings to the context
|
// If it's a loop step, we need to manually add the bindings to the context
|
||||||
let stepFn = await this.getStepFunctionality(step.stepId)
|
let stepFn = await this.getStepFunctionality(step.stepId)
|
||||||
let inputs = await processObject(originalStepInput, this._context)
|
let inputs = await processObject(originalStepInput, this._context)
|
||||||
inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs)
|
inputs = automationUtils.cleanInputValues(
|
||||||
|
inputs,
|
||||||
|
step.schema.inputs
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// appId is always passed
|
// appId is always passed
|
||||||
|
@ -416,10 +476,15 @@ class Orchestrator {
|
||||||
// so that we can finish iterating through the steps and record that it stopped
|
// so that we can finish iterating through the steps and record that it stopped
|
||||||
if (step.stepId === FILTER_STEP_ID && !outputs.result) {
|
if (step.stepId === FILTER_STEP_ID && !outputs.result) {
|
||||||
stopped = true
|
stopped = true
|
||||||
this.updateExecutionOutput(step.id, step.stepId, step.inputs, {
|
this.updateExecutionOutput(
|
||||||
|
step.id,
|
||||||
|
step.stepId,
|
||||||
|
step.inputs,
|
||||||
|
{
|
||||||
...outputs,
|
...outputs,
|
||||||
...STOPPED_STATUS,
|
...STOPPED_STATUS,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (loopStep && loopSteps) {
|
if (loopStep && loopSteps) {
|
||||||
|
@ -446,6 +511,9 @@ class Orchestrator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
stepSpan?.finish()
|
||||||
|
}
|
||||||
|
|
||||||
if (loopStep && iterations === 0) {
|
if (loopStep && iterations === 0) {
|
||||||
loopStep = undefined
|
loopStep = undefined
|
||||||
|
@ -515,6 +583,8 @@ class Orchestrator {
|
||||||
}
|
}
|
||||||
return this.executionOutput
|
return this.executionOutput
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function execute(job: Job<AutomationData>, callback: WorkerCallback) {
|
export function execute(job: Job<AutomationData>, callback: WorkerCallback) {
|
||||||
|
|
|
@ -56,10 +56,12 @@ module.exports.processJS = (handlebars, context) => {
|
||||||
const res = { data: runJS(js, sandboxContext) }
|
const res = { data: runJS(js, sandboxContext) }
|
||||||
return `{{${LITERAL_MARKER} js_result-${JSON.stringify(res)}}}`
|
return `{{${LITERAL_MARKER} js_result-${JSON.stringify(res)}}}`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`JS error: ${typeof error} ${JSON.stringify(error)}`)
|
|
||||||
if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
|
if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
|
||||||
return "Timed out while executing JS"
|
return "Timed out while executing JS"
|
||||||
}
|
}
|
||||||
|
if (error.name === "ExecutionTimeoutError") {
|
||||||
|
return "Request JS execution limit hit"
|
||||||
|
}
|
||||||
return "Error while executing JS"
|
return "Error while executing JS"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ module.exports.doesContainString = templates.doesContainString
|
||||||
module.exports.disableEscaping = templates.disableEscaping
|
module.exports.disableEscaping = templates.disableEscaping
|
||||||
module.exports.findHBSBlocks = templates.findHBSBlocks
|
module.exports.findHBSBlocks = templates.findHBSBlocks
|
||||||
module.exports.convertToJS = templates.convertToJS
|
module.exports.convertToJS = templates.convertToJS
|
||||||
|
module.exports.setJSRunner = templates.setJSRunner
|
||||||
module.exports.FIND_ANY_HBS_REGEX = templates.FIND_ANY_HBS_REGEX
|
module.exports.FIND_ANY_HBS_REGEX = templates.FIND_ANY_HBS_REGEX
|
||||||
|
|
||||||
if (!process.env.NO_JS) {
|
if (!process.env.NO_JS) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ const {
|
||||||
findDoubleHbsInstances,
|
findDoubleHbsInstances,
|
||||||
} = require("./utilities")
|
} = require("./utilities")
|
||||||
const { convertHBSBlock } = require("./conversion")
|
const { convertHBSBlock } = require("./conversion")
|
||||||
|
const javascript = require("./helpers/javascript")
|
||||||
|
|
||||||
const hbsInstance = handlebars.create()
|
const hbsInstance = handlebars.create()
|
||||||
registerAll(hbsInstance)
|
registerAll(hbsInstance)
|
||||||
|
@ -362,6 +363,8 @@ module.exports.doesContainString = (template, string) => {
|
||||||
return exports.doesContainStrings(template, [string])
|
return exports.doesContainStrings(template, [string])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports.setJSRunner = javascript.setJSRunner
|
||||||
|
|
||||||
module.exports.convertToJS = hbs => {
|
module.exports.convertToJS = hbs => {
|
||||||
const blocks = exports.findHBSBlocks(hbs)
|
const blocks = exports.findHBSBlocks(hbs)
|
||||||
let js = "return `",
|
let js = "return `",
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import vm from "vm"
|
import vm from "vm"
|
||||||
import templates from "./index.js"
|
import templates from "./index.js"
|
||||||
import { setJSRunner } from "./helpers/javascript"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ES6 entrypoint for rollup
|
* ES6 entrypoint for rollup
|
||||||
|
@ -20,6 +19,7 @@ export const doesContainString = templates.doesContainString
|
||||||
export const disableEscaping = templates.disableEscaping
|
export const disableEscaping = templates.disableEscaping
|
||||||
export const findHBSBlocks = templates.findHBSBlocks
|
export const findHBSBlocks = templates.findHBSBlocks
|
||||||
export const convertToJS = templates.convertToJS
|
export const convertToJS = templates.convertToJS
|
||||||
|
export const setJSRunner = templates.setJSRunner
|
||||||
export const FIND_ANY_HBS_REGEX = templates.FIND_ANY_HBS_REGEX
|
export const FIND_ANY_HBS_REGEX = templates.FIND_ANY_HBS_REGEX
|
||||||
|
|
||||||
if (process && !process.env.NO_JS) {
|
if (process && !process.env.NO_JS) {
|
||||||
|
|
Loading…
Reference in New Issue