support customization for cloudfront

This commit is contained in:
Emil Lerch 2022-05-28 18:05:13 -07:00
parent 652a04d0c3
commit 73d056e845
Signed by: lobo
GPG Key ID: A7B62D657EF764F8

View File

@ -60,6 +60,7 @@ const EndPoint = struct {
fn deinit(self: EndPoint) void { fn deinit(self: EndPoint) void {
self.allocator.free(self.uri); self.allocator.free(self.uri);
self.allocator.free(self.host);
} }
}; };
pub const AwsHttp = struct { pub const AwsHttp = struct {
@ -94,14 +95,31 @@ pub const AwsHttp = struct {
/// service called, and will set up the signing options. The return /// service called, and will set up the signing options. The return
/// value is simply a raw HttpResult /// value is simply a raw HttpResult
pub fn callApi(self: Self, service: []const u8, request: HttpRequest, options: Options) !HttpResult { pub fn callApi(self: Self, service: []const u8, request: HttpRequest, options: Options) !HttpResult {
const endpoint = try regionSubDomain(self.allocator, service, options.region, options.dualstack); // This function or regionSubDomain needs altering for virtual host
// addressing (for S3). Botocore, and I suspect other SDKs, have
// hardcoded exceptions for S3:
// https://github.com/boto/botocore/blob/f2b0dbb800b8dc2a3541334d5ca1190faf900150/botocore/utils.py#L2160-L2181
// Boto assumes virtual host addressing unless the endpoint url is configured
//
// NOTE: There are 4 rest_xml services. They are:
// * CloudFront
// * Route53
// * S3
// * S3 control
//
// All 4 are non-standard. Route53 and CloudFront are global endpoints
// S3 uses virtual host addressing (except when it doesn't), and
// S3 control uses <account-id>.s3-control.<region>.amazonaws.com
//
// So this regionSubDomain call needs to handle generic customization
const endpoint = try endpointForRequest(self.allocator, service, options.region, options.dualstack);
defer endpoint.deinit(); defer endpoint.deinit();
log.debug("Calling endpoint {s}", .{endpoint.uri}); log.debug("Calling endpoint {s}", .{endpoint.uri});
// TODO: Should we allow customization here? // TODO: Should we allow customization here?
const creds = try credentials.getCredentials(self.allocator, .{}); const creds = try credentials.getCredentials(self.allocator, .{});
defer creds.deinit(); defer creds.deinit();
const signing_config: signing.Config = .{ const signing_config: signing.Config = .{
.region = options.region, .region = getRegion(service, options.region),
.service = options.sigv4_service_name orelse service, .service = options.sigv4_service_name orelse service,
.credentials = creds, .credentials = creds,
}; };
@ -161,6 +179,11 @@ pub const AwsHttp = struct {
const url = try std.fmt.allocPrint(self.allocator, "{s}{s}{s}", .{ endpoint.uri, request.path, request.query }); const url = try std.fmt.allocPrint(self.allocator, "{s}{s}{s}", .{ endpoint.uri, request.path, request.query });
defer self.allocator.free(url); defer self.allocator.free(url);
log.debug("Request url: {s}", .{url}); log.debug("Request url: {s}", .{url});
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// PLEASE READ!! IF YOU ARE LOOKING AT THIS LINE OF CODE DUE TO A
// SEGFAULT IN INIT, IT IS PROBABLY BECAUSE THE HOST DOES NOT EXIST
// https://github.com/ziglang/zig/issues/11358
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
var req = try zfetch.Request.init(self.allocator, url, self.trust_chain); var req = try zfetch.Request.init(self.allocator, url, self.trust_chain);
defer req.deinit(); defer req.deinit();
@ -168,7 +191,7 @@ pub const AwsHttp = struct {
try req.do(method, headers, if (request_cp.body.len == 0) null else request_cp.body); try req.do(method, headers, if (request_cp.body.len == 0) null else request_cp.body);
// TODO: Timeout - is this now above us? // TODO: Timeout - is this now above us?
log.debug("request_complete. Response code {d}: {s}", .{ req.status.code, req.status.reason }); log.debug("Request Complete. Response code {d}: {s}", .{ req.status.code, req.status.reason });
log.debug("Response headers:", .{}); log.debug("Response headers:", .{});
var resp_headers = try std.ArrayList(Header).initCapacity(self.allocator, req.headers.list.items.len); var resp_headers = try std.ArrayList(Header).initCapacity(self.allocator, req.headers.list.items.len);
defer resp_headers.deinit(); defer resp_headers.deinit();
@ -204,6 +227,11 @@ pub const AwsHttp = struct {
} }
}; };
fn getRegion(service: []const u8, region: []const u8) []const u8 {
if (std.mem.eql(u8, service, "cloudfront")) return "us-east-1";
return region;
}
fn addHeaders(allocator: std.mem.Allocator, headers: *std.ArrayList(base.Header), host: []const u8, body: []const u8, content_type: []const u8, additional_headers: []Header) !?[]const u8 { fn addHeaders(allocator: std.mem.Allocator, headers: *std.ArrayList(base.Header), host: []const u8, body: []const u8, content_type: []const u8, additional_headers: []Header) !?[]const u8 {
try headers.append(.{ .name = "Accept", .value = "application/json" }); try headers.append(.{ .name = "Accept", .value = "application/json" });
try headers.append(.{ .name = "Host", .value = host }); try headers.append(.{ .name = "Host", .value = host });
@ -225,15 +253,24 @@ fn getEnvironmentVariable(allocator: std.mem.Allocator, key: []const u8) !?[]con
}; };
} }
fn regionSubDomain(allocator: std.mem.Allocator, service: []const u8, region: []const u8, useDualStack: bool) !EndPoint { fn endpointForRequest(allocator: std.mem.Allocator, service: []const u8, region: []const u8, use_dual_stack: bool) !EndPoint {
const environment_override = try getEnvironmentVariable(allocator, "AWS_ENDPOINT_URL"); const environment_override = try getEnvironmentVariable(allocator, "AWS_ENDPOINT_URL");
if (environment_override) |override| { if (environment_override) |override| {
const uri = try allocator.dupeZ(u8, override); const uri = try allocator.dupeZ(u8, override);
return endPointFromUri(allocator, uri); return endPointFromUri(allocator, uri);
} }
if (std.mem.eql(u8, service, "cloudfront")) {
return EndPoint{
.uri = try allocator.dupe(u8, "https://cloudfront.amazonaws.com"),
.host = try allocator.dupe(u8, "cloudfront.amazonaws.com"),
.scheme = "https",
.port = 443,
.allocator = allocator,
};
}
// Fallback to us-east-1 if global endpoint does not exist. // Fallback to us-east-1 if global endpoint does not exist.
const realregion = if (std.mem.eql(u8, region, "aws-global")) "us-east-1" else region; const realregion = if (std.mem.eql(u8, region, "aws-global")) "us-east-1" else region;
const dualstack = if (useDualStack) ".dualstack" else ""; const dualstack = if (use_dual_stack) ".dualstack" else "";
const domain = switch (std.hash_map.hashString(region)) { const domain = switch (std.hash_map.hashString(region)) {
US_ISO_EAST_1_HASH => "c2s.ic.gov", US_ISO_EAST_1_HASH => "c2s.ic.gov",
@ -243,7 +280,7 @@ fn regionSubDomain(allocator: std.mem.Allocator, service: []const u8, region: []
}; };
const uri = try std.fmt.allocPrintZ(allocator, "https://{s}{s}.{s}.{s}", .{ service, dualstack, realregion, domain }); const uri = try std.fmt.allocPrintZ(allocator, "https://{s}{s}.{s}.{s}", .{ service, dualstack, realregion, domain });
const host = uri["https://".len..]; const host = try allocator.dupe(u8, uri["https://".len..]);
log.debug("host: {s}, scheme: {s}, port: {}", .{ host, "https", 443 }); log.debug("host: {s}, scheme: {s}, port: {}", .{ host, "https", 443 });
return EndPoint{ return EndPoint{
.uri = uri, .uri = uri,
@ -291,7 +328,7 @@ fn endPointFromUri(allocator: std.mem.Allocator, uri: []const u8) !EndPoint {
if (host_end == 0) { if (host_end == 0) {
host_end = uri.len; host_end = uri.len;
} }
host = uri[host_start..host_end]; host = try allocator.dupe(u8, uri[host_start..host_end]);
log.debug("host: {s}, scheme: {s}, port: {}", .{ host, scheme, port }); log.debug("host: {s}, scheme: {s}, port: {}", .{ host, scheme, port });
return EndPoint{ return EndPoint{
@ -302,3 +339,25 @@ fn endPointFromUri(allocator: std.mem.Allocator, uri: []const u8) !EndPoint {
.port = port, .port = port,
}; };
} }
test "endpointForRequest standard operation" {
const allocator = std.testing.allocator;
const service = "dynamodb";
const region = "us-west-2";
const use_dual_stack = false;
const endpoint = try endpointForRequest(allocator, service, region, use_dual_stack);
defer endpoint.deinit();
try std.testing.expectEqualStrings("https://dynamodb.us-west-2.amazonaws.com", endpoint.uri);
}
test "endpointForRequest for cloudfront" {
const allocator = std.testing.allocator;
const service = "cloudfront";
const region = "us-west-2";
const use_dual_stack = false;
const endpoint = try endpointForRequest(allocator, service, region, use_dual_stack);
defer endpoint.deinit();
try std.testing.expectEqualStrings("https://cloudfront.amazonaws.com", endpoint.uri);
}