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,
|
.target = b.graph.host,
|
||||||
.optimize = .ReleaseSafe,
|
.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 LambdaConfig = @import("lambdabuild.zig").Config;
|
||||||
|
pub const LambdaBuildInfo = @import("lambdabuild.zig").BuildInfo;
|
||||||
|
|
||||||
/// Configure Lambda build steps for a Zig project.
|
/// 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
|
/// Lambda functions to AWS. The `lambda_zig_dep` parameter must be the
|
||||||
/// dependency object obtained from `b.dependency("lambda_zig", ...)`.
|
/// 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
|
/// ## Build Steps
|
||||||
///
|
///
|
||||||
/// The following build steps are added:
|
/// 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
|
/// - `-Dallow-principal=[string]`: AWS service principal to grant invoke permission
|
||||||
/// (e.g., "alexa-appkit.amazon.com" for Alexa Skills Kit)
|
/// (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
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// ```zig
|
/// ```zig
|
||||||
|
|
@ -161,13 +186,15 @@ pub const LambdaConfig = @import("lambdabuild.zig").Config;
|
||||||
/// const exe = b.addExecutable(.{ ... });
|
/// const exe = b.addExecutable(.{ ... });
|
||||||
/// b.installArtifact(exe);
|
/// b.installArtifact(exe);
|
||||||
///
|
///
|
||||||
/// // Use default config (function name defaults to "zig-fn")
|
/// // Configure Lambda build and get deployment info
|
||||||
/// try lambda_zig.configureBuild(b, lambda_zig_dep, exe, .{});
|
/// const lambda = try lambda_zig.configureBuild(b, lambda_zig_dep, exe, .{
|
||||||
///
|
|
||||||
/// // Or specify project-level defaults
|
|
||||||
/// try lambda_zig.configureBuild(b, lambda_zig_dep, exe, .{
|
|
||||||
/// .default_function_name = "my-function",
|
/// .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(
|
pub fn configureBuild(
|
||||||
|
|
@ -175,11 +202,11 @@ pub fn configureBuild(
|
||||||
lambda_zig_dep: *std.Build.Dependency,
|
lambda_zig_dep: *std.Build.Dependency,
|
||||||
exe: *std.Build.Step.Compile,
|
exe: *std.Build.Step.Compile,
|
||||||
config: LambdaConfig,
|
config: LambdaConfig,
|
||||||
) !void {
|
) !LambdaBuildInfo {
|
||||||
// Get lambda_build from the lambda_zig dependency's Build context
|
// Get lambda_build from the lambda_zig dependency's Build context
|
||||||
const lambda_build_dep = lambda_zig_dep.builder.dependency("lambda_build", .{
|
const lambda_build_dep = lambda_zig_dep.builder.dependency("lambda_build", .{
|
||||||
.target = b.graph.host,
|
.target = b.graph.host,
|
||||||
.optimize = .ReleaseSafe,
|
.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",
|
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.
|
/// Configure Lambda build steps for a Zig project.
|
||||||
///
|
///
|
||||||
/// Adds the following build steps:
|
/// Adds the following build steps:
|
||||||
|
|
@ -28,12 +54,15 @@ pub const Config = struct {
|
||||||
///
|
///
|
||||||
/// The `config` parameter allows setting project-level defaults that can
|
/// The `config` parameter allows setting project-level defaults that can
|
||||||
/// still be overridden via command-line options.
|
/// 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(
|
pub fn configureBuild(
|
||||||
b: *std.Build,
|
b: *std.Build,
|
||||||
lambda_build_dep: *std.Build.Dependency,
|
lambda_build_dep: *std.Build.Dependency,
|
||||||
exe: *std.Build.Step.Compile,
|
exe: *std.Build.Step.Compile,
|
||||||
config: Config,
|
config: Config,
|
||||||
) !void {
|
) !BuildInfo {
|
||||||
// Get the lambda-build CLI artifact from the dependency
|
// Get the lambda-build CLI artifact from the dependency
|
||||||
const cli = lambda_build_dep.artifact("lambda-build");
|
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 (env_file) |ef| deploy_cmd.addArgs(&.{ "--env-file", ef });
|
||||||
if (allow_principal) |ap| deploy_cmd.addArgs(&.{ "--allow-principal", ap });
|
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);
|
deploy_cmd.step.dependOn(&package_cmd.step);
|
||||||
|
|
||||||
const deploy_step = b.step("awslambda_deploy", "Deploy the Lambda function");
|
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");
|
const run_step = b.step("awslambda_run", "Invoke the deployed Lambda function");
|
||||||
run_step.dependOn(&invoke_cmd.step);
|
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 role_name: []const u8 = "lambda_basic_execution";
|
||||||
var arch: ?[]const u8 = null;
|
var arch: ?[]const u8 = null;
|
||||||
var allow_principal: ?[]const u8 = null;
|
var allow_principal: ?[]const u8 = null;
|
||||||
|
var deploy_output: ?[]const u8 = null;
|
||||||
|
|
||||||
// Environment variables storage
|
// Environment variables storage
|
||||||
var env_vars = std.StringHashMap([]const u8).init(options.allocator);
|
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;
|
i += 1;
|
||||||
if (i >= args.len) return error.MissingAllowPrincipal;
|
if (i >= args.len) return error.MissingAllowPrincipal;
|
||||||
allow_principal = args[i];
|
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")) {
|
} else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
||||||
printHelp(options.stdout);
|
printHelp(options.stdout);
|
||||||
try options.stdout.flush();
|
try options.stdout.flush();
|
||||||
|
|
@ -98,6 +103,7 @@ pub fn run(args: []const []const u8, options: RunOptions) !void {
|
||||||
.arch = arch,
|
.arch = arch,
|
||||||
.env_vars = if (env_vars.count() > 0) &env_vars else null,
|
.env_vars = if (env_vars.count() > 0) &env_vars else null,
|
||||||
.allow_principal = allow_principal,
|
.allow_principal = allow_principal,
|
||||||
|
.deploy_output = deploy_output,
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,6 +193,7 @@ fn printHelp(writer: anytype) void {
|
||||||
\\ --env-file <path> Load environment variables from file (KEY=VALUE format)
|
\\ --env-file <path> Load environment variables from file (KEY=VALUE format)
|
||||||
\\ --allow-principal <p> Grant invoke permission to AWS service principal
|
\\ --allow-principal <p> Grant invoke permission to AWS service principal
|
||||||
\\ (e.g., alexa-appkit.amazon.com)
|
\\ (e.g., alexa-appkit.amazon.com)
|
||||||
|
\\ --deploy-output <path> Write deployment info (ARN, region, etc.) to JSON file
|
||||||
\\ --help, -h Show this help message
|
\\ --help, -h Show this help message
|
||||||
\\
|
\\
|
||||||
\\Environment File Format:
|
\\Environment File Format:
|
||||||
|
|
@ -212,6 +219,7 @@ const DeployOptions = struct {
|
||||||
arch: ?[]const u8,
|
arch: ?[]const u8,
|
||||||
env_vars: ?*const std.StringHashMap([]const u8),
|
env_vars: ?*const std.StringHashMap([]const u8),
|
||||||
allow_principal: ?[]const u8,
|
allow_principal: ?[]const u8,
|
||||||
|
deploy_output: ?[]const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
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;
|
var create_options = options.aws_options;
|
||||||
create_options.diagnostics = &create_diagnostics;
|
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(.{
|
const create_result = aws.Request(services.lambda.create_function).call(.{
|
||||||
.function_name = deploy_opts.function_name,
|
.function_name = deploy_opts.function_name,
|
||||||
.architectures = architectures,
|
.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});
|
try options.stdout.print("Updated function: {s}\n", .{deploy_opts.function_name});
|
||||||
if (update_result.response.function_arn) |arn| {
|
if (update_result.response.function_arn) |arn| {
|
||||||
try options.stdout.print("ARN: {s}\n", .{arn});
|
try options.stdout.print("ARN: {s}\n", .{arn});
|
||||||
|
function_arn = try options.allocator.dupe(u8, arn);
|
||||||
}
|
}
|
||||||
try options.stdout.flush();
|
try options.stdout.flush();
|
||||||
|
|
||||||
|
|
@ -318,6 +331,11 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
||||||
try addPermission(deploy_opts.function_name, principal, options);
|
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;
|
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});
|
try options.stdout.print("Created function: {s}\n", .{deploy_opts.function_name});
|
||||||
if (create_result.response.function_arn) |arn| {
|
if (create_result.response.function_arn) |arn| {
|
||||||
try options.stdout.print("ARN: {s}\n", .{arn});
|
try options.stdout.print("ARN: {s}\n", .{arn});
|
||||||
|
function_arn = try options.allocator.dupe(u8, arn);
|
||||||
}
|
}
|
||||||
try options.stdout.flush();
|
try options.stdout.flush();
|
||||||
|
|
||||||
|
|
@ -339,6 +358,11 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
||||||
if (deploy_opts.allow_principal) |principal| {
|
if (deploy_opts.allow_principal) |principal| {
|
||||||
try addPermission(deploy_opts.function_name, principal, options);
|
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
|
/// 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.print("Added invoke permission for: {s}\n", .{principal});
|
||||||
try options.stdout.flush();
|
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