Skip to content

feat(perl): add script commands trigger - #446

Merged
jymaire merged 7 commits into
kestra-io:mainfrom
Abhishek84313:feat/perl-script-commands-trigger
Sep 23, 2026
Merged

jymaire merged 7 commits into
kestra-io:mainfrom
Abhishek84313:feat/perl-script-commands-trigger

Conversation

@Abhishek84313

Copy link
Copy Markdown
Contributor

What changes are being made and why?

Adds ScriptTrigger and CommandsTrigger to plugin-script-perl, so a flow can be triggered when a Perl script or set of Perl commands completes, with an exit condition deciding whether the trigger fires.

plugin-script-perl was the last of the scripting submodules missing both. This follows the existing implementations in plugin-script-go, plugin-script-node, plugin-script-python, plugin-script-ruby, plugin-script-shell and plugin-script-r — same package as the task classes, no nested subpackage, and the same property set (containerImage, script/commands, exitCondition, interval, edge).

Both triggers implement PollingTriggerInterface and emit an output carrying the timestamp, the rendered condition, the exit code and any structured vars; execution id, namespace and flow id come from TriggerService.generateExecution. exitCondition accepts either exit N (compared against the exit code) or a regex, with a substring fallback when the regex is invalid, matched against emitted vars and failure logs. edge mode (default true) emits only on a transition from not-matching to matching, so a persistently failing script does not fire on every poll.

Part of #313. Closes #429.

One deviation from the issue worth flagging for review: #429 proposes commandPattern / argumentsPattern / exitCondition, but every existing submodule uses the containerImage / script-or-commands / exitCondition / interval / edge shape, where the trigger runs the script rather than matching a pattern against externally observed executions. I followed the established convention for consistency. If the schema in the issue is the intended direction, it is a change across all submodules and is probably better settled separately.


How the changes have been QAed?

ScriptTrigger — fires when the script exits non-zero:

id: perl_script_trigger
namespace: company.team

triggers:
  - id: on_perl_fail
    type: io.kestra.plugin.scripts.perl.ScriptTrigger
    interval: PT10S
    exitCondition: "exit 1"
    edge: true
    containerImage: perl
    script: |
      print "about to fail\n";
      exit 1;

tasks:
  - id: log
    type: io.kestra.plugin.core.log.Log
    message: "Triggered with exitCode={{ trigger.exitCode }} (condition={{ trigger.condition }})"
id: perl_commands_trigger
namespace: company.team

triggers:
  - id: on_perl_commands
    type: io.kestra.plugin.scripts.perl.CommandsTrigger
    interval: PT10S
    exitCondition: "toto"
    edge: true
    containerImage: perl
    commands:
      - echo '::{"outputs":{"listing":"toto"}}::'

tasks:
  - id: log
    type: io.kestra.plugin.core.log.Log
    message: "Triggered with vars={{ trigger.vars }}"

@github-project-automation github-project-automation Bot moved this to To review in Pull Requests Sep 20, 2026
@MilosPaunovic MilosPaunovic added kind/external Pull requests raised by community contributors area/plugin Plugin-related issue or feature request labels Sep 21, 2026
@MilosPaunovic
MilosPaunovic requested review from a team and jymaire September 21, 2026 06:05
@jymaire

jymaire commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

thanks for the PR, it seems you also did #432 in the same time? could you remove the commit and open its own PR? (and comment on issue so I can assign you)

The R ScriptTrigger/CommandsTrigger in this branch are byte-for-byte
identical to PR kestra-io#438, which is dedicated to the R module. Remove them
here so kestra-io#446 is scoped to the Perl triggers and kestra-io#438 owns the R work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jymaire

jymaire commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Hi @Abhishek84313 I dropped your modification on R script, as they already are on another PR

@jymaire jymaire changed the title Feat/perl script commands trigger feat(perl): add script commands trigger Sep 22, 2026

@jymaire jymaire left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two inline findings from review: a blocking ReDoS gap in matchesCondition and a note on non-persisted edge state.

Comment thread plugin-script-perl/src/main/java/io/kestra/plugin/scripts/perl/ScriptTrigger.java Outdated
Comment thread plugin-script-perl/src/main/java/io/kestra/plugin/scripts/perl/ScriptTrigger.java Outdated
jymaire and others added 2 commits September 23, 2026 16:02
Run the user-supplied exitCondition regex with a 5s timeout and fall
back to substring matching, as the Ruby/Shell/Node/Bun triggers do, so a
catastrophic-backtracking pattern can no longer hang the scheduler poll
thread. Document the in-memory edge-state limitation and add condition
tests for pathological and invalid regexes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ample

(a+)+$ is memoized by the JDK 25 regex engine and fails instantly, so the
test never reached the 5s guard; use (.*a){20}$ and assert the elapsed
time. The CommandsTrigger example ran `perl missing.pl`, which exits 2 and
never matched `exit 1`; use `perl -e 'exit 1'` instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The in-memory lastMatched flag was rebuilt with the trigger on every poll,
so edge mode fired on every matching poll. Keep the previous result in the
namespace KV store instead, as the Bun/.NET/PowerShell triggers do.

Replaces the tautological AtomicBoolean edge tests with EdgeStateTest and
an evaluate-level test that polls through a serialized copy.

Refs kestra-io#449

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jymaire

jymaire commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

QA report — Perl ScriptTrigger / CommandsTrigger

Edition: OSS | Docker tag: v1.3.39 | PR head: 0ba7de7
Instance: http://gmknr.kestra.docker.localhost:1355/ (local, left running for manual review — docker rm -f kestra-gmknr to stop)
Screenshots: https://claude.ai/artifact/ANisDPSWMPGSZX9krfS9do

Summary

# Flow Covers Result
1 script_trigger ScriptTrigger @Example, exit 1, edge mode ✅ 1 execution, no re-fire after restart
2 commands_trigger CommandsTrigger @Example (as fixed in f613113), edge mode ✅ 1 execution, no re-fire after restart
3 commands_trigger_vars CommandsTrigger substring match on emitted vars ✅ 1 execution, vars={"listing":"toto"}
4 script_trigger_redos Catastrophic regex (.*a){20}$ ✅ never fires, scheduler keeps polling ⚠️ see note
5 perl_script_nonreg Non-regression: Perl Script task ✅ SUCCESS, answer=42

Edge mode, before vs after 0ba7de7 (interval: PT10S, condition always true):

  • f613113 (in-memory lastMatched): flows 1 and 3 fired on every poll — 22 executions in ~4.5 min each. The original @Example for flow 2 (perl missing.pl) never fired; fixed in f613113.
  • 0ba7de7 (namespace KV store): with the edge keys cleared and flows 1–3 enabled, each fired once in ~90 s. After docker restart and a further ~90 s of polling, no new executions. The KV keys trigger-edge-<len>-<flow>-<trigger> kept updating on every poll.

Flow 1: script_trigger (✅)

Flow YAML
id: script_trigger
namespace: company.team

triggers:
  - id: script_failure
    type: io.kestra.plugin.scripts.perl.ScriptTrigger
    interval: PT10S
    exitCondition: "exit 1"
    edge: true
    containerImage: perl
    script: |
      # This fails with a non-zero exit code.
      exit 1;

tasks:
  - id: log
    type: io.kestra.plugin.core.log.Log
    message: "Triggered with exitCode={{ trigger.exitCode }} (condition={{ trigger.condition }})"

Executions (screenshot): 1 execution (SUCCESS, 0.49 s) after enabling on 0ba7de7, none after restart. Earlier rows are the f613113 build firing every ~10 s.

Logs synthesis: Triggered with exitCode=1 (condition=exit 1).

Trigger outputs: exitCode: 1, condition: exit 1, timestamp, no vars (failed run).

Flow 2: commands_trigger (✅)

Flow YAML
id: commands_trigger
namespace: company.team

triggers:
  - id: commands_failure
    type: io.kestra.plugin.scripts.perl.CommandsTrigger
    interval: PT10S
    exitCondition: "exit 1"
    edge: true
    containerImage: perl
    commands:
      - perl -e 'exit 1'

tasks:
  - id: log
    type: io.kestra.plugin.core.log.Log
    message: "Triggered with exitCode={{ trigger.exitCode }} (condition={{ trigger.condition }})"

Executions (screenshot): 1 execution (SUCCESS, 0.53 s) on 0ba7de7, none after restart.

Trigger outputs: exitCode: 1, condition: exit 1.

Flow 3: commands_trigger_vars (✅)

Flow YAML
id: commands_trigger_vars
namespace: company.team

triggers:
  - id: on_perl_commands
    type: io.kestra.plugin.scripts.perl.CommandsTrigger
    interval: PT10S
    exitCondition: "toto"
    edge: true
    containerImage: perl
    commands:
      - echo '::{"outputs":{"listing":"toto"}}::'

tasks:
  - id: log
    type: io.kestra.plugin.core.log.Log
    message: "Triggered with vars={{ trigger.vars }}"

Executions (screenshot): 1 execution on 0ba7de7, none after restart.

Logs synthesis (screenshot): Triggered with vars={"listing":"toto"}.

Trigger outputs: exitCode: 0, condition: toto, vars: {listing: toto}.

Edge state (screenshot): one KV key per trigger in company.team, updated after the restart.

Flow 4: script_trigger_redos (✅ ⚠️)

Flow YAML
id: script_trigger_redos
namespace: company.team

triggers:
  - id: redos
    type: io.kestra.plugin.scripts.perl.ScriptTrigger
    interval: PT10S
    exitCondition: "(.*a){20}$"
    edge: true
    containerImage: perl
    script: |
      print '::{"outputs":{"k":"' . ('a' x 40) . '!"}}::' . "\n";

tasks:
  - id: log
    type: io.kestra.plugin.core.log.Log
    message: "SHOULD NOT FIRE vars={{ trigger.vars }}"

Executions (screenshot): none — the 5 s timeout fires and the substring fallback does not match, so the scheduler is not blocked.

⚠️ CompletableFuture.get(5s) does not stop the matcher: each poll leaves a runaway ForkJoinPool.commonPool thread (container CPU climbed to ~650 % within minutes). Same behaviour as the Ruby guard on main; tracked family-wide in #450. Tested on f613113 — the regex guard is unchanged in 0ba7de7.

Flow 5: perl_script_nonreg (✅)

Flow YAML
id: perl_script_nonreg
namespace: company.team

tasks:
  - id: perl
    type: io.kestra.plugin.scripts.perl.Script
    containerImage: perl
    script: |
      print "hello from perl $]\n";
      print '::{"outputs":{"answer":42}}::' . "\n";

Gantt (screenshot)

Task Status Duration
perl SUCCESS 0.58s
Total SUCCESS 0.58s

Logs synthesis: hello from perl 5.044000, Command succeed with exit code 0, no WARN/ERROR.

Outputs synthesis (screenshot): vars.answer: 42, exitCode: 0.

Timeouts

None.

Tests

./gradlew :plugin-script-perl:test --rerun-tasks on 0ba7de7: 53 passed, 0 failed.

@jymaire jymaire left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I edited PR to apply fixes so the PR doesn't stay stale. Thanks for your contribution!

@jymaire
jymaire merged commit 711e88c into kestra-io:main Sep 23, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from To review to Done in Pull Requests Sep 23, 2026
@Abhishek84313

Copy link
Copy Markdown
Contributor Author

@jymaire Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/plugin Plugin-related issue or feature request kind/external Pull requests raised by community contributors

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Introduce ScriptTrigger & CommandsTrigger for the Perl plugin

3 participants