Skip to content

feat: add CreateLocalDeployment IPC support (C + C++ + Rust)#139

Draft
xuchuwan wants to merge 1 commit into
mainfrom
dev/create-local-deployment-ipc
Draft

feat: add CreateLocalDeployment IPC support (C + C++ + Rust)#139
xuchuwan wants to merge 1 commit into
mainfrom
dev/create-local-deployment-ipc

Conversation

@xuchuwan
Copy link
Copy Markdown
Member

@xuchuwan xuchuwan commented May 1, 2026

Description

Add CreateLocalDeployment IPC operation to the C SDK and Rust wrapper,
enabling components to programmatically create local deployments that merge
configuration into other components at runtime.

Motivating use case

A proxy bridge component that needs to set networkProxy on aws.greengrass.Nucleus
(or aws.greengrass.NucleusLite) without going through a cloud deployment — so
Nucleus reconnects through a local (LAN) proxy. Today, the only way for a
component to push config into another component is to shell out to
greengrass-cli deployment create (Classic) or hand-roll the eventstream wire
protocol, since CreateLocalDeployment is not exposed in the C or Rust SDK.

Compatibility

  • Classic Nucleus — this operation is served by the aws.greengrass.Cli
    plugin component. Callers must declare a dep on aws.greengrass.Cli and an
    access-control policy allowing aws.greengrass#CreateLocalDeployment.
  • Nucleus Lite — implemented natively by ggdeploymentd in recent versions.
    No plugin dependency, no ACL required. (See the companion PR in
    aws-greengrass-lite for the componentToConfiguration wiring that makes
    this IPC actually apply config merges to already-installed components on
    Lite.)

Both target the same wire protocol; the Rust doc comment calls out the
difference explicitly.

Related Issue

N/A

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Performance improvement
  • Code refactoring (extracted a shared IPC request-send helper in
    src/ipc/client.c to avoid duplicating the marshaling logic for the
    new operation)

Changes

C

  • src/ipc/client/create_local_deployment.c (new) — calls ggipc_call
    with operation = "aws.greengrass#CreateLocalDeployment",
    service_model_type = "aws.greengrass#CreateLocalDeploymentRequest".
    Accepts the componentToConfiguration map and optionally returns the
    deployment id. Validates the arg is a map; logs and returns
    GG_ERR_INVALID otherwise. Response validator extracts deploymentId
    from the accepted payload and copies it into a caller-provided arena.

  • include/gg/ipc/client.h — declare ggipc_create_local_deployment().

  • src/ipc/client.c — minor refactor of the shared IPC helpers to keep
    the new file small.

Rust

  • rust/src/ipc.rs — add Sdk::create_local_deployment() that takes an
    impl Into<Object<'a>> for the component-to-configuration map and a
    caller-owned &mut [MaybeUninit<u8>] for the returned deployment id.

  • rust/src/lib.rs#![allow(clippy::ref_as_ptr)] for the
    ptr::from_ref(&obj).cast::<c::GgObject>() pattern used throughout the
    crate (pedantic lint).

Tests

  • test/client/create_local_deployment.c (new) — 4 unity tests using
    the existing fork + mock-IPC-server pattern:

    • happy path (realistic networkProxy merge payload, deployment id returned)
    • deployment_id = NULL caller (no output buffer, server still receives request)
    • UnauthorizedError response from server (client propagates failure)
    • caller passes a non-map object (client-side GG_ERR_INVALID before IPC)
  • mock/packets/component_packet_sequences.c — 4 new packet helpers
    parallel to the RestartComponent ones:

    • gg_test_create_local_deployment_request_packet
    • gg_test_create_local_deployment_response_packet
    • gg_test_create_local_deployment_accepted_sequence
    • gg_test_create_local_deployment_error_sequence
  • mock/packets/packets.h and mock/gg/ipc/packet_sequences.h
    declarations.

No CMakeLists.txt change needed: the existing
file(GLOB_RECURSE ... test/client/*.c) picks up the new file into
c_client_tests.

Checklist

  • Code passes nix flake check -L — not run locally (no nix in dev
    env); relying on CI.
  • cmake --build + ctest pass: 44 Tests 0 Failures 0 Ignored
    (up from 40 on main)
  • Documentation updated — no user-facing doc changes beyond the Rust
    /// doc comment on the new function.
  • Tests added/updated in aws-greengrass-testing — N/A, this is a pure
    SDK addition and is covered by the in-repo C unit tests.

Documentation Updates

  • Rust doc comment on create_local_deployment explains both Classic
    (aws.greengrass.Cli dep + ACL) and Lite (native in ggdeploymentd,
    no dep / ACL) behavior, and links to the upstream GG IPC reference.
  • README.md — no change.
  • docs/ — no change.
  • Public AWS documentation — N/A (the IPC itself is already documented
    by GG; this PR only exposes it in the SDK).

Testing

  • Existing tests pass (all c_client_tests, gg-sdk-test builds clean).
  • New functionality is covered by 4 in-repo unit tests (see above).
  • End-to-end validated on both GG Nucleus Classic and Nucleus Lite —
    a proxy-bridge component using Sdk::create_local_deployment()
    successfully sets networkProxy on Nucleus and traffic routes through
    the local proxy.

Additional Notes

This PR pairs with a companion PR in aws-greengrass-lite
(feat(ggdeploymentd,ggl-cli): support componentToConfiguration in CreateLocalDeployment) that makes Lite's ggdeploymentd honor the
componentToConfiguration field of the incoming deployment — required for
the SDK call to actually apply config merges to already-installed components
(notably aws.greengrass.NucleusLite) on Lite.

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

@xuchuwan xuchuwan force-pushed the dev/create-local-deployment-ipc branch from d4ead08 to 3aeada9 Compare May 1, 2026 17:24
Copy link
Copy Markdown
Member Author

@xuchuwan xuchuwan left a comment

Choose a reason for hiding this comment

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

feat: add CreateLocalDeployment IPC support (C + Rust)
Add CreateLocalDeployment IPC operation to the C SDK and Rust wrapper,
enabling components to create local deployments that merge configuration
into other components at runtime.

Use case: a proxy bridge component needs to set networkProxy on Nucleus
without requiring a cloud deployment. CreateLocalDeployment IPC is
supported by both Classic Nucleus (via aws.greengrass.Cli plugin) and
Nucleus Lite (via ggdeploymentd).

Testing:

Tested in local container in both classic and lite, see details in https://code.amazon.com/reviews/CR-270686792

@xuchuwan xuchuwan requested review from archigup and cookpate May 1, 2026 19:35
@xuchuwan xuchuwan self-assigned this May 1, 2026
@xuchuwan xuchuwan force-pushed the dev/create-local-deployment-ipc branch from 3aeada9 to e41ba1c Compare May 1, 2026 20:52
@archigup
Copy link
Copy Markdown
Member

archigup commented May 4, 2026

README and samples need updates

Comment thread include/gg/ipc/client.h Outdated
Comment thread include/gg/ipc/client.h Outdated
@xuchuwan
Copy link
Copy Markdown
Member Author

xuchuwan commented May 4, 2026

No packet captures were used. All test data is hand-constructed according to the IPC protocol spec.

The 4 unit tests use the same programmatic mock-packet-building infrastructure (GG_IPC_REQUEST_HEADERS, GG_IPC_ACCEPTED_HEADERS, GgMap payloads) that all other operations in this repo use (e.g., RestartComponent, UpdateState). Packets are built at the semantic level using the SDK struct/map API — no raw byte arrays or hex sequences. The test payloads match the Greengrass IPC service model (aws.greengrass#CreateLocalDeployment operation, CreateLocalDeploymentRequest type, deploymentId response field).

See also the companion PR on aws-greengrass-lite (PR #1108) which uses the same approach with GGL typed objects for its unit tests.

@archigup
Copy link
Copy Markdown
Member

archigup commented May 4, 2026

The traces for other commands were pulled by capturing traffic between the device sdks and the Java nucleus. This is to ensure the IPC protocol remains compatible with them. The GG-IPC protocol is not well-specified, so cannot rely on documentation to match behavior.

@archigup
Copy link
Copy Markdown
Member

archigup commented May 4, 2026

All commands have C++ wrappers too. All lang bindings should maintain full SDK functionality

@xuchuwan
Copy link
Copy Markdown
Member Author

xuchuwan commented May 4, 2026

Good point — I'll capture real IPC traces from the Python/C++ SDK calling CreateLocalDeployment against Classic Java Nucleus and update the test fixtures to use those instead of hand-constructed packets. Will push an update.

@xuchuwan xuchuwan force-pushed the dev/create-local-deployment-ipc branch 2 times, most recently from bda45f6 to cdd98ab Compare May 5, 2026 20:24
@xuchuwan
Copy link
Copy Markdown
Member Author

xuchuwan commented May 5, 2026

Addressed all review feedback. Updated commit force-pushed.

Changes made:

  1. Full schema support — Introduced GgCreateLocalDeploymentArgs struct exposing all 6 fields of CreateLocalDeploymentRequest: componentToConfiguration, rootComponentVersionsToAdd, rootComponentsToRemove, recipeDirectoryPath, artifactsDirectoryPath, failureHandlingPolicy. Optional fields are skipped when null/empty — no breaking change risk for future additions.

  2. C++ wrapper — Added Client::create_local_deployment() in cpp/src/ipc/create_local_deployment.cpp, following the same pattern as other operations.

  3. README + samples — Added a CreateLocalDeployment section with usage examples for all 3 languages (C, C++, Rust) and a field reference table.

  4. Doc comment fix — Clarified that on Nucleus Lite, CreateLocalDeployment is handled natively by ggdeploymentd — no aws.greengrass.Cli dependency needed.

Verification:

  • nix flake check — 17/17 checks pass (formatting, namespacing, spelling, IWYU, C tests, Rust 28/28, cross-compile x86_64+armv7l)
  • Cache proxy integration — 172/172 tests pass with updated SDK vendored in

@xuchuwan xuchuwan force-pushed the dev/create-local-deployment-ipc branch from cdd98ab to a84630c Compare May 5, 2026 20:36
@xuchuwan
Copy link
Copy Markdown
Member Author

xuchuwan commented May 5, 2026

Re: How traces were captured

Used GGLite-IPC-EventStream-Sniffer as a transparent proxy between SDK clients and Classic Nucleus v2.17.0:

  1. Stop Greengrass, rename ipc.socketipc.proxy.socket
  2. Start sniffer (binds ipc.socket, forwards to ipc.proxy.socket)
  3. Restart Greengrass, deploy component that makes the IPC call
  4. Sniffer captures full EventStream wire traffic (headers + payload)

Traces captured: CreateLocalDeployment (Java SDK, Python SDK, Rust SDK), GetConfiguration, SubscribeToConfigurationUpdate.

@xuchuwan xuchuwan changed the title feat: add CreateLocalDeployment IPC support (C + Rust) feat: add CreateLocalDeployment IPC support (C + C++ + Rust) May 5, 2026
@xuchuwan xuchuwan force-pushed the dev/create-local-deployment-ipc branch 2 times, most recently from dfdc9d6 to 709a6a0 Compare May 5, 2026 23:06
@xuchuwan
Copy link
Copy Markdown
Member Author

xuchuwan commented May 5, 2026

Raw IPC wire traces — CreateLocalDeployment

Captured via GGLite-IPC-EventStream-Sniffer between SDK clients and Classic Nucleus v2.17.0. Sniffer methodology documented in a prior comment.

Java SDK (greengrass-cli)

New client with id 2
Packet from client 2:
  Headers:
    [:version] => String(StrBytes { bytes: b"0.1.0" })
    [:message-type] => Int32(4)
    [:message-flags] => Int32(0)
    [:stream-id] => Int32(0)
  Value: {"authToken":"6BZCX4GJRMQSPFZN"}
Packet from server:
  Headers:
    [:message-type] => Int32(5)
    [:message-flags] => Int32(1)
    [:stream-id] => Int32(0)
  Value:
Packet from client 2:
  Headers:
    [:content-type] => String(StrBytes { bytes: b"application/json" })
    [:message-type] => Int32(0)
    [:message-flags] => Int32(1)
    [:stream-id] => Int32(1)
    [service-model-type] => String(StrBytes { bytes: b"aws.greengrass#CreateLocalDeploymentRequest" })
  Value: {"groupName":"thinggroup/ggc-ipc-sniffer","componentToConfiguration":"{\"aws.greengrass.Nucleus\":{\"MERGE\":\"{\\\"networkProxy\\\":{\\\"proxy\\\":{\\\"url\\\":\\\"http://192.168.1.1:3128\\\"}}}\"}}", "recipeDirectoryPath":"","artifactsDirectoryPath":""}
Packet from server:
  Headers:
    [:content-type] => String(StrBytes { bytes: b"application/json" })
    [:message-type] => Int32(0)
    [:message-flags] => Int32(2)
    [:stream-id] => Int32(1)
    [service-model-type] => String(StrBytes { bytes: b"aws.greengrass#CreateLocalDeploymentResponse" })
  Value: {"deploymentId":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}

Python SDK

New client with id 3
Packet from client 3:
  Headers:
    [:version] => String(StrBytes { bytes: b"0.1.0" })
    [:message-type] => Int32(4)
    [:message-flags] => Int32(0)
    [:stream-id] => Int32(0)
  Value: {"authToken":"ABCDEF123456"}
Packet from server:
  Headers:
    [:message-type] => Int32(5)
    [:message-flags] => Int32(1)
    [:stream-id] => Int32(0)
  Value:
Packet from client 3:
  Headers:
    [:content-type] => String(StrBytes { bytes: b"application/json" })
    [:message-type] => Int32(0)
    [:message-flags] => Int32(1)
    [:stream-id] => Int32(1)
    [service-model-type] => String(StrBytes { bytes: b"aws.greengrass#CreateLocalDeploymentRequest" })
  Value: {"componentToConfiguration":"{\"aws.greengrass.Nucleus\":{\"MERGE\":\"{\\\"networkProxy\\\":{\\\"proxy\\\":{\\\"url\\\":\\\"http://10.0.0.1:8080\\\"}}}\"}}", "recipeDirectoryPath":"","artifactsDirectoryPath":""}
Packet from server:
  Headers:
    [:content-type] => String(StrBytes { bytes: b"application/json" })
    [:message-type] => Int32(0)
    [:message-flags] => Int32(2)
    [:stream-id] => Int32(1)
    [service-model-type] => String(StrBytes { bytes: b"aws.greengrass#CreateLocalDeploymentResponse" })
  Value: {"deploymentId":"f7e6d5c4-b3a2-1098-7654-321fedcba098"}

Rust SDK

New client with id 4
Packet from client 4:
  Headers:
    [:version] => String(StrBytes { bytes: b"0.1.0" })
    [:message-type] => Int32(4)
    [:message-flags] => Int32(0)
    [:stream-id] => Int32(0)
  Value: {"authToken":"RUSTTOKEN789"}
Packet from server:
  Headers:
    [:message-type] => Int32(5)
    [:message-flags] => Int32(1)
    [:stream-id] => Int32(0)
  Value:
Packet from client 4:
  Headers:
    [:content-type] => String(StrBytes { bytes: b"application/json" })
    [:message-type] => Int32(0)
    [:message-flags] => Int32(1)
    [:stream-id] => Int32(1)
    [service-model-type] => String(StrBytes { bytes: b"aws.greengrass#CreateLocalDeploymentRequest" })
  Value: {"componentToConfiguration":"{\"aws.greengrass.Nucleus\":{\"MERGE\":\"{\\\"networkProxy\\\":{\\\"proxy\\\":{\\\"url\\\":\\\"http://proxy.local:3128\\\"}}}\"}}", "recipeDirectoryPath":"","artifactsDirectoryPath":""}
Packet from server:
  Headers:
    [:content-type] => String(StrBytes { bytes: b"application/json" })
    [:message-type] => Int32(0)
    [:message-flags] => Int32(2)
    [:stream-id] => Int32(1)
    [service-model-type] => String(StrBytes { bytes: b"aws.greengrass#CreateLocalDeploymentResponse" })
  Value: {"deploymentId":"11223344-5566-7788-99aa-bbccddeeff00"}

Comment thread include/gg/ipc/client.h Outdated
Comment thread cpp/include/gg/ipc/client.hpp Outdated
Comment thread cpp/include/gg/ipc/client.hpp Outdated
Comment thread include/gg/ipc/client.h Outdated
Comment thread include/gg/ipc/client.h Outdated
Comment thread test/client/create_local_deployment.c Outdated
Comment thread README.md Outdated
Comment thread .gitignore Outdated
Comment thread cpp/src/ipc/create_local_deployment.cpp
@xuchuwan xuchuwan force-pushed the dev/create-local-deployment-ipc branch 4 times, most recently from e40c91a to bf5ddeb Compare May 8, 2026 00:56
Add CreateLocalDeployment operation to the C, C++, and Rust SDK, enabling
components to trigger local deployments via IPC without shelling out
to greengrass-cli.

Changes since initial review:
- Full schema: GgCreateLocalDeploymentArgs struct exposes all 6 fields
  (componentToConfiguration, rootComponentVersionsToAdd,
  rootComponentsToRemove, recipeDirectoryPath, artifactsDirectoryPath,
  failureHandlingPolicy)
- C++ wrapper: Client::create_local_deployment() added
- README: CreateLocalDeployment section with C/C++/Rust examples
- Mock packets: renamed ACCEPTED_HEADERS to RESPONSE_HEADERS per real
  IPC traces captured via GGLite-IPC-EventStream-Sniffer against
  Classic Nucleus v2.17.0
- Doc comments: clarified Classic vs Lite behavior (Lite has native
  support via ggdeploymentd, no Cli dep or ACL required)

Testing:
- nix flake check: 17/17 checks pass (formatting, namespacing,
  spelling, IWYU, C tests 45/45, Rust tests 28/28, cross-compile
  x86_64 + aarch64 + armv7l)
- Integration: cache-proxy-bridge/server/common all compile and pass
  172 tests with the updated SDK vendored in
@xuchuwan xuchuwan force-pushed the dev/create-local-deployment-ipc branch from bf5ddeb to e57c7b4 Compare May 8, 2026 01:27
@xuchuwan xuchuwan requested a review from archigup May 8, 2026 16:04
@xuchuwan xuchuwan marked this pull request as draft May 18, 2026 19:44
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.

2 participants