Skip to content

feat: add file operation actions - #2151

Merged
yohamta0 merged 4 commits into
mainfrom
feat/file-actions
May 15, 2026
Merged

yohamta0 merged 4 commits into
mainfrom
feat/file-actions

Conversation

@yohamta0

@yohamta0 yohamta0 commented May 14, 2026 •

Copy link
Copy Markdown
Member

Summary

Add built-in local file operation actions for workflows, including stat, read, write, copy, move, delete, mkdir, and list.

Changes

  • Add the file executor and register file action normalization for file.* actions.
  • Wire file action configuration into DAG schema validation, including legacy type: file configs.
  • Harden file operations against typoed config keys, self-targeted copy/move operations, nested directory copy targets, and unsafe move overwrite behavior.
  • Add unit, schema, parser, and integration coverage for file operations and conservative filesystem behavior.

Related Issues

N/A

Validation

  • git diff --check
  • go test ./internal/core/spec ./internal/cmn/schema ./internal/runtime/builtin/file -count=1
  • go test ./internal/runtime/builtin/... -count=1
  • go test ./internal/intg -run TestFileActions -count=1

Checklist

  • Tests added or updated for the new file action behavior.
  • Local validation completed.
  • Scope is limited to file action product code and its tests.

@coderabbitai

coderabbitai Bot commented May 14, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c7cdd361-3ff7-440e-9bd5-f613528822ae

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a complete "file" executor for the Dagu DAG system, enabling built-in filesystem operations (stat, read, write, copy, move, delete, mkdir, list) within DAG workflows. It defines the JSON schema contract, registers the executor type in the spec layer, implements the runtime operations with configuration validation, and provides unit and integration tests validating the complete flow.

Changes

File Executor Feature

Layer / File(s) Summary
DAG schema contract for file actions
internal/cmn/schema/dag.schema.json, internal/cmn/schema/dag_schema_test.go
Schema now recognizes file.* built-in actions and requires a with object matching fileActionConfig, which defines path, content, mode, format, pattern, and operational flags (overwrite, recursive, atomic, create_dirs, missing_ok, dry_run, include_dirs, follow_symlinks, max_bytes). Schema tests validate file action configurations and reject invalid noop+config combinations.
Builtin type and action registration
internal/core/spec/step_types.go, internal/core/spec/step_v2.go, internal/core/spec/step_test.go, internal/core/spec/step_v2_test.go
Step spec declares "file" as a builtin executor type; builtinActionNormalizers maps file.stat, file.read, file.write, file.copy, file.move, file.delete, file.mkdir, file.list to the file executor with operation-specific commands. Unit tests verify each action normalizes to the file executor with correct command and non-empty config.
File executor configuration and validation
internal/runtime/builtin/file/config.go
Config struct with path, content, mode, format, pattern fields and operational flags; decodeConfig coerces maps to the struct via mapstructure; validateConfig enforces operation-specific required fields, validates format (raw/json), pattern glob syntax, mode strings, and non-negative max_bytes; schema is registered for validation tooling.
File executor implementation
internal/runtime/builtin/file/file.go
Executor implementation with registration for type "file" and command support; Run dispatcher routes to operation handlers (stat, read, write, mkdir, copy, move, delete, list); each operation resolves paths (with ~ expansion, working-directory joining), performs filesystem action, and writes JSON result to stdout. Copy supports recursive directory traversal and symlink recreation; move uses os.Rename with cross-device fallback via copy-then-remove; delete rejects root paths and requires recursive flag for directories; list filters by pattern and include_dirs with optional recursion.
File executor unit tests
internal/runtime/builtin/file/file_test.go
End-to-end tests exercising write/read/stat, copy/move/list/delete, conservative defaults (rejecting overwrite/recursive-directory-delete), mkdir parents, and missing_ok; helper utilities construct executor from DAG/step, capture stdout, and assert JSON fields and filesystem state.
Runtime wiring and integration test
internal/runtime/builtin/builtin.go, internal/intg/file_test.go
Blank import registers file executor in builtin module; integration test orchestrates a DAG with mkdir, write, copy, list, read, delete steps and validates outputs (copy.txt exists, content is "alpha") and side effects (input.txt removed).

Sequence Diagram(s)

sequenceDiagram
    participant DAG as DAG Loader
    participant Normalizer as StepV2 Normalizer
    participant Validator as Schema Validator
    participant Executor as FileExecutor
    participant FS as Filesystem
    DAG->>Validator: Load step with action: file.write
    Validator->>Validator: Check action in file.* set
    Validator->>Validator: Validate with object against fileActionConfig
    Validator-->>DAG: Valid step
    DAG->>Normalizer: Normalize step
    Normalizer->>Normalizer: Match file.write in builtinActionNormalizers
    Normalizer-->>DAG: Resolved to executorType: file, command: write
    DAG->>Executor: Run(ctx, step)
    Executor->>Executor: Decode and validate configuration
    Executor->>Executor: Extract operation (write) from command
    Executor->>FS: Create parent dirs and write file
    FS-->>Executor: File created
    Executor->>Executor: Marshal operation result to JSON
    Executor-->>DAG: JSON output on stdout, exit code 0
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • dagucloud/dagu#2122: Introduced v2 action schema and step normalization infrastructure that the file executor directly extends with file.* actions and builtinActionNormalizers.
  • dagucloud/dagu#2021: Modified DAG step schema with field validation that provides the foundational contract this PR's file.* actions depend on.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add file operation actions' is concise, specific, and directly summarizes the main change—adding built-in file operation actions to the system.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description includes all required sections: Summary, Changes (bulleted list), Related Issues, and a Checklist with completed items.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/file-actions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cmn/schema/dag.schema.json`:
- Around line 5583-5584: The schema now allows builtinExecutorType value "file"
but never wires that type to the fileActionConfig schema, so steps/executors
with type:"file" are unconstrained; update the schema so "file" is handled the
same as action: "file.*" by adding an explicit branch in definitions.step.allOf
and definitions.executorObject.allOf that maps type == "file" to the existing
fileActionConfig (or references the same fileActionConfig definition), ensuring
executor and step validation uses fileActionConfig instead of permitting
arbitrary config.

In `@internal/runtime/builtin/file/config.go`:
- Around line 56-61: The decoder currently allows unknown keys because
mapstructure.DecoderConfig has ErrorUnused: false; in decodeConfig enable strict
decoding by setting ErrorUnused: true on the mapstructure.DecoderConfig (i.e.,
in the mapstructure.NewDecoder call) so unknown fields produce an error, and
also update the executor JSON schema for the same config (the schema block that
defines the config object) to include "additionalProperties": false so the
schema rejects unknown keys; reference mapstructure.NewDecoder/DecoderConfig,
ErrorUnused, decodeConfig and the JSON schema object/AdditionalProperties to
locate the two changes.

In `@internal/runtime/builtin/file/file.go`:
- Around line 359-370: The current overwrite flow removes the destination before
the move completes, causing possible data loss; change the logic in the
file-move routine so you first attempt to move (call os.Rename(source,
destination)) and if that returns syscall.EXDEV fall back to
e.moveAcrossDevices(source, destination), and only after the move succeeds
perform cleanup of any previous destination when e.cfg.Overwrite requires it (or
ensure the move implementation itself replaces atomically); update error
handling to return failures from os.Rename or moveAcrossDevices without having
already removed destination so the original destination remains intact on error.
- Around line 303-315: After resolving source and destination in runCopy (after
calls to e.resolvePath) and runMove, add an explicit equality guard that
compares the resolved source and destination strings and returns a clear
non-destructive error if they are identical; this prevents copyRegularFile from
opening the destination with os.O_TRUNC and runMove from calling
os.RemoveAll(destination) before os.Rename when paths are the same. Locate the
checks in runCopy and runMove (use the resolved variables named source and
destination) and return an appropriate error value (not performing any
filesystem operations) when source == destination.
- Around line 515-530: The code must reject cases where the destination is
inside the source tree before creating the destination to avoid WalkDir
recursing into the new dir; in the copy path (inside the block handling
info.IsDir) and in moveAcrossDevices/copyPath fallback, resolve and clean both
source and destination (apply filepath.EvalSymlinks when FollowSymlinks is true,
then filepath.Abs and filepath.Clean), compute rel :=
filepath.Rel(sourceResolved, destResolved) and if rel == "." or (rel != "" &&
!strings.HasPrefix(rel, ".."+string(filepath.Separator)) && rel != "..") return
an error like "destination is under source: copy not allowed"; perform this
check before calling ensureDir, before calling e.copyDir, and in the
moveAcrossDevices path so overlapping trees are rejected upfront.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 026a218d-d0ef-4b30-8f28-30c3cd1db026

📥 Commits

Reviewing files that changed from the base of the PR and between 4638232 and 25f366d.

📒 Files selected for processing (11)
  • internal/cmn/schema/dag.schema.json
  • internal/cmn/schema/dag_schema_test.go
  • internal/core/spec/step_test.go
  • internal/core/spec/step_types.go
  • internal/core/spec/step_v2.go
  • internal/core/spec/step_v2_test.go
  • internal/intg/file_test.go
  • internal/runtime/builtin/builtin.go
  • internal/runtime/builtin/file/config.go
  • internal/runtime/builtin/file/file.go
  • internal/runtime/builtin/file/file_test.go

Comment thread internal/cmn/schema/dag.schema.json
Comment thread internal/runtime/builtin/file/config.go
Comment thread internal/runtime/builtin/file/file.go
Comment thread internal/runtime/builtin/file/file.go Outdated
Comment thread internal/runtime/builtin/file/file.go
@yohamta0
yohamta0 merged commit 474c6df into main May 15, 2026
10 checks passed
@yohamta0
yohamta0 deleted the feat/file-actions branch May 15, 2026 09:03
@coderabbitai coderabbitai Bot mentioned this pull request May 16, 2026
4 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant