Skip to content

Move CLI behind a Lambda: Part 1 - #15

Merged
ethanholz merged 9 commits into
omsf-eco-infra:mainfrom
dwhswenson:cli-lambda-phase1
May 26, 2026
Merged

Move CLI behind a Lambda: Part 1#15
ethanholz merged 9 commits into
omsf-eco-infra:mainfrom
dwhswenson:cli-lambda-phase1

Conversation

@dwhswenson

Copy link
Copy Markdown
Member

We want to move our CLI behind a lambda, so that the lambda role can have certain permissions without giving them to arbitrary users.

This is part 1 in that process. This will probably (I hope!) be the most complicated PR of the sequence. In this PR, we lay down the general structure for these, and migrate the status command to using the lambda backend. Future PRs will migrate the other commands.

A detailed plan is committed here in PLAN-cli-lambda.md. Quick summary:

  • New lambda function at modules/cli-lambda
  • Dockerfile for that at lambda/cli_lambda.Dockerfile. Need a separate Dockerfile because we need to launch a server in order to stream responses back as NDJSON.
  • Command details in src/devbox/commands/status.py. Others will move there.
  • Other CLI helper code in src/devbox/cli_lambda/, src/devbox/cli_protocol.py, and src/devbox/remote_client.py

Includes:
* New lambda terraform for the CLI
* Separate docker image for CLI Lambda (because streaming responses
  require different setup)
* Relevant code for lambda in src/devbox/cli_lambda
* Helper for local client in src/devbox/remote_client.py
* Shared local/remote code defining protocol in src/devbox/cli_protocol
* Updates so we use the lambda for the `status` command

Assisted-by: Codex:GPT-5.4
Co-local code for a given example.

Assisted-by: Codex:GPT-5.4
@codecov

codecov Bot commented Apr 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.76%. Comparing base (1a63771) to head (ef5f4e7).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #15      +/-   ##
==========================================
+ Coverage   99.75%   99.76%   +0.01%     
==========================================
  Files          13       17       +4     
  Lines        2818     2938     +120     
==========================================
+ Hits         2811     2931     +120     
  Misses          7        7              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI 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.

Pull request overview

This PR introduces the foundational structure to run CLI commands via a Lambda Function URL (IAM-authenticated + SigV4), and migrates the status command to use the new remote backend while leaving other commands local.

Changes:

  • Added a shared wire protocol (cli_protocol.py), a generic remote invocation client (remote_client.py), and a Starlette-based Lambda app (cli_lambda/) that streams NDJSON events.
  • Implemented status as the first migrated command via a shared command module (commands/status.py) used by both the local CLI and the Lambda handler.
  • Added Terraform + Docker packaging for the new CLI Lambda, plus unit tests covering contracts, dispatch, remote invocation, and the status command behavior.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/test_remote_client.py Adds unit coverage for signed HTTP invocation and NDJSON parsing/validation behaviors.
tests/test_cli.py Updates CLI tests to patch the new run_status_command path for status.
tests/commands/test_status.py Adds command-level tests spanning payload building, result rehydration, and Lambda handler behavior for status.
tests/cli_lambda/test_contracts.py Adds contract tests for request envelope parsing and NDJSON event encoding.
tests/cli_lambda/test_app.py Adds dispatcher/executor tests for routing and terminal error mapping.
src/devbox/remote_client.py Introduces SigV4-signed Function URL invocation and NDJSON event streaming parser.
src/devbox/commands/status.py Adds shared status implementation for CLI-side invocation + Lambda-side handler and serialization/rehydration.
src/devbox/commands/init.py Creates the new devbox.commands package namespace.
src/devbox/cli.py Migrates the Click status command to delegate to run_status_command.
src/devbox/cli_protocol.py Defines shared protocol constants/enums for actions/events across CLI and Lambda.
src/devbox/cli_lambda/contracts.py Adds request envelope validation and event build/encode helpers for the Lambda HTTP surface.
src/devbox/cli_lambda/app.py Adds Starlette app routing, dispatch table, and NDJSON streaming responses.
src/devbox/cli_lambda/init.py Creates the devbox.cli_lambda package namespace.
pyproject.toml Adds runtime/test deps (requests, responses) and optional deps for the Lambda app (starlette, uvicorn); packages new modules.
PLAN-cli-lambda.md Documents the phased migration plan and the phase 1 contract/validation log.
pixi.lock Locks new Python dependencies added for the remote client and Lambda app tests/runtime.
modules/cli-lambda/variables.tf Defines module inputs for the CLI Lambda deployment.
modules/cli-lambda/outputs.tf Exposes Lambda name, Function URL, and the published SSM parameter name.
modules/cli-lambda/main.tf Builds/pushes the image, provisions Lambda + Function URL, IAM, logging, and SSM publication.
main.tf Wires the new cli_lambda module into the root Terraform stack.
lambdas/cli_lambda.Dockerfile Adds the Lambda container image build (Lambda Web Adapter + uvicorn/Starlette app).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/devbox/cli_lambda/contracts.py Outdated
Comment thread src/devbox/remote_client.py Outdated
Comment thread src/devbox/cli_lambda/app.py Outdated
Comment on lines +95 to +97
project = validate_status_payload(envelope.payload)
manager_prefix = envelope.param_prefix.strip("/") or "devbox"
manager = DevBoxManager(prefix=manager_prefix)

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

envelope.param_prefix comes from the client request and is used to instantiate DevBoxManager. For future actions that read SSM/Dynamo based on this prefix, this becomes a trust boundary: a caller could supply an unexpected prefix and potentially target different parameters/tables if the Lambda role is permitted. Consider server-side validation/normalization (e.g., restrict to an allowlist or ignore the request value and use a configured prefix) before using it to construct AWS resource names.

Copilot uses AI. Check for mistakes.

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.

Moved to an issue to be handled later. Marked as a point in the plan doc.

Comment on lines +5 to +18
from enum import StrEnum

NDJSON_MIME_TYPE = "application/x-ndjson"
REQUEST_VERSION = "v1"
FUNCTION_URL_PARAMETER_SUFFIX = "/cli/functionUrl"


class CliAction(StrEnum):
"""Supported Lambda-backed CLI actions."""

STATUS = "status"


class CliEventType(StrEnum):

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

enum.StrEnum is only available on Python 3.11+. The project metadata currently declares requires-python = ">=3.9" (pyproject.toml), so installing/running on Python 3.10 would fail at import time here. Either avoid StrEnum (use class CliAction(str, Enum): ...) or bump the project’s minimum supported Python version to 3.11+ to match this dependency.

Suggested change
from enum import StrEnum
NDJSON_MIME_TYPE = "application/x-ndjson"
REQUEST_VERSION = "v1"
FUNCTION_URL_PARAMETER_SUFFIX = "/cli/functionUrl"
class CliAction(StrEnum):
"""Supported Lambda-backed CLI actions."""
STATUS = "status"
class CliEventType(StrEnum):
from enum import Enum
NDJSON_MIME_TYPE = "application/x-ndjson"
REQUEST_VERSION = "v1"
FUNCTION_URL_PARAMETER_SUFFIX = "/cli/functionUrl"
class CliAction(str, Enum):
"""Supported Lambda-backed CLI actions."""
STATUS = "status"
class CliEventType(str, Enum):

Copilot uses AI. Check for mistakes.

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.

Addressed in 708c564 by bumping minimum Python version (3.11 is already pretty old).

* Raised minimum Python version to 3.11
* Fixed unused NDJSON_MIME_TYPE import (and downstream importers)
* Handled potential `requests` errors
* Fixed type annotation in app.py
* Removed unused test code
* Plan updates for future improvements that are beyond the scope of this
  PR

Assisted-by: Codex:GPT-5.4
@dwhswenson
dwhswenson requested a review from ethanholz April 1, 2026 01:56

@ethanholz ethanholz 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.

Would like to see some docstrings so I have a bit better understanding of some of the validation. Testing and code looks good.

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.

Would love to see some docstrings for these functions.

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.

Added in ef5f4e7 (along with other non-CLI, non-test functions in this PR.)

Comment thread PLAN-cli-lambda.md

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.

Would maybe like a line in this plan about using numpy docstrings for all functions in future phases so that these get added during generation.

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.

Agreed. Added in ef5f4e7 (currently line 21).

Also include this in the plan, so further code will behave the same.

@dwhswenson dwhswenson 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.

Ready for another round of review!

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.

Added in ef5f4e7 (along with other non-CLI, non-test functions in this PR.)

Comment thread PLAN-cli-lambda.md

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.

Agreed. Added in ef5f4e7 (currently line 21).

@dwhswenson
dwhswenson requested a review from ethanholz April 1, 2026 13:17
@ethanholz
ethanholz merged commit a9844a3 into omsf-eco-infra:main May 26, 2026
5 checks passed
ethanholz pushed a commit to ethanholz/devbox that referenced this pull request Jun 22, 2026
…da-phase1"

This reverts commit a9844a3, reversing
changes made to 1a63771.
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.

3 participants