147 lines
4.8 KiB
Zig
147 lines
4.8 KiB
Zig
|
const std = @import("std");
|
||
|
const aws = @import("aws").aws;
|
||
|
|
||
|
const Iam = @This();
|
||
|
|
||
|
step: std.Build.Step,
|
||
|
options: Options,
|
||
|
/// resolved_arn will be set only after make is run
|
||
|
resolved_arn: []const u8 = undefined,
|
||
|
|
||
|
arn_buf: [2048]u8 = undefined, // https://docs.aws.amazon.com/IAM/latest/APIReference/API_Role.html has 2k limit
|
||
|
const base_id: std.Build.Step.Id = .custom;
|
||
|
|
||
|
pub const Options = struct {
|
||
|
name: []const u8 = "",
|
||
|
role_name: []const u8,
|
||
|
role_arn: ?[]const u8,
|
||
|
};
|
||
|
|
||
|
pub fn create(owner: *std.Build, options: Options) *Iam {
|
||
|
const name = owner.dupe(options.name);
|
||
|
const step_name = owner.fmt("{s} {s}{s}", .{
|
||
|
"aws lambda",
|
||
|
"iam",
|
||
|
name,
|
||
|
});
|
||
|
const self = owner.allocator.create(Iam) catch @panic("OOM");
|
||
|
self.* = .{
|
||
|
.step = std.Build.Step.init(.{
|
||
|
.id = base_id,
|
||
|
.name = step_name,
|
||
|
.owner = owner,
|
||
|
.makeFn = make,
|
||
|
}),
|
||
|
.options = options,
|
||
|
};
|
||
|
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
/// gets an IamArn from the name in cache. If not in cache, null is returned
|
||
|
/// Note that cache is not account specific, so if you're banging around multiple
|
||
|
/// accounts, you'll want to use different local zig caches for each
|
||
|
pub fn getIamArnFromName(step: *std.Build.Step, name: []const u8) !?[]const u8 {
|
||
|
try step.owner.cache_root.handle.makePath("iam");
|
||
|
// we should be able to use the role name, as only the following characters
|
||
|
// are allowed: _+=,.@-.
|
||
|
const iam_file = try std.fmt.allocPrint(
|
||
|
step.owner.allocator,
|
||
|
"iam{s}{s}",
|
||
|
.{ std.fs.path.sep_str, name },
|
||
|
);
|
||
|
const buff = try step.owner.allocator.alloc(u8, 64);
|
||
|
const arn = step.owner.cache_root.handle.readFile(iam_file, buff) catch return null;
|
||
|
return arn;
|
||
|
}
|
||
|
|
||
|
fn make(step: *std.Build.Step, node: std.Progress.Node) anyerror!void {
|
||
|
_ = node;
|
||
|
const self: *Iam = @fieldParentPtr("step", step);
|
||
|
|
||
|
if (try getIamArnFromName(step, self.options.role_name)) |a| {
|
||
|
step.result_cached = true;
|
||
|
@memcpy(self.arn_buf[0..a.len], a);
|
||
|
self.resolved_arn = self.arn_buf[0..a.len];
|
||
|
return; // exists in cache - nothing to do
|
||
|
}
|
||
|
|
||
|
var client = aws.Client.init(self.step.owner.allocator, .{});
|
||
|
defer client.deinit();
|
||
|
const services = aws.Services(.{.iam}){};
|
||
|
|
||
|
var arn = blk: {
|
||
|
var diagnostics = aws.Diagnostics{
|
||
|
.http_code = undefined,
|
||
|
.response_body = undefined,
|
||
|
.allocator = self.step.owner.allocator,
|
||
|
};
|
||
|
const options = aws.Options{
|
||
|
.client = client,
|
||
|
.diagnostics = &diagnostics,
|
||
|
};
|
||
|
|
||
|
const call = aws.Request(services.iam.get_role).call(.{
|
||
|
.role_name = self.options.role_name, // TODO: if we have a role_arn, we should use it and skip
|
||
|
}, options) catch |e| {
|
||
|
defer diagnostics.deinit();
|
||
|
if (diagnostics.http_code == 404) break :blk null;
|
||
|
return step.fail(
|
||
|
"Unknown error {} from IAM GetRole. HTTP code {}, message: {s}",
|
||
|
.{ e, diagnostics.http_code, diagnostics.response_body },
|
||
|
);
|
||
|
};
|
||
|
defer call.deinit();
|
||
|
|
||
|
break :blk try step.owner.allocator.dupe(u8, call.response.role.arn);
|
||
|
};
|
||
|
// Now ARN will either be null (does not exist), or a value
|
||
|
|
||
|
if (arn == null) {
|
||
|
// we need to create the role before proceeding
|
||
|
const options = aws.Options{
|
||
|
.client = client,
|
||
|
};
|
||
|
|
||
|
const create_call = try aws.Request(services.iam.create_role).call(.{
|
||
|
.role_name = self.options.role_name,
|
||
|
.assume_role_policy_document =
|
||
|
\\{
|
||
|
\\ "Version": "2012-10-17",
|
||
|
\\ "Statement": [
|
||
|
\\ {
|
||
|
\\ "Sid": "",
|
||
|
\\ "Effect": "Allow",
|
||
|
\\ "Principal": {
|
||
|
\\ "Service": "lambda.amazonaws.com"
|
||
|
\\ },
|
||
|
\\ "Action": "sts:AssumeRole"
|
||
|
\\ }
|
||
|
\\ ]
|
||
|
\\}
|
||
|
,
|
||
|
}, options);
|
||
|
defer create_call.deinit();
|
||
|
arn = try step.owner.allocator.dupe(u8, create_call.response.role.arn);
|
||
|
const attach_call = try aws.Request(services.iam.attach_role_policy).call(.{
|
||
|
.policy_arn = "arn:aws:iam::aws:policy/AWSLambdaExecute",
|
||
|
.role_name = self.options.role_name,
|
||
|
}, options);
|
||
|
defer attach_call.deinit();
|
||
|
}
|
||
|
|
||
|
@memcpy(self.arn_buf[0..arn.?.len], arn.?);
|
||
|
self.resolved_arn = self.arn_buf[0..arn.?.len];
|
||
|
|
||
|
// NOTE: This must match getIamArnFromName
|
||
|
const iam_file = try std.fmt.allocPrint(
|
||
|
step.owner.allocator,
|
||
|
"iam{s}{s}",
|
||
|
.{ std.fs.path.sep_str, self.options.role_name },
|
||
|
);
|
||
|
try step.owner.cache_root.handle.writeFile(.{
|
||
|
.sub_path = iam_file,
|
||
|
.data = arn.?,
|
||
|
});
|
||
|
}
|