diff --git a/lambdabuild.zig b/lambdabuild.zig index fd651cf..e151089 100644 --- a/lambdabuild.zig +++ b/lambdabuild.zig @@ -93,7 +93,10 @@ pub fn configureBuild(b: *std.Build, exe: *std.Build.Step.Compile, function_name const iam_step = b.step("awslambda_iam", "Create/Get IAM role for function"); iam_step.dependOn(&iam.step); - const region = b.option([]const u8, "region", "Region to use [default is autodetect from environment/config]") orelse try findRegionFromSystem(b.allocator); + var region = @import("lambdabuild/Region.zig"){ + .allocator = b.allocator, + .specified_region = b.option([]const u8, "region", "Region to use [default is autodetect from environment/config]"), + }; // Deployment const deploy = Deploy.create(b, .{ @@ -101,7 +104,7 @@ pub fn configureBuild(b: *std.Build, exe: *std.Build.Step.Compile, function_name .package = package_step.packagedFileLazyPath(), .arch = exe.root_module.resolved_target.?.result.cpu.arch, .iam_step = iam, - .region = region, + .region = ®ion, }); deploy.step.dependOn(&package_step.step); @@ -115,7 +118,7 @@ pub fn configureBuild(b: *std.Build, exe: *std.Build.Step.Compile, function_name const invoke = Invoke.create(b, .{ .name = function_name, .payload = payload, - .region = region, + .region = ®ion, }); invoke.step.dependOn(&deploy.step); const run_step = b.step("awslambda_run", "Run the app in AWS lambda"); diff --git a/lambdabuild/Deploy.zig b/lambdabuild/Deploy.zig index 231c4a0..2f9b047 100644 --- a/lambdabuild/Deploy.zig +++ b/lambdabuild/Deploy.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Region = @import("Region.zig"); const aws = @import("aws").aws; const Deploy = @This(); @@ -22,7 +23,7 @@ pub const Options = struct { iam_step: *@import("Iam.zig"), /// Region for deployment - region: []const u8, + region: *Region, }; pub fn create(owner: *std.Build, options: Options) *Deploy { @@ -92,7 +93,7 @@ fn make(step: *std.Build.Step, node: std.Progress.Node) anyerror!void { const options = aws.Options{ .client = client, .diagnostics = &diagnostics, - .region = self.options.region, + .region = try self.options.region.region(), }; aws.globalLogControl(.info, .warn, .info, true); @@ -128,7 +129,7 @@ fn make(step: *std.Build.Step, node: std.Progress.Node) anyerror!void { const base64_bytes = encoder.encode(base64_buf, bytes); const options = aws.Options{ .client = client, - .region = self.options.region, + .region = try self.options.region.region(), }; const arm64_arch = [_][]const u8{"arm64"}; const x86_64_arch = [_][]const u8{"x86_64"}; diff --git a/lambdabuild/Invoke.zig b/lambdabuild/Invoke.zig index 9a1155c..53504d0 100644 --- a/lambdabuild/Invoke.zig +++ b/lambdabuild/Invoke.zig @@ -1,6 +1,6 @@ const std = @import("std"); const aws = @import("aws").aws; - +const Region = @import("Region.zig"); const Invoke = @This(); step: std.Build.Step, @@ -16,7 +16,7 @@ pub const Options = struct { payload: []const u8, /// Region for deployment - region: []const u8, + region: *Region, }; pub fn create(owner: *std.Build, options: Options) *Invoke { @@ -50,7 +50,7 @@ fn make(step: *std.Build.Step, node: std.Progress.Node) anyerror!void { const options = aws.Options{ .client = client, - .region = self.options.region, + .region = try self.options.region.region(), }; const call = try aws.Request(services.lambda.invoke).call(.{ .function_name = self.options.name, diff --git a/lambdabuild/Region.zig b/lambdabuild/Region.zig new file mode 100644 index 0000000..2e2ccef --- /dev/null +++ b/lambdabuild/Region.zig @@ -0,0 +1,55 @@ +const std = @import("std"); + +specified_region: ?[]const u8, +allocator: std.mem.Allocator, +/// internal state, please do not use +_calculated_region: ?[]const u8 = null, +const Region = @This(); +pub fn region(self: *Region) ![]const u8 { + if (self.specified_region) |r| return r; // user specified + if (self._calculated_region) |r| return r; // cached + self._calculated_region = try findRegionFromSystem(self.allocator); + return self._calculated_region.?; +} + +// AWS_CONFIG_FILE (default is ~/.aws/config +// AWS_DEFAULT_REGION +fn findRegionFromSystem(allocator: std.mem.Allocator) ![]const u8 { + const env_map = try std.process.getEnvMap(allocator); + if (env_map.get("AWS_DEFAULT_REGION")) |r| return r; + const config_file_path = env_map.get("AWS_CONFIG_FILE") orelse + try std.fs.path.join(allocator, &[_][]const u8{ + env_map.get("HOME") orelse env_map.get("USERPROFILE").?, + ".aws", + "config", + }); + const config_file = try std.fs.openFileAbsolute(config_file_path, .{}); + defer config_file.close(); + const config_bytes = try config_file.readToEndAlloc(allocator, 1024 * 1024); + const profile = env_map.get("AWS_PROFILE") orelse "default"; + var line_iterator = std.mem.split(u8, config_bytes, "\n"); + var in_profile = false; + while (line_iterator.next()) |line| { + const trimmed = std.mem.trim(u8, line, " \t\r"); + if (trimmed.len == 0 or trimmed[0] == '#') continue; + if (!in_profile) { + if (trimmed[0] == '[' and trimmed[trimmed.len - 1] == ']') { + // this is a profile directive! + // std.debug.print("profile: {s}, in file: {s}\n", .{ profile, trimmed[1 .. trimmed.len - 1] }); + if (std.mem.eql(u8, profile, trimmed[1 .. trimmed.len - 1])) { + in_profile = true; + } + } + continue; // we're only looking for a profile at this point + } + // look for our region directive + if (trimmed[0] == '[' and trimmed[trimmed.len - 1] == ']') + return error.RegionNotFound; // we've hit another profile without getting our region + if (!std.mem.startsWith(u8, trimmed, "region")) continue; + var equalityiterator = std.mem.split(u8, trimmed, "="); + _ = equalityiterator.next() orelse return error.RegionNotFound; + const raw_val = equalityiterator.next() orelse return error.RegionNotFound; + return try allocator.dupe(u8, std.mem.trimLeft(u8, raw_val, " \t")); + } + return error.RegionNotFound; +}