2014-09-15 23:13:44 +08:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// This is the main entrypoint to interact with the Docker registry.
|
|
|
|
|
|
|
|
// Helpful resources
|
2015-08-31 20:23:31 +08:00
|
|
|
//
|
2014-09-15 23:13:44 +08:00
|
|
|
// https://docs.angularjs.org/tutorial/step_11
|
|
|
|
// https://docs.angularjs.org/api/ngResource/service/$resource
|
|
|
|
|
|
|
|
angular.module('registry-services', ['ngResource'])
|
2015-08-31 20:23:31 +08:00
|
|
|
.factory('RegistryHost', ['$resource', function($resource){
|
|
|
|
return $resource('registry-host.json', {}, {
|
2014-09-29 20:04:40 +08:00
|
|
|
'query': {
|
|
|
|
method:'GET',
|
|
|
|
isArray: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}])
|
2015-08-31 20:23:31 +08:00
|
|
|
// Repository returns:
|
2015-09-03 16:38:50 +08:00
|
|
|
//
|
2015-08-31 20:23:31 +08:00
|
|
|
// {
|
|
|
|
// repos: [
|
|
|
|
// {username: 'SomeNamespace', name: 'SomeNamespace/SomeRepo1', selected: true|false},
|
|
|
|
// {username: 'SomeOtherNamespace', name: 'SomeOtherNamespace/SomeRepo2', selected: true|false},
|
|
|
|
// {username: 'SomeCompletelyDifferenNamespace', name: 'SomeCompletelyDifferenNamespace/SomeRepo3', selected: true|false}
|
|
|
|
// ],
|
|
|
|
// nextLink: '/v2/_catalog?last=SomeNamespace%F2SomeRepo&n=1'
|
|
|
|
// }
|
2015-09-03 16:38:50 +08:00
|
|
|
//
|
|
|
|
// The "nextLink" element is a preparation for supporting pagination
|
|
|
|
// (see https://github.com/docker/distribution/blob/master/docs/spec/api.md#pagination)
|
|
|
|
//
|
|
|
|
// On subsequent calls to "Repository()" you may pass in "n" as the number of
|
|
|
|
// elements per page as well as "last" which is the "nextLink" from the last
|
|
|
|
// call to Repository.
|
2015-08-31 20:23:31 +08:00
|
|
|
.factory('Repository', ['$resource', function($resource){
|
2015-09-03 16:38:50 +08:00
|
|
|
return $resource('/v2/_catalog?n=:n&last=:last', {}, {
|
2014-09-17 16:23:12 +08:00
|
|
|
'query': {
|
|
|
|
method:'GET',
|
2015-08-31 20:23:31 +08:00
|
|
|
isArray: false,
|
2014-09-17 16:23:12 +08:00
|
|
|
transformResponse: function(data, headers){
|
2015-08-31 20:23:31 +08:00
|
|
|
var repos = angular.fromJson(data).repositories;
|
2015-09-03 16:38:50 +08:00
|
|
|
|
|
|
|
// Extract the "last=" part from Link header:
|
|
|
|
//
|
|
|
|
// Link: </v2/_catalog?last=namespace%2repository&n=10>; rel="next"
|
|
|
|
//
|
|
|
|
// We only want to extrace the "last" part and store it like this
|
|
|
|
//
|
|
|
|
// lastNamespace = namespace
|
|
|
|
// lastRepository = repository
|
|
|
|
//
|
|
|
|
// TODO: Can we clean this up a bit?
|
|
|
|
var last = undefined;
|
|
|
|
var lastNamespace = undefined;
|
|
|
|
var lastRepository = undefined;
|
|
|
|
var linkHeader = headers()['link'];
|
|
|
|
//console.log('linkHeader='+linkHeader);
|
|
|
|
if (linkHeader) {
|
|
|
|
var lastUrl = ''+linkHeader.split(';')[0].replace('<','').replace('>','');
|
|
|
|
var startPos = lastUrl.search('last=');
|
|
|
|
//console.log('startPos=' + startPos);
|
|
|
|
if (startPos >= 0) {
|
|
|
|
var endPos = lastUrl.substring(startPos).search('&');
|
|
|
|
//console.log('endPos=' + endPos);
|
|
|
|
if (endPos >= 0) {
|
|
|
|
last = lastUrl.substring(startPos+'last='.length, startPos+endPos);
|
|
|
|
//console.log('last=' + last);
|
|
|
|
var parts = last.split('%2F');
|
|
|
|
//console.log('parts=' + parts);
|
|
|
|
if (parts.length == 2) {
|
|
|
|
lastNamespace = parts[0];
|
|
|
|
lastRepository = parts[1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-31 20:23:31 +08:00
|
|
|
var ret = {
|
|
|
|
repos: [],
|
2015-09-03 16:38:50 +08:00
|
|
|
lastNamespace: lastNamespace,
|
|
|
|
lastRepository: lastRepository
|
2015-08-31 20:23:31 +08:00
|
|
|
};
|
2015-09-03 16:38:50 +08:00
|
|
|
|
2015-08-31 20:23:31 +08:00
|
|
|
angular.forEach(repos, function(value/*, key*/) {
|
|
|
|
ret.repos.push({
|
|
|
|
username: ''+value.split('/')[0],
|
|
|
|
name: value,
|
|
|
|
selected: false
|
|
|
|
});
|
2015-02-04 17:24:19 +08:00
|
|
|
});
|
2015-09-03 16:38:50 +08:00
|
|
|
|
2015-08-31 20:23:31 +08:00
|
|
|
return ret;
|
2014-09-17 16:23:12 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
'delete': {
|
2015-08-31 20:23:31 +08:00
|
|
|
url: '/v2/repositories/:repoUser/:repoName/',
|
2014-09-17 16:23:12 +08:00
|
|
|
method: 'DELETE',
|
2015-08-31 20:23:31 +08:00
|
|
|
}
|
2014-09-16 16:11:17 +08:00
|
|
|
});
|
|
|
|
}])
|
2015-08-31 20:23:31 +08:00
|
|
|
.factory('Tag', ['$resource', function($resource){
|
2014-09-18 20:10:51 +08:00
|
|
|
// TODO: rename :repo to repoUser/repoString for convenience.
|
2015-09-03 23:03:40 +08:00
|
|
|
// https://github.com/docker/distribution/blob/master/docs/spec/api.md#listing-image-tags
|
|
|
|
return $resource('/v2/:repoUser/:repoName/tags/list', {}, {
|
|
|
|
// Response example:
|
|
|
|
// {"name":"kkleine/docker-registry-frontend","tags":["v2", "v1-deprecated"]}
|
2014-09-17 16:23:12 +08:00
|
|
|
'query': {
|
|
|
|
method:'GET',
|
|
|
|
isArray: true,
|
2015-08-31 20:23:31 +08:00
|
|
|
transformResponse: function(data/*, headers*/){
|
2014-09-17 16:23:12 +08:00
|
|
|
var res = [];
|
|
|
|
var resp = angular.fromJson(data);
|
2015-09-03 23:03:40 +08:00
|
|
|
for (var idx in resp.tags){
|
|
|
|
res.push({
|
|
|
|
name: resp.tags[idx],
|
|
|
|
imageId: 'ImageIDOf'+resp.tags[idx],
|
|
|
|
selected: false
|
|
|
|
});
|
2014-09-17 16:23:12 +08:00
|
|
|
}
|
|
|
|
return res;
|
|
|
|
},
|
|
|
|
},
|
2014-09-16 22:25:27 +08:00
|
|
|
'delete': {
|
|
|
|
url: '/v1/repositories/:repoUser/:repoName/tags/:tagName',
|
|
|
|
method: 'DELETE',
|
|
|
|
},
|
|
|
|
'exists': {
|
|
|
|
url: '/v1/repositories/:repoUser/:repoName/tags/:tagName',
|
2014-09-17 16:23:12 +08:00
|
|
|
method: 'GET',
|
2015-08-31 20:23:31 +08:00
|
|
|
transformResponse: function(data/*, headers*/){
|
2014-09-17 16:23:12 +08:00
|
|
|
// data will be the image ID if successful or an error object.
|
|
|
|
data = angular.isString(angular.fromJson(data));
|
|
|
|
return data;
|
2014-09-16 22:25:27 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
// Usage: Tag.save({repoUser:'someuser', repoName: 'someRepo', tagName: 'someTagName'}, imageId);
|
|
|
|
'save': {
|
|
|
|
method:'PUT',
|
|
|
|
url: '/v1/repositories/:repoUser/:repoName/tags/:tagName',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}])
|
2016-04-11 15:46:40 +08:00
|
|
|
.factory('Manifest', ['$resource', function($resource){
|
|
|
|
|
|
|
|
return $resource('/v2/:repoUser/:repoName/manifests/:tagName', {}, {
|
|
|
|
// Response example:
|
|
|
|
// {
|
|
|
|
// "schemaVersion": 1,
|
|
|
|
// "name": "arthur/busybox",
|
|
|
|
// "tag": "demo",
|
|
|
|
// "architecture": "amd64",
|
|
|
|
// "fsLayers": [
|
|
|
|
// {
|
|
|
|
// "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
|
|
|
|
// },
|
|
|
|
// {
|
|
|
|
// "blobSum": "sha256:d7e8ec85c5abc60edf74bd4b8d68049350127e4102a084f22060f7321eac3586"
|
|
|
|
// }
|
|
|
|
// ],
|
|
|
|
// "history": [
|
|
|
|
// {
|
|
|
|
// "v1Compatibility": "{\"id\":\"3e1018ee907f25aef8c50016296ab33624796511fdbfdbbdeca6a3ed2d0ba4e2\",\"parent\":\"176dfc9032a1ec3ac8586b383e325e1a65d1f5b5e6f46c2a55052b5aea8310f7\",\"created\":\"2016-01-12T17:47:39.251310827Z\",\"container\":\"2732d16efa11ab7da6393645e85a7f2070af94941a782a69e86457a2284f4a69\",\"container_config\":{\"Hostname\":\"ea7fe68f39fd\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) LABEL awesome=Not yet!\"],\"Image\":\"176dfc9032a1ec3ac8586b383e325e1a65d1f5b5e6f46c2a55052b5aea8310f7\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":{\"awesome\":\"Not yet!\",\"test\":\"yes\",\"working\":\"true\"}},\"docker_version\":\"1.9.1\",\"author\":\"Arthur\",\"config\":{\"Hostname\":\"ea7fe68f39fd\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"sh\"],\"Image\":\"176dfc9032a1ec3ac8586b383e325e1a65d1f5b5e6f46c2a55052b5aea8310f7\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":{\"awesome\":\"Not yet!\",\"test\":\"yes\",\"working\":\"true\"}},\"architecture\":\"amd64\",\"os\":\"linux\"}"
|
|
|
|
// },
|
|
|
|
// {
|
|
|
|
// "v1Compatibility": "{\"id\":\"5c5fb281b01ee091a0fffa5b4a4c7fb7d358e7fb7c49c263d6d7a4e35d199fd0\",\"created\":\"2015-12-08T18:31:50.979824705Z\",\"container\":\"ea7fe68f39fd0df314e841247fb940ddef4c02ab7b5edb0ee724adc3174bc8d9\",\"container_config\":{\"Hostname\":\"ea7fe68f39fd\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ADD file:c295b0748bf05d4527f500b62ff269bfd0037f7515f1375d2ee474b830bad382 in /\"],\"Image\":\"\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"ea7fe68f39fd\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":1113436}"
|
|
|
|
// }
|
|
|
|
// ],
|
|
|
|
// }
|
|
|
|
'query': {
|
|
|
|
method:'GET',
|
|
|
|
isArray: false,
|
|
|
|
transformResponse: function(data, headers){
|
|
|
|
var res = {};
|
|
|
|
var history = [];
|
|
|
|
var tmp;
|
|
|
|
var resp = angular.fromJson(data);
|
|
|
|
var v1Compatibility = undefined;
|
|
|
|
|
|
|
|
for (var idx in resp.history){
|
|
|
|
|
|
|
|
v1Compatibility = angular.fromJson(resp.history[idx].v1Compatibility);
|
|
|
|
|
|
|
|
if(v1Compatibility !== undefined){
|
|
|
|
tmp = {
|
|
|
|
id : v1Compatibility.id,
|
|
|
|
os : v1Compatibility.os,
|
|
|
|
docker_version: v1Compatibility.docker_version,
|
|
|
|
created: v1Compatibility.created,
|
|
|
|
// parentLayer: v1Compatibility.parent
|
|
|
|
};
|
|
|
|
if(v1Compatibility.author){
|
|
|
|
tmp.author = v1Compatibility.author;
|
|
|
|
}
|
|
|
|
if(v1Compatibility.config && v1Compatibility.config.Labels){
|
|
|
|
tmp.labels = v1Compatibility.config.Labels;
|
|
|
|
}
|
|
|
|
history.push(tmp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(history.length > 0){
|
|
|
|
res = history[0];
|
|
|
|
res.history = history;
|
|
|
|
}
|
|
|
|
res.fsLayers = resp.fsLayers;
|
|
|
|
res.digest = headers('docker-content-digest');
|
|
|
|
res.architecture = resp.architecture;
|
|
|
|
return res;
|
|
|
|
},
|
|
|
|
}
|
2014-09-16 22:25:27 +08:00
|
|
|
});
|
|
|
|
}])
|
2016-04-11 15:46:40 +08:00
|
|
|
// This is not totally working right now (problem with big layers)
|
|
|
|
/*
|
|
|
|
.factory('Blob', ['$resource', function($resource){
|
|
|
|
return $resource('/v2/:repoUser/:repoName/blobs/:digest', {}, {
|
|
|
|
|
|
|
|
'query': {
|
|
|
|
method:'HEAD',
|
|
|
|
interceptor: function(data, headers){
|
|
|
|
var res = {contentLength: parseInt(headers('content-length'))};
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-16 16:11:17 +08:00
|
|
|
});
|
2016-04-11 15:46:40 +08:00
|
|
|
}]) */ ;
|