update readme and build process
This commit is contained in:
parent
d1e93d8529
commit
bde519af0b
4 changed files with 227 additions and 43 deletions
112
README.md
112
README.md
|
|
@ -1,6 +1,7 @@
|
||||||
# Water Recirculation Alexa Skill
|
# Home control Alexa Skill
|
||||||
|
|
||||||
An Alexa skill that triggers water recirculation on Rinnai tankless water heaters.
|
An Alexa skill that triggers water recirculation on Rinnai tankless water heaters
|
||||||
|
and supports homeassistant device control
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
@ -8,6 +9,11 @@ An Alexa skill that triggers water recirculation on Rinnai tankless water heater
|
||||||
|
|
||||||
This will authenticate with the Rinnai API and start a 15-minute recirculation cycle.
|
This will authenticate with the Rinnai API and start a 15-minute recirculation cycle.
|
||||||
|
|
||||||
|
> "Alexa, turn on the bedroom light"
|
||||||
|
|
||||||
|
This will use the homeassistant REST API to find the bedroom device and turn it on.
|
||||||
|
You can turn on, turn off, toggle, and query status
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
Requires [Zig 0.15](https://ziglang.org/) and [mise](https://mise.jdx.dev/) for version management.
|
Requires [Zig 0.15](https://ziglang.org/) and [mise](https://mise.jdx.dev/) for version management.
|
||||||
|
|
@ -21,15 +27,13 @@ zig build
|
||||||
# Release build (arm64)
|
# Release build (arm64)
|
||||||
zig build -Doptimize=ReleaseFast
|
zig build -Doptimize=ReleaseFast
|
||||||
|
|
||||||
# Build for native target (e.g., for local testing)
|
# Run tests (uses host CPU/OS)
|
||||||
zig build -Dtarget=native
|
zig build test
|
||||||
|
|
||||||
# Run tests
|
|
||||||
zig build test -Dtarget=native
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
|
- [aws-sdk-for-zig](https://git.lerch.org/lobo/aws-sdk-for-zig) - AWS SDK for Zig
|
||||||
- [lambda-zig](https://git.lerch.org/lobo/lambda-zig) - AWS Lambda runtime for Zig
|
- [lambda-zig](https://git.lerch.org/lobo/lambda-zig) - AWS Lambda runtime for Zig
|
||||||
- [controlr](https://git.lerch.org/lobo/controlr) - Rinnai API client (provides `rinnai` module)
|
- [controlr](https://git.lerch.org/lobo/controlr) - Rinnai API client (provides `rinnai` module)
|
||||||
|
|
||||||
|
|
@ -105,18 +109,41 @@ This will:
|
||||||
bun x ask smapi list-skills-for-vendor
|
bun x ask smapi list-skills-for-vendor
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Rinnai Credentials
|
### 3. Environment Variables
|
||||||
|
|
||||||
The Lambda function needs your Rinnai account credentials to authenticate with the water heater API.
|
The Lambda function needs credentials for the services it interacts with.
|
||||||
|
|
||||||
Create a `.env` file in the project root (this file is gitignored):
|
Create a `.env` file in the project root (this file is gitignored):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# .env
|
# .env
|
||||||
|
|
||||||
|
# Rinnai API credentials for water heater control
|
||||||
COGNITO_USERNAME=your@email.com
|
COGNITO_USERNAME=your@email.com
|
||||||
COGNITO_PASSWORD=your_password
|
COGNITO_PASSWORD=your_password
|
||||||
|
|
||||||
|
# Home Assistant configuration
|
||||||
|
HOME_ASSISTANT_URL=https://your-homeassistant.example.com
|
||||||
|
HOME_ASSISTANT_TOKEN=your_long_lived_access_token
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Rinnai Credentials
|
||||||
|
|
||||||
|
The `COGNITO_USERNAME` and `COGNITO_PASSWORD` are your Rinnai app login credentials.
|
||||||
|
These are used to authenticate with the Rinnai API for water recirculation control.
|
||||||
|
|
||||||
|
#### Home Assistant Token
|
||||||
|
|
||||||
|
To generate a long-lived access token in Home Assistant:
|
||||||
|
|
||||||
|
1. Go to your Home Assistant profile (click your username in the sidebar)
|
||||||
|
2. Scroll down to "Long-Lived Access Tokens"
|
||||||
|
3. Click "Create Token"
|
||||||
|
4. Give it a name (e.g., "Alexa Lambda")
|
||||||
|
5. Copy the token immediately (it won't be shown again)
|
||||||
|
|
||||||
|
The `HOME_ASSISTANT_URL` should be the external URL of your Home Assistant instance.
|
||||||
|
|
||||||
These credentials will be automatically deployed to Lambda when you use the `-Denv-file=.env` option.
|
These credentials will be automatically deployed to Lambda when you use the `-Denv-file=.env` option.
|
||||||
|
|
||||||
## Build Steps
|
## Build Steps
|
||||||
|
|
@ -138,13 +165,12 @@ These credentials will be automatically deployed to Lambda when you use the `-De
|
||||||
|--------|-------------|---------|
|
|--------|-------------|---------|
|
||||||
| `-Doptimize=ReleaseFast` | Build with optimizations | Debug |
|
| `-Doptimize=ReleaseFast` | Build with optimizations | Debug |
|
||||||
| `-Dtarget=native` | Build for local machine | aarch64-linux |
|
| `-Dtarget=native` | Build for local machine | aarch64-linux |
|
||||||
| `-Dfunction-name=NAME` | Lambda function name | zig-fn |
|
| `-Dfunction-name=NAME` | Lambda function name | exe name (house-control) |
|
||||||
| `-Dprofile=PROFILE` | AWS profile to use | default |
|
| `-Dprofile=PROFILE` | AWS profile to use | default |
|
||||||
| `-Dregion=REGION` | AWS region | from profile |
|
| `-Dregion=REGION` | AWS region | from profile |
|
||||||
| `-Drole-name=ROLE` | IAM role name | lambda_basic_execution |
|
| `-Drole-name=ROLE` | IAM role name | lambda_basic_execution |
|
||||||
| `-Dpayload=JSON` | Payload for `awslambda_run` | {} |
|
| `-Dpayload=JSON` | Payload for `awslambda_run` | {} |
|
||||||
| `-Denv-file=PATH` | Environment variables file | none |
|
| `-Denv-file=PATH` | Environment variables file | none |
|
||||||
| `-Dallow-principal=PRINCIPAL` | AWS service principal to grant invoke permission | none |
|
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
|
|
@ -154,38 +180,44 @@ Before deploying, ensure you have:
|
||||||
|
|
||||||
1. **AWS Account** with credentials configured (see [Deployment Setup](#deployment-setup))
|
1. **AWS Account** with credentials configured (see [Deployment Setup](#deployment-setup))
|
||||||
2. **Amazon Developer Account** with ASK CLI authenticated (`bun x ask configure`)
|
2. **Amazon Developer Account** with ASK CLI authenticated (`bun x ask configure`)
|
||||||
3. **Rinnai credentials** in `.env` file (see [Rinnai Credentials](#3-rinnai-credentials))
|
3. **Credentials** in `.env` file (see [Environment Variables](#3-environment-variables)):
|
||||||
|
- Rinnai account credentials (for water recirculation)
|
||||||
|
- Home Assistant URL and long-lived access token (for device control)
|
||||||
|
|
||||||
### Full Deployment (Lambda + Alexa Skill)
|
### Full Deployment (Lambda + Alexa Skill)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
zig build deploy -Doptimize=ReleaseFast \
|
zig build deploy -Doptimize=ReleaseFast \
|
||||||
-Dfunction-name=water-recirculation \
|
|
||||||
-Dprofile=personal \
|
-Dprofile=personal \
|
||||||
-Dregion=us-west-2 \
|
-Dregion=us-west-2 \
|
||||||
-Denv-file=.env \
|
-Denv-file=.env
|
||||||
-Dallow-principal=alexa-appkit.amazon.com
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will:
|
This command orchestrates a multi-step deployment pipeline:
|
||||||
1. Build the Lambda function for arm64
|
|
||||||
2. Package it into a zip file
|
1. **Build** - Compile Lambda function for arm64
|
||||||
3. Create/update the Lambda function in AWS
|
2. **Package** - Create deployment zip with bootstrap executable
|
||||||
4. Set environment variables from `.env`
|
3. **Deploy Lambda** - Create/update function in AWS, set env vars from `.env`
|
||||||
5. Grant Alexa Skills Kit permission to invoke the function
|
4. **Generate skill.json** - Inject Lambda ARN into `skill.template.json`
|
||||||
6. Deploy the Alexa skill metadata via ASK CLI
|
5. **ASK Deploy** - Deploy Alexa skill metadata and interaction model
|
||||||
|
6. **Add Permission** - Grant Alexa permission to invoke Lambda with skill-specific token
|
||||||
|
|
||||||
|
The permission step uses the skill ID (from `.ask/ask-states.json`) as an
|
||||||
|
`event_source_token` condition, restricting invocation to only this specific
|
||||||
|
Alexa skill rather than allowing any Alexa skill to invoke the Lambda.
|
||||||
|
|
||||||
### Lambda Only
|
### Lambda Only
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
zig build awslambda_deploy -Doptimize=ReleaseFast \
|
zig build awslambda_deploy -Doptimize=ReleaseFast \
|
||||||
-Dfunction-name=water-recirculation \
|
|
||||||
-Dprofile=personal \
|
-Dprofile=personal \
|
||||||
-Dregion=us-west-2 \
|
-Dregion=us-west-2 \
|
||||||
-Denv-file=.env \
|
-Denv-file=.env
|
||||||
-Dallow-principal=alexa-appkit.amazon.com
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note: This only deploys the Lambda function. To invoke the function from Alexa,
|
||||||
|
you must also deploy the skill (`zig build deploy`) to set up the permission.
|
||||||
|
|
||||||
### Alexa Skill Only
|
### Alexa Skill Only
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -197,21 +229,29 @@ zig build ask_deploy
|
||||||
```
|
```
|
||||||
water_recirculation/
|
water_recirculation/
|
||||||
├── build.zig # Build configuration
|
├── build.zig # Build configuration
|
||||||
├── build.zig.zon # Dependencies (lambda-zig, controlr)
|
├── build.zig.zon # Dependencies
|
||||||
├── .env # Rinnai credentials (gitignored, create locally)
|
├── .env # Credentials (gitignored)
|
||||||
|
├── skill.template.json # Skill manifest template (Lambda ARN placeholder)
|
||||||
├── ask-resources.json # ASK CLI deployment config
|
├── ask-resources.json # ASK CLI deployment config
|
||||||
├── package.json # Node.js deps for ASK CLI
|
├── package.json # Node.js deps for ASK CLI
|
||||||
├── src/
|
├── src/
|
||||||
│ └── main.zig # Alexa request handler + tests
|
│ ├── main.zig # Lambda entry point
|
||||||
|
│ └── homeassistant.zig # Home Assistant API client
|
||||||
|
├── tools/
|
||||||
|
│ ├── gen-skill-json.zig # Generates skill.json from template
|
||||||
|
│ └── add-alexa-permission.zig # Adds skill-specific Lambda permission
|
||||||
├── skill-package/
|
├── skill-package/
|
||||||
│ ├── skill.json # Alexa skill manifest
|
│ ├── skill.json # Generated (gitignored)
|
||||||
│ └── interactionModels/
|
│ └── interactionModels/
|
||||||
│ └── custom/
|
│ └── custom/
|
||||||
│ └── en-US.json # Interaction model
|
│ └── en-US.json # Interaction model with intents/slots
|
||||||
|
└── .ask/
|
||||||
|
└── ask-states.json # ASK CLI state (contains skill ID)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Sample Utterances
|
## Sample Utterances
|
||||||
|
|
||||||
|
### Water Recirculation
|
||||||
- "start the hot water"
|
- "start the hot water"
|
||||||
- "turn on the hot water"
|
- "turn on the hot water"
|
||||||
- "heat the water"
|
- "heat the water"
|
||||||
|
|
@ -219,17 +259,23 @@ water_recirculation/
|
||||||
- "start recirculation"
|
- "start recirculation"
|
||||||
- "warm up the water"
|
- "warm up the water"
|
||||||
|
|
||||||
|
### Home Assistant Device Control
|
||||||
|
- "turn on the bedroom light"
|
||||||
|
- "turn off the kitchen"
|
||||||
|
- "toggle the basement fireplace"
|
||||||
|
- "is the deck on"
|
||||||
|
- "check the family room"
|
||||||
|
|
||||||
## Lambda Details
|
## Lambda Details
|
||||||
|
|
||||||
- **Function**: `water-recirculation`
|
- **Function**: `house-control`
|
||||||
- **Region**: us-west-2
|
- **Region**: us-west-2
|
||||||
- **Architecture**: arm64 (Graviton)
|
- **Architecture**: arm64 (Graviton)
|
||||||
- **Runtime**: provided.al2023
|
- **Runtime**: provided.al2023
|
||||||
- **ARN**: `arn:aws:lambda:us-west-2:932028523435:function:water-recirculation`
|
|
||||||
|
|
||||||
## Alexa Skill
|
## Alexa Skill
|
||||||
|
|
||||||
- **Skill ID**: `amzn1.ask.skill.c373c562-d574-4f38-bd06-001e96426d12`
|
- **Skill ID**: `amzn1.ask.skill.5cc9bf04-8be9-4229-936d-49a22fae6a3e`
|
||||||
- **Invocation**: "Alexa, ask house to..."
|
- **Invocation**: "Alexa, ask house to..."
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
||||||
49
build.zig
49
build.zig
|
|
@ -47,9 +47,8 @@ pub fn build(b: *std.Build) !void {
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
||||||
// Configure Lambda build steps and get deployment info
|
// Configure Lambda build steps and get deployment info
|
||||||
const lambda = try lambda_zig.configureBuild(b, lambda_zig_dep, exe, .{
|
// Function name defaults to exe.name ("house-control")
|
||||||
.default_function_name = "house-control",
|
const lambda = try lambda_zig.configureBuild(b, lambda_zig_dep, exe, .{});
|
||||||
});
|
|
||||||
|
|
||||||
// Build the gen-skill-json tool (runs on host)
|
// Build the gen-skill-json tool (runs on host)
|
||||||
const gen_skill_json_module = b.createModule(.{
|
const gen_skill_json_module = b.createModule(.{
|
||||||
|
|
@ -65,7 +64,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
// Generate skill.json from template using Lambda ARN
|
// Generate skill.json from template using Lambda ARN
|
||||||
const gen_skill_cmd = b.addRunArtifact(gen_skill_json_exe);
|
const gen_skill_cmd = b.addRunArtifact(gen_skill_json_exe);
|
||||||
gen_skill_cmd.addFileArg(lambda.deploy_output);
|
gen_skill_cmd.addFileArg(lambda.deploy_output);
|
||||||
gen_skill_cmd.addFileArg(b.path("skill-package/skill.template.json"));
|
gen_skill_cmd.addFileArg(b.path("skill.template.json"));
|
||||||
gen_skill_cmd.step.dependOn(lambda.deploy_step);
|
gen_skill_cmd.step.dependOn(lambda.deploy_step);
|
||||||
|
|
||||||
// Capture generated skill.json
|
// Capture generated skill.json
|
||||||
|
|
@ -85,12 +84,43 @@ pub fn build(b: *std.Build) !void {
|
||||||
// ASK deploy depends on skill.json being generated
|
// ASK deploy depends on skill.json being generated
|
||||||
ask_deploy_cmd.step.dependOn(&write_skill_json.step);
|
ask_deploy_cmd.step.dependOn(&write_skill_json.step);
|
||||||
|
|
||||||
const ask_deploy_step = b.step("ask_deploy", "Deploy Alexa skill metadata via ASK CLI");
|
// Add Alexa skill-specific Lambda permission
|
||||||
ask_deploy_step.dependOn(&ask_deploy_cmd.step);
|
//
|
||||||
|
// Alexa requires a skill-specific Lambda permission with the skill ID as an
|
||||||
|
// event_source_token condition. This is more secure than a generic principal-based
|
||||||
|
// permission (--allow-principal) and restricts invocation to only our specific skill.
|
||||||
|
//
|
||||||
|
// We use our own tool instead of lambda-zig's built-in --allow-principal because:
|
||||||
|
// 1. The skill ID isn't known until after ASK deploy runs
|
||||||
|
// 2. event_source_token is Alexa-specific (not supported by lambda-zig)
|
||||||
|
//
|
||||||
|
// Pipeline: Lambda deploy -> gen-skill-json -> ASK deploy -> add-alexa-permission
|
||||||
|
const aws_dep = b.dependency("aws", .{
|
||||||
|
.target = native_target,
|
||||||
|
.optimize = .ReleaseFast,
|
||||||
|
});
|
||||||
|
const add_alexa_perm_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("tools/add-alexa-permission.zig"),
|
||||||
|
.target = native_target,
|
||||||
|
.optimize = .ReleaseFast,
|
||||||
|
});
|
||||||
|
add_alexa_perm_module.addImport("aws", aws_dep.module("aws"));
|
||||||
|
const add_alexa_perm_exe = b.addExecutable(.{
|
||||||
|
.name = "add-alexa-permission",
|
||||||
|
.root_module = add_alexa_perm_module,
|
||||||
|
});
|
||||||
|
const add_alexa_perm_cmd = b.addRunArtifact(add_alexa_perm_exe);
|
||||||
|
add_alexa_perm_cmd.addFileArg(lambda.deploy_output);
|
||||||
|
add_alexa_perm_cmd.addFileArg(b.path(".ask/ask-states.json"));
|
||||||
|
// Must run after ASK deploy (which creates/updates skill ID) and Lambda deploy
|
||||||
|
add_alexa_perm_cmd.step.dependOn(&ask_deploy_cmd.step);
|
||||||
|
|
||||||
// Full deploy step - deploys Lambda, generates skill.json, deploys Alexa skill
|
const ask_deploy_step = b.step("ask_deploy", "Deploy Alexa skill metadata via ASK CLI");
|
||||||
|
ask_deploy_step.dependOn(&add_alexa_perm_cmd.step);
|
||||||
|
|
||||||
|
// Full deploy step - deploys Lambda, generates skill.json, deploys Alexa skill, adds permission
|
||||||
const full_deploy_step = b.step("deploy", "Deploy Lambda function and Alexa skill");
|
const full_deploy_step = b.step("deploy", "Deploy Lambda function and Alexa skill");
|
||||||
full_deploy_step.dependOn(&ask_deploy_cmd.step);
|
full_deploy_step.dependOn(&add_alexa_perm_cmd.step);
|
||||||
|
|
||||||
// Test step - use native target for tests (not cross-compiled Lambda target)
|
// Test step - use native target for tests (not cross-compiled Lambda target)
|
||||||
const lambda_zig_dep_native = b.dependency("lambda_zig", .{
|
const lambda_zig_dep_native = b.dependency("lambda_zig", .{
|
||||||
|
|
@ -119,6 +149,9 @@ pub fn build(b: *std.Build) !void {
|
||||||
const run_main_tests = b.addRunArtifact(main_tests);
|
const run_main_tests = b.addRunArtifact(main_tests);
|
||||||
const test_step = b.step("test", "Run unit tests");
|
const test_step = b.step("test", "Run unit tests");
|
||||||
test_step.dependOn(&run_main_tests.step);
|
test_step.dependOn(&run_main_tests.step);
|
||||||
|
// Also verify tools compile
|
||||||
|
test_step.dependOn(&gen_skill_json_exe.step);
|
||||||
|
test_step.dependOn(&add_alexa_perm_exe.step);
|
||||||
|
|
||||||
// Run step for local testing (uses native target)
|
// Run step for local testing (uses native target)
|
||||||
const run_module = b.createModule(.{
|
const run_module = b.createModule(.{
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,12 @@
|
||||||
.hash = "controlr-0.1.0-upFm0LtgAAAo85RiRDa0WmSrORIhAa5bCF8UdT9rDyUk",
|
.hash = "controlr-0.1.0-upFm0LtgAAAo85RiRDa0WmSrORIhAa5bCF8UdT9rDyUk",
|
||||||
},
|
},
|
||||||
.lambda_zig = .{
|
.lambda_zig = .{
|
||||||
.url = "git+https://git.lerch.org/lobo/lambda-zig#56ac230e5e6c849376a72e12f9e65ea3800fe18e",
|
.url = "git+https://git.lerch.org/lobo/lambda-zig#f444697d93244425fa799023d0e7adf80ecaa1de",
|
||||||
.hash = "lambda_zig-0.1.0-_G43_2ZbAQAirikqqFgrxNwwluSxOBzN4PnrRMr4IGNx",
|
.hash = "lambda_zig-0.1.0-_G43_9VdAQBdYVii9jenUiYxPssqZudPv23LEJVA2W0b",
|
||||||
|
},
|
||||||
|
.aws = .{
|
||||||
|
.url = "git+https://git.lerch.org/lobo/aws-sdk-for-zig#5c7aed071f6251d53a1627080a21d604ff58f0a5",
|
||||||
|
.hash = "aws-0.0.1-SbsFcFE7CgDBilPa15i4gIB6Qr5ozBz328O63abDQDDk",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
|
|
|
||||||
101
tools/add-alexa-permission.zig
Normal file
101
tools/add-alexa-permission.zig
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
//! Adds Alexa skill-specific Lambda permission.
|
||||||
|
//! Alexa requires the Lambda policy to include the skill ID as an event source token condition.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const aws = @import("aws");
|
||||||
|
const json = std.json;
|
||||||
|
|
||||||
|
pub fn main() !u8 {
|
||||||
|
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
const args = try std.process.argsAlloc(allocator);
|
||||||
|
defer std.process.argsFree(allocator, args);
|
||||||
|
|
||||||
|
if (args.len != 3) {
|
||||||
|
std.debug.print("Usage: {s} <deploy-output.json> <ask-states.json>\n", .{args[0]});
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read deploy output to get function name and region
|
||||||
|
const deploy_output = std.fs.cwd().readFileAlloc(allocator, args[1], 1024 * 1024) catch |err| {
|
||||||
|
std.debug.print("Failed to read deploy output '{s}': {}\n", .{ args[1], err });
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
defer allocator.free(deploy_output);
|
||||||
|
|
||||||
|
const deploy_parsed = json.parseFromSlice(json.Value, allocator, deploy_output, .{}) catch |err| {
|
||||||
|
std.debug.print("Failed to parse deploy output: {}\n", .{err});
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
defer deploy_parsed.deinit();
|
||||||
|
|
||||||
|
const function_name = deploy_parsed.value.object.get("function_name").?.string;
|
||||||
|
const region = deploy_parsed.value.object.get("region").?.string;
|
||||||
|
|
||||||
|
// Read ask-states.json to get skill ID
|
||||||
|
const ask_states = std.fs.cwd().readFileAlloc(allocator, args[2], 1024 * 1024) catch |err| {
|
||||||
|
std.debug.print("Failed to read ask-states.json '{s}': {}\n", .{ args[2], err });
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
defer allocator.free(ask_states);
|
||||||
|
|
||||||
|
const ask_parsed = json.parseFromSlice(json.Value, allocator, ask_states, .{}) catch |err| {
|
||||||
|
std.debug.print("Failed to parse ask-states.json: {}\n", .{err});
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
defer ask_parsed.deinit();
|
||||||
|
|
||||||
|
const skill_id = ask_parsed.value.object.get("profiles").?.object.get("default").?.object.get("skillId").?.string;
|
||||||
|
|
||||||
|
std.debug.print("Adding Alexa permission for skill {s} to function {s} in {s}\n", .{ skill_id, function_name, region });
|
||||||
|
|
||||||
|
// Build statement ID from skill ID (use last 12 chars to keep it short but unique)
|
||||||
|
var statement_id_buf: [64]u8 = undefined;
|
||||||
|
const statement_id = std.fmt.bufPrint(&statement_id_buf, "alexa-skill-{s}", .{skill_id[skill_id.len - 12 ..]}) catch {
|
||||||
|
std.debug.print("Failed to build statement ID\n", .{});
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create AWS client and options
|
||||||
|
var client = aws.Client.init(allocator, .{});
|
||||||
|
defer client.deinit();
|
||||||
|
|
||||||
|
var diagnostics: aws.Diagnostics = .{
|
||||||
|
.response_status = undefined,
|
||||||
|
.response_body = undefined,
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const opts = aws.Options{
|
||||||
|
.client = client,
|
||||||
|
.region = region,
|
||||||
|
.diagnostics = &diagnostics,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add permission with skill ID as event source token
|
||||||
|
const services = aws.Services(.{.lambda}){};
|
||||||
|
|
||||||
|
_ = aws.Request(services.lambda.add_permission).call(.{
|
||||||
|
.function_name = function_name,
|
||||||
|
.statement_id = statement_id,
|
||||||
|
.action = "lambda:InvokeFunction",
|
||||||
|
.principal = "alexa-appkit.amazon.com",
|
||||||
|
.event_source_token = skill_id,
|
||||||
|
}, opts) catch |err| {
|
||||||
|
defer diagnostics.deinit();
|
||||||
|
|
||||||
|
// 409 Conflict means permission already exists - that's fine
|
||||||
|
if (diagnostics.response_status == .conflict) {
|
||||||
|
std.debug.print("Permission already exists for skill: {s}\n", .{skill_id});
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.print("AddPermission failed: {} (HTTP {})\n", .{ err, diagnostics.response_status });
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
std.debug.print("Added Alexa permission for skill: {s}\n", .{skill_id});
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue