forked from lobo/lambda-zig
tell sdk that the profile is a command line flag/change precedence
This commit is contained in:
parent
446b726cf9
commit
b3f5cb8203
5 changed files with 74 additions and 92 deletions
|
|
@ -4,8 +4,8 @@
|
|||
.fingerprint = 0x6e61de08e7e51114,
|
||||
.dependencies = .{
|
||||
.aws = .{
|
||||
.url = "git+https://git.lerch.org/lobo/aws-sdk-for-zig#686b18d1f4329e80cf6d9b916eaa0c231333edb9",
|
||||
.hash = "aws-0.0.1-SbsFcAc3CgCdWfayHWFazNfJBxkzLyU2wOJSj7h4W17-",
|
||||
.url = "git+https://git.lerch.org/lobo/aws-sdk-for-zig#4df27142d0efa560bd13f14cef8298ee9bceafc8",
|
||||
.hash = "aws-0.0.1-SbsFcP05CgDKqHcuxwvc-FZ5ITDVGqeGL9uUU7eE38nb",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
|
|
|
|||
|
|
@ -231,19 +231,8 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
|||
const base64_data = try std.fmt.allocPrint(options.allocator, "{b64}", .{zip_data});
|
||||
defer options.allocator.free(base64_data);
|
||||
|
||||
var client = aws.Client.init(options.allocator, .{});
|
||||
defer client.deinit();
|
||||
|
||||
const services = aws.Services(.{.lambda}){};
|
||||
|
||||
const region = options.region orelse "us-east-1";
|
||||
|
||||
const aws_options = aws.Options{
|
||||
.client = client,
|
||||
.region = region,
|
||||
.credential_options = .{ .profile = .{ .profile_name = options.profile } },
|
||||
};
|
||||
|
||||
// Convert arch string to Lambda format
|
||||
const lambda_arch: []const u8 = if (std.mem.eql(u8, arch_str, "aarch64") or std.mem.eql(u8, arch_str, "arm64"))
|
||||
"arm64"
|
||||
|
|
@ -273,12 +262,9 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
|||
.allocator = options.allocator,
|
||||
};
|
||||
|
||||
const create_options = aws.Options{
|
||||
.client = client,
|
||||
.region = region,
|
||||
.diagnostics = &create_diagnostics,
|
||||
.credential_options = .{ .profile = .{ .profile_name = options.profile } },
|
||||
};
|
||||
// Use the shared aws_options but add diagnostics for create call
|
||||
var create_options = options.aws_options;
|
||||
create_options.diagnostics = &create_diagnostics;
|
||||
|
||||
const create_result = aws.Request(services.lambda.create_function).call(.{
|
||||
.function_name = deploy_opts.function_name,
|
||||
|
|
@ -301,7 +287,7 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
|||
.function_name = deploy_opts.function_name,
|
||||
.architectures = architectures,
|
||||
.zip_file = base64_data,
|
||||
}, aws_options);
|
||||
}, options.aws_options);
|
||||
defer update_result.deinit();
|
||||
|
||||
try options.stdout.print("Updated function: {s}\n", .{deploy_opts.function_name});
|
||||
|
|
@ -311,11 +297,11 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
|||
try options.stdout.flush();
|
||||
|
||||
// Wait for function to be ready before updating configuration
|
||||
try waitForFunctionReady(deploy_opts.function_name, aws_options);
|
||||
try waitForFunctionReady(deploy_opts.function_name, options);
|
||||
|
||||
// Update environment variables if provided
|
||||
if (env_variables) |vars| {
|
||||
try updateFunctionConfiguration(deploy_opts.function_name, vars, aws_options, options);
|
||||
try updateFunctionConfiguration(deploy_opts.function_name, vars, options);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -333,7 +319,7 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
|||
try options.stdout.flush();
|
||||
|
||||
// Wait for function to be ready before returning
|
||||
try waitForFunctionReady(deploy_opts.function_name, aws_options);
|
||||
try waitForFunctionReady(deploy_opts.function_name, options);
|
||||
}
|
||||
|
||||
/// Build environment variables in the format expected by AWS Lambda API
|
||||
|
|
@ -364,7 +350,6 @@ fn buildEnvVariables(
|
|||
fn updateFunctionConfiguration(
|
||||
function_name: []const u8,
|
||||
env_variables: []EnvVar,
|
||||
aws_options: aws.Options,
|
||||
options: RunOptions,
|
||||
) !void {
|
||||
const services = aws.Services(.{.lambda}){};
|
||||
|
|
@ -374,24 +359,24 @@ fn updateFunctionConfiguration(
|
|||
const update_config_result = try aws.Request(services.lambda.update_function_configuration).call(.{
|
||||
.function_name = function_name,
|
||||
.environment = .{ .variables = env_variables },
|
||||
}, aws_options);
|
||||
}, options.aws_options);
|
||||
defer update_config_result.deinit();
|
||||
|
||||
try options.stdout.print("Updated environment variables\n", .{});
|
||||
try options.stdout.flush();
|
||||
|
||||
// Wait for configuration update to complete
|
||||
try waitForFunctionReady(function_name, aws_options);
|
||||
try waitForFunctionReady(function_name, options);
|
||||
}
|
||||
|
||||
fn waitForFunctionReady(function_name: []const u8, aws_options: aws.Options) !void {
|
||||
fn waitForFunctionReady(function_name: []const u8, options: RunOptions) !void {
|
||||
const services = aws.Services(.{.lambda}){};
|
||||
|
||||
var retries: usize = 30; // Up to ~6 seconds total
|
||||
while (retries > 0) : (retries -= 1) {
|
||||
const result = aws.Request(services.lambda.get_function).call(.{
|
||||
.function_name = function_name,
|
||||
}, aws_options) catch |err| {
|
||||
}, options.aws_options) catch |err| {
|
||||
// Function should exist at this point, but retry on transient errors
|
||||
std.log.warn("GetFunction failed during wait: {}", .{err});
|
||||
std.Thread.sleep(200 * std.time.ns_per_ms);
|
||||
|
|
|
|||
|
|
@ -58,10 +58,6 @@ fn printHelp(writer: *std.Io.Writer) void {
|
|||
/// Get or create an IAM role for Lambda execution
|
||||
/// Returns the role ARN
|
||||
pub fn getOrCreateRole(role_name: []const u8, options: RunOptions) ![]const u8 {
|
||||
var client = aws.Client.init(options.allocator, .{});
|
||||
defer client.deinit();
|
||||
|
||||
// Try to get existing role
|
||||
const services = aws.Services(.{.iam}){};
|
||||
|
||||
var diagnostics = aws.Diagnostics{
|
||||
|
|
@ -70,11 +66,9 @@ pub fn getOrCreateRole(role_name: []const u8, options: RunOptions) ![]const u8 {
|
|||
.allocator = options.allocator,
|
||||
};
|
||||
|
||||
const aws_options = aws.Options{
|
||||
.client = client,
|
||||
.diagnostics = &diagnostics,
|
||||
.credential_options = .{ .profile = .{ .profile_name = options.profile } },
|
||||
};
|
||||
// Use the shared aws_options but add diagnostics for this call
|
||||
var aws_options = options.aws_options;
|
||||
aws_options.diagnostics = &diagnostics;
|
||||
|
||||
const get_result = aws.Request(services.iam.get_role).call(.{
|
||||
.role_name = role_name,
|
||||
|
|
@ -82,7 +76,7 @@ pub fn getOrCreateRole(role_name: []const u8, options: RunOptions) ![]const u8 {
|
|||
defer diagnostics.deinit();
|
||||
if (diagnostics.http_code == 404) {
|
||||
// Role doesn't exist, create it
|
||||
return try createRole(options.allocator, role_name, client, options.profile);
|
||||
return try createRole(role_name, options);
|
||||
}
|
||||
std.log.err("IAM GetRole failed: {} (HTTP {})", .{ err, diagnostics.http_code });
|
||||
return error.IamGetRoleFailed;
|
||||
|
|
@ -93,14 +87,9 @@ pub fn getOrCreateRole(role_name: []const u8, options: RunOptions) ![]const u8 {
|
|||
return try options.allocator.dupe(u8, get_result.response.role.arn);
|
||||
}
|
||||
|
||||
fn createRole(allocator: std.mem.Allocator, role_name: []const u8, client: aws.Client, profile: ?[]const u8) ![]const u8 {
|
||||
fn createRole(role_name: []const u8, options: RunOptions) ![]const u8 {
|
||||
const services = aws.Services(.{.iam}){};
|
||||
|
||||
const aws_options = aws.Options{
|
||||
.client = client,
|
||||
.credential_options = .{ .profile = .{ .profile_name = profile } },
|
||||
};
|
||||
|
||||
const assume_role_policy =
|
||||
\\{
|
||||
\\ "Version": "2012-10-17",
|
||||
|
|
@ -122,10 +111,10 @@ fn createRole(allocator: std.mem.Allocator, role_name: []const u8, client: aws.C
|
|||
const create_result = try aws.Request(services.iam.create_role).call(.{
|
||||
.role_name = role_name,
|
||||
.assume_role_policy_document = assume_role_policy,
|
||||
}, aws_options);
|
||||
}, options.aws_options);
|
||||
defer create_result.deinit();
|
||||
|
||||
const arn = try allocator.dupe(u8, create_result.response.role.arn);
|
||||
const arn = try options.allocator.dupe(u8, create_result.response.role.arn);
|
||||
|
||||
// Attach the Lambda execution policy
|
||||
std.log.info("Attaching AWSLambdaExecute policy", .{});
|
||||
|
|
@ -133,7 +122,7 @@ fn createRole(allocator: std.mem.Allocator, role_name: []const u8, client: aws.C
|
|||
const attach_result = try aws.Request(services.iam.attach_role_policy).call(.{
|
||||
.policy_arn = "arn:aws:iam::aws:policy/AWSLambdaExecute",
|
||||
.role_name = role_name,
|
||||
}, aws_options);
|
||||
}, options.aws_options);
|
||||
defer attach_result.deinit();
|
||||
|
||||
// IAM role creation can take a moment to propagate
|
||||
|
|
|
|||
|
|
@ -57,19 +57,7 @@ fn printHelp(writer: *std.Io.Writer) void {
|
|||
}
|
||||
|
||||
fn invokeFunction(function_name: []const u8, payload: []const u8, options: RunOptions) !void {
|
||||
// Note: Profile is expected to be set via AWS_PROFILE env var before invoking this tool
|
||||
// (e.g., via aws-vault exec)
|
||||
|
||||
var client = aws.Client.init(options.allocator, .{});
|
||||
defer client.deinit();
|
||||
|
||||
const services = aws.Services(.{.lambda}){};
|
||||
const region = options.region orelse "us-east-1";
|
||||
|
||||
const aws_options = aws.Options{
|
||||
.client = client,
|
||||
.region = region,
|
||||
};
|
||||
|
||||
std.log.info("Invoking function: {s}", .{function_name});
|
||||
|
||||
|
|
@ -78,7 +66,7 @@ fn invokeFunction(function_name: []const u8, payload: []const u8, options: RunOp
|
|||
.payload = payload,
|
||||
.log_type = "Tail",
|
||||
.invocation_type = "RequestResponse",
|
||||
}, aws_options);
|
||||
}, options.aws_options);
|
||||
defer result.deinit();
|
||||
|
||||
// Print response payload
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
//! invoke Invoke the deployed function
|
||||
|
||||
const std = @import("std");
|
||||
const aws = @import("aws");
|
||||
const package = @import("package.zig");
|
||||
const iam_cmd = @import("iam.zig");
|
||||
const deploy_cmd = @import("deploy.zig");
|
||||
|
|
@ -21,8 +22,8 @@ pub const RunOptions = struct {
|
|||
allocator: std.mem.Allocator,
|
||||
stdout: *std.Io.Writer,
|
||||
stderr: *std.Io.Writer,
|
||||
region: ?[]const u8 = null,
|
||||
profile: ?[]const u8 = null,
|
||||
region: []const u8,
|
||||
aws_options: aws.Options,
|
||||
};
|
||||
|
||||
pub fn main() !u8 {
|
||||
|
|
@ -35,46 +36,42 @@ pub fn main() !u8 {
|
|||
var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
|
||||
var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer);
|
||||
|
||||
var options = RunOptions{
|
||||
.allocator = allocator,
|
||||
.stdout = &stdout_writer.interface,
|
||||
.stderr = &stderr_writer.interface,
|
||||
};
|
||||
|
||||
run(&options) catch |err| {
|
||||
options.stderr.print("Error: {}\n", .{err}) catch {};
|
||||
options.stderr.flush() catch {};
|
||||
run(allocator, &stdout_writer.interface, &stderr_writer.interface) catch |err| {
|
||||
stderr_writer.interface.print("Error: {}\n", .{err}) catch {};
|
||||
stderr_writer.interface.flush() catch {};
|
||||
return 1;
|
||||
};
|
||||
try options.stderr.flush();
|
||||
try options.stdout.flush();
|
||||
try stderr_writer.interface.flush();
|
||||
try stdout_writer.interface.flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn run(options: *RunOptions) !void {
|
||||
const args = try std.process.argsAlloc(options.allocator);
|
||||
defer std.process.argsFree(options.allocator, args);
|
||||
fn run(allocator: std.mem.Allocator, stdout: *std.Io.Writer, stderr: *std.Io.Writer) !void {
|
||||
const args = try std.process.argsAlloc(allocator);
|
||||
defer std.process.argsFree(allocator, args);
|
||||
|
||||
if (args.len < 2) {
|
||||
printUsage(options.stderr);
|
||||
try options.stderr.flush();
|
||||
printUsage(stderr);
|
||||
try stderr.flush();
|
||||
return error.MissingCommand;
|
||||
}
|
||||
|
||||
// Parse global options and find command
|
||||
var cmd_start: usize = 1;
|
||||
var region: []const u8 = "us-east-1";
|
||||
var profile: ?[]const u8 = null;
|
||||
|
||||
while (cmd_start < args.len) {
|
||||
const arg = args[cmd_start];
|
||||
if (std.mem.eql(u8, arg, "--region")) {
|
||||
cmd_start += 1;
|
||||
if (cmd_start >= args.len) return error.MissingRegionValue;
|
||||
options.region = args[cmd_start];
|
||||
region = args[cmd_start];
|
||||
cmd_start += 1;
|
||||
} else if (std.mem.eql(u8, arg, "--profile")) {
|
||||
cmd_start += 1;
|
||||
if (cmd_start >= args.len) return error.MissingProfileValue;
|
||||
options.profile = args[cmd_start];
|
||||
profile = args[cmd_start];
|
||||
cmd_start += 1;
|
||||
} else if (std.mem.startsWith(u8, arg, "--")) {
|
||||
// Unknown global option - might be command-specific, let command handle it
|
||||
|
|
@ -86,29 +83,52 @@ fn run(options: *RunOptions) !void {
|
|||
}
|
||||
|
||||
if (cmd_start >= args.len) {
|
||||
printUsage(options.stderr);
|
||||
try options.stderr.flush();
|
||||
printUsage(stderr);
|
||||
try stderr.flush();
|
||||
return error.MissingCommand;
|
||||
}
|
||||
|
||||
// Create AWS client and options once, used by all commands
|
||||
var client = aws.Client.init(allocator, .{});
|
||||
defer client.deinit();
|
||||
|
||||
const aws_options = aws.Options{
|
||||
.client = client,
|
||||
.region = region,
|
||||
.credential_options = .{
|
||||
.profile = .{
|
||||
.profile_name = profile,
|
||||
.prefer_profile_from_file = profile != null,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const options = RunOptions{
|
||||
.allocator = allocator,
|
||||
.stdout = stdout,
|
||||
.stderr = stderr,
|
||||
.region = region,
|
||||
.aws_options = aws_options,
|
||||
};
|
||||
|
||||
const command = args[cmd_start];
|
||||
const cmd_args = args[cmd_start + 1 ..];
|
||||
|
||||
if (std.mem.eql(u8, command, "package")) {
|
||||
try package.run(cmd_args, options.*);
|
||||
try package.run(cmd_args, options);
|
||||
} else if (std.mem.eql(u8, command, "iam")) {
|
||||
try iam_cmd.run(cmd_args, options.*);
|
||||
try iam_cmd.run(cmd_args, options);
|
||||
} else if (std.mem.eql(u8, command, "deploy")) {
|
||||
try deploy_cmd.run(cmd_args, options.*);
|
||||
try deploy_cmd.run(cmd_args, options);
|
||||
} else if (std.mem.eql(u8, command, "invoke")) {
|
||||
try invoke_cmd.run(cmd_args, options.*);
|
||||
try invoke_cmd.run(cmd_args, options);
|
||||
} else if (std.mem.eql(u8, command, "--help") or std.mem.eql(u8, command, "-h")) {
|
||||
printUsage(options.stdout);
|
||||
try options.stdout.flush();
|
||||
printUsage(stdout);
|
||||
try stdout.flush();
|
||||
} else {
|
||||
options.stderr.print("Unknown command: {s}\n\n", .{command}) catch {};
|
||||
printUsage(options.stderr);
|
||||
try options.stderr.flush();
|
||||
stderr.print("Unknown command: {s}\n\n", .{command}) catch {};
|
||||
printUsage(stderr);
|
||||
try stderr.flush();
|
||||
return error.UnknownCommand;
|
||||
}
|
||||
}
|
||||
|
|
@ -120,7 +140,7 @@ fn printUsage(writer: *std.Io.Writer) void {
|
|||
\\Lambda deployment CLI tool
|
||||
\\
|
||||
\\Global Options:
|
||||
\\ --region <region> AWS region (default: from AWS config)
|
||||
\\ --region <region> AWS region (default: us-east-1)
|
||||
\\ --profile <profile> AWS profile to use
|
||||
\\
|
||||
\\Commands:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue