Deploy lab¶
Creates the enhanced bus, the IoT → bus adapter Lambda (eb-bridge), wires
the existing iot_talk_* events/telemetry rules to it, and RAM-shares the bus
to dev — AWS CLI only. Before this: Prerequisites
and Check profiles (out/dev/account-id.txt must exist).
What this creates¶
| Resource | Detail |
|---|---|
Enhanced bus lab-events |
7-day retention; ARN captured — never invent it |
Lambda eb-bridge |
Required adapter: IoT rule → eventsv2 PutEvents + EventGroupId |
IAM role eb-bridge-role |
Logs + PutEvents on the bus |
| IoT rule action | Extra Lambda action on events + telemetry rules → eb-bridge |
RAM share eb-enhanced-bus-share |
Principal = dev account id |
Why a Lambda? IoT topic-rule actions cannot PutEvents onto an enhanced
bus (event-busv2/...) with SystemMetadata.EventGroupId. eb-bridge is that
hop. The existing iot-talk-ingest action stays; talk DynamoDB / dashboard are
unchanged.
Mutation guard
These calls create billable / account resources. Tear down when finished.
Load context¶
export AWS_PROFILE=lab AWS_REGION=ap-southeast-2
mkdir -p out/lab
LAB_ACCOUNT="$(aws sts get-caller-identity --query Account --output text)"
DEV_ACCOUNT="$(cat out/dev/account-id.txt)"
echo "$LAB_ACCOUNT" > out/lab/account-id.txt
echo "$AWS_REGION" > out/lab/region.txt
echo "LAB=$LAB_ACCOUNT DEV=$DEV_ACCOUNT"
LAB=111122223333 DEV=444455556666
Enhanced bus¶
BUS_ARN="$(aws eventsv2 create-event-bus \
--name lab-events \
--description "Enhanced bus for cross-account ordered health alerts" \
--storage-configuration RetentionPeriodInDays=7 \
--tags Key=Project,Value=eb-enhanced-bus \
--query EventBusArn --output text)"
echo "$BUS_ARN" | tee out/lab/bus-arn.txt
arn:aws:events:ap-southeast-2:111122223333:event-busv2/lab-events/4af03huoxjozsje2bexey6iw8
aws eventsv2 describe-event-bus --event-bus-arn "$BUS_ARN" \
--query '{State:State,RetentionDays:StorageConfiguration.RetentionPeriodInDays}' \
--output json
{
"State": "ACTIVE",
"RetentionDays": 7
}
If State is still CREATING, re-run the describe until ACTIVE.
Bridge role + Lambda¶
aws iam create-role \
--role-name eb-bridge-role \
--assume-role-policy-document '{
"Version":"2012-10-17",
"Statement":[{
"Effect":"Allow",
"Principal":{"Service":"lambda.amazonaws.com"},
"Action":"sts:AssumeRole"
}]
}' \
--tags Key=Project,Value=eb-enhanced-bus \
--query Role.Arn --output text
arn:aws:iam::111122223333:role/eb-bridge-role
aws iam put-role-policy \
--role-name eb-bridge-role \
--policy-name eb-bridge-inline \
--policy-document "{
\"Version\": \"2012-10-17\",
\"Statement\": [
{
\"Effect\": \"Allow\",
\"Action\": [\"logs:CreateLogGroup\", \"logs:CreateLogStream\", \"logs:PutLogEvents\"],
\"Resource\": \"arn:aws:logs:${AWS_REGION}:${LAB_ACCOUNT}:*\"
},
{
\"Effect\": \"Allow\",
\"Action\": [\"eventbridge:PutEvents\", \"events:PutEvents\"],
\"Resource\": \"${BUS_ARN}\"
}
]
}"
(no output)
BRIDGE_ROLE_ARN="$(aws iam get-role --role-name eb-bridge-role --query Role.Arn --output text)"
sleep 10
# handler.py + vendored eventbridgev2 botocore model (Lambda runtime lacks it)
(cd bridge && zip -q -r ../out/lab/eb-bridge.zip handler.py data)
(no output)
eb-bridge calls boto3.client("eventbridgev2"). The managed Lambda runtime's
botocore does not ship that model yet, so the zip includes bridge/data/
(same service model AWS CLI ≥ 2.37.3 uses). Do not zip handler.py alone.
aws lambda create-function \
--function-name eb-bridge \
--runtime python3.14 \
--role "$BRIDGE_ROLE_ARN" \
--handler handler.handler \
--timeout 10 \
--memory-size 128 \
--zip-file fileb://out/lab/eb-bridge.zip \
--environment "Variables={EVENT_BUS_ARN=${BUS_ARN},SOURCE=iot.lab}" \
--tags Project=eb-enhanced-bus \
--query FunctionArn --output text | tee out/lab/bridge-fn-arn.txt
arn:aws:lambda:ap-southeast-2:111122223333:function:eb-bridge
SOURCE=iot.lab is stamped on every publish — same value the dev Subscriber
filter matches. If the function already exists, update code and env instead:
aws lambda update-function-code \
--function-name eb-bridge \
--zip-file fileb://out/lab/eb-bridge.zip \
--query FunctionArn --output text
aws lambda wait function-updated --function-name eb-bridge
aws lambda update-function-configuration \
--function-name eb-bridge \
--environment "Variables={EVENT_BUS_ARN=${BUS_ARN},SOURCE=iot.lab}" \
--query FunctionArn --output text
arn:aws:lambda:ap-southeast-2:111122223333:function:eb-bridge
Wire IoT rules → bridge¶
Live feed is mostly telemetry (devices/+/telemetry, fleet/+/telemetry).
Wire those rules (plus events) so each keeps iot-talk-ingest and adds
eb-bridge. Skip camera — the bridge rejects camera blobs.
BRIDGE_ARN="$(cat out/lab/bridge-fn-arn.txt)"
INGEST_ARN="arn:aws:lambda:${AWS_REGION}:${LAB_ACCOUNT}:function:iot-talk-ingest"
declare -A RULE_SQL=(
[iot_talk_events]="SELECT * FROM 'devices/+/events'"
[iot_talk_telemetry]="SELECT * FROM 'devices/+/telemetry'"
[iot_talk_fleet_events]="SELECT * FROM 'fleet/+/events'"
[iot_talk_fleet_telemetry]="SELECT * FROM 'fleet/+/telemetry'"
)
for RULE_NAME in "${!RULE_SQL[@]}"; do
RULE_ARN="arn:aws:iot:${AWS_REGION}:${LAB_ACCOUNT}:rule/${RULE_NAME}"
SID="AllowIot_${RULE_NAME}"
aws lambda add-permission \
--function-name eb-bridge \
--statement-id "$SID" \
--action lambda:InvokeFunction \
--principal iot.amazonaws.com \
--source-arn "$RULE_ARN" \
--source-account "$LAB_ACCOUNT" 2>/dev/null || true
aws iot replace-topic-rule \
--rule-name "$RULE_NAME" \
--topic-rule-payload "{
\"sql\": \"${RULE_SQL[$RULE_NAME]}\",
\"actions\": [
{\"lambda\": {\"functionArn\": \"$INGEST_ARN\"}},
{\"lambda\": {\"functionArn\": \"$BRIDGE_ARN\"}}
],
\"ruleDisabled\": false,
\"awsIotSqlVersion\": \"2016-03-23\"
}"
echo -n "$RULE_NAME: "
aws iot get-topic-rule --rule-name "$RULE_NAME" \
--query 'rule.actions[].lambda.functionArn' --output text
done
iot_talk_events: …:function:iot-talk-ingest …:function:eb-bridge
iot_talk_telemetry: …:function:iot-talk-ingest …:function:eb-bridge
iot_talk_fleet_events: …:function:iot-talk-ingest …:function:eb-bridge
iot_talk_fleet_telemetry: …:function:iot-talk-ingest …:function:eb-bridge
If add-permission says the statement already exists, continue — the rule
replace is what matters.
RAM share to dev¶
SHARE_ARN="$(aws ram create-resource-share \
--name eb-enhanced-bus-share \
--resource-arns "$BUS_ARN" \
--principals "$DEV_ACCOUNT" \
--no-allow-external-principals \
--tags key=Project,value=eb-enhanced-bus \
--query resourceShare.resourceShareArn --output text)"
echo "$SHARE_ARN" | tee out/lab/share-arn.txt
arn:aws:ram:ap-southeast-2:111122223333:resource-share/ffdea398-4ee7-4967-956a-f433e9ae8edf
Outputs¶
| File | Content |
|---|---|
out/lab/bus-arn.txt |
Enhanced bus ARN |
out/lab/bridge-fn-arn.txt |
eb-bridge function ARN |
out/lab/share-arn.txt |
RAM share ARN |
Next: Deploy dev. Verify can still smoke with CLI
put-events (same envelope the bridge stamps); live devices hit the bus via
eb-bridge.