Fix for #7431 - reboot didn't work at all previously which is why apps couldn't be published with it enabled, this is now a self host only feature, I've removed the ability to enable a reboot cron in the Cloud and it will not run the lookup/execution.

This commit is contained in:
mike12345567 2022-09-07 17:05:17 +01:00
parent 5bec5dff55
commit dcada36111
8 changed files with 103 additions and 29 deletions

View File

@ -254,8 +254,17 @@ export async function getAllApps({ dev, all, idsOnly, efficient }: any = {}) {
return false return false
}) })
if (idsOnly) { if (idsOnly) {
const devAppIds = appDbNames.filter(appId => isDevAppID(appId))
const prodAppIds = appDbNames.filter(appId => !isDevAppID(appId))
switch (dev) {
case true:
return devAppIds
case false:
return prodAppIds
default:
return appDbNames return appDbNames
} }
}
const appPromises = appDbNames.map((app: any) => const appPromises = appDbNames.map((app: any) =>
// skip setup otherwise databases could be re-created // skip setup otherwise databases could be re-created
getAppMetadata(app) getAppMetadata(app)

View File

@ -1,6 +1,7 @@
<script> <script>
import { Button, Select, Input, Label } from "@budibase/bbui" import { Button, Select, Input, Label } from "@budibase/bbui"
import { createEventDispatcher } from "svelte" import { onMount, createEventDispatcher } from "svelte"
import { flags } from "stores/backend"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export let value export let value
@ -29,11 +30,16 @@
label: "Every Night at Midnight", label: "Every Night at Midnight",
value: "0 0 * * *", value: "0 0 * * *",
}, },
{ ]
onMount(() => {
if (!$flags.cloud) {
CRON_EXPRESSIONS.push({
label: "Every Budibase Reboot", label: "Every Budibase Reboot",
value: "@reboot", value: "@reboot",
}, })
] }
})
</script> </script>
<div class="block-field"> <div class="block-field">

View File

@ -125,7 +125,7 @@ async function deployApp(deployment: any) {
const prodAppDoc = await db.get(DocumentType.APP_METADATA) const prodAppDoc = await db.get(DocumentType.APP_METADATA)
appDoc._rev = prodAppDoc._rev appDoc._rev = prodAppDoc._rev
} catch (err) { } catch (err) {
// ignore the error - doesn't exist delete appDoc._rev
} }
// switch to production app ID // switch to production app ID

View File

