add ability for consumers to get deployment information
Some checks failed
Lambda-Zig Build / build (push) Failing after 10m40s
Some checks failed
Lambda-Zig Build / build (push) Failing after 10m40s
This commit is contained in:
parent
ed9c7ced6c
commit
140f9e9c55
3 changed files with 164 additions and 10 deletions
45
build.zig
45
build.zig
|
|
@ -107,11 +107,13 @@ fn configureBuildInternal(b: *std.Build, exe: *std.Build.Step.Compile) !void {
|
|||
.target = b.graph.host,
|
||||
.optimize = .ReleaseSafe,
|
||||
});
|
||||
try @import("lambdabuild.zig").configureBuild(b, lambda_build_dep, exe, .{});
|
||||
// Ignore return value for internal builds
|
||||
_ = try @import("lambdabuild.zig").configureBuild(b, lambda_build_dep, exe, .{});
|
||||
}
|
||||
|
||||
/// Re-export LambdaConfig for consumers
|
||||
/// Re-export types for consumers
|
||||
pub const LambdaConfig = @import("lambdabuild.zig").Config;
|
||||
pub const LambdaBuildInfo = @import("lambdabuild.zig").BuildInfo;
|
||||
|
||||
/// Configure Lambda build steps for a Zig project.
|
||||
///
|
||||
|
|
@ -119,6 +121,11 @@ pub const LambdaConfig = @import("lambdabuild.zig").Config;
|
|||
/// Lambda functions to AWS. The `lambda_zig_dep` parameter must be the
|
||||
/// dependency object obtained from `b.dependency("lambda_zig", ...)`.
|
||||
///
|
||||
/// Returns a `LambdaBuildInfo` struct containing:
|
||||
/// - References to all build steps (package, iam, deploy, invoke)
|
||||
/// - A `deploy_output` LazyPath to a JSON file with deployment info
|
||||
/// - The function name used
|
||||
///
|
||||
/// ## Build Steps
|
||||
///
|
||||
/// The following build steps are added:
|
||||
|
|
@ -144,6 +151,24 @@ pub const LambdaConfig = @import("lambdabuild.zig").Config;
|
|||
/// - `-Dallow-principal=[string]`: AWS service principal to grant invoke permission
|
||||
/// (e.g., "alexa-appkit.amazon.com" for Alexa Skills Kit)
|
||||
///
|
||||
/// ## Deploy Output
|
||||
///
|
||||
/// The `deploy_output` field in the returned struct is a LazyPath to a JSON file
|
||||
/// containing deployment information (available after deploy completes):
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "arn": "arn:aws:lambda:us-east-1:123456789012:function:my-function",
|
||||
/// "function_name": "my-function",
|
||||
/// "partition": "aws",
|
||||
/// "region": "us-east-1",
|
||||
/// "account_id": "123456789012",
|
||||
/// "role_arn": "arn:aws:iam::123456789012:role/lambda_basic_execution",
|
||||
/// "architecture": "arm64",
|
||||
/// "environment_keys": ["MY_VAR"]
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```zig
|
||||
|
|
@ -161,13 +186,15 @@ pub const LambdaConfig = @import("lambdabuild.zig").Config;
|
|||
/// const exe = b.addExecutable(.{ ... });
|
||||
/// b.installArtifact(exe);
|
||||
///
|
||||
/// // Use default config (function name defaults to "zig-fn")
|
||||
/// try lambda_zig.configureBuild(b, lambda_zig_dep, exe, .{});
|
||||
///
|
||||
/// // Or specify project-level defaults
|
||||
/// try lambda_zig.configureBuild(b, lambda_zig_dep, exe, .{
|
||||
/// // Configure Lambda build and get deployment info
|
||||
/// const lambda = try lambda_zig.configureBuild(b, lambda_zig_dep, exe, .{
|
||||
/// .default_function_name = "my-function",
|
||||
/// });
|
||||
///
|
||||
/// // Use lambda.deploy_output in other steps that need the ARN
|
||||
/// const my_step = b.addRunArtifact(my_tool);
|
||||
/// my_step.addFileArg(lambda.deploy_output);
|
||||
/// my_step.step.dependOn(lambda.deploy_step); // Ensure deploy runs first
|
||||
/// }
|
||||
/// ```
|
||||
pub fn configureBuild(
|
||||
|
|
@ -175,11 +202,11 @@ pub fn configureBuild(
|
|||
lambda_zig_dep: *std.Build.Dependency,
|
||||
exe: *std.Build.Step.Compile,
|
||||
config: LambdaConfig,
|
||||
) !void {
|
||||
) !LambdaBuildInfo {
|
||||
// Get lambda_build from the lambda_zig dependency's Build context
|
||||
const lambda_build_dep = lambda_zig_dep.builder.dependency("lambda_build", .{
|
||||
.target = b.graph.host,
|
||||
.optimize = .ReleaseSafe,
|
||||
});
|
||||
try @import("lambdabuild.zig").configureBuild(b, lambda_build_dep, exe, config);
|
||||
return @import("lambdabuild.zig").configureBuild(b, lambda_build_dep, exe, config);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,32 @@ pub const Config = struct {
|
|||
default_role_name: []const u8 = "lambda_basic_execution",
|
||||
};
|
||||
|
||||
/// Information about the configured Lambda build steps.
|
||||
///
|
||||
/// Returned by `configureBuild` to allow consumers to depend on steps
|
||||
/// and access deployment outputs.
|
||||
pub const BuildInfo = struct {
|
||||
/// Package step - creates the deployment zip
|
||||
package_step: *std.Build.Step,
|
||||
|
||||
/// IAM step - creates/verifies the IAM role
|
||||
iam_step: *std.Build.Step,
|
||||
|
||||
/// Deploy step - deploys the function to AWS Lambda
|
||||
deploy_step: *std.Build.Step,
|
||||
|
||||
/// Invoke step - invokes the deployed function
|
||||
invoke_step: *std.Build.Step,
|
||||
|
||||
/// LazyPath to JSON file with deployment info.
|
||||
/// Contains: arn, function_name, region, account_id, role_arn, architecture, environment_keys
|
||||
/// Available after deploy_step completes.
|
||||
deploy_output: std.Build.LazyPath,
|
||||
|
||||
/// The function name used for deployment
|
||||
function_name: []const u8,
|
||||
};
|
||||
|
||||
/// Configure Lambda build steps for a Zig project.
|
||||
///
|
||||
/// Adds the following build steps:
|
||||
|
|
@ -28,12 +54,15 @@ pub const Config = struct {
|
|||
///
|
||||
/// The `config` parameter allows setting project-level defaults that can
|
||||
/// still be overridden via command-line options.
|
||||
///
|
||||
/// Returns a `BuildInfo` struct containing references to all steps and
|
||||
/// a `deploy_output` LazyPath to the deployment info JSON file.
|
||||
pub fn configureBuild(
|
||||
b: *std.Build,
|
||||
lambda_build_dep: *std.Build.Dependency,
|
||||
exe: *std.Build.Step.Compile,
|
||||
config: Config,
|
||||
) !void {
|
||||
) !BuildInfo {
|
||||
// Get the lambda-build CLI artifact from the dependency
|
||||
const cli = lambda_build_dep.artifact("lambda-build");
|
||||
|
||||
|
|
@ -117,6 +146,9 @@ pub fn configureBuild(
|
|||
});
|
||||
if (env_file) |ef| deploy_cmd.addArgs(&.{ "--env-file", ef });
|
||||
if (allow_principal) |ap| deploy_cmd.addArgs(&.{ "--allow-principal", ap });
|
||||
// Add deploy output file for deployment info JSON
|
||||
deploy_cmd.addArg("--deploy-output");
|
||||
const deploy_output = deploy_cmd.addOutputFileArg("deploy-output.json");
|
||||
deploy_cmd.step.dependOn(&package_cmd.step);
|
||||
|
||||
const deploy_step = b.step("awslambda_deploy", "Deploy the Lambda function");
|
||||
|
|
@ -138,4 +170,13 @@ pub fn configureBuild(
|
|||
|
||||
const run_step = b.step("awslambda_run", "Invoke the deployed Lambda function");
|
||||
run_step.dependOn(&invoke_cmd.step);
|
||||
|
||||
return .{
|
||||
.package_step = package_step,
|
||||
.iam_step = iam_step,
|
||||
.deploy_step = deploy_step,
|
||||
.invoke_step = run_step,
|
||||
.deploy_output = deploy_output,
|
||||
.function_name = function_name,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ pub fn run(args: []const []const u8, options: RunOptions) !void {
|
|||
var role_name: []const u8 = "lambda_basic_execution";
|
||||
var arch: ?[]const u8 = null;
|
||||
var allow_principal: ?[]const u8 = null;
|
||||
var deploy_output: ?[]const u8 = null;
|
||||
|
||||
// Environment variables storage
|
||||
var env_vars = std.StringHashMap([]const u8).init(options.allocator);
|
||||
|
|
@ -65,6 +66,10 @@ pub fn run(args: []const []const u8, options: RunOptions) !void {
|
|||
i += 1;
|
||||
if (i >= args.len) return error.MissingAllowPrincipal;
|
||||
allow_principal = args[i];
|
||||
} else if (std.mem.eql(u8, arg, "--deploy-output")) {
|
||||
i += 1;
|
||||
if (i >= args.len) return error.MissingDeployOutput;
|
||||
deploy_output = args[i];
|
||||
} else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
||||
printHelp(options.stdout);
|
||||
try options.stdout.flush();
|
||||
|
|
@ -98,6 +103,7 @@ pub fn run(args: []const []const u8, options: RunOptions) !void {
|
|||
.arch = arch,
|
||||
.env_vars = if (env_vars.count() > 0) &env_vars else null,
|
||||
.allow_principal = allow_principal,
|
||||
.deploy_output = deploy_output,
|
||||
}, options);
|
||||
}
|
||||
|
||||
|
|
@ -187,6 +193,7 @@ fn printHelp(writer: anytype) void {
|
|||
\\ --env-file <path> Load environment variables from file (KEY=VALUE format)
|
||||
\\ --allow-principal <p> Grant invoke permission to AWS service principal
|
||||
\\ (e.g., alexa-appkit.amazon.com)
|
||||
\\ --deploy-output <path> Write deployment info (ARN, region, etc.) to JSON file
|
||||
\\ --help, -h Show this help message
|
||||
\\
|
||||
\\Environment File Format:
|
||||
|
|
@ -212,6 +219,7 @@ const DeployOptions = struct {
|
|||
arch: ?[]const u8,
|
||||
env_vars: ?*const std.StringHashMap([]const u8),
|
||||
allow_principal: ?[]const u8,
|
||||
deploy_output: ?[]const u8,
|
||||
};
|
||||
|
||||
fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
||||
|
|
@ -275,6 +283,10 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
|||
var create_options = options.aws_options;
|
||||
create_options.diagnostics = &create_diagnostics;
|
||||
|
||||
// Track the function ARN from whichever path succeeds
|
||||
var function_arn: ?[]const u8 = null;
|
||||
defer if (function_arn) |arn| options.allocator.free(arn);
|
||||
|
||||
const create_result = aws.Request(services.lambda.create_function).call(.{
|
||||
.function_name = deploy_opts.function_name,
|
||||
.architectures = architectures,
|
||||
|
|
@ -302,6 +314,7 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
|||
try options.stdout.print("Updated function: {s}\n", .{deploy_opts.function_name});
|
||||
if (update_result.response.function_arn) |arn| {
|
||||
try options.stdout.print("ARN: {s}\n", .{arn});
|
||||
function_arn = try options.allocator.dupe(u8, arn);
|
||||
}
|
||||
try options.stdout.flush();
|
||||
|
||||
|
|
@ -318,6 +331,11 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
|||
try addPermission(deploy_opts.function_name, principal, options);
|
||||
}
|
||||
|
||||
// Write deploy output if requested
|
||||
if (deploy_opts.deploy_output) |output_path| {
|
||||
try writeDeployOutput(output_path, function_arn.?, role_arn, lambda_arch, deploy_opts.env_vars);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -329,6 +347,7 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
|||
try options.stdout.print("Created function: {s}\n", .{deploy_opts.function_name});
|
||||
if (create_result.response.function_arn) |arn| {
|
||||
try options.stdout.print("ARN: {s}\n", .{arn});
|
||||
function_arn = try options.allocator.dupe(u8, arn);
|
||||
}
|
||||
try options.stdout.flush();
|
||||
|
||||
|
|
@ -339,6 +358,11 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
|||
if (deploy_opts.allow_principal) |principal| {
|
||||
try addPermission(deploy_opts.function_name, principal, options);
|
||||
}
|
||||
|
||||
// Write deploy output if requested
|
||||
if (deploy_opts.deploy_output) |output_path| {
|
||||
try writeDeployOutput(output_path, function_arn.?, role_arn, lambda_arch, deploy_opts.env_vars);
|
||||
}
|
||||
}
|
||||
|
||||
/// Build environment variables in the format expected by AWS Lambda API
|
||||
|
|
@ -487,3 +511,65 @@ fn addPermission(
|
|||
try options.stdout.print("Added invoke permission for: {s}\n", .{principal});
|
||||
try options.stdout.flush();
|
||||
}
|
||||
|
||||
/// Write deployment information to a JSON file
|
||||
fn writeDeployOutput(
|
||||
output_path: []const u8,
|
||||
function_arn: []const u8,
|
||||
role_arn: []const u8,
|
||||
architecture: []const u8,
|
||||
env_vars: ?*const std.StringHashMap([]const u8),
|
||||
) !void {
|
||||
// Parse ARN to extract components
|
||||
// ARN format: arn:{partition}:lambda:{region}:{account_id}:function:{name}
|
||||
var arn_parts = std.mem.splitScalar(u8, function_arn, ':');
|
||||
_ = arn_parts.next(); // arn
|
||||
const partition = arn_parts.next() orelse return error.InvalidArn;
|
||||
_ = arn_parts.next(); // lambda
|
||||
const region = arn_parts.next() orelse return error.InvalidArn;
|
||||
const account_id = arn_parts.next() orelse return error.InvalidArn;
|
||||
_ = arn_parts.next(); // function
|
||||
const function_name = arn_parts.next() orelse return error.InvalidArn;
|
||||
|
||||
const file = try std.fs.cwd().createFile(output_path, .{});
|
||||
defer file.close();
|
||||
|
||||
var write_buffer: [4096]u8 = undefined;
|
||||
var buffered = file.writer(&write_buffer);
|
||||
const writer = &buffered.interface;
|
||||
|
||||
try writer.print(
|
||||
\\{{
|
||||
\\ "arn": "{s}",
|
||||
\\ "function_name": "{s}",
|
||||
\\ "partition": "{s}",
|
||||
\\ "region": "{s}",
|
||||
\\ "account_id": "{s}",
|
||||
\\ "role_arn": "{s}",
|
||||
\\ "architecture": "{s}",
|
||||
\\ "environment_keys": [
|
||||
, .{ function_arn, function_name, partition, region, account_id, role_arn, architecture });
|
||||
|
||||
// Write environment variable keys
|
||||
if (env_vars) |vars| {
|
||||
var it = vars.keyIterator();
|
||||
var first = true;
|
||||
while (it.next()) |key| {
|
||||
if (!first) {
|
||||
try writer.writeAll(",");
|
||||
}
|
||||
try writer.print("\n \"{s}\"", .{key.*});
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
try writer.writeAll(
|
||||
\\
|
||||
\\ ]
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
try writer.flush();
|
||||
|
||||
std.log.info("Wrote deployment info to: {s}", .{output_path});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue