Skip to content

Commit 2d93d3d

Browse files
committed
initial commit
0 parents  commit 2d93d3d

File tree

4 files changed

+288
-0
lines changed

4 files changed

+288
-0
lines changed

Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM perl:5.30.1
2+
3+
WORKDIR /app
4+
EXPOSE 3000 8080
5+
6+
ENV MOJO_VERSION 8.33
7+
COPY hub.pl /app/
8+
RUN cpanm Mojolicious@"$MOJO_VERSION"
9+
RUN cpanm Cache::FileCache Syntax::Keyword::Try && rm -r /root/.cpanm
10+
RUN cpanm IO::Socket::SSL && rm -r /root/.cpanm
11+
12+
ENTRYPOINT ["hypnotoad", "--foreground", "/app/hub.pl"]

ecr.pl

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env perl
2+
use Mojolicious::Lite;
3+
use Paws ();
4+
use Paws::Credential::File;
5+
use Cpanel::JSON::XS;
6+
7+
Paws->default_config->immutable(1);
8+
Paws->preload_service('ECR');
9+
10+
plugin 'DefaultHelpers';
11+
12+
helper paws => sub {
13+
state $paws = Paws->new(
14+
config => {
15+
region => 'ap-south-1',
16+
credentials => Paws::Credential::File->new(
17+
profile => 'prod'
18+
),
19+
}
20+
);
21+
};
22+
23+
helper ecr => sub {
24+
state $client = $_[0]->paws->service('ECR');
25+
};
26+
27+
helper 'docker.manifests' => sub {
28+
my ($c, $name, $ref) = @_;
29+
30+
my $imageStruct = {};
31+
if ($ref =~ m/^\w+:([A-Fa-f0-9]+$)/) {
32+
$imageStruct->{'ImageDigest'} = $ref;
33+
}
34+
else {
35+
$imageStruct->{'ImageTag'} = $ref;
36+
}
37+
38+
my $images = $c->ecr->BatchGetImage(ImageIds => [$imageStruct], RepositoryName => $name)->Images;
39+
if (my $image = $images->[0]) {
40+
return decode_json($image->ImageManifest);
41+
}
42+
43+
return undef;
44+
};
45+
46+
under '/v2/';
47+
48+
get '/' => sub {
49+
my $c = shift;
50+
51+
$c->render(json => ['ok']);
52+
};
53+
54+
any '/*name/manifests/*ref' => sub {
55+
my $c = shift;
56+
57+
$c->render_later;
58+
59+
my $name = $c->param('name');
60+
my $ref = $c->param('ref');
61+
62+
my $image = $c->docker->manifests($name, $ref);
63+
my $status = $image ? 200 : 404;
64+
65+
if ($c->req->method eq 'HEAD') {
66+
return $c->rendered($status);
67+
}
68+
69+
return $c->render(json => {}, status => $status) unless $image;
70+
71+
my $mediaType = $image->{'mediaType'};
72+
$c->res->headers->content_type($mediaType);
73+
74+
return $c->render(json => $image, status => $status);
75+
};
76+
77+
get '/*name/blobs/:ref' => sub {
78+
my $c = shift;
79+
80+
$c->render_later;
81+
82+
my $name = $c->param('name');
83+
my $ref = $c->param('ref');
84+
85+
my $url = $c->ecr->GetDownloadUrlForLayer(LayerDigest => $ref, RepositoryName => $name);
86+
$c->res->headers->header('Location' => $url->DownloadUrl);
87+
$c->res->headers->header('Docker-Content-Digest' => $url->LayerDigest);
88+
return $c->rendered(307);
89+
};
90+
91+
get '/*name/tags/list' => sub {
92+
my $c = shift;
93+
my $name = $c->param('name');
94+
95+
my $imagesResponse = $c->ecr->DescribeImages(RepositoryName => $name);
96+
my $imageDetails = $imagesResponse->ImageDetails;
97+
98+
if (my $imageDetail = $imageDetails->[0]) {
99+
my $response = {
100+
name => $name,
101+
tags => $imageDetail->imageDetail
102+
};
103+
return $c->render(json => $response);
104+
}
105+
106+
return $c->rendered(404);
107+
};
108+
109+
app->start;

hub.conf

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
proxy_cache_path /docker_mirror_cache levels=1:2 max_size=32g inactive=5d keys_zone=cache:10m use_temp_path=off;
2+
server {
3+
listen 6666 ssl http2 default_server;
4+
server_name _;
5+
6+
set $docker_proxy_request_type "unknown";
7+
8+
add_header X-Docker-Registry-Proxy-Cache-Upstream-Status "$upstream_cache_status";
9+
10+
ssl_certificate "/etc/nginx/server.crt";
11+
ssl_certificate_key "/etc/nginx/server.key";
12+
13+
chunked_transfer_encoding on;
14+
15+
proxy_read_timeout 900;
16+
proxy_cache_lock on;
17+
proxy_cache_lock_timeout 880s;
18+
proxy_cache_valid 200 206 5d;
19+
proxy_force_ranges on;
20+
proxy_ignore_client_abort on;
21+
proxy_cache_revalidate on;
22+
proxy_hide_header Set-Cookie;
23+
proxy_ignore_headers X-Accel-Expires Expires Cache-Control Set-Cookie;
24+
25+
26+
27+
location / {
28+
proxy_pass http://localhost:3000;
29+
proxy_cache cache;
30+
proxy_cache_key $uri;
31+
proxy_intercept_errors on;
32+
}
33+
}

hub.pl

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env perl
2+
3+
use Mojolicious::Lite;
4+
use Cache::FileCache;
5+
use Syntax::Keyword::Try;
6+
7+
helper 'registry' => sub { 'registry.docker.io' };
8+
helper 'authUrl' => sub { 'https://auth.docker.io/token' };
9+
helper 'cache' => sub { state $cache = Cache::FileCache->new; };
10+
11+
$ENV{MOJO_MAX_MESSAGE_SIZE} = 524288000;
12+
app->config(hypnotoad => { clients => $ENV{HYPNOTOAD_CLIENTS} // 100 });
13+
14+
helper authToken => sub {
15+
my ($self, $repo) = @_;
16+
17+
my $cached_token = $self->cache->get($repo);
18+
if (! defined $cached_token ) {
19+
app->log->info("Requesting for token");
20+
my $url = sprintf("%s?service=%s&scope=repository:%s:pull", $self->authUrl, $self->registry, $repo);
21+
my $tx = $self->ua->get($url);
22+
my $res = $tx->result->json;
23+
24+
my $token = $res->{'token'};
25+
my $ttl = $res->{'expires_in'};
26+
$self->cache->set($repo, $token, $ttl);
27+
28+
return $token;
29+
}
30+
app->log->info("Token found in the cache");
31+
return $cached_token;
32+
};
33+
34+
helper authHeader => sub {
35+
my ($self, $repo, $type) = @_;
36+
37+
my $header = { Authorization => undef, Accept => $type };
38+
my $token = $self->authToken($repo);
39+
$header->{'Authorization'} = 'Bearer ' . $token;
40+
return $header;
41+
};
42+
43+
get '/' => sub {
44+
my $c = shift;
45+
46+
$c->render(json => ['ok']);
47+
};
48+
49+
under '/v2';
50+
get '/' => sub {
51+
my $c = shift;
52+
53+
$c->render(json => ['ok']);
54+
};
55+
56+
any '/*name/manifests/*ref' => sub {
57+
my $c = shift;
58+
59+
$c->render_later;
60+
61+
my $name = $c->param('name');
62+
my $ref = $c->param('ref');
63+
64+
my $imageName = $name;
65+
if (index ($name, '/') == -1 ) {
66+
$imageName = 'library/' . $name;
67+
}
68+
69+
my $auth_head = $c->authHeader($imageName, 'application/vnd.docker.distribution.manifest.v2+json');
70+
my $url = sprintf("https://%s/v2/%s/manifests/%s", 'registry-1.docker.io', $imageName, $ref);
71+
my $tx = $c->ua->get($url, $auth_head);
72+
73+
my $response = {};
74+
my $status = 404;
75+
if ($tx->res->code == 200) {
76+
$response = $tx->res->json;
77+
$status = 200;
78+
my $mediaType = $response->{'mediaType'};
79+
$c->res->headers->content_type($mediaType);
80+
}
81+
return $c->render(json => $response, status => $status);
82+
};
83+
84+
get '/*name/blobs/:ref' => sub {
85+
my $c = shift;
86+
87+
$c->render_later;
88+
$c->inactivity_timeout(0);
89+
90+
my $name = $c->param('name');
91+
my $ref = $c->param('ref');
92+
93+
my $imageName = $name;
94+
if (index ($name, '/') == -1 ) {
95+
$imageName = 'library/' . $name;
96+
}
97+
my $auth_head = $c->authHeader($imageName, 'application/vnd.docker.distribution.manifest.v2+json');
98+
my $url = sprintf("https://%s/v2/%s/blobs/%s", 'registry-1.docker.io', $imageName, $ref);
99+
my $tx = $c->app->ua->get($url, $auth_head);
100+
101+
if (my $location = $tx->res->headers->location) {
102+
$c->res->code(200);
103+
$c->res->headers->content_type("application/octet-stream");
104+
105+
my $length = $c->app->ua->head($location)->res->headers->content_length;
106+
$c->res->headers->content_length($length);
107+
108+
$c->app->ua->max_response_size(0);
109+
$c->app->log->info("calling remote location $location");
110+
111+
my $d_tx = $c->app->ua->build_tx('GET' => $location);
112+
$d_tx->res->content->unsubscribe('read')->on(
113+
read => sub {
114+
my (undef, $chunk) = @_;
115+
if ($chunk) {
116+
try {
117+
return $c->write($chunk, sub { return });
118+
}
119+
catch {
120+
$c->app->log->info("Writing chunk failed: $@");
121+
$d_tx->res->content->unsubscribe('read');
122+
}
123+
}
124+
}
125+
);
126+
$c->app->ua->start_p($d_tx)->then(sub {
127+
my $tx = shift;
128+
})->catch(sub {
129+
my $err = shift;
130+
warn "Connection error: $err";
131+
})->wait;
132+
}
133+
};
134+
app->start;

0 commit comments

Comments
 (0)