@ -1,16 +1,19 @@
const { processEvent } = require("./utils") const { processEvent } = require("./utils")
const { queue, shutdown } = require("./bullboard") const { queue, shutdown } = require("./bullboard")
const { TRIGGER_DEFINITIONS } = require("./triggers") const { TRIGGER_DEFINITIONS, rebootTrigger } = require("./triggers")
const { ACTION_DEFINITIONS } = require("./actions") const { ACTION_DEFINITIONS } = require("./actions")
/** /**
* This module is built purely to kick off the worker farm and manage the inputs/outputs * This module is built purely to kick off the worker farm and manage the inputs/outputs
*/ */
exports.init = function () { exports.init = async function () {
// this promise will not complete // this promise will not complete
return queue.process(async job => { const promise = queue.process(async job => {
await processEvent(job) await processEvent(job)
}) })
// on init we need to trigger any reboot automations
await rebootTrigger()
return promise
} }
exports.getQueues = () => { exports.getQueues = () => {

View File

@ -9,6 +9,7 @@ const { checkTestFlag } = require("../utilities/redis")
const utils = require("./utils") const utils = require("./utils")
const env = require("../environment") const env = require("../environment")
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context") const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
const { getAllApps } = require("@budibase/backend-core/db")
const TRIGGER_DEFINITIONS = definitions const TRIGGER_DEFINITIONS = definitions
const JOB_OPTS = { const JOB_OPTS = {
@ -16,21 +17,24 @@ const JOB_OPTS = {
removeOnFail: true, removeOnFail: true,
} }
async function getAllAutomations() {
const db = getAppDB()
let automations = await db.allDocs(
getAutomationParams(null, { include_docs: true })
)
return automations.rows.map(row => row.doc)
}
async function queueRelevantRowAutomations(event, eventType) { async function queueRelevantRowAutomations(event, eventType) {
if (event.appId == null) { if (event.appId == null) {
throw `No appId specified for ${eventType} - check event emitters.` throw `No appId specified for ${eventType} - check event emitters.`
} }
doInAppContext(event.appId, async () => { doInAppContext(event.appId, async () => {
const db = getAppDB() let automations = await getAllAutomations()
let automations = await db.allDocs(
getAutomationParams(null, { include_docs: true })
)
// filter down to the correct event type // filter down to the correct event type
automations = automations.rows automations = automations.filter(automation => {
.map(automation => automation.doc)
.filter(automation => {
const trigger = automation.definition.trigger const trigger = automation.definition.trigger
return trigger && trigger.event === eventType return trigger && trigger.event === eventType
}) })
@ -110,4 +114,34 @@ exports.externalTrigger = async function (
} }
} }
exports.rebootTrigger = async () => {
// reboot cron option is only available on the main thread at
// startup and only usable in self host
if (env.isInThread() || !env.SELF_HOSTED) {
return
}
// iterate through all production apps, find the reboot crons
// and trigger events for them
const appIds = await getAllApps({ dev: false, idsOnly: true })
for (let prodAppId of appIds) {
await doInAppContext(prodAppId, async () => {
let automations = await getAllAutomations()
let rebootEvents = []
for (let automation of automations) {
if (utils.isRebootTrigger(automation)) {
const job = {
automation,
event: {
appId: prodAppId,
timestamp: Date.now(),
},
}
rebootEvents.push(queue.add(job, JOB_OPTS))
}
}
await Promise.all(rebootEvents)
})
}
}
exports.TRIGGER_DEFINITIONS = TRIGGER_DEFINITIONS exports.TRIGGER_DEFINITIONS = TRIGGER_DEFINITIONS

View File

@ -17,6 +17,7 @@ import { tenancy } from "@budibase/backend-core"
import { quotas } from "@budibase/pro" import { quotas } from "@budibase/pro"
import { Automation } from "@budibase/types" import { Automation } from "@budibase/types"
const REBOOT_CRON = "@reboot"
const WH_STEP_ID = definitions.WEBHOOK.stepId const WH_STEP_ID = definitions.WEBHOOK.stepId
const CRON_STEP_ID = definitions.CRON.stepId const CRON_STEP_ID = definitions.CRON.stepId
const Runner = new Thread(ThreadType.AUTOMATION) const Runner = new Thread(ThreadType.AUTOMATION)
@ -109,22 +110,33 @@ export async function clearMetadata() {
await db.bulkDocs(automationMetadata) await db.bulkDocs(automationMetadata)
} }
/** export function isCronTrigger(auto: Automation) {
* This function handles checking of any cron jobs that need to be enabled/updated.
* @param {string} appId The ID of the app in which we are checking for webhooks
* @param {object|undefined} automation The automation object to be updated.
*/
export async function enableCronTrigger(appId: any, automation: any) {
const trigger = automation ? automation.definition.trigger : null
function isCronTrigger(auto: any) {
return ( return (
auto && auto &&
auto.definition.trigger && auto.definition.trigger &&
auto.definition.trigger.stepId === CRON_STEP_ID auto.definition.trigger.stepId === CRON_STEP_ID
) )
} }
export function isRebootTrigger(auto: Automation) {
const trigger = auto ? auto.definition.trigger : null
return isCronTrigger(auto) && trigger?.inputs.cron === REBOOT_CRON
}
/**
* This function handles checking of any cron jobs that need to be enabled/updated.
* @param {string} appId The ID of the app in which we are checking for webhooks
* @param {object|undefined} automation The automation object to be updated.
*/
export async function enableCronTrigger(appId: any, automation: Automation) {
const trigger = automation ? automation.definition.trigger : null
// need to create cron job // need to create cron job
if (isCronTrigger(automation) && trigger?.inputs.cron) { if (
isCronTrigger(automation) &&
!isRebootTrigger(automation) &&
trigger?.inputs.cron
) {
// make a job id rather than letting Bull decide, makes it easier to handle on way out // make a job id rather than letting Bull decide, makes it easier to handle on way out
const jobId = `${appId}_cron_${newid()}` const jobId = `${appId}_cron_${newid()}`
const job: any = await queue.add( const job: any = await queue.add(

View File

@ -458,6 +458,9 @@ class Orchestrator {
export function execute(input: AutomationEvent, callback: WorkerCallback) { export function execute(input: AutomationEvent, callback: WorkerCallback) {
const appId = input.data.event.appId const appId = input.data.event.appId
if (!appId) {
throw new Error("Unable to execute, event doesn't contain app ID.")
}
doInAppContext(appId, async () => { doInAppContext(appId, async () => {
const automationOrchestrator = new Orchestrator( const automationOrchestrator = new Orchestrator(
input.data.automation, input.data.automation,
@ -475,6 +478,9 @@ export function execute(input: AutomationEvent, callback: WorkerCallback) {
export const removeStalled = async (input: AutomationEvent) => { export const removeStalled = async (input: AutomationEvent) => {
const appId = input.data.event.appId const appId = input.data.event.appId
if (!appId) {
throw new Error("Unable to execute, event doesn't contain app ID.")
}
await doInAppContext(appId, async () => { await doInAppContext(appId, async () => {
const automationOrchestrator = new Orchestrator( const automationOrchestrator = new Orchestrator(
input.data.automation, input.data.automation,

View File

@ -25,6 +25,10 @@ export interface AutomationStep {
export interface AutomationTrigger { export interface AutomationTrigger {
id: string id: string
stepId: string stepId: string
inputs: {
[key: string]: any
}
cronJobId?: string
} }
export enum AutomationStatus { export enum AutomationStatus {