initial S3 support
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Emil Lerch 2022-05-25 15:27:13 -07:00
parent e6d2559b80
commit f9cf8de76f
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
2 changed files with 59 additions and 11 deletions

View File

@ -89,37 +89,48 @@ pub fn Request(comptime action: anytype) type {
switch (Self.service_meta.aws_protocol) { switch (Self.service_meta.aws_protocol) {
.query, .ec2_query => return Self.callQuery(request, options), .query, .ec2_query => return Self.callQuery(request, options),
.json_1_0, .json_1_1 => return Self.callJson(request, options), .json_1_0, .json_1_1 => return Self.callJson(request, options),
.rest_json_1 => return Self.callRestJson(request, options), .rest_json_1, .rest_xml => return Self.callRest(request, options),
.rest_xml => @compileError("XML responses may be blocked on a zig compiler bug scheduled to be fixed in 0.10.0"),
} }
} }
/// Rest Json is the most complex and so we handle this seperately /// Rest Json is the most complex and so we handle this seperately
fn callRestJson(request: ActionRequest, options: Options) !FullResponseType { /// Oddly, Xml is similar enough we can route rest_xml through here as well
fn callRest(request: ActionRequest, options: Options) !FullResponseType {
// TODO: Does it work to merge restXml into this?
const Action = @TypeOf(action); const Action = @TypeOf(action);
var aws_request: awshttp.HttpRequest = .{ var aws_request: awshttp.HttpRequest = .{
.method = Action.http_config.method, .method = Action.http_config.method,
.content_type = "application/json", .content_type = "application/json",
.path = Action.http_config.uri, .path = Action.http_config.uri,
}; };
if (Self.service_meta.aws_protocol == .rest_xml) {
aws_request.content_type = "application/xml";
}
log.debug("Rest JSON v1 method: '{s}'", .{aws_request.method}); log.debug("Rest method: '{s}'", .{aws_request.method});
log.debug("Rest JSON v1 success code: '{d}'", .{Action.http_config.success_code}); log.debug("Rest success code: '{d}'", .{Action.http_config.success_code});
log.debug("Rest JSON v1 raw uri: '{s}'", .{Action.http_config.uri}); log.debug("Rest raw uri: '{s}'", .{Action.http_config.uri});
aws_request.path = try buildPath(options.client.allocator, Action.http_config.uri, ActionRequest, request); aws_request.path = try buildPath(options.client.allocator, Action.http_config.uri, ActionRequest, request);
defer options.client.allocator.free(aws_request.path); defer options.client.allocator.free(aws_request.path);
log.debug("Rest JSON v1 processed uri: '{s}'", .{aws_request.path}); log.debug("Rest processed uri: '{s}'", .{aws_request.path});
aws_request.query = try buildQuery(options.client.allocator, request); aws_request.query = try buildQuery(options.client.allocator, request);
log.debug("Rest JSON v1 query: '{s}'", .{aws_request.query}); log.debug("Rest query: '{s}'", .{aws_request.query});
defer options.client.allocator.free(aws_request.query); defer options.client.allocator.free(aws_request.query);
// We don't know if we need a body...guessing here, this should cover most // We don't know if we need a body...guessing here, this should cover most
var buffer = std.ArrayList(u8).init(options.client.allocator); var buffer = std.ArrayList(u8).init(options.client.allocator);
defer buffer.deinit(); defer buffer.deinit();
var nameAllocator = std.heap.ArenaAllocator.init(options.client.allocator); var nameAllocator = std.heap.ArenaAllocator.init(options.client.allocator);
defer nameAllocator.deinit(); defer nameAllocator.deinit();
if (Self.service_meta.aws_protocol == .rest_json_1) {
if (std.mem.eql(u8, "PUT", aws_request.method) or std.mem.eql(u8, "POST", aws_request.method)) { if (std.mem.eql(u8, "PUT", aws_request.method) or std.mem.eql(u8, "POST", aws_request.method)) {
try json.stringify(request, .{ .whitespace = .{} }, buffer.writer()); try json.stringify(request, .{ .whitespace = .{} }, buffer.writer());
} }
}
if (Self.service_meta.aws_protocol == .rest_xml) {
if (std.mem.eql(u8, "PUT", aws_request.method) or std.mem.eql(u8, "POST", aws_request.method)) {
return error.NotImplemented;
}
}
aws_request.body = buffer.items; aws_request.body = buffer.items;
return try Self.callAws(aws_request, .{ return try Self.callAws(aws_request, .{
@ -240,6 +251,8 @@ pub fn Request(comptime action: anytype) type {
isJson = true; isJson = true;
} else if (std.mem.startsWith(u8, h.value, "text/xml")) { } else if (std.mem.startsWith(u8, h.value, "text/xml")) {
isJson = false; isJson = false;
} else if (std.mem.startsWith(u8, h.value, "application/xml")) {
isJson = false;
} else { } else {
log.err("Unexpected content type: {s}", .{h.value}); log.err("Unexpected content type: {s}", .{h.value});
return error.UnexpectedContentType; return error.UnexpectedContentType;
@ -360,12 +373,40 @@ pub fn Request(comptime action: anytype) type {
// Big thing is that requestid, which we'll need to fetch "manually" // Big thing is that requestid, which we'll need to fetch "manually"
const xml_options = xml_shaper.ParseOptions{ .allocator = options.client.allocator }; const xml_options = xml_shaper.ParseOptions{ .allocator = options.client.allocator };
const parsed = try xml_shaper.parse(action.Response, result.body, xml_options); const parsed = try xml_shaper.parse(action.Response, result.body, xml_options);
errdefer parsed.deinit();
var free_rid = false;
// This needs to get into FullResponseType somehow: defer parsed.deinit(); // This needs to get into FullResponseType somehow: defer parsed.deinit();
const request_id = blk: { const request_id = blk: {
if (parsed.document.root.getCharData("requestId")) |elem| if (parsed.document.root.getCharData("requestId")) |elem|
break :blk elem; break :blk elem;
var rid: ?[]const u8 = null;
// This "thing" is called:
// * Host ID
// * Extended Request ID
// * Request ID 2
//
// I suspect it identifies the S3 frontend server and they are
// trying to obscure that fact. But several SDKs go with host id,
// so we'll use that
var host_id: ?[]const u8 = null;
for (result.headers) |header| {
if (std.ascii.eqlIgnoreCase(header.name, "x-amz-request-id")) {
rid = header.value;
}
if (std.ascii.eqlIgnoreCase(header.name, "x-amz-id-2")) {
host_id = header.value;
}
}
if (rid) |r| {
if (host_id) |h| {
free_rid = true;
break :blk try std.fmt.allocPrint(options.client.allocator, "{s}, host_id: {s}", .{ r, h });
}
break :blk r;
}
return error.RequestIdNotFound; return error.RequestIdNotFound;
}; };
defer if (free_rid) options.client.allocator.free(request_id);
return FullResponseType{ return FullResponseType{
.response = parsed.parsed_value, .response = parsed.parsed_value,

View File

@ -48,6 +48,7 @@ const Tests = enum {
rest_json_1_query_no_input, rest_json_1_query_no_input,
rest_json_1_query_with_input, rest_json_1_query_with_input,
rest_json_1_work_with_lambda, rest_json_1_work_with_lambda,
rest_xml_no_input,
}; };
pub fn main() anyerror!void { pub fn main() anyerror!void {
@ -88,7 +89,7 @@ pub fn main() anyerror!void {
}; };
defer client.deinit(); defer client.deinit();
const services = aws.Services(.{ .sts, .ec2, .dynamo_db, .ecs, .lambda, .sqs }){}; const services = aws.Services(.{ .sts, .ec2, .dynamo_db, .ecs, .lambda, .sqs, .s3 }){};
for (tests.items) |t| { for (tests.items) |t| {
std.log.info("===== Start Test: {s} =====", .{@tagName(t)}); std.log.info("===== Start Test: {s} =====", .{@tagName(t)});
@ -214,6 +215,12 @@ pub fn main() anyerror!void {
next = more.response.next_token; next = more.response.next_token;
} }
}, },
.rest_xml_no_input => {
const result = try client.call(services.s3.list_buckets.Request{}, options);
defer result.deinit();
std.log.info("request id: {s}", .{result.response_metadata.request_id});
std.log.info("bucket count: {d}", .{result.response.buckets.?.len});
},
} }
std.log.info("===== End Test: {s} =====\n", .{@tagName(t)}); std.log.info("===== End Test: {s} =====\n", .{@tagName(t)});
} }