Skip to content

Commit 4a90b53

Browse files
author
Mitchell
committed
feat: add CreateLocalDeployment IPC support (C + C++ + Rust)
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
1 parent 494a597 commit 4a90b53

14 files changed

Lines changed: 671 additions & 1 deletion

File tree

.cspell.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"**/CMakeLists.txt",
1414
"**/*.yml",
1515
"**/*.nix",
16-
"**/iwyu_mappings.yml"
16+
"**/iwyu_mappings.yml",
17+
"design/**"
1718
]
1819
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ result
88
result-*
99
compile_commands.json
1010
target/
11+
design/

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ The following Greengrass v2 IPC operations are currently supported by this SDK:
3131
- [UpdateThingShadow](https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-local-shadows.html#ipc-operation-updatethingshadow)
3232
- [DeleteThingShadow](https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-local-shadows.html#ipc-operation-deletethingshadow)
3333
- [ListNamedShadowsForThing](https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-local-shadows.html#ipc-operation-listnamedshadowsforthing)
34+
- [CreateLocalDeployment](https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-local-deployments-components.html#ipc-operation-createlocaldeployment)
35+
> For other fields, use the generic `call()` method.
3436
3537
## Sample Greengrass Components
3638

cpp/include/gg/ipc/client.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <utility>
2323

2424
extern "C" {
25+
#include <gg/ipc/client.h>
2526
#include <gg/ipc/types.h>
2627
}
2728

@@ -162,6 +163,23 @@ class Client {
162163
/// <https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-local-deployments-components.html#ipc-operation-restartcomponent>
163164
std::error_code restart_component(std::string_view component_name) noexcept;
164165

166+
/// Create a local deployment on the core device.
167+
/// Triggers a local deployment that can merge configuration into
168+
/// components, add/remove root components, and specify local
169+
/// recipe/artifact paths.
170+
///
171+
/// On Classic Nucleus: requires a dependency on `aws.greengrass.Cli` and an
172+
/// access-control policy allowing `aws.greengrass#CreateLocalDeployment`.
173+
/// On Nucleus Lite: handled natively by ggdeploymentd — no dependency or
174+
/// ACL required.
175+
/// See:
176+
/// <https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-local-deployments-components.html#ipc-operation-createlocaldeployment>
177+
std::error_code create_local_deployment(
178+
const GgCreateLocalDeploymentArgs &args,
179+
std::span<std::byte> deployment_id_mem = {},
180+
std::string_view *deployment_id = nullptr
181+
) noexcept;
182+
165183
/// Get component configuration value.
166184
/// Retrieves configuration for the specified key path.
167185
/// Pass empty span for complete config.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// aws-greengrass-component-sdk - Lightweight AWS IoT Greengrass SDK
2+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
#include <gg/ipc/client.hpp>
6+
#include <cstddef>
7+
#include <span>
8+
#include <string_view>
9+
#include <system_error>
10+
11+
extern "C" {
12+
#include <gg/ipc/client.h>
13+
}
14+
15+
namespace gg::ipc {
16+
17+
std::error_code Client::create_local_deployment(
18+
const GgCreateLocalDeploymentArgs &args,
19+
std::span<std::byte> deployment_id_mem,
20+
std::string_view *deployment_id
21+
) noexcept {
22+
GgBuffer id_mem = { reinterpret_cast<uint8_t *>(deployment_id_mem.data()),
23+
deployment_id_mem.size() };
24+
GgBuffer id_out = {};
25+
auto err = ggipc_create_local_deployment(
26+
&args, id_mem, deployment_id != nullptr ? &id_out : nullptr
27+
);
28+
if ((deployment_id != nullptr) && (err == GG_ERR_OK)) {
29+
*deployment_id
30+
= { reinterpret_cast<const char *>(id_out.data), id_out.len };
31+
}
32+
return err;
33+
}
34+
35+
}

include/gg/ipc/client.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,62 @@ GgError ggipc_update_state(GgComponentState state);
116116
/// <https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-local-deployments-components.html#ipc-operation-restartcomponent>
117117
GgError ggipc_restart_component(GgBuffer component_name);
118118

119+
/// Arguments for ggipc_create_local_deployment.
120+
/// All fields are optional; set GgObject fields to GG_OBJ_NULL and GgBuffer
121+
/// fields to (GgBuffer){0} when not used.
122+
typedef struct {
123+
/// Map of component name → configuration merge map. Must be GG_TYPE_MAP
124+
/// if provided (non-null).
125+
GgObject component_to_configuration;
126+
/// Map of component name → version string to add as root components.
127+
/// Must be GG_TYPE_MAP if provided.
128+
GgObject root_component_versions_to_add;
129+
/// List of component name buffers to remove from root components.
130+
/// Must be GG_TYPE_LIST if provided.
131+
GgObject root_components_to_remove;
132+
/// Path to a directory containing local recipe files.
133+
GgBuffer recipe_directory_path;
134+
/// Path to a directory containing local artifact files.
135+
GgBuffer artifacts_directory_path;
136+
/// Failure handling policy: "ROLLBACK" or "DO_NOTHING".
137+
GgBuffer failure_handling_policy;
138+
} GgCreateLocalDeploymentArgs;
139+
140+
/// Create a local deployment on the core device.
141+
/// Triggers a local deployment that can merge configuration into components,
142+
/// add/remove root components, and specify local recipe/artifact paths.
143+
///
144+
/// `args->component_to_configuration` — map whose keys are component names
145+
/// and whose values are maps of config to merge. Optional (set GG_OBJ_NULL
146+
/// to omit).
147+
/// `args->root_component_versions_to_add` — map of component name → version
148+
/// string to add as root components. Optional.
149+
/// `args->root_components_to_remove` — list of component name buffers to
150+
/// remove from root components. Optional.
151+
/// `args->recipe_directory_path` — path to local recipe directory. Optional.
152+
/// `args->artifacts_directory_path` — path to local artifact directory.
153+
/// Optional.
154+
/// `args->failure_handling_policy` — "ROLLBACK" or "DO_NOTHING". Optional.
155+
///
156+
/// If `deployment_id` is not NULL, on success it is populated with a view into
157+
/// `deployment_id_mem` holding the returned deployment id.
158+
/// `deployment_id_mem` must remain valid and unmodified while
159+
/// `deployment_id` is used.
160+
///
161+
/// On Classic Nucleus: requires a dependency on the `aws.greengrass.Cli`
162+
/// component and an `aws.greengrass.Cli` access-control policy allowing
163+
/// `aws.greengrass#CreateLocalDeployment`.
164+
/// On Nucleus Lite: CreateLocalDeployment is handled natively by ggdeploymentd
165+
/// — no Cli component dependency or ACL required.
166+
/// See:
167+
/// <https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-local-deployments-components.html#ipc-operation-createlocaldeployment>
168+
ACCESS(read_write, 3)
169+
GgError ggipc_create_local_deployment(
170+
const GgCreateLocalDeploymentArgs *args,
171+
GgBuffer deployment_id_mem,
172+
GgBuffer *deployment_id
173+
);
174+
119175
/// Get component configuration value.
120176
/// Retrieves configuration for the specified key path.
121177
/// Pass empty list for complete config.

misc/dictionary.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ ffat
1010
fileb
1111
flto
1212
fstrict
13+
ggdeploymentd
1314
ggipc
1415
greengrassv2
1516
idents

mock/gg/ipc/packet_sequences.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@ GgipcPacketSequence gg_test_restart_component_error_sequence(
8484
int32_t stream_id, GgBuffer component_name
8585
);
8686

87+
GgipcPacketSequence gg_test_create_local_deployment_accepted_sequence(
88+
int32_t stream_id,
89+
GgObject component_to_configuration,
90+
GgBuffer deployment_id
91+
);
92+
93+
GgipcPacketSequence gg_test_create_local_deployment_error_sequence(
94+
int32_t stream_id, GgObject component_to_configuration
95+
);
96+
97+
GgipcPacketSequence gg_test_create_local_deployment_versions_accepted_sequence(
98+
int32_t stream_id,
99+
GgObject root_component_versions_to_add,
100+
GgBuffer deployment_id
101+
);
102+
87103
// Shadow sequences
88104

89105
GgipcPacketSequence gg_test_shadow_update_accepted_sequence(

mock/packets/component_packet_sequences.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,101 @@ GgipcPacketSequence gg_test_restart_component_error_sequence(
114114
.len = 2
115115
};
116116
}
117+
118+
GgipcPacket gg_test_create_local_deployment_request_packet(
119+
int32_t stream_id, GgObject component_to_configuration
120+
) {
121+
static GgKV payload[1];
122+
payload[0]
123+
= gg_kv(GG_STR("componentToConfiguration"), component_to_configuration);
124+
size_t payload_len = sizeof(payload) / sizeof(payload[0]);
125+
126+
return (GgipcPacket) { .direction = CLIENT_TO_SERVER,
127+
.has_payload = true,
128+
.payload = gg_obj_map((GgMap) {
129+
.pairs = payload, .len = payload_len }),
130+
.headers = GG_IPC_REQUEST_HEADERS(
131+
stream_id, "aws.greengrass#CreateLocalDeployment"
132+
),
133+
.header_count = GG_IPC_REQUEST_HEADERS_COUNT };
134+
}
135+
136+
GgipcPacket gg_test_create_local_deployment_response_packet(
137+
int32_t stream_id, GgBuffer deployment_id
138+
) {
139+
static GgKV payload[1];
140+
payload[0] = gg_kv(GG_STR("deploymentId"), gg_obj_buf(deployment_id));
141+
size_t payload_len = sizeof(payload) / sizeof(payload[0]);
142+
143+
return (GgipcPacket) { .direction = SERVER_TO_CLIENT,
144+
.has_payload = true,
145+
.payload = gg_obj_map((GgMap) {
146+
.pairs = payload, .len = payload_len }),
147+
.headers = GG_IPC_ACCEPTED_HEADERS(
148+
stream_id, "aws.greengrass#CreateLocalDeployment"
149+
),
150+
.header_count = GG_IPC_ACCEPTED_HEADERS_COUNT };
151+
}
152+
153+
GgipcPacketSequence gg_test_create_local_deployment_accepted_sequence(
154+
int32_t stream_id,
155+
GgObject component_to_configuration,
156+
GgBuffer deployment_id
157+
) {
158+
return (GgipcPacketSequence) {
159+
.packets = { gg_test_create_local_deployment_request_packet(
160+
stream_id, component_to_configuration
161+
),
162+
gg_test_create_local_deployment_response_packet(
163+
stream_id, deployment_id
164+
) },
165+
.len = 2
166+
};
167+
}
168+
169+
GgipcPacketSequence gg_test_create_local_deployment_error_sequence(
170+
int32_t stream_id, GgObject component_to_configuration
171+
) {
172+
return (GgipcPacketSequence) {
173+
.packets = { gg_test_create_local_deployment_request_packet(
174+
stream_id, component_to_configuration
175+
),
176+
gg_test_ipc_permissions_error_packet(stream_id) },
177+
.len = 2
178+
};
179+
}
180+
181+
GgipcPacket gg_test_create_local_deployment_versions_request_packet(
182+
int32_t stream_id, GgObject root_component_versions_to_add
183+
) {
184+
static GgKV payload[1];
185+
payload[0] = gg_kv(
186+
GG_STR("rootComponentVersionsToAdd"), root_component_versions_to_add
187+
);
188+
size_t payload_len = sizeof(payload) / sizeof(payload[0]);
189+
190+
return (GgipcPacket) { .direction = CLIENT_TO_SERVER,
191+
.has_payload = true,
192+
.payload = gg_obj_map((GgMap) {
193+
.pairs = payload, .len = payload_len }),
194+
.headers = GG_IPC_REQUEST_HEADERS(
195+
stream_id, "aws.greengrass#CreateLocalDeployment"
196+
),
197+
.header_count = GG_IPC_REQUEST_HEADERS_COUNT };
198+
}
199+
200+
GgipcPacketSequence gg_test_create_local_deployment_versions_accepted_sequence(
201+
int32_t stream_id,
202+
GgObject root_component_versions_to_add,
203+
GgBuffer deployment_id
204+
) {
205+
return (GgipcPacketSequence) {
206+
.packets = { gg_test_create_local_deployment_versions_request_packet(
207+
stream_id, root_component_versions_to_add
208+
),
209+
gg_test_create_local_deployment_response_packet(
210+
stream_id, deployment_id
211+
) },
212+
.len = 2
213+
};
214+
}

mock/packets/packets.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,18 @@ GgipcPacket gg_test_restart_component_response_packet(
165165
int32_t stream_id, GgBuffer restart_status
166166
);
167167

168+
GgipcPacket gg_test_create_local_deployment_request_packet(
169+
int32_t stream_id, GgObject component_to_configuration
170+
);
171+
172+
GgipcPacket gg_test_create_local_deployment_response_packet(
173+
int32_t stream_id, GgBuffer deployment_id
174+
);
175+
176+
GgipcPacket gg_test_create_local_deployment_versions_request_packet(
177+
int32_t stream_id, GgObject root_component_versions_to_add
178+
);
179+
168180
// Shadow operations
169181

170182
GgipcPacket gg_test_shadow_update_request_packet(

0 commit comments

Comments
 (0)