Skip to content

feat: job bundle browser/sharing - #1181

Draft
epmog wants to merge 89 commits into
aws-deadline:mainlinefrom
epmog:bundle-sharing
Draft

feat: job bundle browser/sharing#1181
epmog wants to merge 89 commits into
aws-deadline:mainlinefrom
epmog:bundle-sharing

Conversation

@epmog

@epmog epmog commented May 29, 2026

Copy link
Copy Markdown
Contributor

📄 Start here: Review the design document included in this PR
for the full design rationale and architecture.

What was the problem/requirement? (What/Why)

When using deadline bundle gui-submit --browse, the "Load a different job bundle" button, or "Submit
Job Bundle" menu item in Deadline Cloud monitor, users are presented with a native OS folder picker.
This is inadequate because:

  • Job bundles are not distinguishable from regular folders by name alone
  • Users must already know where their bundles live and navigate there manually
  • There is no preview of what a bundle contains — users pick blindly
  • There is no way to share bundles across a team without out-of-band file sharing
  • The picker always starts at the job history directory or home, with no way to configure a default
    location

What was the solution? (How)

Replace the native folder picker with a custom job bundle browser dialog.

image

It provides:

  1. A navigable tree view showing only folders and job bundles (📦/📁 icons), with a text filter for
    quick search
  2. A preview panel showing bundle name, description, steps, and parameters when a bundle is selected
  3. Three browsing sources: Local filesystem, S3 (shared team bundles), and Job History (previously
    submitted bundles)
  4. S3 bundle sharing via .ojd archives (zip format) stored in the queue's job attachment bucket
    under a job-bundles/ prefix — no extra configuration needed
  5. A "Save bundle as" button in the submitter dialog to upload the current bundle to S3 for team
    access, or export locally
  6. ETag-based caching of S3 archives for fast repeated access
  7. A configurable default browse directory setting
  8. Bundle visibility — hide/unhide bundles on S3 via a sidecar manifest with optimistic concurrency
  9. Background loading — dialog appears instantly (~0.26s), S3 data loads asynchronously with
    parallelized API calls
  10. Keyboard navigation — source radios, filter, tree, and buttons are all tab-navigable

Key design decisions:

  • S3 only supports .ojd archives (not loose folder bundles) — this gives a single canonical sharing
    format with atomic uploads and simple cache validation
  • The bundle name in the browser preview is shown unresolved (with {{Param.X}} intact) so users see
    the template identity rather than a specific parameterization
  • S3 bucket is derived from the queue's existing job attachment settings — zero additional configuration
  • from_config parallelizes the GetQueue API call with S3 credential resolution for faster cold starts
  • Pre-warms boto3 session and Deadline client before starting background thread to avoid GIL contention

Additionally there are new CLI commands:

Command Description
deadline bundle list [path] List bundles in a local directory (default: configured dir or home)
deadline bundle list --queue List .ojd bundles from the queue's S3 job-bundles folder
deadline bundle upload <dir> Package a local bundle as .ojd and upload to S3
deadline bundle download <name> Download an .ojd bundle from S3 (uses ETag cache)
deadline bundle info <name> Show bundle details (same info as the preview panel)
deadline bundle hide <name> Hide a bundle from the browser and list (remains in S3)
deadline bundle unhide <name> Unhide a previously hidden bundle
deadline bundle cache clean Remove cached S3 bundles from local disk
deadline bundle cache update Re-download stale cached bundles by checking ETags

Examples:

  $ deadline bundle list --queue
  blender-render
  maya-arnold

  $ deadline bundle upload ./my-render-job --name custom-name
  Archiving  [####################################]  100%
  Uploading  [####################################]  100%
  Uploaded bundle to s3://my-farm-bucket/DeadlineCloud/job-bundles/custom-name.ojd

  $ deadline bundle download blender-render -o /tmp/bundles
  Downloading  [####################################]  100%
  Extracting   [####################################]  100%
  Downloaded bundle to: /tmp/bundles/blender-render

  $ deadline bundle download blender-render
  Downloaded bundle to: /Users/me/.deadline/cache/job-bundles/abc123/blender-render

  $ deadline bundle info blender-render --queue
  Path: s3://my-farm-bucket/DeadlineCloud/job-bundles/blender-render.ojd
  Name: Blender Render
  Description: Renders a Blender scene file using Cycles
  Steps:
    • RenderBlender
  Parameters:
    Frames (STRING) = 1-100
    OutputDir (PATH) = /tmp/output

  $ deadline bundle list --queue --output json | jq -r '.[0].path'
  s3://my-farm-bucket/DeadlineCloud/job-bundles/blender-render.ojd

New configuration:

Setting Description
settings.job_bundle_default_directory Default local directory when browsing for job bundles (also
in settings dialog)

Also exposed in the Deadline Cloud settings dialog (Settings → General settings) as a "Job bundle
directory" picker.

What is the impact of this change?

  • Users get a richer bundle selection experience with preview and filtering
  • Teams can share job bundles via S3 without manual file distribution
  • The existing --browse flag and "Load a different job bundle" button now open the new dialog instead
    of the native picker
  • New .ojd archive format introduced as the standard for shared bundles
  • Progress bars shown for archive/upload/download/extract operations (chunked for large files)
  • MCP tools (list_shared_bundles, upload_bundle, download_bundle) enable AI assistants to manage
    shared bundles

How was this change tested?

  • Have you run the unit tests?
  • Have you run the integration tests?

Was this change documented?

  • Are relevant docstrings in the code base updated?
  • Has the README.md been updated?

Design document at docs/design/job-bundle-browser.md covers the full design. CLI commands include help
text. Code has docstrings on all public classes and methods. README updated with MCP examples for bundle
operations.

Does this PR introduce new dependencies?

  • This PR does not add any new dependencies.

Is this a breaking change?

No

Does this change impact security?

The S3 archive cache at ~/.deadline/cache/job-bundles/ stores extracted bundle contents. Archives are
validated before extraction to prevent path traversal attacks (absolute paths and ../ entries are
rejected, using os.path.commonpath with os.path.realpath for cross-platform safety). Symlinks are
skipped during archiving (os.walk(followlinks=False)). The cache directory follows the same permission
model as the existing ~/.deadline/ directory.

S3 access uses the same boto3 session/profile as the rest of deadline-cloud — no new credential flows.
Bundle visibility updates use S3 conditional writes (If-Match) for safe concurrent access.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this
contribution, under the terms of your choice.

@github-actions github-actions Bot added the waiting-on-maintainers Waiting on the maintainers to review. label May 29, 2026
Comment thread src/deadline/client/job_bundle/repository.py Dismissed
Comment thread src/deadline/client/job_bundle/repository.py Dismissed
Comment thread src/deadline/client/job_bundle/repository.py Dismissed
Comment thread src/deadline/client/job_bundle/repository.py Fixed
Comment thread src/deadline/client/job_bundle/repository.py Fixed
@epmog
epmog force-pushed the bundle-sharing branch 4 times, most recently from 562da40 to d9eb4aa Compare May 29, 2026 21:22
Comment thread test/unit/deadline_client/ui/widgets/test_job_bundle_settings_tab.py Dismissed
@epmog
epmog force-pushed the bundle-sharing branch 4 times, most recently from c4f510a to 0e6eb04 Compare May 29, 2026 22:22
Job bundles can be either:

- **Directories** — a folder containing `template.yaml` or `template.json` at the root, plus any scripts, data files, and `asset_references.yaml`.
- **Archives** — an `.ojd` file (zip format under the hood) containing a job bundle. The template can be at the archive root or inside a single wrapper directory.

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.

This makes sense to me. I think it should be formalized in the OJD spec as part of this project.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yea, we've put off formalizing job bundles for a while now. Something we can get started

Comment thread docs/design/job-bundle-browser.md Outdated
Comment thread docs/design/job-bundle-browser.md Outdated
Comment thread docs/design/job-bundle-browser.md Outdated
Comment thread docs/design/job-bundle-browser.md Outdated
Comment thread docs/design/job-bundle-browser.md Outdated
@crowecawcaw

Copy link
Copy Markdown
Contributor

Thought from offline discussion: would be great to have a way to rename, update, or delete bundles from UI, especially for the queue view.

@andychoquette

Copy link
Copy Markdown
Contributor

a few UX thoughts:

  1. Consider allowing users to toggle visibility of hidden folders in the folder explorer. Showing them by default adds a lot of noise, and I can't imagine many users will be storing job bundles in hidden folders.
Screenshot 2026-06-08 at 12 36 03 PM
  1. Maybe we can render the bundle parameters as a table? Its challenging to visually parse data from a bulleted list where kv pairs are delimited by =s.
Screenshot 2026-06-08 at 12 37 12 PM
  1. Since the source selection affects the folder picker component, we should flip these options. Ideally, users can achieve their goal in a form linearly, top-to-bottom, and I find myself jumping past the folder picker to select a source, just to return to the folder picker. Subjective, but worth considering.
Screenshot 2026-06-08 at 3 33 57 PM
  1. If the S3 (queue) option is disabled, due to missing permission for example, we should tell the user why in an inline alert.
Screenshot 2026-06-08 at 3 33 39 PM
  1. This is existing behavior I think, but after exporting a bundle, the window is closed automatically. This is unexpected and not communicated.

  2. I find myself wanting to browse around in the bundle view, but its only accessible via an action button. Consider giving it a dedicated tab so it can be navigated to and from with ease. This'll also allow us to eliminate the Load bundle button, too. Here's an idea:

Screenshot 2026-06-08 at 1 31 12 PM
  1. The saving / sharing experience is a bit confusing, and the confusion is exacerbated by this new functionality. For example, when I export my job bundle, it isn't configurable during the interaction and closes the submitter when done.

An option: drop the Export and Share buttons in favor of a "Save bundle" button that opens a dedicated save dialog where we can communicate where it will be saved and what the implications are (e.g. sharing to queue shares to all queue users). Example:

2026-06-08 15 11 29

We can also include an info alert that describes the implications of sharing when sharing is selected:

Screenshot 2026-06-08 at 3 34 18 PM

Comment thread src/deadline/client/ui/dialogs/submit_job_to_deadline_dialog.py Outdated
Comment thread src/deadline/client/ui/dialogs/submit_job_to_deadline_dialog.py Outdated
Comment thread src/deadline/client/ui/dialogs/submit_job_to_deadline_dialog.py Outdated
Comment thread src/deadline/client/ui/dialogs/submit_job_to_deadline_dialog.py Outdated
@github-actions

Copy link
Copy Markdown

Code Review Complete - I have reviewed PR 1181 (job bundle browser/sharing feature). CRITICAL ISSUE FOUND: The Share button uses .zip extension instead of .ojd at line 731 of submit_job_to_deadline_dialog.py. This breaks the browser S3 listing logic which filters by .ojd extension. Shared bundles will not appear in the Queue source. I have posted detailed inline comments on this and other minor issues. Overall the code is well-designed with good test coverage and proper security controls (zip slip protection, cache isolation). Once the extension bug is fixed the PR should be good to merge.

@github-actions

Copy link
Copy Markdown

CRITICAL BUG: File extension mismatch in Share button (submit_job_to_deadline_dialog.py line ~2147)

The Share button uploads bundles with .zip extension but should use .ojd:
Current: s3_key = f"{prefix}/{bundle_name}.zip"
Should be: s3_key = f"{prefix}/{bundle_name}.ojd"

Impact: Bundles shared via the UI will NOT appear in the browser dialog because S3BundleRepository only lists .ojd files.

SECURITY: Good practices found:

  • Zip path traversal protection in _safe_zip_extract (repository.py:42-51)
  • Symlink safety with follow_symlinks=False
  • S3 metadata truncation to safe lengths
  • YAML safe_load used correctly

MINOR ISSUES:

  1. Inconsistent use of hardcoded .ojd vs ARCHIVE_EXTENSION constant (bundle_group.py:750)
  2. Silent overwrite behavior when uploading bundles with same name
  3. ETag comparison could normalize quote formats (repository.py:1342)
  4. Exception messages displayed in QMessageBox may contain untrusted data

The critical bug must be fixed. Security is solid overall.

@github-actions

Copy link
Copy Markdown

Advisory Code Review - PR 1181

CRITICAL BUG FOUND:
Line 2165 uses .zip instead of .ojd - bundles uploaded via Share button will NOT appear in browser.
Fix: Change to .ojd extension

SECURITY ISSUES:

  1. Zip bomb protection missing in _safe_zip_extract (repository.py:50) - add size/file limits
  2. ReDoS risk from unbounded regex on user input (submit_job_to_deadline_dialog.py:2152)

CORRECTNESS ISSUES:
3. Encapsulation violation accessing _local_repo private attr (job_bundle_settings_tab.py:2796)
4. Silent metadata truncation without warnings
5. Fragile S3 initialization logic

STRENGTHS:

  • Path traversal protection is correct
  • ETag caching prevents TOCTOU
  • Good test coverage
  • No injection vulnerabilities

VERDICT: Well-designed feature with one critical bug. Fix .zip->.ojd and add zip bomb protection, then ready to merge.

Reviewed by: Claude Sonnet 4.5
Commit: c17fd50

@github-actions

Copy link
Copy Markdown

Detailed Findings

Issue 1: Critical Extension Bug
File: src/deadline/client/ui/dialogs/submit_job_to_deadline_dialog.py
Line: around 2165 in on_share_bundle method
Problem: Uses .zip extension instead of .ojd
Impact: Bundles uploaded via Share button will not appear in browser
Fix: Change s3_key line to use .ojd extension

Issue 2: Zip Bomb Protection Missing
File: src/deadline/client/job_bundle/repository.py
Function: _safe_zip_extract at line 41
Problem: No validation of uncompressed size or file count
Risk: Malicious archives could consume all disk space or CPU
Fix: Add MAX_SIZE and MAX_FILES checks before extraction

Issue 3: Encapsulation Violation
File: src/deadline/client/ui/widgets/job_bundle_settings_tab.py
Line: around 2796
Problem: Direct access to private _local_repo attribute
Fix: Add public resolve_bundle() method to dialog class

Issue 4: ReDoS Risk
File: src/deadline/client/ui/dialogs/submit_job_to_deadline_dialog.py
Line: around 2152
Problem: Regex on unbounded user input
Fix: Add length check before regex substitution

Issue 5: Silent Truncation
Files: bundle_group.py line 729, submit_job_to_deadline_dialog.py line 2127
Problem: Metadata truncated without user feedback
Fix: Add logger.warning() when truncating

@github-actions

Copy link
Copy Markdown

Review Complete

I have reviewed all 3579 lines of changes in this PR. The feature is well-architected with good separation of concerns and comprehensive test coverage.

Key Takeaways:

MUST FIX:

  • Extension bug in Share button (.zip should be .ojd)

SHOULD FIX:

  • Add zip bomb protection limits
  • Fix encapsulation violation in widget code

NICE TO HAVE:

  • Add warnings for metadata truncation
  • Add input length validation before regex
  • Improve S3 init error handling

The path traversal protection is correctly implemented and the caching strategy is sound. With the critical bug fixed and zip bomb protection added, this feature is ready for production use.

Questions or concerns about any finding? Please reply to this thread.

Comment thread src/deadline/client/job_bundle/repository.py Outdated
Comment thread src/deadline/client/cli/_groups/bundle_group.py Outdated
Comment thread src/deadline/client/cli/_groups/bundle_group.py Outdated
@github-actions

Copy link
Copy Markdown

Code Review Summary - I reviewed PR 1181 for the job bundle browser feature. Found 2 security issues: (1) Path traversal vulnerability on Windows in zip extraction validation at repository.py:49 - recommend using os.path.commonpath() (2) Symlink following in bundle upload at bundle_group.py:827 - os.walk() follows symlinks by default. Also found 1 bug: silent metadata truncation at bundle_group.py:815 without warnings. Overall the implementation is well-structured with good tests and caching design. Security issues should be addressed before merging.

Comment thread src/deadline/client/ui/dialogs/job_bundle_browser_dialog.py Outdated
@crowecawcaw

Copy link
Copy Markdown
Contributor

Collection of UX feedback:

Bundle preview — spacing and sizing

  • Title should be the largest text
  • Parameter table should be indented
  • Parameters should come before steps (OJD convention)
  • Steps and parameters should be open by default

Queue tab

  • Shows only a loading state when I have no queue bundles

Upload/download

  • Can I upload and download from the GUI? Maybe right-click → upload/download

History/source confusion

  • History seems to show history only for a queue, but the "queue" source isn't selected, so it seems like it should show all previously submitted jobs. "Local"
    also isn't queue-limited. Not sure what the right answer is, but the expectation could be clearer.

"Show hidden" is inconsistent across tabs (nit)

  • Queue: shows bundles that were previously hidden
  • Local: shows folders and files starting with a period
  • History: effectively does nothing

"Filter bundles" behavior

  • Not sure how it works. Searching opens sub-directories but doesn't seem to find a bundle on my desktop. Is it searching? Applying a basic filter to top-level
    items might be more intuitive.
  • After entering filter text and then deleting it, all sub-directories stay expanded.

Show in Finder (idea)

  • In local and history browsers, add a right-click → "show in Finder" option

Path input

  • Can't be edited. Maybe make it static text if we need it — but I'm not sure we do, since the same info is shown visually in the browser.

Naming (nit/idea)

  • Rename "History" → "Local history"? "Queue" means remote, "local" means on the workstation, but "history" is ambiguous — history of queue submissions, or
    history from this workstation?

Comment thread docs/design/job-bundle-browser.md Outdated
Comment thread docs/design/job-bundle-browser.md
Comment thread docs/design/job-bundle-browser.md
Comment thread docs/design/job-bundle-browser.md Outdated
Comment thread docs/design/job-bundle-browser.md Outdated
Comment thread docs/design/job-bundle-browser.md
epmog added 23 commits July 8, 2026 12:01
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
  The bundle browser aborted an in-progress S3 download with
  QThread.terminate(), which kills the thread mid-request and can leave the
  boto3 client/socket inconsistent and the cache partially written.

  Add a cancel flag checked in the download progress callback that raises to
  unwind the transfer cleanly, wire it to the dialog's Cancel/close, and wait
  for the worker to finish before clearing the partial cache.

  All nine review items are now implemented, each with tests verified to fail on the
  pre-fix code and a clean fmt/lint/mypy/build. Nothing has been committed — let me know
  if you'd like me to stage and commit these (individually per issue, or grouped), or if
  there's anything else to adjust.

Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
… there are none

Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
@epmog

epmog commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Collection of UX feedback:

Bundle preview — spacing and sizing

* Title should be the largest text

done

* Parameter table should be indented

done

* Parameters should come before steps (OJD convention)

done

* Steps and parameters should be open by default

done

Queue tab

* Shows only a loading state when I have no queue bundles

Fixed the message. For what it's worth it was actually a credential issue. If you don't have bundles but have valid creds, it shows "No bundles", but it was stuck in "Loading" if you didn't have valid creds.

Upload/download

* Can I upload and download from the GUI? Maybe right-click → upload/download

A good idea. There's a download button specifically for queue bundles now, we could have a feature request to upload local ones though.

History/source confusion

* History seems to show history only for a queue, but the "queue" source isn't selected, so it seems like it should show all previously submitted jobs. "Local"
  also isn't queue-limited. Not sure what the right answer is, but the expectation could be clearer.

It's based on AWS profile. Always has been. Could potentially to "Profile history" but I'm very much in favour of shorter succinct names.

"Show hidden" is inconsistent across tabs (nit)

* Queue: shows bundles that were previously hidden

* Local: shows folders and files starting with a period

Queue/Local match in my mind. We've created a Hide/Unhide mechanism for the queue via an S3 mechanism (something users don't need to understand). Local filesystems already have the concept of hidden folders/files so we leverage that instead.

* History: effectively does nothing

While we don't expect users to really do anything in history (they can choose the folder but it's written to by us), you can definitely put hidden stuff in there. I don't want to change that because I value the consistency in the UI and switching between sources shouldn't change that.

"Filter bundles" behavior

* Not sure how it works. Searching opens sub-directories but doesn't seem to find a bundle on my desktop. Is it searching? Applying a basic filter to top-level
  items might be more intuitive.

Folders (Queue/Local) are lazy loaded because it can be expensive to perform all those operations. Locally, we're looking at any folder that contains a template.yaml to determine if it's a bundle which requires you to go a few layers down. We don't explore that or S3 fully unless a user starts going down those nested folders. I've made a change to preload 1 more layer down to maybe help make that feel a little better.

* After entering filter text and then deleting it, all sub-directories stay expanded.

Fixed. Now it does it based on if you had actually selected something.

Show in Finder (idea)

* In local and history browsers, add a right-click → "show in Finder" option

I had a similar idea in my head that I implemented. Decided to put a button in the preview to handle and show download size (Queue)

Path input

* Can't be edited. Maybe make it static text if we need it — but I'm not sure we do, since the same info is shown visually in the browser.

I made it look less editable, not sure if it looks better or not. I knowing what bundle/object i'm looking at is important to me, especially if I wanna do something outside the browser with it.

Naming (nit/idea)

* Rename "History" → "Local history"? "Queue" means remote, "local" means on the workstation, but "history" is ambiguous — history of queue submissions, or
  history from this workstation?

See reply above

epmog added 2 commits July 14, 2026 17:34
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on-maintainers Waiting on the maintainers to review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants