Skip to content

Add support for services#163

Open
alexcos20 wants to merge 4 commits into
feature/next-node-4from
feature/services
Open

Add support for services#163
alexcos20 wants to merge 4 commits into
feature/next-node-4from
feature/services

Conversation

@alexcos20

@alexcos20 alexcos20 commented Jul 22, 2026

Copy link
Copy Markdown
Member

Fixes #160

Add Service-on-Demand support to ocean-cli

Adds full CLI support for Service-on-Demand — launching long-running Docker
containers (JupyterLab, inference servers, nginx, …) on an Ocean Node compute
environment, paid up-front via on-chain escrow for a requested duration, and
reachable through forwarded ports. Unlike a compute job (runs an algorithm to
completion and exits), a service stays up until it expires, is stopped, or
is extended.

New commands (8)

Command Aliases Purpose
getServiceTemplates serviceTemplates List the node's service templates + compatible environments
startService Pay & launch a service (from --template or --image)
getServiceStatus myServices Show your service(s) with full detail + endpoints
getServices listServices SERVICES_LIST — list services across all owners (docker spec stripped), with --status/--include-all/--from filters
serviceLogs computeServiceLogs Stream a service's container logs (optional --since)
extendService Pay to push the expiry out
restartService Recreate the container (same ports/expiry, free); optional --cmd/--entrypoint override
stopService Tear the service down

All commands support both positional args and named options, work over HTTP and
P2P (transport auto-selected by ocean.js), and are picked up automatically by the
interactive REPL (no manual command-list maintenance).

Changes

  • src/serviceHelpers.ts (new) — pure, testable helpers: status labels &
    coloring, environment↔template resource matching (availableFor,
    envSatisfiesTemplate, findServiceEnvironments, templateMismatchReason),
    default-resource resolution, client-side cost estimation, userData
    parsing/validation (keys-only echo — values are never logged), escrow
    pre-verification with actionable remediation output, status polling, and a
    human-first job pretty-printer.
  • src/commands.ts — eight new Commands methods plus a shared payment-prompt
    helper. startService orchestrates the full flow: resolve env → resolve
    container spec (template and/or explicit flags, with the template.command → dockerCmd / template.entrypoint → dockerEntrypoint rename) → resolve
    resources → estimate cost → pre-verify escrow → confirm → start → poll to
    Running and print the endpoint.
  • src/cli.ts — command registration + option parsing (ports, JSON
    cmd/entrypoint, status filter). Read-only getServiceTemplates/getServices
    accept an optional --node target, mirroring getComputeEnvironments.
  • test/serviceFlow.test.ts (new) — end-to-end system test covering the full
    lifecycle, with a skipLifecycle guard so it skips cleanly on a node without
    services support.
  • README.md — feature bullet, a "Service-on-Demand" examples section, and a
    per-command option reference for all eight commands.

