@aws/nx-plugin-mcp 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/aws-nx-mcp.js +12 -0
- package/docs/get_started/tutorials/dungeon-game/2.mdx +4 -0
- package/docs/get_started/tutorials/dungeon-game/3.mdx +1 -1
- package/docs/guides/greengrass-component.mdx +769 -0
- package/generators.json +8 -0
- package/package.json +1 -1
- package/src/greengrass/component/schema.json +82 -0
package/bin/aws-nx-mcp.js
CHANGED
|
@@ -22201,6 +22201,7 @@ const NX_VERSION = {
|
|
|
22201
22201
|
"@aws-sdk/client-iam": "3.1152.0",
|
|
22202
22202
|
"@aws-sdk/client-bedrock-agentcore": "3.1152.0",
|
|
22203
22203
|
"@aws-sdk/client-bedrock-runtime": "3.1152.0",
|
|
22204
|
+
"@aws-sdk/client-greengrassv2": "3.1152.0",
|
|
22204
22205
|
"@aws-sdk/client-s3": "3.1152.0",
|
|
22205
22206
|
"@aws-sdk/client-sts": "3.1152.0",
|
|
22206
22207
|
"@aws-sdk/client-cognito-identity-provider": "3.1152.0",
|
|
@@ -22258,6 +22259,7 @@ const NX_VERSION = {
|
|
|
22258
22259
|
"@types/node": "26.6.5",
|
|
22259
22260
|
"@types/aws-lambda": "8.10.164",
|
|
22260
22261
|
"@types/cors": "2.8.19",
|
|
22262
|
+
"@types/js-yaml": "4.0.9",
|
|
22261
22263
|
"@types/pg": "8.23.1",
|
|
22262
22264
|
"@types/ws": "8.18.2",
|
|
22263
22265
|
"@types/express": "5.0.6",
|
|
@@ -22274,6 +22276,7 @@ const NX_VERSION = {
|
|
|
22274
22276
|
aws4fetch: "1.0.20",
|
|
22275
22277
|
"aws-cdk": "2.1145.0",
|
|
22276
22278
|
"aws-cdk-lib": "2.273.0",
|
|
22279
|
+
"aws-iot-device-sdk-v2": "1.28.0",
|
|
22277
22280
|
"aws-xray-sdk-core": "3.12.0",
|
|
22278
22281
|
constructs: "10.8.1",
|
|
22279
22282
|
cors: "2.8.6",
|
|
@@ -22296,6 +22299,7 @@ const NX_VERSION = {
|
|
|
22296
22299
|
husky: "9.1.7",
|
|
22297
22300
|
"fs-extra": "11.4.1",
|
|
22298
22301
|
"@types/fs-extra": "11.0.4",
|
|
22302
|
+
"js-yaml": "4.3.2",
|
|
22299
22303
|
mariadb: "3.5.4",
|
|
22300
22304
|
mise: "2026.10.14",
|
|
22301
22305
|
npm: "12.2.1",
|
|
@@ -55690,6 +55694,14 @@ const generatorsJsonEntries = {
|
|
|
55690
55694
|
"connection/react-agentcore-gateway"
|
|
55691
55695
|
]
|
|
55692
55696
|
},
|
|
55697
|
+
"greengrass#component": {
|
|
55698
|
+
"factory": "./src/greengrass/component/generator",
|
|
55699
|
+
"schema": "./src/greengrass/component/schema.json",
|
|
55700
|
+
"description": "Add an AWS IoT Greengrass component to an existing Python or TypeScript project",
|
|
55701
|
+
"metric": "g74",
|
|
55702
|
+
"guidePages": ["greengrass-component"],
|
|
55703
|
+
"experimental": true
|
|
55704
|
+
},
|
|
55693
55705
|
"license": {
|
|
55694
55706
|
"factory": "./src/license/generator",
|
|
55695
55707
|
"schema": "./src/license/schema.json",
|
|
@@ -174,6 +174,10 @@ The procedures and MCP server we just wrote import `@dungeon-adventure/dungeon-d
|
|
|
174
174
|
|
|
175
175
|
<NxCommands commands={["sync"]} />
|
|
176
176
|
|
|
177
|
+
Install dependencies to resolve the new workspace links and update the lockfile:
|
|
178
|
+
|
|
179
|
+
<InstallCommand />
|
|
180
|
+
|
|
177
181
|
<Aside type="tip" title="Why this step is needed">
|
|
178
182
|
`nx sync` declares `@dungeon-adventure/dungeon-db` in the `game-api` and `inventory` `package.json` files and adds the matching TypeScript project references for you, saving you from needing to add this by hand when declaring dependencies between projects.
|
|
179
183
|
</Aside>
|
|
@@ -48,7 +48,7 @@ If you encounter any lint errors, run the following command to automatically fix
|
|
|
48
48
|
</Aside>
|
|
49
49
|
|
|
50
50
|
<Aside type="tip" title="The workspace is out of sync">
|
|
51
|
-
If the build reports `The workspace is out of sync`, run `nx sync
|
|
51
|
+
If the build reports `The workspace is out of sync`, run `nx sync`, install dependencies, and build again.
|
|
52
52
|
</Aside>
|
|
53
53
|
|
|
54
54
|
### Chat with your Agent
|
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Greengrass Component
|
|
3
|
+
description: Add an AWS IoT Greengrass v2 component to an existing Python or TypeScript project, with reusable CDK or Terraform building blocks for publishing and deploying it
|
|
4
|
+
generator: greengrass#component
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
import { FileTree, Tabs, TabItem } from '@astrojs/starlight/components';
|
|
8
|
+
import ArchitectureDiagram from '@components/architecture-diagram.astro';
|
|
9
|
+
import GeneratorParameters from '@components/generator-parameters.astro';
|
|
10
|
+
import Infrastructure from '@components/infrastructure.astro';
|
|
11
|
+
import Link from '@components/link.astro';
|
|
12
|
+
import NxCommands from '@components/nx-commands.astro';
|
|
13
|
+
import OptionFilter from '@components/option-filter.astro';
|
|
14
|
+
import RunGenerator from '@components/run-generator.astro';
|
|
15
|
+
import Snippet from '@components/snippet.astro';
|
|
16
|
+
|
|
17
|
+
The `greengrass#component` generator adds an [AWS IoT Greengrass v2](https://docs.aws.amazon.com/greengrass/v2/developerguide/what-is-iot-greengrass.html) component to an existing Python or TypeScript project. It creates the recipe, the entrypoint, a unit test for Python components, and the Nx targets that package the component for a core device. With the default `infra`, it also vends reusable CDK constructs or Terraform modules that publish the component version, own an artifact repository, and describe a deployment. You compose those building blocks in your own infrastructure code; the generator never creates a device, a fleet or a deployment on its own.
|
|
18
|
+
|
|
19
|
+
The host project must already exist. Create one with <Link path="guides/python-project">`py#project`</Link> or <Link path="guides/typescript-project">`ts#project`</Link> first.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Generate a component
|
|
24
|
+
|
|
25
|
+
<RunGenerator generator="greengrass#component" requiredParameters={{ project: 'telemetry', name: 'pulse', language: 'python' }} />
|
|
26
|
+
|
|
27
|
+
Pass `language` explicitly in scripts and documentation. When it is omitted, the generator infers `typescript` from a `tsconfig.json` in the selected project, or `python` from a `pyproject.toml` there. A project with both files must pass `--language`; a project with neither is not a supported host.
|
|
28
|
+
|
|
29
|
+
`ipc` defaults to `false` for both languages. Pass `--ipc=true` to add Greengrass IPC access or `--ipc=false` to keep it disabled.
|
|
30
|
+
|
|
31
|
+
The recipe's `ComponentPublisher` defaults to the workspace npm scope. Pass `--publisher` to set another value. There is no Greengrass publisher setting in `aws-nx-plugin.config.mts`.
|
|
32
|
+
|
|
33
|
+
For `iac: terraform`, create a Terraform application with `terraform#project`
|
|
34
|
+
before running this generator. The Greengrass generator adds its `awscc`
|
|
35
|
+
provider configuration to that application. If you create the Terraform
|
|
36
|
+
application later, rerun `greengrass#component` so it can add the provider.
|
|
37
|
+
|
|
38
|
+
`--project` accepts the unqualified project name. Generated projects are namespace-qualified, so `nx run` needs the full name: `<scope>.telemetry` for a Python project and `@<scope>/telemetry` for a TypeScript project. The examples below write `<project>` for that name, and `@my-scope/infra` for an infrastructure project generated with `--name=infra` in a workspace whose npm scope is `my-scope`.
|
|
39
|
+
|
|
40
|
+
### Hardware and operating systems
|
|
41
|
+
|
|
42
|
+
`platform` selects the operating system and CPU architecture of the **core
|
|
43
|
+
device**, not its board name. The current targets are 64-bit Linux with glibc:
|
|
44
|
+
|
|
45
|
+
| Device | Generator target | Status |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| Linux PC, industrial gateway or EC2 x86_64 instance | `linux-amd64` | Supported; validated on an EC2 core |
|
|
48
|
+
| Raspberry Pi with a 64-bit CPU and 64-bit Linux, or another Linux ARM64 gateway | `linux-arm64` | Packaging is supported; Raspberry Pi hardware has not been validated |
|
|
49
|
+
| A fleet containing both of the above | `linux-amd64-arm64` | One component version with architecture-specific manifests |
|
|
50
|
+
| Raspberry Pi running 32-bit Linux / ARMv7 | None | Not supported by this generator, although the classic Greengrass nucleus supports ARMv7 |
|
|
51
|
+
| Raspberry Pi Pico, ESP32 or another microcontroller | None | Use as an IoT client or peripheral of a core device |
|
|
52
|
+
|
|
53
|
+
On a Raspberry Pi, check both `uname -m` and `getconf LONG_BIT`; this path needs
|
|
54
|
+
`aarch64` and `64`. A 64-bit processor running a 32-bit userspace is not an
|
|
55
|
+
ARM64 deployment target. The device also needs the Python interpreter named in
|
|
56
|
+
the generated recipe or Node.js for TypeScript, plus the
|
|
57
|
+
[Greengrass nucleus prerequisites](https://docs.aws.amazon.com/greengrass/v2/developerguide/greengrass-nucleus-component.html).
|
|
58
|
+
Python dependency vendoring targets `manylinux_2_28`, so use glibc 2.28 or later.
|
|
59
|
+
GPIO libraries and custom native dependencies must provide compatible binaries
|
|
60
|
+
for the selected platform. The generator does not provision those peripherals.
|
|
61
|
+
|
|
62
|
+
The generated Python recipe pins the interpreter to the host project's
|
|
63
|
+
`requires-python` floor, for example `python3.14`. Install that interpreter on
|
|
64
|
+
each core device that runs the component. A TypeScript component needs Node.js
|
|
65
|
+
on the core device.
|
|
66
|
+
|
|
67
|
+
For `linux-amd64-arm64`, the recipe contains one manifest per architecture.
|
|
68
|
+
Greengrass selects the first manifest whose `Platform` matches the core device,
|
|
69
|
+
so keep each manifest's artifact and environment settings paired with its
|
|
70
|
+
architecture.
|
|
71
|
+
|
|
72
|
+
ESP32 firmware runs separately from these Python and TypeScript components.
|
|
73
|
+
It can send MQTT messages to AWS IoT Core, or connect to a local Greengrass core
|
|
74
|
+
using [client device support](https://docs.aws.amazon.com/greengrass/v2/developerguide/interact-with-local-iot-devices.html).
|
|
75
|
+
The local route requires a broker, client authentication and MQTT bridge
|
|
76
|
+
configuration; this generator does not scaffold them or the firmware.
|
|
77
|
+
ARMv7, Windows, musl Linux and other CPU architectures require additional
|
|
78
|
+
packaging and runtime work. Changing the recipe's architecture label alone is
|
|
79
|
+
not sufficient.
|
|
80
|
+
|
|
81
|
+
### Options
|
|
82
|
+
|
|
83
|
+
<GeneratorParameters generator="greengrass#component" />
|
|
84
|
+
|
|
85
|
+
## Generator Output
|
|
86
|
+
|
|
87
|
+
<OptionFilter when={{ language: 'python' }} description="Python component layout">
|
|
88
|
+
<FileTree>
|
|
89
|
+
- greengrass
|
|
90
|
+
- pulse
|
|
91
|
+
- recipe.yaml the component recipe
|
|
92
|
+
- main.py the component entrypoint
|
|
93
|
+
- tests
|
|
94
|
+
- greengrass
|
|
95
|
+
- test_pulse.py a unit test that loads main.py by path
|
|
96
|
+
</FileTree>
|
|
97
|
+
|
|
98
|
+
The `pulse-vendor` target vendors the project's dependencies for the selected device platform with `uv pip install --only-binary :all:`. With `platform: linux-amd64-arm64`, it vendors once per architecture and the recipe carries one manifest per architecture.
|
|
99
|
+
</OptionFilter>
|
|
100
|
+
|
|
101
|
+
<OptionFilter when={{ language: 'typescript' }} description="TypeScript component layout">
|
|
102
|
+
<FileTree>
|
|
103
|
+
- greengrass
|
|
104
|
+
- pulse
|
|
105
|
+
- recipe.yaml the component recipe
|
|
106
|
+
- src
|
|
107
|
+
- greengrass
|
|
108
|
+
- pulse
|
|
109
|
+
- main.ts the component entrypoint
|
|
110
|
+
</FileTree>
|
|
111
|
+
|
|
112
|
+
The project's `bundle` target bundles the entrypoint before packaging. With `ipc: true`, the `pulse-vendor` target also stages the `aws-crt` native addon for the selected architecture.
|
|
113
|
+
</OptionFilter>
|
|
114
|
+
|
|
115
|
+
The recipe, the entrypoint and, for Python, the test are yours from the first run: a rerun never rewrites them. The targets, metadata and dependency declarations are framework-managed and converge on every run. The shared packaging scripts under `packages/common/scripts/src/greengrass/` are created once; delete that directory and rerun the generator to refresh them.
|
|
116
|
+
|
|
117
|
+
The targets use the kebab-case component name:
|
|
118
|
+
|
|
119
|
+
| Target | Purpose |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `<component>-vendor` | Vendors Python dependencies, or the TypeScript CRT addon when IPC is enabled |
|
|
122
|
+
| `<component>-artifact` | Writes the resolved recipe and the deployable archive under `dist/<project-root>/greengrass/<component>/greengrass-build/` |
|
|
123
|
+
| `<component>-deploy-local` | Creates a local deployment through `greengrass-cli` on a core device and waits for a settled component state |
|
|
124
|
+
| `<component>-logs` | Tails the component log on a core device |
|
|
125
|
+
|
|
126
|
+
`<component>-artifact` is a dependency of the host project's `build` and `assemble` targets, so a normal build produces the artifact.
|
|
127
|
+
|
|
128
|
+
## Infrastructure
|
|
129
|
+
|
|
130
|
+
<OptionFilter when={{ infra: 'component-version' }} description="Building blocks vended with infra=component-version">
|
|
131
|
+
<Snippet name="shared-constructs" />
|
|
132
|
+
|
|
133
|
+
The default `infra: component-version` adds the Greengrass core files below when they are missing and one app wrapper per component. Existing core files are preserved, so local edits survive later runs. It does not create a device, a thing group or a deployment.
|
|
134
|
+
|
|
135
|
+
<Infrastructure>
|
|
136
|
+
<Fragment slot="cdk">
|
|
137
|
+
<FileTree>
|
|
138
|
+
- packages/common/constructs/src
|
|
139
|
+
- core/greengrass
|
|
140
|
+
- artifact-bucket.ts `GreengrassArtifactBucket`: a retained, versioned, TLS-only repository with `grantReadTo`
|
|
141
|
+
- component-version.ts `GreengrassComponentVersion`: uploads the artifact and publishes the version
|
|
142
|
+
- deployment.ts `GreengrassDeployment`: the deployment, an optional thing group, and `addComponent`
|
|
143
|
+
- next-version.ts the deploy-time resolver for `NEXT_PATCH`, `NEXT_MINOR` and `NEXT_MAJOR`
|
|
144
|
+
- recipe.ts recipe reading and artifact URI substitution at synth time
|
|
145
|
+
- app/greengrass
|
|
146
|
+
- pulse.ts `Pulse`, a `GreengrassComponentVersion` pinned to this component's build output
|
|
147
|
+
</FileTree>
|
|
148
|
+
|
|
149
|
+
The shared constructs project's `build` and `assemble` targets depend on the host project's, so an infrastructure build or deploy always reads a fresh artifact.
|
|
150
|
+
</Fragment>
|
|
151
|
+
<Fragment slot="terraform">
|
|
152
|
+
<FileTree>
|
|
153
|
+
- packages/common/terraform/src
|
|
154
|
+
- core/greengrass
|
|
155
|
+
- artifact-bucket a retained, versioned, TLS-only repository with an optional token exchange role grant
|
|
156
|
+
- component-version uploads the artifact and publishes the version through `awscc_greengrassv2_component_version`
|
|
157
|
+
- deployment `awscc_greengrassv2_deployment` with an optional `aws_iot_thing_group`
|
|
158
|
+
- app/greengrass-component
|
|
159
|
+
- pulse a module wrapping `component-version` with this component's build paths and a `bucket_name` input
|
|
160
|
+
</FileTree>
|
|
161
|
+
|
|
162
|
+
Neither Greengrass resource exists in the `hashicorp/aws` provider. The modules use [`hashicorp/awscc`](https://registry.terraform.io/providers/hashicorp/awscc/latest/docs). When `iac: terraform` is selected, the Greengrass generator adds the `awscc` required provider and `provider "awscc" { region = var.aws_region }` to the Terraform application root if they are missing. Existing provider configuration is preserved.
|
|
163
|
+
</Fragment>
|
|
164
|
+
</Infrastructure>
|
|
165
|
+
|
|
166
|
+
#### Architecture
|
|
167
|
+
|
|
168
|
+
The component version publishes a built artifact into an explicitly owned repository. Your stack or root module grants the core devices' token exchange role read access and adds the version to one or more deployments. The default read grant is scoped to the workspace's component namespace; narrow it further when a role's devices only need a subset of those components.
|
|
169
|
+
|
|
170
|
+
<ArchitectureDiagram />
|
|
171
|
+
</OptionFilter>
|
|
172
|
+
|
|
173
|
+
<OptionFilter when={{ infra: 'none' }} description="Local-only component">
|
|
174
|
+
`infra: none` creates the recipe, the entrypoint, the Python test, the targets and the shared scripts only. No infrastructure project or shared constructs are created or changed. Rerun with `infra: component-version` later to add the infrastructure. A rerun cannot switch a component that already owns infrastructure back to `none`.
|
|
175
|
+
</OptionFilter>
|
|
176
|
+
|
|
177
|
+
## Build and run locally
|
|
178
|
+
|
|
179
|
+
Build the artifact on your development machine:
|
|
180
|
+
|
|
181
|
+
<NxCommands commands={['run <project>:pulse-artifact']} />
|
|
182
|
+
|
|
183
|
+
The output under `dist/<project-root>/greengrass/pulse/greengrass-build/` holds `recipes/` with the resolved recipe and `artifacts/<ComponentName>/<ComponentVersion>/` with the archives. A Python component builds one archive per manifest, named `<component>-<architecture>.zip` when it targets both architectures. A TypeScript component builds one archive that every manifest shares; with `ipc: true` it carries the `aws-crt` addon for each architecture and each manifest's `Setenv` selects its own. The `BUCKET_NAME`, `COMPONENT_NAME` and `COMPONENT_VERSION` placeholders in the recipe's artifact URI are substituted at publish time, and the archive basename must stay the one the `Run` step unarchives to.
|
|
184
|
+
|
|
185
|
+
The next two targets run on a machine with the classic Greengrass nucleus and `greengrass-cli` installed, not on an ordinary workstation:
|
|
186
|
+
|
|
187
|
+
<NxCommands commands={['run <project>:pulse-deploy-local', 'run <project>:pulse-logs']} />
|
|
188
|
+
|
|
189
|
+
`GREENGRASS_ROOT` defaults to `/greengrass/v2`. `pulse-deploy-local` submits a local deployment, then polls the component's lifecycle state and exits non-zero unless it stays `RUNNING` or `FINISHED` across three checks. A local deployment pins that version on the device and conflicts with a later cloud deployment of a different version; see [local and cloud deployments](#local-and-cloud-deployments).
|
|
190
|
+
|
|
191
|
+
<OptionFilter when={{ infra: 'component-version' }} description="Composing the vended building blocks into a deployment">
|
|
192
|
+
## Deploying
|
|
193
|
+
|
|
194
|
+
Deployment composition lives in your infrastructure project. The normal flow is one repository, one read grant, one app wrapper per component, and one deployment per target. Use one deployment for an existing thing group or thing and set its `targetArn`; create another deployment for a different target.
|
|
195
|
+
|
|
196
|
+
<Infrastructure>
|
|
197
|
+
<Fragment slot="cdk">
|
|
198
|
+
Create a CDK application if the workspace has none:
|
|
199
|
+
|
|
200
|
+
<RunGenerator generator="ts#infra" requiredParameters={{ name: 'infra' }} />
|
|
201
|
+
|
|
202
|
+
The token exchange role and the target thing group already exist in the account: the role is what your core devices assume, and the group is where they are registered. The stack imports both through CloudFormation parameters.
|
|
203
|
+
|
|
204
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
205
|
+
import {
|
|
206
|
+
GreengrassArtifactBucket,
|
|
207
|
+
GreengrassDeployment,
|
|
208
|
+
Pulse,
|
|
209
|
+
} from '@my-scope/common-constructs';
|
|
210
|
+
import { CfnParameter, Stack, type StackProps } from 'aws-cdk-lib';
|
|
211
|
+
import { Role } from 'aws-cdk-lib/aws-iam';
|
|
212
|
+
import type { Construct } from 'constructs';
|
|
213
|
+
|
|
214
|
+
export class ApplicationStack extends Stack {
|
|
215
|
+
constructor(scope: Construct, id: string, props?: StackProps) {
|
|
216
|
+
super(scope, id, props);
|
|
217
|
+
|
|
218
|
+
const tokenExchangeRoleArn = new CfnParameter(
|
|
219
|
+
this,
|
|
220
|
+
'TokenExchangeRoleArn',
|
|
221
|
+
{ type: 'String' },
|
|
222
|
+
);
|
|
223
|
+
const thingGroupArn = new CfnParameter(this, 'ThingGroupArn', {
|
|
224
|
+
type: 'String',
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// One explicitly owned repository, and the grant the devices need.
|
|
228
|
+
const artifacts = new GreengrassArtifactBucket(this, 'Artifacts');
|
|
229
|
+
const tokenExchangeRole = Role.fromRoleArn(
|
|
230
|
+
this,
|
|
231
|
+
'TokenExchangeRole',
|
|
232
|
+
tokenExchangeRoleArn.valueAsString,
|
|
233
|
+
);
|
|
234
|
+
artifacts.grantReadTo(tokenExchangeRole);
|
|
235
|
+
|
|
236
|
+
// The generated wrapper takes the repository construct itself.
|
|
237
|
+
const pulse = new Pulse(this, 'Pulse', { bucket: artifacts });
|
|
238
|
+
|
|
239
|
+
// addComponent records the resolved version and orders the deployment
|
|
240
|
+
// resource after the component version resource.
|
|
241
|
+
const fleet = new GreengrassDeployment(this, 'Fleet', {
|
|
242
|
+
targetArn: thingGroupArn.valueAsString,
|
|
243
|
+
});
|
|
244
|
+
fleet.addComponent(pulse);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Synchronize project references, bootstrap the account and Region once, then deploy. The `deploy` target runs `cdk deploy --require-approval=never` in the infrastructure project after `^assemble`, which rebuilds the component artifact first. The generated `main.ts` places every stack in a stage named after the project, `my-scope-infra-sandbox` for `@my-scope/infra`, so `cdk deploy` needs a selector for that stage. Take the stage name from `packages/infra/src/main.ts` and pass it with `/**` after `--`, together with the parameter values:
|
|
250
|
+
|
|
251
|
+
<NxCommands env={{ AWS_PROFILE: '<profile>', AWS_REGION: '<region>' }} commands={['sync', 'run @my-scope/infra:bootstrap', "run @my-scope/infra:deploy -- 'my-scope-infra-sandbox/**' --parameters TokenExchangeRoleArn=<role-arn> --parameters ThingGroupArn=<thing-group-arn>"]} />
|
|
252
|
+
|
|
253
|
+
`deploy` waits for full stabilization and accepts replacement updates. `deploy-sandbox` uses express mode, which refuses a replacement update unless you pass `--rollback`. Use `deploy` for routine revisions, or pass `--rollback` when using `deploy-sandbox`.
|
|
254
|
+
|
|
255
|
+
`fleet.addComponent(pulse)` is required, not decorative. With a fixed `ComponentVersion` the deployment's component map holds a literal string, so CloudFormation sees no dependency between the deployment and the component version and creates them in parallel. The IoT job can then reach devices before the version exists, and they fail with `FAILED_NO_STATE_CHANGE`. With `NEXT_PATCH` the map holds the component version resource's `ComponentVersion` attribute, and synth fails with a message naming `addComponent` if a raw `NEXT_*` string reaches the deployment.
|
|
256
|
+
|
|
257
|
+
#### Two components in one fleet
|
|
258
|
+
|
|
259
|
+
Generate a second component into the same or another project, then add both to one deployment. Overrides replace the named top-level field of that component's entry; the version always comes from the construct.
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
const pulse = new Pulse(this, 'Pulse', { bucket: artifacts });
|
|
263
|
+
const uploader = new Uploader(this, 'Uploader', { bucket: artifacts });
|
|
264
|
+
|
|
265
|
+
const cameras = new GreengrassDeployment(this, 'Cameras', {
|
|
266
|
+
thingGroupName: 'cameras',
|
|
267
|
+
});
|
|
268
|
+
cameras.addComponent(pulse);
|
|
269
|
+
cameras.addComponent(uploader, {
|
|
270
|
+
configurationUpdate: { merge: { uploadIntervalSeconds: 60 } },
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`thingGroupName` creates a new `AWS::IoT::ThingGroup`. It does not enroll devices; a device joins a group through its provisioning process. Use `targetArn` or `thingName` for a group or thing that already exists.
|
|
275
|
+
|
|
276
|
+
#### One component in two fleets
|
|
277
|
+
|
|
278
|
+
Register the same construct with two deployments. Each deployment carries its own configuration:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
new GreengrassDeployment(this, 'WestFleet', {
|
|
282
|
+
thingGroupName: 'west-fleet',
|
|
283
|
+
}).addComponent(pulse);
|
|
284
|
+
|
|
285
|
+
new GreengrassDeployment(this, 'EastFleet', {
|
|
286
|
+
thingGroupName: 'east-fleet',
|
|
287
|
+
}).addComponent(pulse, {
|
|
288
|
+
configurationUpdate: { merge: { sampleIntervalSeconds: 60 } },
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Adding the same construct twice to one deployment is a no-op unless the second call supplies overrides, which update the entry. Adding a different construct under the same component name to one deployment fails at construction time.
|
|
293
|
+
|
|
294
|
+
#### Public components and raw entries
|
|
295
|
+
|
|
296
|
+
Components you did not build in this workspace go into the `components` map. A published public component needs no dependency, because it already exists. A raw entry for a version created in the same stack, for example a hand-written `CfnComponentVersion`, needs the ordering edge you would otherwise get from `addComponent`:
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
import { CfnComponentVersion } from 'aws-cdk-lib/aws-greengrassv2';
|
|
300
|
+
|
|
301
|
+
const fleet = new GreengrassDeployment(this, 'Fleet', {
|
|
302
|
+
targetArn: thingGroupArn.valueAsString,
|
|
303
|
+
components: {
|
|
304
|
+
// Published by AWS; pick a version from
|
|
305
|
+
// `aws greengrassv2 list-component-versions`.
|
|
306
|
+
'aws.greengrass.LogManager': {
|
|
307
|
+
componentVersion: '2.3.8',
|
|
308
|
+
configurationUpdate: {
|
|
309
|
+
merge: {
|
|
310
|
+
logsUploaderConfiguration: {
|
|
311
|
+
systemLogsConfiguration: { uploadToCloudWatch: 'true' },
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
// Created in this stack, so the deployment must wait for it.
|
|
317
|
+
'com.example.Handwritten': { componentVersion: '1.2.0' },
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
fleet.addComponent(pulse);
|
|
321
|
+
|
|
322
|
+
const handwritten = new CfnComponentVersion(this, 'Handwritten', {
|
|
323
|
+
inlineRecipe: handwrittenRecipe,
|
|
324
|
+
});
|
|
325
|
+
fleet.deployment.node.addDependency(handwritten);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
`configurationUpdate.merge` is a plain object here. The construct serializes it to the JSON string CloudFormation expects.
|
|
329
|
+
|
|
330
|
+
#### Import an existing repository
|
|
331
|
+
|
|
332
|
+
One stack owns the repository. Every other stack imports it by name and adds its own grant in the owning stack, or attaches an equivalent `s3:GetObject` policy to the token exchange role itself:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
const artifacts = new GreengrassArtifactBucket(this, 'Artifacts', {
|
|
336
|
+
existingBucketName: 'central-greengrass-artifacts',
|
|
337
|
+
});
|
|
338
|
+
const pulse = new Pulse(this, 'Pulse', { bucket: artifacts });
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
An imported bucket keeps its own encryption, versioning and access settings. The resolver includes the bucket name in the content identity of a `NEXT_*` version, so moving a component to another repository allocates a new version for unchanged bytes.
|
|
342
|
+
</Fragment>
|
|
343
|
+
<Fragment slot="terraform">
|
|
344
|
+
Use the Terraform application created before running `greengrass#component`.
|
|
345
|
+
If you create it here instead, rerun the Greengrass component generator before
|
|
346
|
+
continuing so it can add the `awscc` provider configuration.
|
|
347
|
+
|
|
348
|
+
<RunGenerator generator="terraform#project" requiredParameters={{ name: 'infra', type: 'application' }} />
|
|
349
|
+
|
|
350
|
+
Declare the token exchange role as an input and set its value in the environment file the vended `plan` target reads with `-var-file=env/dev.tfvars`. The role is what your core devices assume, and it exists before this configuration runs:
|
|
351
|
+
|
|
352
|
+
```hcl title="packages/infra/src/variables.tf"
|
|
353
|
+
variable "token_exchange_role_arn" {
|
|
354
|
+
description = "ARN of the IAM role the core devices assume"
|
|
355
|
+
type = string
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
variable "thing_group_arn" {
|
|
359
|
+
description = "ARN of the existing thing group to deploy to"
|
|
360
|
+
type = string
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
```hcl title="packages/infra/src/env/dev.tfvars"
|
|
365
|
+
environment = "dev"
|
|
366
|
+
token_exchange_role_arn = "arn:aws:iam::123456789012:role/GreengrassCoreTokenExchangeRole"
|
|
367
|
+
thing_group_arn = "arn:aws:iot:us-east-1:123456789012:thinggroup/pumps"
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Reference the component module's outputs directly in the core deployment module's `components` map. The `component_version` output carries a `depends_on` onto the component version resource, so the deployment waits for the version to exist even when its number is known at plan time. No module-level `depends_on` is needed for this flow.
|
|
371
|
+
|
|
372
|
+
```hcl title="packages/infra/src/main.tf"
|
|
373
|
+
module "artifacts" {
|
|
374
|
+
source = "../../common/terraform/src/core/greengrass/artifact-bucket"
|
|
375
|
+
token_exchange_role_arn = var.token_exchange_role_arn
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
module "pulse" {
|
|
379
|
+
source = "../../common/terraform/src/app/greengrass-component/pulse"
|
|
380
|
+
bucket_name = module.artifacts.bucket_name
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
module "fleet" {
|
|
384
|
+
source = "../../common/terraform/src/core/greengrass/deployment"
|
|
385
|
+
target_arn = var.thing_group_arn
|
|
386
|
+
components = {
|
|
387
|
+
(module.pulse.component_name) = {
|
|
388
|
+
component_version = module.pulse.component_version
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Bootstrap the remote state once, then plan and apply. `plan` depends on `init`, `validate` and `assemble`, which rebuilds the component artifact first, and `apply` applies the plan file that `plan` wrote:
|
|
395
|
+
|
|
396
|
+
<NxCommands env={{ AWS_PROFILE: '<profile>', AWS_REGION: '<region>' }} commands={['run @my-scope/infra:bootstrap', 'run @my-scope/infra:plan', 'run @my-scope/infra:apply']} />
|
|
397
|
+
|
|
398
|
+
`thing_group_name` creates a new `aws_iot_thing_group`; it does not enroll devices. Use `target_arn` for a group or thing that already exists, or `thing_name` for one thing. A literal `target_arn` is checked against the current partition, account and Region at plan time.
|
|
399
|
+
|
|
400
|
+
Terraform uses an explicit semantic version in the component recipe. Choose a
|
|
401
|
+
new version for each changed component because published versions are
|
|
402
|
+
immutable. Terraform does not resolve the plugin's `NEXT_*` sentinels or call
|
|
403
|
+
Greengrass APIs to select a version; its `awscc` provider uses the credentials
|
|
404
|
+
and Region configured for Terraform.
|
|
405
|
+
|
|
406
|
+
The `plan` target runs `assemble` and records the planned artifact digests.
|
|
407
|
+
Keep the recipe and build output unchanged between `plan` and `apply`. If
|
|
408
|
+
either changes, rerun `plan` and apply the new plan so the published bytes
|
|
409
|
+
match the planned version and object key.
|
|
410
|
+
|
|
411
|
+
#### Staged rollouts and retained versions
|
|
412
|
+
|
|
413
|
+
Bumping the recipe version replaces Terraform's managed component version and
|
|
414
|
+
deletes the outgoing version from the Greengrass registry. Changed artifact
|
|
415
|
+
content also replaces its S3 keys and deletes the outgoing objects. A version
|
|
416
|
+
or recipe change with unchanged artifact bytes can reuse the same keys.
|
|
417
|
+
Deployments that still reference a deleted version fail, including a second
|
|
418
|
+
thing group left on the previous version. `create_before_destroy` orders
|
|
419
|
+
resource creation; it does not retain old versions or wait for every device
|
|
420
|
+
to finish its rollout.
|
|
421
|
+
|
|
422
|
+
Retain the entire publishing module before a staged rollout, including its
|
|
423
|
+
component version and S3 object resources. Retaining only the version leaves
|
|
424
|
+
its artifacts subject to deletion. `prevent_destroy` is not a substitute:
|
|
425
|
+
it rejects version replacements instead of keeping the outgoing version.
|
|
426
|
+
|
|
427
|
+
First remove the existing `module "pulse"` block and replace every deployment
|
|
428
|
+
reference to its outputs with the currently published name and version.
|
|
429
|
+
Keep the shared `artifacts` module, deployment addresses, other components and
|
|
430
|
+
fleet configuration. For example, these two existing groups remain on `1.0.0`
|
|
431
|
+
while the publisher leaves Terraform management:
|
|
432
|
+
|
|
433
|
+
```hcl title="packages/infra/src/main.tf"
|
|
434
|
+
removed {
|
|
435
|
+
from = module.pulse
|
|
436
|
+
lifecycle {
|
|
437
|
+
destroy = false
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
module "fleet" {
|
|
442
|
+
source = "../../common/terraform/src/core/greengrass/deployment"
|
|
443
|
+
target_arn = var.thing_group_arn
|
|
444
|
+
components = {
|
|
445
|
+
"com.my-scope.Pulse" = { component_version = "1.0.0" }
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
module "stable_fleet" {
|
|
450
|
+
source = "../../common/terraform/src/core/greengrass/deployment"
|
|
451
|
+
target_arn = "arn:aws:iot:us-east-1:123456789012:thinggroup/stable"
|
|
452
|
+
components = {
|
|
453
|
+
"com.my-scope.Pulse" = { component_version = "1.0.0" }
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Use your actual component name, version and existing group ARNs. Plan and
|
|
459
|
+
apply this retention step separately. The plan must forget the publisher's
|
|
460
|
+
version and object resources without destroying them or revising the fleets:
|
|
461
|
+
|
|
462
|
+
<NxCommands env={{ AWS_PROFILE: '<profile>', AWS_REGION: '<region>' }} commands={['run @my-scope/infra:plan', 'run @my-scope/infra:apply']} />
|
|
463
|
+
|
|
464
|
+
Only after that apply, change `ComponentVersion` in the user-owned recipe to a
|
|
465
|
+
new explicit version such as `1.0.1`. Add a publisher under a new module
|
|
466
|
+
address and advance the pilot fleet; keep the stable fleet's literal
|
|
467
|
+
`1.0.0` entry from above:
|
|
468
|
+
|
|
469
|
+
```hcl title="packages/infra/src/main.tf"
|
|
470
|
+
module "pulse_v2" {
|
|
471
|
+
source = "../../common/terraform/src/app/greengrass-component/pulse"
|
|
472
|
+
bucket_name = module.artifacts.bucket_name
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
module "fleet" {
|
|
476
|
+
source = "../../common/terraform/src/core/greengrass/deployment"
|
|
477
|
+
target_arn = var.thing_group_arn
|
|
478
|
+
components = {
|
|
479
|
+
(module.pulse_v2.component_name) = {
|
|
480
|
+
component_version = module.pulse_v2.component_version
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Plan again to rebuild the artifact and publish the new version, then apply.
|
|
487
|
+
After verifying the pilot devices, update `stable_fleet` to the same publisher
|
|
488
|
+
outputs and plan/apply that promotion. The generated wrapper reads the current
|
|
489
|
+
build output; adding another instance of it does not preserve an older build.
|
|
490
|
+
|
|
491
|
+
Repeat the separate retention step for `pulse_v2` before replacing or removing
|
|
492
|
+
it while any group or rollback can need its version. Unchanged bytes can share
|
|
493
|
+
S3 keys across retained versions, so a later publisher can manage an object
|
|
494
|
+
that an older version still references. Keep an inventory of retained versions
|
|
495
|
+
and their artifact keys; delete a key only when no retained version, deployment
|
|
496
|
+
or rollback needs it. Forgetting resources keeps them available but transfers
|
|
497
|
+
their cleanup to you.
|
|
498
|
+
|
|
499
|
+
#### Two components in one fleet
|
|
500
|
+
|
|
501
|
+
```hcl
|
|
502
|
+
module "uploader" {
|
|
503
|
+
source = "../../common/terraform/src/app/greengrass-component/uploader"
|
|
504
|
+
bucket_name = module.artifacts.bucket_name
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
module "cameras" {
|
|
508
|
+
source = "../../common/terraform/src/core/greengrass/deployment"
|
|
509
|
+
thing_group_name = "cameras"
|
|
510
|
+
components = {
|
|
511
|
+
(module.pulse.component_name) = {
|
|
512
|
+
component_version = module.pulse.component_version
|
|
513
|
+
}
|
|
514
|
+
(module.uploader.component_name) = {
|
|
515
|
+
component_version = module.uploader.component_version
|
|
516
|
+
configuration_update = {
|
|
517
|
+
merge = jsonencode({ uploadIntervalSeconds = 60 })
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
`configuration_update.merge` must already be a JSON string, so wrap it in `jsonencode`. This differs from the CDK construct, which takes an object.
|
|
525
|
+
|
|
526
|
+
#### One component in two fleets
|
|
527
|
+
|
|
528
|
+
Two deployment modules reference the same component outputs:
|
|
529
|
+
|
|
530
|
+
```hcl
|
|
531
|
+
module "west_fleet" {
|
|
532
|
+
source = "../../common/terraform/src/core/greengrass/deployment"
|
|
533
|
+
thing_group_name = "west-fleet"
|
|
534
|
+
components = {
|
|
535
|
+
(module.pulse.component_name) = {
|
|
536
|
+
component_version = module.pulse.component_version
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
module "east_fleet" {
|
|
542
|
+
source = "../../common/terraform/src/core/greengrass/deployment"
|
|
543
|
+
thing_group_name = "east-fleet"
|
|
544
|
+
components = {
|
|
545
|
+
(module.pulse.component_name) = {
|
|
546
|
+
component_version = module.pulse.component_version
|
|
547
|
+
configuration_update = {
|
|
548
|
+
merge = jsonencode({ sampleIntervalSeconds = 60 })
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
#### Public components and literal versions
|
|
556
|
+
|
|
557
|
+
A published public component is a literal entry and needs no dependency. A literal version for a resource created in the same apply, for example a hand-written `awscc_greengrassv2_component_version`, is the one case for a module-level `depends_on`. With `depends_on`, Terraform cannot read the deployment module's identity data sources until the dependency exists, so its target checks are deferred from plan to apply. They still fail before the deployment is created.
|
|
558
|
+
|
|
559
|
+
```hcl
|
|
560
|
+
module "fleet" {
|
|
561
|
+
source = "../../common/terraform/src/core/greengrass/deployment"
|
|
562
|
+
target_arn = var.thing_group_arn
|
|
563
|
+
components = {
|
|
564
|
+
"aws.greengrass.LogManager" = {
|
|
565
|
+
component_version = "2.3.8"
|
|
566
|
+
configuration_update = {
|
|
567
|
+
merge = jsonencode({
|
|
568
|
+
logsUploaderConfiguration = {
|
|
569
|
+
systemLogsConfiguration = { uploadToCloudWatch = "true" }
|
|
570
|
+
}
|
|
571
|
+
})
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
"com.example.Handwritten" = {
|
|
575
|
+
component_version = "1.2.0"
|
|
576
|
+
}
|
|
577
|
+
(module.pulse.component_name) = {
|
|
578
|
+
component_version = module.pulse.component_version
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
# Only for the literal version created by awscc_greengrassv2_component_version.handwritten.
|
|
583
|
+
depends_on = [awscc_greengrassv2_component_version.handwritten]
|
|
584
|
+
}
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
#### Import an existing repository
|
|
588
|
+
|
|
589
|
+
Set `existing_bucket_name` on the `artifact-bucket` module. The module then manages neither the bucket's settings nor its policy, so grant the token exchange role `s3:GetObject` on the artifact prefix in the configuration that owns the bucket:
|
|
590
|
+
|
|
591
|
+
```hcl
|
|
592
|
+
module "artifacts" {
|
|
593
|
+
source = "../../common/terraform/src/core/greengrass/artifact-bucket"
|
|
594
|
+
existing_bucket_name = "central-greengrass-artifacts"
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
</Fragment>
|
|
598
|
+
</Infrastructure>
|
|
599
|
+
|
|
600
|
+
### Verify on the device
|
|
601
|
+
|
|
602
|
+
A completed stack update or apply proves that the deployment resource exists. A deployment and its IoT job can both report `SUCCEEDED` while the component on the device is `BROKEN`. Check the device and the component state after every deployment:
|
|
603
|
+
|
|
604
|
+
```bash
|
|
605
|
+
aws --profile <profile> --region <region> greengrassv2 get-core-device \
|
|
606
|
+
--core-device-thing-name <thing-name> --query status
|
|
607
|
+
aws --profile <profile> --region <region> greengrassv2 list-installed-components \
|
|
608
|
+
--core-device-thing-name <thing-name>
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
Use `<component>-logs` to inspect the process log if the device reports a broken state.
|
|
612
|
+
</OptionFilter>
|
|
613
|
+
|
|
614
|
+
## Recipes and IPC
|
|
615
|
+
|
|
616
|
+
<Tabs syncKey="greengrass-language">
|
|
617
|
+
<TabItem label="Python" _filter={{ language: 'python' }}>
|
|
618
|
+
With the default `ipc: false`, `main.py` logs a line every 30 seconds and the
|
|
619
|
+
recipe has no IPC access control. With `ipc: true`, it publishes a heartbeat to
|
|
620
|
+
the component's own local topic every 30 seconds through
|
|
621
|
+
`GreengrassCoreIPCClientV2`:
|
|
622
|
+
|
|
623
|
+
```python title="greengrass/pulse/main.py"
|
|
624
|
+
client.publish_to_topic(
|
|
625
|
+
topic=TOPIC,
|
|
626
|
+
publish_message=PublishMessage(json_message=JsonMessage(message={"sequence": sequence})),
|
|
627
|
+
)
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
The recipe grants `aws.greengrass#PublishToTopic` on
|
|
631
|
+
`<ComponentName>/heartbeat` under `aws.greengrass.ipc.pubsub`. Local pub/sub
|
|
632
|
+
reaches other components on the same core device, not AWS IoT Core. To publish
|
|
633
|
+
to AWS IoT Core, call `publish_to_iot_core`, grant
|
|
634
|
+
`aws.greengrass#PublishToIoTCore` under `aws.greengrass.ipc.mqttproxy` in the
|
|
635
|
+
recipe, and give the device's IoT policy `iot:Publish` on the exact topic.
|
|
636
|
+
</TabItem>
|
|
637
|
+
<TabItem label="TypeScript" _filter={{ language: 'typescript' }}>
|
|
638
|
+
With the default `ipc: false`, `main.ts` logs a line every 30 seconds and the
|
|
639
|
+
recipe has no IPC access control. With `ipc: true`, it publishes a heartbeat to
|
|
640
|
+
the component's own local topic through the AWS IoT Device SDK's Greengrass IPC
|
|
641
|
+
client, and `pulse-vendor` stages the `aws-crt` native addon for the selected
|
|
642
|
+
architecture. Publishing to AWS IoT Core uses the same client's
|
|
643
|
+
`publishToIoTCore` operation, an `aws.greengrass.ipc.mqttproxy` authorization
|
|
644
|
+
in the recipe, and `iot:Publish` in the device's IoT policy.
|
|
645
|
+
</TabItem>
|
|
646
|
+
</Tabs>
|
|
647
|
+
|
|
648
|
+
Access control in the recipe is enforced by the nucleus on the device. Depending on the SDK alone authorizes nothing, so widen `operations` and `resources` as the component grows.
|
|
649
|
+
|
|
650
|
+
The generated recipes target the classic `aws.greengrass.Nucleus`. [Nucleus Lite](https://docs.aws.amazon.com/greengrass/v2/developerguide/greengrass-nucleus-lite-component.html) implements a subset of the IPC operations, does not run plugin components, and gained container support only in v2.15. Verify the recipe against the nucleus your fleet runs; the generator does not detect nucleus capabilities.
|
|
651
|
+
|
|
652
|
+
On Ubuntu 25.10 and later, `sudo-rs` can prevent IPC components from starting with Greengrass Nucleus 2.18.3 because the IPC environment is lost. The generator does not change the host's `sudo` security configuration. Use a device setup that provides the classic `sudo` behavior when IPC is required.
|
|
653
|
+
|
|
654
|
+
## Automatic versioning
|
|
655
|
+
|
|
656
|
+
For CDK deployments, set `ComponentVersion` in `recipe.yaml` to `NEXT_PATCH`,
|
|
657
|
+
`NEXT_MINOR` or `NEXT_MAJOR` to ask the plugin's resolver to allocate a version
|
|
658
|
+
from the highest strict semantic version already published in the account and
|
|
659
|
+
Region:
|
|
660
|
+
|
|
661
|
+
| Sentinel | Highest published `1.4.2` becomes | Nothing published yet |
|
|
662
|
+
| --- | --- | --- |
|
|
663
|
+
| `NEXT_PATCH` | `1.4.3` | `1.0.0` |
|
|
664
|
+
| `NEXT_MINOR` | `1.5.0` | `1.0.0` |
|
|
665
|
+
| `NEXT_MAJOR` | `2.0.0` | `1.0.0` |
|
|
666
|
+
|
|
667
|
+
The first version is `1.0.0`. The resolver reuses the highest published version
|
|
668
|
+
when its content tag matches and the version is deployable; otherwise it
|
|
669
|
+
publishes the next version selected by the sentinel. The tag covers the recipe
|
|
670
|
+
without its version line, the artifact bytes, and the artifact bucket name.
|
|
671
|
+
Unchanged content therefore reuses a version, while changing the content or
|
|
672
|
+
bucket publishes a new one. The generator never rewrites `recipe.yaml`.
|
|
673
|
+
|
|
674
|
+
`NEXT_*` is an Nx plugin convention, not a Greengrass version. CDK resolves the
|
|
675
|
+
sentinel during deployment, and `<project>:pulse-deploy-local` resolves it
|
|
676
|
+
against versions available on the device. GDK uses `NEXT_PATCH` as a setting in
|
|
677
|
+
`gdk-config.json`, not as a recipe field, and does not support `NEXT_MINOR` or
|
|
678
|
+
`NEXT_MAJOR`. The plugin does not invoke GDK.
|
|
679
|
+
|
|
680
|
+
Terraform requires an explicit semantic version. Each changed artifact needs a
|
|
681
|
+
new `ComponentVersion` because Greengrass versions are immutable. Terraform
|
|
682
|
+
does not resolve `NEXT_*`.
|
|
683
|
+
|
|
684
|
+
The CDK resolver runs during CloudFormation deployment under a generated Lambda
|
|
685
|
+
execution role, scoped to the stack's component names. Do not run concurrent publishes for the same component:
|
|
686
|
+
deployments can select the same next version, causing one publish to fail.
|
|
687
|
+
|
|
688
|
+
## Artifact storage and retention
|
|
689
|
+
|
|
690
|
+
Use one artifact repository per account and Region, owned by one stack or root
|
|
691
|
+
module. Import it elsewhere with `existingBucketName` in CDK or
|
|
692
|
+
`existing_bucket_name` in Terraform. The generated bucket is versioned,
|
|
693
|
+
encrypted with SSE-S3, private, and requires TLS. Greengrass artifacts are not
|
|
694
|
+
CDK assets; `cdk gc` can remove an asset still needed by a component version.
|
|
695
|
+
|
|
696
|
+
Both IaC paths use the same S3 object key layout:
|
|
697
|
+
`<ComponentName>/<first 32 hex characters of sha256>/<file>`. The digest hashes
|
|
698
|
+
sorted artifact names and their full SHA-256 hashes, using the same formula
|
|
699
|
+
for one or multiple artifacts. Only the key prefix is shortened; artifact
|
|
700
|
+
digests and automatic version identities retain the full hashes.
|
|
701
|
+
CDK's bucket-deployment tag limits the prefix to 102 characters, leaving 68
|
|
702
|
+
characters for the component name. The generator checks that limit when CDK
|
|
703
|
+
infrastructure is selected, and synthesis checks edits to the recipe.
|
|
704
|
+
Terraform's object uploads do not have this CDK limit.
|
|
705
|
+
|
|
706
|
+
Keep published artifacts available while a deployment or rollback can need
|
|
707
|
+
their component versions. CDK retains replaced component versions and their
|
|
708
|
+
uploads by default. Terraform deletes replaced versions and outgoing object
|
|
709
|
+
keys; see [staged rollouts and retained versions](#staged-rollouts-and-retained-versions)
|
|
710
|
+
before bumping a version used by multiple groups. Removing a registry version
|
|
711
|
+
does not uninstall components already running on devices.
|
|
712
|
+
Terraform's repository sets `prevent_destroy`; remove that literal lifecycle
|
|
713
|
+
setting only for deliberate teardown after its artifacts are no longer needed.
|
|
714
|
+
|
|
715
|
+
The default token exchange role grant allows `s3:GetObject` in the workspace's
|
|
716
|
+
component namespace. For a narrower grant, set `keyPrefix` in CDK or
|
|
717
|
+
`token_exchange_key_prefix` in Terraform. An imported bucket keeps its own
|
|
718
|
+
policy, so manage the grant where the bucket or role is owned.
|
|
719
|
+
|
|
720
|
+
## Deployment revisions
|
|
721
|
+
|
|
722
|
+
Changing a component version or deployment configuration replaces the
|
|
723
|
+
deployment resource. CloudFormation allows up to 10 deployment resources to be
|
|
724
|
+
added or deleted in one stack operation; split a large rollout into batches.
|
|
725
|
+
Use the regular `deploy` target for replacements. `deploy-sandbox` uses
|
|
726
|
+
CloudFormation express mode, which requires `--rollback` for a replacement
|
|
727
|
+
update.
|
|
728
|
+
|
|
729
|
+
Deleting the deployment resource removes cloud management state but does not
|
|
730
|
+
uninstall the component from a device. First deploy a revision that omits the
|
|
731
|
+
component, wait for it to disappear from `list-installed-components`, and then
|
|
732
|
+
remove its IaC resources.
|
|
733
|
+
|
|
734
|
+
## Local and cloud deployments
|
|
735
|
+
|
|
736
|
+
A local deployment from `<component>-deploy-local` pins a component version on
|
|
737
|
+
that device. A cloud deployment that requests a different version cannot
|
|
738
|
+
satisfy the pin. Remove the local deployment before updating that device from
|
|
739
|
+
the cloud:
|
|
740
|
+
|
|
741
|
+
```bash
|
|
742
|
+
sudo /greengrass/v2/bin/greengrass-cli deployment create --remove com.example.Pulse
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
Use local deployments for development devices and cloud deployments for fleet
|
|
746
|
+
targets.
|
|
747
|
+
|
|
748
|
+
## Troubleshooting and service limits
|
|
749
|
+
|
|
750
|
+
After a cloud deployment, check both the Greengrass deployment status and the
|
|
751
|
+
component's device state:
|
|
752
|
+
|
|
753
|
+
```bash
|
|
754
|
+
aws --profile <profile> --region <region> greengrassv2 get-core-device \
|
|
755
|
+
--core-device-thing-name <thing-name> --query status
|
|
756
|
+
aws --profile <profile> --region <region> greengrassv2 list-installed-components \
|
|
757
|
+
--core-device-thing-name <thing-name>
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
A job can report `SUCCEEDED` while a component is `BROKEN`. Use
|
|
761
|
+
`<component>-logs` to inspect its process log. IPC components on Ubuntu 25.10
|
|
762
|
+
and later can also fail under `sudo-rs` with Nucleus 2.18.3; the generator
|
|
763
|
+
leaves the host's security settings alone.
|
|
764
|
+
|
|
765
|
+
Greengrass limits one component to 5,000 versions per Region and
|
|
766
|
+
`CreateComponentVersion` to one request per second per Region. A recipe is
|
|
767
|
+
limited to 16 KB, and a deployment document to 31 KB for a thing group or 7 KB
|
|
768
|
+
for a single thing. Avoid using new versions as heartbeats and split large
|
|
769
|
+
deployments into smaller batches.
|
package/generators.json
CHANGED
|
@@ -91,6 +91,14 @@
|
|
|
91
91
|
"connection/react-agentcore-gateway"
|
|
92
92
|
]
|
|
93
93
|
},
|
|
94
|
+
"greengrass#component": {
|
|
95
|
+
"factory": "./src/greengrass/component/generator",
|
|
96
|
+
"schema": "./src/greengrass/component/schema.json",
|
|
97
|
+
"description": "Add an AWS IoT Greengrass component to an existing Python or TypeScript project",
|
|
98
|
+
"metric": "g74",
|
|
99
|
+
"guidePages": ["greengrass-component"],
|
|
100
|
+
"experimental": true
|
|
101
|
+
},
|
|
94
102
|
"license": {
|
|
95
103
|
"factory": "./src/license/generator",
|
|
96
104
|
"schema": "./src/license/schema.json",
|
package/package.json
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/schema",
|
|
3
|
+
"$id": "greengrass#component",
|
|
4
|
+
"title": "greengrass#component",
|
|
5
|
+
"x-label": "Greengrass Component",
|
|
6
|
+
"description": "Add an AWS IoT Greengrass component to an existing Python or TypeScript project. Language is inferred from the selected project's marker when unambiguous.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"project": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "The existing Python or TypeScript host project",
|
|
12
|
+
"x-prompt": "Select the project to add the Greengrass component to",
|
|
13
|
+
"x-dropdown": "projects"
|
|
14
|
+
},
|
|
15
|
+
"name": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "The component name",
|
|
18
|
+
"x-priority": "important",
|
|
19
|
+
"$default": { "$source": "argv", "index": 0 },
|
|
20
|
+
"x-prompt": "What name would you like for the Greengrass component?"
|
|
21
|
+
},
|
|
22
|
+
"language": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"enum": ["typescript", "python"],
|
|
25
|
+
"description": "The component language. Omit it only when the host has exactly one supported language marker."
|
|
26
|
+
},
|
|
27
|
+
"componentName": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "The recipe ComponentName. Defaults to a reversed-domain name derived from the workspace npm scope and component name."
|
|
30
|
+
},
|
|
31
|
+
"componentVersion": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"default": "1.0.0",
|
|
34
|
+
"description": "The recipe ComponentVersion. CDK resolves NEXT_* at deploy time; Terraform requires an explicit semantic version. With infra=none, NEXT_* is written unchanged."
|
|
35
|
+
},
|
|
36
|
+
"publisher": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "The recipe ComponentPublisher. Defaults to the workspace npm scope."
|
|
39
|
+
},
|
|
40
|
+
"ipc": {
|
|
41
|
+
"type": "boolean",
|
|
42
|
+
"default": false,
|
|
43
|
+
"description": "Whether to wire the Greengrass IPC client. Defaults to false for both languages."
|
|
44
|
+
},
|
|
45
|
+
"gdkConfig": {
|
|
46
|
+
"type": "boolean",
|
|
47
|
+
"default": false,
|
|
48
|
+
"description": "Vend gdk-config.json for using the GDK CLI by hand. GDK supports NEXT_PATCH, not NEXT_MINOR or NEXT_MAJOR.",
|
|
49
|
+
"x-priority": "low"
|
|
50
|
+
},
|
|
51
|
+
"platform": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"enum": ["linux-amd64", "linux-arm64", "linux-amd64-arm64"],
|
|
54
|
+
"default": "linux-arm64",
|
|
55
|
+
"description": "The core device platform or platforms.",
|
|
56
|
+
"x-priority": "important",
|
|
57
|
+
"x-prompt": "Which core device platform(s) would you like to target?"
|
|
58
|
+
},
|
|
59
|
+
"infra": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"enum": ["component-version", "none"],
|
|
62
|
+
"default": "component-version",
|
|
63
|
+
"description": "component-version vends reusable Greengrass component infrastructure; none supports local development only.",
|
|
64
|
+
"x-priority": "important",
|
|
65
|
+
"x-prompt": "Which infrastructure would you like to provision?"
|
|
66
|
+
},
|
|
67
|
+
"iac": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"enum": ["inherit", "cdk", "terraform"],
|
|
70
|
+
"default": "inherit",
|
|
71
|
+
"description": "The IaC provider, resolved only when infrastructure is requested.",
|
|
72
|
+
"x-priority": "important",
|
|
73
|
+
"x-prompt": "Which provider would you like to manage your infrastructure? (default: Inherit)"
|
|
74
|
+
},
|
|
75
|
+
"preferInstallDependencies": {
|
|
76
|
+
"type": "boolean",
|
|
77
|
+
"default": true,
|
|
78
|
+
"description": "Whether to prefer installing dependencies after the generator runs."
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"required": ["project", "name"]
|
|
82
|
+
}
|