Run Deck as the workflow owner, and drop the job interval
Build package / php-lint (8.2) (push) Successful in 49s
Build package / php-lint (8.3) (push) Successful in 42s
Build package / php-lint (8.4) (push) Successful in 37s
Build package / xml-lint (push) Successful in 13s
Build package / unit-tests (push) Successful in 44s
Build package / package (push) Successful in 59s
Build package / php-lint (8.2) (push) Successful in 49s
Build package / php-lint (8.3) (push) Successful in 42s
Build package / php-lint (8.4) (push) Successful in 37s
Build package / xml-lint (push) Successful in 13s
Build package / unit-tests (push) Successful in 44s
Build package / package (push) Successful in 59s
Workflows were being switched off with "its board no longer exists or is
no longer available to you" for boards that were perfectly intact.
Deck does not read the session for permissions. PermissionService -- the
class behind every check, including the ones inside CardService::reorder()
-- takes the current user as a plain `private ?string $userId`, filled
from the app container's `userId` service (ISession::get('user_id'),
registered shared). Pimple resolves that once per process and caches it,
and ServerContainer caches Deck's app container just as long. In cron the
value is null whenever a Deck-owned job ran earlier in the same pass, and
otherwise the first workflow owner touched -- never the user being
impersonated. null fails every check, so Deck answered NoPermissionException
for an untouched board and findStackIds() read that as "the board is gone".
IUserSession::setUser() never had any effect on this path.
- DeckIntegrationService::beginUserContext()/endUserContext() pin
PermissionService (mandatory), CardService, BoardService and
ActivityManager (best effort) to the workflow owner, and restore them
afterwards. If PermissionService cannot be pinned, the runner skips that
user instead of acting under someone else's permissions.
- findStackIds() now takes the uid as an argument and is assembled from
pieces that cannot answer for the wrong user: BoardMapper and StackMapper
carry no user state, and getPermissions() is handed the uid explicitly.
It no longer goes through StackService::findAll().
- NoPermissionException is no longer treated as "board missing". Unknown
failures throw, which leaves the workflow enabled.
This also fixes the second half of the same defect: card moves silently
failed for every user except the first one processed in a cron pass.
Separately, RunWorkflowsJob is now a plain Job instead of a TimedJob and
runs on every cron pass. The workflow_deck_automation.interval config key
is gone. Time sensitivity is no longer merely declared but structurally
unreachable: JobList::add() leaves the column at its TIME_SENSITIVE
default, and the ratchet in setLastRun() only fires for TimedJob.
This commit is contained in:
@@ -6,51 +6,47 @@ namespace OCA\WorkflowDeckAutomation\BackgroundJob;
|
||||
|
||||
use OCA\WorkflowDeckAutomation\Service\WorkflowRunner;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\IJob;
|
||||
use OCP\BackgroundJob\TimedJob;
|
||||
use OCP\IConfig;
|
||||
|
||||
class RunWorkflowsJob extends TimedJob {
|
||||
/**
|
||||
* config.php key holding how often (in seconds) workflows are checked.
|
||||
*/
|
||||
public const INTERVAL_CONFIG_KEY = 'workflow_deck_automation.interval';
|
||||
|
||||
public const DEFAULT_INTERVAL = 5 * 60;
|
||||
|
||||
/**
|
||||
* Nextcloud's cron itself only ticks every 5 minutes by default, so
|
||||
* anything below a minute would only add load without running more
|
||||
* often. Values under this are clamped rather than rejected.
|
||||
*/
|
||||
public const MINIMUM_INTERVAL = 60;
|
||||
use OCP\BackgroundJob\Job;
|
||||
|
||||
/**
|
||||
* Evaluates all enabled workflows, once per cron pass.
|
||||
*
|
||||
* Deliberately a plain `Job`, not a `TimedJob`: there is no interval to
|
||||
* configure and none to compare against, so the cadence is exactly
|
||||
* Nextcloud's cron cadence — every pass, no matter how often cron is set
|
||||
* up to run. `TimedJob` with `setInterval(0)` would behave almost the
|
||||
* same, but it keeps an interval concept around that nothing uses.
|
||||
*
|
||||
* **Time sensitivity still matters, and it comes for free here.**
|
||||
* `OC\Core\Service\CronService::runCli()` reads `maintenance_window_start`
|
||||
* (default `100`, i.e. unset) and, whenever the current UTC hour is outside
|
||||
* `[start, start+4]`, asks `JobList::getNext($onlyTimeSensitive = true)`,
|
||||
* which filters on the `time_sensitive` *column* of `oc_jobs`. A job stamped
|
||||
* insensitive is then skipped for the other 20 hours of the day — this app
|
||||
* shipped that way once and the job looked completely dead, showing
|
||||
* `last_run = 1970-01-01` in `occ background-job:list` indefinitely. Note
|
||||
* the gate only exists in `runCli()`, so ajax/webcron are unaffected and
|
||||
* `occ background-job:worker '<class>'` bypasses it, which is why manual
|
||||
* testing can look fine while cron does nothing.
|
||||
*
|
||||
* A plain `Job` can never acquire that stamp: `JobList::add()` leaves the
|
||||
* column at its `TIME_SENSITIVE` default, and the one place that ever
|
||||
* downgrades it, `JobList::setLastRun()`, is guarded by
|
||||
* `$job instanceof TimedJob && !$job->isTimeSensitive()` — with no `else`.
|
||||
* Rows written by an older version of this app keep whatever they were
|
||||
* stamped with, though, so an instance that ran the `TIME_INSENSITIVE`
|
||||
* version still needs the one-off `UPDATE oc_jobs SET time_sensitive = 0`
|
||||
* documented in the README.
|
||||
*/
|
||||
class RunWorkflowsJob extends Job {
|
||||
public function __construct(
|
||||
ITimeFactory $time,
|
||||
IConfig $config,
|
||||
private WorkflowRunner $runner,
|
||||
) {
|
||||
parent::__construct($time);
|
||||
// TimedJob compares against this value on every cron pass, so a
|
||||
// changed config.php takes effect on the next pass — no occ command
|
||||
// and no re-registration of the job needed.
|
||||
$this->setInterval(self::resolveInterval($config));
|
||||
// Must stay TIME_SENSITIVE. If `maintenance_window_start` is set in
|
||||
// config.php — and Nextcloud's admin overview actively nags admins
|
||||
// to set it — CronService only picks up TIME_INSENSITIVE jobs during
|
||||
// those 4 hours and skips them for the rest of the day. A job whose
|
||||
// whole point is a 5-minute (configurable down to 60s) reaction time
|
||||
// would then run a handful of times per night and look completely
|
||||
// dead in between.
|
||||
$this->setTimeSensitivity(IJob::TIME_SENSITIVE);
|
||||
$this->setAllowParallelRuns(false);
|
||||
}
|
||||
|
||||
private static function resolveInterval(IConfig $config): int {
|
||||
$interval = $config->getSystemValueInt(self::INTERVAL_CONFIG_KEY, self::DEFAULT_INTERVAL);
|
||||
return max(self::MINIMUM_INTERVAL, $interval);
|
||||
}
|
||||
|
||||
protected function run($argument): void {
|
||||
$this->runner->run();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user