Notable correctness details

  • serviceRestart arg order (#2114): dockerCmd/dockerEntrypoint sit between
    userData and signal — the call passes all three before the AbortSignal.
  • getServices is node-wide, not owner-scoped (#2115): returns ServiceJobListed
    (docker image-spec fields stripped); results may include other owners' services.
  • serviceGetStreamableLogs (#2113): returns an async-iterable or null
    the null case is handled, and consumption reuses the exact buffer-then-print
    pattern from computeStreamableLogs (no forced timeout — logs are long-lived).
  • Escrow is pre-verified client-side before start/extend (shortfalls otherwise
    surface only as an async Error/*Failed status), and the CLI prints the exact
    depositEscrow / authorizeEscrow remediation commands on failure.
  • userData values are never logged — only keys are echoed.
  • Interactive-loop-safe: service Commands methods never process.exit on
    recoverable errors; the payment prompt opens its own readline (the REPL pauses to
    yield stdin), mirroring startCompute.

Testing

Verified end-to-end against a running barge (ocean-node v4) — all eight commands,
both the happy path and failure/skip paths. test/serviceFlow.test.ts passes
10/10 in ~40 s using a lightweight custom image
(nginxinc/nginx-unprivileged:alpine on port 8080):

✔ lists service templates with 'getServiceTemplates'
✔ finds a compute environment with services enabled
✔ funds escrow (deposit + authorize the env consumer)
✔ starts a service and reaches Running with an endpoint
✔ shows the service via getServiceStatus (single + list)
✔ lists the service via getServices (SERVICES_LIST) without docker spec
✔ fetches service logs (lenient)
✔ extends the service expiry with extendService
✔ restarts the container with restartService
✔ stops the service with stopService
10 passing (40s)

The client-side cost estimate matched the node-computed cost exactly, and
getServices confirmed that dockerCmd/dockerEntrypoint/dockerfile are
stripped from ServiceJobListed.

Summary by CodeRabbit

  • New Features
    • Added Service-on-Demand support to the Ocean CLI, including browsing service templates, starting services, monitoring status, viewing service logs, extending runtimes, restarting containers, and stopping services.
    • Added support for custom images, resource settings, ports, commands/entrypoints, and user data.
    • Added payment confirmation, escrow verification, status polling, and service cost estimation.
    • Expanded README documentation with the new service workflow and detailed named command options.
  • Chores
    • Updated the Ocean library dependency.
    • CI now runs for pull requests targeting any branch.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The CLI now supports Service-on-Demand workflows: template discovery, escrow verification, service startup, status and log retrieval, extension, restart, and stop operations. Documentation, dependency updates, CI trigger changes, and end-to-end coverage are included.

Changes

Service-on-Demand workflow

Layer / File(s) Summary
Service validation and runtime helpers
package.json, src/serviceHelpers.ts
Adds service status, resource compatibility, cost, user-data, escrow, polling, expiry, and job-rendering utilities, while updating the Ocean library version.
CLI parsing and command entrypoints
src/cli.ts
Adds parsing and validation for service options and wires template, start, status, listing, logs, extend, restart, and stop commands.
Service lifecycle orchestration
src/commands.ts
Implements service discovery, startup, payment confirmation, status/list retrieval, logs, extension, restart, and stop operations.
Lifecycle integration coverage and documentation
test/serviceFlow.test.ts, README.md
Documents the Service-on-Demand workflow and exercises escrow setup plus the service lifecycle end to end.

CI pull request trigger

Layer / File(s) Summary
All pull request targets
.github/workflows/ci.yml
Removes the target-branch restriction from the pull-request CI trigger.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested reviewers: bogdanfazakas, giurgiur99, andreip136

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Commands
  participant serviceHelpers
  participant Escrow
  participant ProviderInstance
  participant Node
  CLI->>Commands: startService(options)
  Commands->>serviceHelpers: resolve resources and estimate cost
  Commands->>Escrow: verify service escrow
  Commands->>ProviderInstance: serviceStart(params)
  ProviderInstance->>Node: create service
  Node-->>ProviderInstance: return service job
  ProviderInstance-->>Commands: return service job
  Commands->>serviceHelpers: pollServiceStatus()
  serviceHelpers->>Node: fetch service status
  Node-->>serviceHelpers: return running status
  serviceHelpers-->>Commands: return running service job
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The CI workflow branch-filter change is unrelated to service support and appears out of scope. Move the CI trigger change to a separate PR unless it is required for service-on-demand support.
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding service support.
Linked Issues check ✅ Passed The PR implements service consumption and management in ocean-cli, matching issue #160's goal.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/services

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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.

@alexcos20 alexcos20 linked an issue Jul 22, 2026 that may be closed by this pull request
@alexcos20

Copy link
Copy Markdown
Member Author

/run-security-scan

@alexcos20 alexcos20 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

AI automated code review (Gemini 3).

Overall risk: low

Summary:
This is an excellent PR introducing the Service-on-Demand lifecycle commands. The architectural split between CLI parsing, core actions, and helpers is clean. Input validation, cost estimations, interactive prompts, and robust polling (especially the old container ID check during restarts) are very well implemented. A few minor informational suggestions are provided for edge cases.

Comments:
• [INFO][style] options.env is not defined as an option for the startService command (only as a positional <computeEnvId>). This fallback is redundant but harmless. You could simplify this to const envId = computeEnvId;.
• [INFO][logic] Because template values are copied before explicit flags are applied, if a template has a tag and the user explicitly passes --checksum, both tag and checksum will be truthy. This causes specCount > 1 and triggers the validation error below. Since cli.ts already prevents mixing --template and --image, this is perfectly fine for now, but worth noting if you ever want to allow users to override a template's image specifications in the future.
• [INFO][style] The userData file and inline parsing logic here works perfectly, though it duplicates some of the validation logic found in parseUserData inside src/serviceHelpers.ts. Since template validation isn't strictly needed for a restart, this is acceptable, but consider reusing parseUserData if you wish to completely centralize the parsing logic.
• [INFO][logic] The use of notContainerId to avoid race conditions when checking for Running status immediately after a restart is a fantastic piece of defensive programming. Excellent handling of this edge case!

@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: 2

🤖 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 `@src/commands.ts`:
- Around line 1873-1879: Update the expiry logging in the service extension
success path near the extendPayments output to guard both oldExpiry and
newJob.expiresAt before calling toISOString(). Reuse the same safe
expiry-formatting behavior established by printServiceJob, ensuring undefined or
zero values do not throw after a successful serviceExtend.

In `@test/serviceFlow.test.ts`:
- Around line 40-49: Call homedir() when constructing the default address-file
path instead of interpolating the function reference; update both
test/serviceFlow.test.ts sites at lines 40-49 and 62-72, including getAddresses
and the before hook default.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7723449d-06bd-4c66-9e53-be4ad933bf80

📥 Commits

Reviewing files that changed from the base of the PR and between f461a86 and f4bd3d9.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • README.md
  • package.json
  • src/cli.ts
  • src/commands.ts
  • src/serviceHelpers.ts
  • test/serviceFlow.test.ts

Comment thread src/commands.ts
Comment thread test/serviceFlow.test.ts
@alexcos20

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@alexcos20

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/cli.ts (2)

659-670: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Error paths return without process.exit(1).

Across the new service commands (startService Lines 659-670, serviceLogs Lines 783-786, extendService Lines 807-814, restartService Lines 845-848, stopService Lines 901-904), validation failures log an error and return without exiting with a non-zero code. Scripts/CI invoking these commands can't detect failure via exit status.

As per coding guidelines, {src/cli.ts,src/commands.ts}: "Use process.exit(code) for CLI exit codes with code 0 for success and 1 for errors."

       if (!envId || !duration || !token) {
         console.error(chalk.red("Missing required arguments: <computeEnvId> <duration> <paymentToken>"));
+        process.exit(1);
         return;
       }

Also applies to: 783-786, 807-814, 845-848, 901-904

🤖 Prompt for 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.

In `@src/cli.ts` around lines 659 - 670, Update validation failure paths in
startService, serviceLogs, extendService, restartService, and stopService to
call process.exit(1) after logging errors instead of returning normally.
Preserve the existing error messages and successful execution flow so CLI
consumers receive a non-zero status only for validation failures.

Source: Coding guidelines


623-707: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make restartService dockerfile flags path-based or rename them explicitly.

startService treats --dockerfile/--additional-docker-files as file paths, but restartService treats the same flag names as inline contents: --dockerfile is passed directly and --additional-docker-files is parsed as JSON without reading a file. Reusing the startService convention with restartService can fail during JSON parsing or send path text as Dockerfile content. Align both commands to the same convention or rename one side’s flags, e.g. --dockerfile-file vs --dockerfile.

🤖 Prompt for 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.

In `@src/cli.ts` around lines 623 - 707, Align restartService with startService’s
path-based convention for --dockerfile and --additional-docker-files: read the
Dockerfile path and parse the additional-files path contents before passing them
to the command implementation. Update the corresponding restartService option
handling and argument mapping, or explicitly rename its inline-content flags to
avoid reusing these path-based names.
🤖 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 `@src/cli.ts`:
- Around line 851-863: Update restartService to explicitly reject supplying both
options.userData and options.userDataFile before the existing parsing branches,
using a clear mutual-exclusion error; preserve the current behavior when only
one option is provided.

---

Outside diff comments:
In `@src/cli.ts`:
- Around line 659-670: Update validation failure paths in startService,
serviceLogs, extendService, restartService, and stopService to call
process.exit(1) after logging errors instead of returning normally. Preserve the
existing error messages and successful execution flow so CLI consumers receive a
non-zero status only for validation failures.
- Around line 623-707: Align restartService with startService’s path-based
convention for --dockerfile and --additional-docker-files: read the Dockerfile
path and parse the additional-files path contents before passing them to the
command implementation. Update the corresponding restartService option handling
and argument mapping, or explicitly rename its inline-content flags to avoid
reusing these path-based names.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f5595119-43b4-4f7e-ae25-1f8669130a5c

📥 Commits

Reviewing files that changed from the base of the PR and between 0bbe0c1 and 09f6ed1.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • README.md
  • package.json
  • src/cli.ts
  • src/commands.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • package.json
  • README.md
  • src/commands.ts

Comment thread src/cli.ts
Comment on lines +851 to +863
if (options.userData) {
const parsed = JSON.parse(options.userData);
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
throw new Error("--user-data must be a JSON object");
}
params.userData = parsed;
} else if (options.userDataFile) {
const parsed = JSON.parse(fs.readFileSync(options.userDataFile, "utf8"));
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
throw new Error("--user-data-file must contain a JSON object");
}
params.userData = parsed;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Silent priority between --user-data and --user-data-file in restartService.

If both flags are supplied, --user-data silently wins with no error (Lines 851-863), unlike the explicit --template/--image conflict check in startService (Lines 667-670). Add an explicit mutual-exclusion error for consistency and to avoid silently ignoring a user-supplied file.

Proposed fix
       try {
+        if (options.userData && options.userDataFile) {
+          throw new Error("Provide either --user-data or --user-data-file, not both.");
+        }
         if (options.userData) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (options.userData) {
const parsed = JSON.parse(options.userData);
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
throw new Error("--user-data must be a JSON object");
}
params.userData = parsed;
} else if (options.userDataFile) {
const parsed = JSON.parse(fs.readFileSync(options.userDataFile, "utf8"));
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
throw new Error("--user-data-file must contain a JSON object");
}
params.userData = parsed;
}
if (options.userData && options.userDataFile) {
throw new Error("Provide either --user-data or --user-data-file, not both.");
}
if (options.userData) {
const parsed = JSON.parse(options.userData);
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
throw new Error("--user-data must be a JSON object");
}
params.userData = parsed;
} else if (options.userDataFile) {
const parsed = JSON.parse(fs.readFileSync(options.userDataFile, "utf8"));
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
throw new Error("--user-data-file must contain a JSON object");
}
params.userData = parsed;
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 857-857: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(options.userDataFile, "utf8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 Prompt for 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.

In `@src/cli.ts` around lines 851 - 863, Update restartService to explicitly
reject supplying both options.userData and options.userDataFile before the
existing parsing branches, using a clear mutual-exclusion error; preserve the
current behavior when only one option is provided.

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.

Integrate (allow consumption of) services in ocean-cli

1 participant