//! 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} \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; }