Skip to content

Instantly share code, notes, and snippets.

@stampycode
Last active October 17, 2018 13:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stampycode/0ba2f54c4e7b118fc879ab378bf2b3d8 to your computer and use it in GitHub Desktop.
Save stampycode/0ba2f54c4e7b118fc879ab378bf2b3d8 to your computer and use it in GitHub Desktop.
Interactive browser for a Docker Registry API (v2)
<?php
/**
* Browse a specified (v2) Docker Registry, examine layers, extract useful data >.>
**/
ini_set('error_reporting', E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED & ~E_WARNING);
$headers = [];
$type = isset($_GET['type']) ? $_GET['type'] : '_catalog';
$registry = (isset($_GET['registry'])) ? $_GET['registry'] : '';
$tag = isset($_GET['tag']) ? $_GET['tag'] : '';
$repo = isset($_GET['repo']) ? $_GET['repo'] : '';
if(!$registry) {
echo "registry query param is required";
die;
}
$delete = $digestsLink = '';
$pre = true;
if($type === '_catalog') {
$url = "/$type?n=1000";
}
elseif($type === 'repo') {
$url = "/$repo/tags/list";
}
elseif($type === 'tag') {
$url = "/$repo/manifests/$tag";
}
elseif($type === 'tagdigests') {
$url = "/$repo/manifests/$tag";
$headers[] = 'Accept: application/vnd.docker.distribution.manifest.v2+json';
}
elseif($type === 'blob') {
$digest = $_GET['digest'];
$url = "/$repo/blobs/$digest";
$headers[] = 'Range: bytes=0-4096';
}
if(isset($_SERVER['PHP_AUTH_USER'])) {
$auth = base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW']);
$headers[] = "Authorization: Basic $auth";
}
//DELETE a TAG
// DELETE /v2/<name>/manifests/<reference>
$host = "https://$registry/v2";
$context = stream_context_create(
[
'http' => [
'method' => $_SERVER['REQUEST_METHOD'],
'header' => implode("\r\n", $headers),
],
]
);
$data = file_get_contents($host . $url, false, $context);
header($http_response_header[0]);
if($http_response_header[0] === 'HTTP/1.1 401 Unauthorized') {
header('WWW-Authenticate: Basic realm="User Visible Realm"');
return;
}
if($_SERVER['REQUEST_METHOD'] !== 'GET') {
header('content-type: text/json');
print_r($http_response_header);
echo $url."\n";
echo $data;
exit;
};
header('content-type: text/html');
if($type !== 'blob') {
$data = json_decode($data, 1);
}
if($type === '_catalog') {
foreach($data['repositories'] as &$repo) {
$repo = "<a href='?registry=$registry&amp;type=repo&amp;repo=$repo'>$repo</a>";
}
}
elseif($type === 'repo') {
usort(
$data['tags'],
function($a, $b) {
return version_compare($a, $b);
}
);
foreach($data['tags'] as &$tag) {
$tag = "<a href='?registry=$registry&amp;type=tag&amp;repo=$repo&amp;tag=$tag'>$tag</a>";
}
}
elseif($type === 'tag') {
$digestsLink = "<a href='?registry=$registry&amp;type=tagdigests&amp;repo=$repo&amp;tag=$tag&amp;showdigests=1'>[see digests]</a>";
foreach($data['history'] as &$hist) {
$hist['v1Compatibility'] = json_decode($hist['v1Compatibility'], 1);
}
foreach($data['fsLayers'] as &$fsLayer) {
$fsLayer['blobSum'] = "<a href='?registry=$registry&amp;type=blob&amp;repo=$repo&amp;digest={$fsLayer['blobSum']}'>{$fsLayer['blobSum']}</a>";
}
}
elseif($type === 'tagdigests') {
$href = "?registry=$registry&amp;type=tag&amp;repo=$repo&amp;tag=". urlencode($data['config']['digest']);
$delete = "<a class='delete' href='$href' data-tag='$tag'>[DELETE TAG]</a>";
}
elseif($type === 'blob') {
$tempfile = tempnam('/tmp', 'blob');
file_put_contents($tempfile, $data);
$data = `/bin/cat $tempfile |gzip -d`;
unlink($tempfile);
$data = str_split($data, 72);
$data = str_split($data, 72);
$color = '#888';
$placeholder = '.';
$repl = function($m) use (&$color, &$placeholder) {
$x = str_repeat($placeholder, strlen($m[0]));
return '<span style="background-color:'.$color.';">'.$x.'</span>';
};
foreach($data as &$d) {
$d = str_replace(['<','>'], ['&lt;','&gt;'], $d);
$d = preg_replace_callback('/\x00+/', $repl, $d);
$color = '#f88';
$d = preg_replace_callback('/[\x01-\x0f]+/', $repl, $d);
$color = '#aaf';
$d = preg_replace_callback('/[\x10-\x1f]+/', $repl, $d);
$color = '#fa0';
$d = preg_replace_callback('/[\x7f-\xff]+/', $repl, $d);
$color = '#ccc';
$d = preg_replace_callback('/[^\x20-\x7f]+/', $repl, $d);
}
$data = implode('<br>', $data);
$pre = false;
}
$prt = function($data, $depth=1) use (&$prt) {
if(!is_array($data)) {
return "'$data'";
}
$out = '{'. "\n";
foreach($data as $k => $d) {
$out .= str_repeat(' ', $depth*4) . "$k: " . $prt($d, $depth + 1) ."\n";
}
$out .= str_repeat(' ', ($depth-1)*4) . '}'. "\n";
return $out;
};
?>
<!DOCTYPE html>
<html><head>
<meta charset="UTF-8">
<title>Interactive Docker Registry</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script
src="https://code.jquery.com/jquery-3.1.1.min.js"
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
crossorigin="anonymous"></script>
<script lang="javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<style>
body { margin: 5px 50px; }
a.delete { color: red; font-weight: bold; cursor: pointer; }
</style>
<script lang="javascript">
$(window).on('load', function(){
$('a.delete').on('click', function(e) {
e.preventDefault();
var link = $(this);
var tag = link.attr('data-tag');
var c = confirm("Are you sure you want to delete tag '" + tag + "' ?");
if(!c) return false;
$.ajax({
type: 'DELETE',
url: link.attr('href')
}).done(function(e) {
window.location.reload();
});
return false;
});
});
</script>
</head><body>
<div class="content">
<h1><a href="?registry=<?= $registry ?>">Interactive Docker Registry</a></h1>
<?= $delete ?>
<?= $digestsLink ?>
<pre>
<?= $prt($data) ?>
</pre>
</div>
</body></html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment