Skip to content

feat: output json integration - #9172

Open
madhavdonthula1 wants to merge 51 commits into
aws:developfrom
madhavdonthula1:feat/output-json-integration
Open

feat: output json integration#9172
madhavdonthula1 wants to merge 51 commits into
aws:developfrom
madhavdonthula1:feat/output-json-integration

Conversation

@madhavdonthula1

@madhavdonthula1 madhavdonthula1 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Add --output json to sam build and sam deploy

Overview

  • This adds a --output json flag to both sam build and sam deploy.
  • The flag defaults to text, so every existing command behaves exactly as before.
  • JSON output is fully opt in and never turns on by itself.
  • CI jobs and agents can now read results as structured data instead of scraping text.
  • Both commands share one flag decorator and one OutputOption enum.

Important clarification on output shape

  • sam build prints exactly one JSON document at the end of the run.
  • sam deploy prints a stream of JSON lines as the deploy progresses.
  • Each deploy line is its own object with a type field.
  • The deploy types are changeset, event, outputs, warning, info, and result.
  • Streaming is intentional because a deploy runs for a long time.
  • A single document would force a consumer to wait until the very end.

Shared result contract (both commands)

  • Every terminal object has type: "result" and a lowercase status.
  • Failures use status: "failure" with error: {type, message}.
  • So a consumer can handle build and deploy failures with one code path.
  • build additionally lists error.resources (the affected resources), which is additive.

sam build files

samcli/commands/build/command.py

  • The command front door that Click runs first.
  • Adds the shared --output flag and threads it into do_cli.
  • Catches any build failure in one place and prints it as JSON.
  • Re-raises the error so telemetry and exit codes are unchanged.

samcli/commands/build/build_context.py

  • The worker that orchestrates the build.
  • Prints the JSON success document listing every built resource.
  • Reports build_dir and template_file as absolute paths in JSON.
  • The relpath transform is kept only for the human text banner.
  • Reports all affected resources as a list in error.resources.
  • Resolves a bare resource id to its full path for a matching namespace.
  • Re-raises a filesystem error as InvalidBuildDirException, not a raw OSError.
  • Skips the beta python-uv confirm prompt in JSON mode.
  • Fails with an actionable error if a JSON build needs a writable mount.

samcli/commands/build/utils.py

  • Extracts the write-mount detection out of the interactive prompt.
  • Lets the build context detect the .NET write-mount requirement without prompting.
  • Routes the mount confirm prompt to stderr in text mode.

samcli/lib/build/build_strategy.py

  • Tags build failures with the resources they affect.
  • Uses one shared context manager so every strategy attributes uniformly.
  • Also covers the cached and incremental build paths.

samcli/lib/build/exceptions.py and samcli/lib/build/workflow_config.py

  • Add a resource_names list attribute to the build exception classes.
  • Declaring it on the class also avoids mypy errors.

samcli/commands/build/core/options.py

  • Registers the output option so it shows under Output Options in help.

sam deploy files

samcli/commands/deploy/command.py

  • The deploy front door that Click runs.
  • Uses the same shared --output flag as build.
  • Rejects --guided and --confirm-changeset with JSON up front.
  • Wraps package and deploy so any failure ends the stream with a result line.
  • Redirects manage_stack progress to devnull under --resolve-s3 in JSON mode.
  • Re-surfaces the resolved bucket as a {type: info} line.
  • Forces the package progress bar off in JSON mode.

samcli/commands/deploy/deploy_context.py

  • Orchestrates the deploy and decides what to emit.
  • Parses the flag once into the OutputOption enum.
  • Suppresses the human deploy-args table and banners in JSON mode.
  • Emits terminal result lines for success, no_changes, and changeset_created.
  • The success line carries express, changeset_id, stack_name, and region.
  • Emits per-resource auth warnings as {type: warning} lines.

samcli/lib/deploy/deployer.py

  • The layer that calls CloudFormation directly.
  • Stores output mode as the OutputOption enum, raising on a bad value.
  • Emits changeset, event, and outputs as JSON lines.
  • Event lines carry detailed_status so failure detail is not lost.
  • Datetimes are serialized with isoformat.
  • Status messages like "Waiting..." are text-only.

samcli/commands/deploy/core/options.py

  • Registers the output option for the deploy help screen.

Shared and support files

samcli/commands/_utils/options.py

  • Holds the single shared --output flag decorator.
  • The accepted values are derived from the OutputOption enum, so they cannot drift.

samcli/commands/_utils/table_print.py

  • The shared table decorator skips the table chrome in JSON mode.
  • Raises on an unrecognized output mode instead of corrupting the stream.

samcli/commands/_utils/cdk_support_decorators.py

  • Routes the CDK-unsupported advisory to stderr so stdout stays clean.

samcli/commands/package/package_context.py

  • Routes the preview-runtime warning to stderr in JSON mode.

samcli/lib/package/language_extensions_packaging.py

  • Routes the parameter-collection warning to stderr unconditionally.

samcli/commands/exceptions.py

  • Adds the shared resource_names attribute to the base UserException.

Testing

  • Unit tests were added for the new JSON success, failure, and streaming paths.
  • Tests cover build attribution, cached builds, and deploy JSON output.
  • Key behaviors were mutation checked by breaking the code on purpose.
  • make pr passes and coverage stays above the required bar.

…able output

Add a --output option to `sam build` that supports "text" (default, unchanged
behavior) and "json" (structured output for programmatic consumers).

When --output json is specified:
- Success output is a JSON object with status, build_dir, template_file, and
  a resources array listing each built function's logical_id, runtime, and
  architecture.
- Error output is a JSON object with status, error type, message, and the
  failing resource name when available.
- Build progress logs are suppressed from stdout (sent to stderr only) so
  that stdout contains only valid JSON.

This enables CI/CD pipelines, IDE extensions, and AI-assisted developer tools
to consume build results programmatically without fragile text parsing.
- Replace sys.exit(1) with raise UserException in error handlers to
  preserve telemetry tracking via @track_command decorator
- Wrap both container and in-process build paths in try/except so
  resource_name is tagged on BuildError regardless of build mode
- Include layers in JSON success output alongside functions
- Rename field from logical_id to resource_id (accurate for nested stacks)
- Add type field ("function" or "layer") to each resource entry
Move the try/except BuildError from app_builder._build_function (which
only covered the ZIP in-process path) to DefaultBuildStrategy's
build_single_function_definition and build_single_layer_definition.

This ensures resource_name is tagged for ALL build paths:
- ZIP functions (in-process and container)
- IMAGE functions (Docker builds)
- Layer builds

Known limitation: BuildInsideContainerError does not inherit from
BuildError (it inherits from Exception directly), so it won't get
resource_name tagged by this mechanism. Fixing this would require
changing the exception hierarchy which is out of scope for this PR.
The error type and message are still present in JSON output for this
case — only the resource field is missing.

Also fixes unit test assertion to expect the new output="text" kwarg.
… types

Expand the except clause in DefaultBuildStrategy to also catch
UnsupportedRuntimeException, BuildInsideContainerError, and
UnsupportedBuilderLibraryVersionError. These exceptions don't inherit
from BuildError but can originate from a specific function build
(deprecated runtime, Docker pull failure, missing builder in container).

Uses dynamic attribute assignment (ex.resource_name = ...) since these
classes don't define resource_name in __init__. The consumer in
build_context.py already uses getattr(ex, "resource_name", None) which
handles both cases safely.
- Apply black formatting to command.py and build_strategy.py
- Fix ruff import ordering in build_strategy.py
- Add type: ignore comments for mypy union-attr and index errors
  (dynamic resource_name assignment on non-BuildError exceptions)
- Regenerate schema/samcli.json to include the new --output parameter
Add --output json option to sam deploy that emits newline-delimited JSON
(NDJSON) streaming output instead of human-formatted tables. Each deploy
stage emits structured JSON lines in real-time as CloudFormation events
arrive, preventing process timeouts during long-running deployments.

Line types emitted:
- changeset: one per resource change (action, logical_id, resource_type)
- event: streaming CFN stack events during deploy
- outputs: stack outputs after completion
- result: final status line (SUCCESS/FAILED/NO_CHANGES/CHANGESET_CREATED)
Ensures stdout is pure NDJSON when --output json is used by:
- Forcing no_progressbar=True on both PackageContext and DeployContext
  S3 uploaders, preventing upload progress lines from leaking to stderr
- Raising samcli logger to WARNING level to suppress LOG.info messages
…eaming

Thread output_mode=self.output_mode through the two describe_stack_events
calls in rollback_delete_stack, preventing table-formatted text from
leaking into the NDJSON stream during failure/rollback scenarios.
When --output json and --confirm-changeset are both set, emit a
structured result line with status CONFIRMATION_REQUIRED and return
without executing, instead of silently skipping the confirmation
and proceeding with the deploy.
Move the FAILED result line to after rollback_delete_stack finishes,
ensuring the result record is always the last line in the NDJSON stream.
Previously it was emitted before rollback, causing event records to
appear after the terminal result line.
Redirect stdout to /dev/null while manage_stack() runs in JSON mode,
preventing its click.echo calls ("Creating the required resources...",
"Successfully created!") from corrupting the NDJSON stream.
…s JSON

- Raise UsageError when --guided and --output json are combined, since
  guided mode requires interactive prompts incompatible with JSON output.
- Emit {"type": "warning", "message": "no authentication", "resource": ...}
  instead of silently dropping the auth warning in JSON mode.
- Move --output into a shared @output_option decorator in _utils/options.py
  so deploy/init can reuse it instead of redeclaring the option
- Give --output its own "Output Options" help group rather than filing it
  under Build Strategy, which is for options that change how the build runs
- Extract the JSON/text reporting out of BuildContext.run() into
  _print_build_success and _print_build_failure
- Collect get_resources_to_build() once in run() and reuse it
- Always emit the "resource" key in error output (null when the failure is
  not attributable to a single resource) so the schema is predictable
- Hoist the wrapped_from lookup to the top of the except block instead of
  computing it in both branches
- Drop the unused resource_name param from BuildError; build_strategy sets
  the attribute at runtime for all handled exception types
Cover _print_build_success and _print_build_failure across JSON and text
modes: resource list shapes for functions/layers, architecture edge cases,
the always-present "resource" error key, error-type passthrough, and the
text-banner suppression path. 23 tests following the existing
TestBuildContext_<method> convention.
- Rename output helpers to structured_output_option (avoid shadowing list command)
- Use OutputOption enum instead of string literals
- Emit JSON error for InvalidBuildDirException; soften "always present" comment
- Single-line JSON (remove indent=2)
- Add resource_name class attribute to all 4 build exception types, drop type: ignore
- Add end-to-end run() JSON test; remove 6 redundant helper tests
- Use function.architecture (resolves x86_64 default) instead of architectures[0]
- Route interactive prompts to stderr (err=True) so stdout stays pure JSON
Move all --output json failure serialization to a single except UserException
handler in do_cli, covering every failure path uniformly (missing layer
BuildMethod, invalid build dir, pre-processing errors) instead of only the
exception types caught inside run().

- Add build_failure_json() helper in build_context as the single source of
  truth for the failure wire format
- do_cli uses the helper and checks OutputOption(output) is OutputOption.json
  (enum, not raw string literal)
- run() only prints the text "Build Failed" banner and re-raises UserException
  carrying resource_name; no double-emit
- Add resource_name class attribute to UserException
- Move JSON failure tests to test_command.py (do_cli level); simplify
  _print_build_failure tests to text-banner only
- Merge two near-identical "stays silent" tests into one parameterized test
- Use OutputOption enum instead of raw "text"/"json" strings so the tests
  match how the real code sets _output (via enum conversion in __init__),
  removing a latent fragility where raw strings passed only by coincidence
UnsupportedBuilderException (raised for an invalid Metadata.BuildMethod, e.g. a
template typo) subclasses plain Exception, so it escaped both run()'s except tuple
and do_cli's UserException handler - exiting 1 with empty stdout in --output json.

- Add it to run()'s except tuple so it converts to UserException and routes through
  build_failure_json, matching its sibling UnsupportedRuntimeException
- Add it to the DefaultBuildStrategy except tuples so resource_name is attributed
- Add resource_name class attribute (Comment aws#5 pattern)
- Extend test_must_catch_known_exceptions to cover it
…ilures

Failure paths that know exactly which resource failed were reporting
"resource": null in --output json:

- FunctionNotFound handler had a dead _print_build_failure(print_text_banner=False)
  no-op and a getattr that FunctionNotFound never carries. Both failure handlers now
  fall back to self._resource_identifier, so sam build <bad-name> reports that name.
- MissingBuildMethodException (raised from _collect_single_buildable_layer, a
  UserException) was not in run()'s except tuple, so it reached do_cli with no
  resource attribution. Added it; the layer name now populates via the fallback.

Tests: use OutputOption.text (not the raw string) in the two success-path text-mode
tests so they actually exercise the enum comparison, and assert resource_name
fallback in test_must_catch_known_exceptions.
…comment

- Extend the try in _setup_build_dir to cover shutil.rmtree, not just mkdir. An
  OSError while clearing the build dir (permission denied, file held open on
  Windows) now surfaces as InvalidBuildDirException -> JSON error, instead of a
  bare OSError that exits 1 with empty stdout in --output json.
- Soften the do_cli handler comment: it covers user-facing failures, not "every
  failure path"; note that an unexpected internal error (bare Exception) is not
  serialized.
- Add test_rmtree_oserror_is_converted_to_invalid_build_dir.
… param

- Change do_cli's except UserException to except Exception so every build-path
  failure emits a JSON document, including bare-Exception template errors
  (InvalidLayerReference, RemoteStackLocationNotSupported, MissingCodeUri,
  InvalidTemplateFile) that are not UserException subclasses. Re-raises
  unconditionally so @track_command still records telemetry and wraps
  non-UserException as UnhandledException.
- Drop the now-dead print_text_banner parameter from _print_build_failure; no
  caller passes False since the FunctionNotFound handler stopped calling it.
- Add test_json_failure_for_bare_exception; simplify the silence test.
process_env_var and process_image_options ran outside the try, so an
InvalidImageException from a malformed --build-image (e.g. --build-image MyFunc=)
exited 1 with empty stdout in --output json. Moving both inside the try routes
them through the central JSON failure handler.
The three comments claimed do_cli's handler serializes "every error reachable
from the build". That is only true for execution failures once do_cli runs.
Errors raised during click option processing (invalid flags, or a --hook-name
prepare-hook failure re-raised by track_command) occur before do_cli and surface
as click's standard usage/stderr output, not JSON.

Soften the three comments to state the guarantee covers execution failures and
note that invocation errors surface via click's stderr, so consumers treat
"exit != 0 with empty stdout" as an invocation error. No behavior change.
…ailures

- Add package_type to each function in the JSON success document so Image
  functions (runtime: null) are distinguishable from Zip, via a new
  _function_to_json helper.
- Restrict the resource_name fallback: InvalidBuildGraphException and
  UnsupportedBuilderLibraryVersionError are not resource-specific, so they keep
  resource=None instead of being attributed to whatever resource the user named.
- Document that error.resource is a representative id when functions share a
  build definition (points to get_resource_full_paths for the full set).
- Tests: package_type for image functions; per-type resource_name expectations.
The JSON success document reports resources[].resource_id as the resource's
full_path (nested-stack-qualified, e.g. ChildStack/MyFn), but the failure
fallback reported the raw CLI identifier (MyFn). Consumers could not correlate
a failure back to the resource_id they saw on success.

Add _resolve_resource_full_path(), which looks the identifier up via the same
function/layer providers the success document uses and returns its full_path,
falling back to the raw identifier only when lookup fails. Both fallback paths
(FunctionNotFound and the general handler) now route through it.
The comment pointed JSON consumers at get_resource_full_paths() as an escape
hatch for the full set of collapsed functions, but that method is internal and
nothing in the failure document exposes it. Naming one representative is the
deliberate contract — error.resource is a single value. Update the comment to
say so and stop describing an escape hatch that does not exist for this output.
@github-actions github-actions Bot added the stage/needs-triage Automatically applied to new issues and PRs, indicating they haven't been looked at. label Aug 7, 2026

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: 9101836..1317e57
Files: 16 of 23 (7 unit-test files skipped per single-pass scope; schema/samcli.json is generated)
Comments: 3

Comment thread samcli/commands/deploy/deploy_context.py Outdated
Comment thread samcli/commands/deploy/deploy_context.py Outdated
Comment thread samcli/commands/deploy/command.py Outdated
…on, trim comment

- Remove the samcli-logger WARNING override in deploy JSON mode. It defeated
  --debug and was never needed for stdout purity since log handlers write to
  stderr.
- Reject --confirm-changeset with --output json up front in do_cli. Previously
  it emitted CONFIRMATION_REQUIRED and exited 0 without deploying, a silent
  no-op that CI could mistake for success (confirm_changeset is persisted by
  --guided). Add tests for this and the existing --guided guard.
- Trim the oversized do_cli failure-handler comment in build/command.py to the
  essential rationale.
PackageContext.run() runs before DeployContext and printed the preview-runtime
warning to stdout, which would corrupt the deploy JSON lines a consumer parses.
Give PackageContext an output mode and send that warning to stderr when output
is JSON, keeping stdout pure. Text mode is unchanged. Add a test for the stderr
routing (mutation-verified) and update the do_cli PackageContext assertion.

Note: a second preview-style warning in language_extensions_packaging.py is a
module-level function without output-mode access and only fires on the
--language-extensions path; left as a follow-up.

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: 9101836..f6b48d9
Files: 16 of 25 (8 unit-test files skipped per single-pass scope; schema/samcli.json is generated)
Comments: 2

Comment thread samcli/commands/deploy/deploy_context.py
Comment thread samcli/commands/package/package_context.py
Functions sharing a runtime, CodeUri, and metadata collapse into one build
definition, so a single build failure can affect several of them. The failure
document reported only the first (error.resource, a string), hiding the others
from consumers.

Change the attribution to a list end to end: resource_name (Optional[str]) ->
resource_names (Optional[List[str]]) on UserException and the five lib build
exceptions; the build-strategy tagging now records every function in the
definition; build_failure_json emits error.resources (array); and
_resolve_resource_full_paths returns a single-element list for the CLI-arg
fallback. A corrupt build graph stays resource_names=None. Tests updated,
including a two-function case that fails if only the first is reported.
…stderr in JSON

Two review findings on the deploy JSON stream:

- Most deploy failures emitted no terminal result line (e.g. an empty changeset
  with the default --fail-on-empty-changeset, an invalid template, or a
  packaging error), so a consumer could not tell failure from a truncated
  stream. Wrap the package+deploy sequence in do_cli with a broad handler that
  emits {type:result,status:FAILED,error} in JSON mode and re-raises, mirroring
  build's central failure serialization. Per-step handlers keep their specific
  statuses.
- warn_parameter_based_collections printed to stdout during the deploy package
  step, corrupting the JSON lines for Fn::ForEach parameter-collection
  templates. Thread a to_stderr flag from PackageContext so it routes to stderr
  in JSON mode; non-JSON callers are unchanged.

Both mutation-verified.

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: 9101836..c920b9b
Files: 18 of 27 (9 unit-test files skipped per single-pass scope; schema/samcli.json is generated)
Comments: 3


Comments on lines outside the diff:

[samcli/commands/deploy/command.py:348] [BUG] The comment on the new handler states the intent as "Guarantee the JSON-lines stream always ends with a terminal result object," but the try starts after the S3/ECR resolution block, so failures there still produce an entirely empty stdout stream with no result line:

  • raise DeployResolveS3AndS3SetError() when both --resolve-s3 and --s3-bucket are given (line 348).
  • manage_stack(...) failures inside the redirect_stdout block — e.g. no permission to create/update aws-sam-cli-managed-default. Note the redirect also swallows the underlying progress text, so stdout is empty either way.
  • _resolve_image_repositories(...) failures with --resolve-image-repos.

These are ordinary permission/argument errors, not edge cases, and they are exactly the paths the previous round flagged as indistinguishable from a truncated stream. Moving the try to enclose the resolution block (it is already after the guided/confirm_changeset usage checks, which should stay outside as Click usage errors) would make the guarantee actually hold.

Comment thread samcli/lib/package/language_extensions_packaging.py Outdated
Comment thread samcli/commands/deploy/deploy_context.py Outdated
The DeployFailedError handler in deploy_context emitted a FAILED result line and
then re-raised. With the new terminal-result handler in do_cli, that re-raise
produced a SECOND FAILED line, so a JSON consumer saw the failure twice. Remove
the now-redundant emit from the handler (do_cli owns the single terminal line);
keep the rollback side-effect and the re-raise. Add a regression test asserting
the context emits no result line for DeployFailedError, mutation-verified.

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: 9101836..88cd8ec
Files: 17 (9 unit-test files skipped per single-pass scope; schema/samcli.json is generated)
Comments: 2

Comment thread samcli/commands/deploy/command.py Outdated
Comment thread samcli/lib/package/language_extensions_packaging.py Outdated
…g caller

Follow-ups on the deploy JSON contract:

- The terminal-result handler opened after the --resolve-s3/--resolve-image-repos
  block, so a bucket/ECR resolution failure (common in CI) exited non-zero with
  zero JSON lines. Move do_cli's try above the guided/else block so those
  failures also emit the terminal FAILED result. Guided/confirm rejections stay
  above the try, so their behavior is unchanged.
- warn_parameter_based_collections had two callers but only the PackageContext
  one received the to_stderr flag; the child-template export path still wrote to
  stdout. Route the warning to stderr unconditionally (it is an advisory already
  recorded via LOG.debug, matching the telemetry and version-check notices) and
  drop the flag plumbing, fixing both callers.

Both mutation-verified.

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: 9101836..dd94fc9
Files: 18 of 27 (9 unit-test files skipped per single-pass scope; schema/samcli.json is generated)
Comments: 1

Comment thread samcli/commands/deploy/command.py
…dout

The @unsupported_command_cdk decorator (on deploy, package, sync, validate)
wrote its 'CDK apps are not officially supported' warning to stdout. It runs
before do_cli, so sam deploy --output json on a CDK-synthesized template leaked
one or two plain-text lines ahead of the JSON stream, breaking a per-line
json.loads consumer. Route both advisory lines to stderr (err=True), matching
how the other package/preview warnings and SAM's telemetry/version notices
already behave. Update the decorator tests to assert the stderr routing.

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: 9101836..210d51a
Files: 18 of 29 (10 unit-test files skipped per single-pass scope; schema/samcli.json is generated)
Comments: 3

All previously raised comments appear addressed in this revision: the blanket handler in do_cli now opens before S3/ECR resolution, the duplicate result line on DeployFailedError is gone, confirm_changeset + --output json is rejected up front, the logger downgrade is removed, and the remaining stdout writers (cdk_support_decorators, warn_parameter_based_collections, _warn_preview_runtime, prompt_experimental, build/utils.prompt) now go to stderr. I traced the deploy path (manage_stack, sync_ecr_stack, PackageContext.run, Template.export, S3Uploader/ECRUploader, Deployer) and found no remaining unconditional stdout writes in JSON mode.

Comment thread samcli/commands/build/build_context.py
Comment thread samcli/commands/deploy/deploy_context.py Outdated
Comment thread samcli/commands/build/build_context.py
…inator

Three review findings on the JSON contract:

- build JSON reported build_dir/template_file as cwd-relative (and Windows
  flipped relative<->absolute on the ValueError fallback). Emit absolute paths
  in JSON; apply the relpath human-readability transform only in the text
  banner.
- deploy --express SUCCESS was byte-identical to a settled deploy, so a consumer
  could not tell the stack may still be stabilizing. Add "express" to the
  SUCCESS payload.
- build and deploy emitted incompatible result schemas (build: lowercase status,
  no type; deploy: uppercase status, type:result). Align on one convention:
  lowercase status everywhere plus a type discriminator on every document. Add
  type to build's success and failure documents; lowercase deploy's statuses.

Tests updated on both sides to assert the shared shape; new tests for absolute
paths, text-mode relpath, and the express flag.

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: 9101836..48bfe7e
Files: 18 of 29 (10 unit-test files skipped per single-pass scope; schema/samcli.json is generated)
Comments: 2

Verified as addressed in this revision: the blanket handler in do_cli now opens before S3/ECR resolution, the duplicate result line on DeployFailedError is gone, --confirm-changeset + --output json is rejected up front, the logger downgrade is removed, build_dir/template_file are now absolute, the express stabilization caveat is carried as an express field, and the remaining stdout writers (_warn_preview_runtime, warn_parameter_based_collections, unsupported_command_cdk, progress bars, manage_stack) are on stderr, suppressed, or discarded in JSON mode.

Comment thread samcli/commands/build/build_context.py
Comment thread samcli/commands/deploy/command.py
Two more shared-contract findings:

- deploy's failure result still diverged from build: status "failed" with a
  bare-string error vs build's status "failure" with a structured error object.
  Align deploy on build's shape - status "failure" and
  error: {type: type(ex).__name__, message: str(ex)} - so a consumer handles
  both commands with one code path.
- --resolve-s3 --output json discarded the auto-resolved bucket name (its
  progress output is redirected to devnull and print_managed_s3_bucket_info is
  skipped). Emit it as a {type: info, managed_s3_bucket: ...} stream line so a
  CI consumer can learn where its artifacts went.

Both mutation-verified.

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: 9101836..b1fe047
Files: 18 of 29 (10 unit-test files skipped per single-pass scope; schema/samcli.json is generated)
Comments: 2

Verified as addressed in this revision: the blanket handler in do_cli now opens before S3/ECR resolution, the duplicate result line on DeployFailedError is gone, --confirm-changeset + --output json is rejected up front, the logger downgrade is removed, build_dir/template_file are absolute, the express caveat is carried as "express": bool, the resolved --resolve-s3 bucket is surfaced as an info line, and the two commands now share type: "result" with lowercase statuses. The remaining stdout writers on the deploy path (_warn_preview_runtime, warn_parameter_based_collections, unsupported_command_cdk, manage_stack, progress bars) are now either on stderr, redirected, or suppressed — I traced the packaging/build paths (s3_uploader, ecr_uploader, app_builder, companion_stack_manager, version_checker) and found no remaining ungated stdout writes.

Comment thread samcli/commands/deploy/deploy_context.py Outdated
Comment thread samcli/commands/_utils/table_print.py Outdated
Two robustness findings:

- The confirmation_required JSON branch in deploy_context was unreachable:
  do_cli rejects --confirm-changeset with --output json up front, and sync (the
  only other DeployContext caller) never enables JSON, so confirm_changeset and
  JSON output cannot co-occur. Delete the branch (and its defense-in-depth test)
  so the status vocabulary carries no case a consumer can never see, and the
  real guard's location is not obscured.
- The pprint_column_names decorator silently fell through to table output for
  any output_mode it did not recognize, which would corrupt a JSON stream on a
  typo. Validate against OutputOption values and raise on anything unexpected.

Both mutation-verified.
Reduce test bloat without losing coverage:
- Merge the two deploy interactive-flag rejection tests (confirm-changeset,
  guided) into one, since both assert the same UsageError.
- Parameterize the two build architecture-reporting tests (absent -> x86_64,
  explicit arm64) into one.
- Drop the standalone TestWarnParameterBasedCollections stderr test that
  duplicated coverage now folded into the existing emits_warning test (which
  gained an err=True assertion).

Distinct-path tests (the three build failure paths, the experimental-flag
cases) are left separate on purpose - they exercise different branches.

Net -29 test lines, all suites green.

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: 9101836..3c760bd
Files: 18 of 29 (10 unit-test files skipped per single-pass scope; schema/samcli.json is generated)
Comments: 1

Comment thread samcli/lib/deploy/deployer.py Outdated
Deployer was the only participant that handled output mode as a bare string,
comparing the literal "json" in five places while every other module converts
once to OutputOption. That divergence was reachable via the public constructor:
DeployContext(output=OutputOption.json) gave the context the enum member but
left Deployer comparing that member against "json" (always false), so text
output would interleave with the JSON stream. Latent today (live callers pass
the click-normalized string) but exactly what the enum exists to prevent.

Convert at the boundary: __init__ stores OutputOption(output_mode), which raises
on an unrecognized value instead of silently degrading to table output; the
self.output_mode comparisons use ; the two decorated
methods still receive the string via kwargs (.value at the call site) so the
shared table_print decorator stays string-based. DeployContext now forwards the
enum and no longer keeps a redundant raw self.output. Tests added for default,
string normalization, enum passthrough, and invalid-value raise.

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: 9101836..3615a41
Files: 18 of 30 (11 unit-test files skipped per single-pass scope; schema/samcli.json is generated)
Comments: 2

Previously raised items verified as addressed in this revision: the blanket handler in do_cli now opens before S3/ECR resolution, the duplicate result line on DeployFailedError is gone, --confirm-changeset + --output json is rejected up front (and --no-confirm-changeset exists as an override), the logger downgrade is removed, build_dir/template_file are absolute, the express caveat is carried as "express": self.express, the resolved bucket is surfaced as an info line instead of discarded, Deployer normalizes the mode to OutputOption at the boundary, warn_parameter_based_collections and the CDK advisory now go to stderr on all paths, and the terminal result vocabulary/error shape now line up between build and deploy.

Comment thread samcli/commands/build/build_context.py Outdated
Comment thread samcli/commands/deploy/deploy_context.py
Skipping the mount-with-write prompt in JSON mode silently proceeded READ-only,
but that is not a viable default for workflows that REQUIRE a writable mount:
DOTNET_CLIPACKAGE_CONFIG (must_mount_with_write_in_container=True) writes build
artifacts into the source directory. A .NET container build with --output json
therefore failed later inside the container with a cryptic permission error and
a status:failure document that never mentioned the fix.

Factor the must_mount_with_write detection out of
prompt_user_to_enable_mount_with_write_if_needed into
resource_requiring_mount_with_write. In JSON mode, if a resource requires a
writable mount, raise a UserException naming the source dir and instructing
--mount-with WRITE; other workflows still use the READ-only default. Text mode
keeps the interactive prompt. Tests added, mutation-verified.

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: 9101836..32fdce3
Files: 18 of 30 (11 unit-test files skipped per single-pass scope; schema/samcli.json is generated)
Comments: 1

Previously raised items verified as addressed in this revision: the blanket handler in do_cli now opens before S3/ECR resolution and covers every failure path; the duplicate result line on DeployFailedError is gone; --confirm-changeset + --output json is rejected up front and the unreachable confirmation_required status is removed; the logger downgrade is gone; build_dir/template_file are absolute; the express stabilization caveat is carried as "express": self.express; the resolved bucket is surfaced as an info line instead of being discarded; Deployer and the table decorator now normalize/validate the mode instead of comparing bare strings; warn_parameter_based_collections, _warn_preview_runtime, the CDK notice, and the two interactive prompts all write to stderr; and the mount-with-write skip now fails with an actionable UserException. I also checked the remaining stdout writers reachable from sam deploy/sam build (manage_stack, S3Uploader, ECRUploader, ApplicationBuilder's stream writer, PackageContext.run, version_checker) — all are either gated, redirected, or already stderr-bound.

Comment thread samcli/commands/deploy/deploy_context.py
Text mode always reports the changeset id (MSG_SHOWCASE_CHANGESET), but in JSON
mode result["Id"] only reached stdout on the --no-execute-changeset path. On a
normal executed deploy the terminal success line carried only stack_name,
region, and express, so a consumer could not link to the changeset in the
console, re-describe it, or correlate the changeset stream lines with a CFN API
call. Add changeset_id to the success result line.

@aws-sam-tooling-bot aws-sam-tooling-bot 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.

Code Review Results

Reviewed: 9101836..9aa2d69
Files: 18 of 30 (11 unit-test files skipped per single-pass scope; schema/samcli.json is generated)
Comments: 1

Previously raised items verified as addressed in this revision: the blanket handler in do_cli now opens before S3/ECR resolution and covers every failure path; the duplicate result line on DeployFailedError is gone; --confirm-changeset + --output json is rejected up front and the unreachable confirmation_required status is removed; the samcli logger downgrade is gone; build_dir/template_file are absolute; the express caveat and changeset_id are now in the JSON result; the resolved managed bucket is surfaced as an info line instead of discarded; warn_parameter_based_collections, _warn_preview_runtime, unsupported_command_cdk, prompt_experimental and the mount-with-write prompt all write to stderr now; the mount-with-write case fails with an actionable UserException rather than silently downgrading; Deployer normalizes output_mode to OutputOption at the boundary; table_print validates the mode; and the build/deploy terminal result objects now share type, lowercase status, and the error: {type, message} shape.

Comment thread samcli/lib/deploy/deployer.py
Text mode surfaces CloudFormation's DetailedStatus (e.g. CONFIGURATION_COMPLETE,
VALIDATION_FAILED) appended to the status; the JSON event line dropped it. A
consumer saw several indistinguishable CREATE_IN_PROGRESS events for one
resource and, worse, lost VALIDATION_FAILED on failure paths - the case where it
matters most. Carry it as its own key (null when CFN omits it) so status stays a
clean enum. Adds the first JSON-mode test for describe_stack_events, covering
both the present and absent cases; mutation-verified.
@madhavdonthula1 madhavdonthula1 changed the title Feat/output json integration feat: output json integration Aug 7, 2026

@reedham-aws reedham-aws 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 only reviewed the build part, but that looks pretty good to me from a logic point of view. I do think some of the inline comments are kind of long and hard to read, so maybe that could use some cleanup. Sorry to be nitpicky on the comments, overall, great work! I will review the deploy part later.

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

Labels

area/build sam build command area/deploy sam deploy command area/schema JSON schema file pr/external stage/needs-triage Automatically applied to new issues and PRs, indicating they haven't been looked at.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants