mirror of https://github.com/docker/docs.git
Image sizes & total progress. Restructure events
This commit is contained in:
parent
b2d319edcb
commit
183517b054
|
@ -19,6 +19,7 @@ var ContainerDetails = React.createClass({
|
|||
};
|
||||
},
|
||||
componentWillReceiveProps: function () {
|
||||
console.log('props');
|
||||
this.update();
|
||||
var self = this;
|
||||
var logs = [];
|
||||
|
@ -60,63 +61,25 @@ var ContainerDetails = React.createClass({
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
componentWillMount: function () {
|
||||
this.update();
|
||||
var self = this;
|
||||
var logs = [];
|
||||
var index = 0;
|
||||
docker.client().getContainer(this.getParams().name).logs({
|
||||
follow: false,
|
||||
stdout: true,
|
||||
timestamps: true
|
||||
}, function (err, stream) {
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', function (buf) {
|
||||
// Every other message is a header
|
||||
if (index % 2 === 1) {
|
||||
var time = buf.substr(0,buf.indexOf(' '));
|
||||
var msg = buf.substr(buf.indexOf(' ')+1);
|
||||
logs.push(convert.toHtml(self._escapeHTML(msg)));
|
||||
}
|
||||
index += 1;
|
||||
});
|
||||
stream.on('end', function (buf) {
|
||||
self.setState({logs: logs});
|
||||
docker.client().getContainer(self.getParams().name).logs({
|
||||
follow: true,
|
||||
stdout: true,
|
||||
timestamps: true,
|
||||
tail: 0
|
||||
}, function (err, stream) {
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', function (buf) {
|
||||
// Every other message is a header
|
||||
if (index % 2 === 1) {
|
||||
var time = buf.substr(0,buf.indexOf(' '));
|
||||
var msg = buf.substr(buf.indexOf(' ')+1);
|
||||
logs.push(convert.toHtml(self._escapeHTML(msg)));
|
||||
self.setState({logs: logs});
|
||||
}
|
||||
index += 1;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
componentDidMount: function () {
|
||||
var containerName = this.getParams().name;
|
||||
ContainerStore.addChangeListener(containerName, this.update);
|
||||
ContainerStore.addChangeListener(ContainerStore.CONTAINERS, this.update);
|
||||
ContainerStore.addChangeListener(ContainerStore.PROGRESS, this.update);
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
var containerName = this.getParams().name;
|
||||
ContainerStore.removeChangeListener(containerName, this.update);
|
||||
ContainerStore.removeChangeListener(ContainerStore.CONTAINERS, this.update);
|
||||
ContainerStore.removeChangeListener(ContainerStore.PROGRESS, this.update);
|
||||
},
|
||||
update: function () {
|
||||
var containerName = this.getParams().name;
|
||||
var name = this.getParams().name;
|
||||
var container = ContainerStore.container(name);
|
||||
var progress = ContainerStore.progress(name);
|
||||
this.setState({
|
||||
container: ContainerStore.containers()[containerName]
|
||||
progress: progress,
|
||||
container: container
|
||||
});
|
||||
},
|
||||
_escapeHTML: function (html) {
|
||||
|
@ -137,6 +100,10 @@ var ContainerDetails = React.createClass({
|
|||
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
|
||||
});
|
||||
|
||||
if (!this.state.container) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var state;
|
||||
if (this.state.container.State.Running) {
|
||||
state = <h2 className="status">running</h2>;
|
||||
|
@ -148,6 +115,7 @@ var ContainerDetails = React.createClass({
|
|||
<div className="details">
|
||||
<div className="details-header">
|
||||
<h1>{this.state.container.Name.replace('/', '')}</h1>
|
||||
<h2>{this.state.progress}</h2>
|
||||
</div>
|
||||
<div className="details-logs">
|
||||
<div className="logs">
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
var async = require('async');
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var Router = require('react-router');
|
||||
var Modal = require('react-bootstrap/Modal');
|
||||
|
@ -6,10 +9,7 @@ var ModalTrigger = require('react-bootstrap/ModalTrigger');
|
|||
var ContainerModal = require('./ContainerModal.react');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var Header = require('./Header.react');
|
||||
var async = require('async');
|
||||
var _ = require('underscore');
|
||||
var docker = require('./docker');
|
||||
var $ = require('jquery');
|
||||
|
||||
var Link = Router.Link;
|
||||
var RouteHandler = Router.RouteHandler;
|
||||
|
@ -24,37 +24,34 @@ var ContainerList = React.createClass({
|
|||
};
|
||||
},
|
||||
componentDidMount: function () {
|
||||
this.update();
|
||||
if (this.state.containers.length > 0) {
|
||||
var name = this.state.containers[0].Name.replace('/', '');
|
||||
active = name;
|
||||
ContainerStore.setActive(name);
|
||||
}
|
||||
ContainerStore.addChangeListener(ContainerStore.CONTAINERS, this.update);
|
||||
ContainerStore.addChangeListener(ContainerStore.ACTIVE, this.update);
|
||||
this.updateContainers();
|
||||
ContainerStore.addChangeListener(ContainerStore.ACTIVE, this.updateActive);
|
||||
ContainerStore.addChangeListener(ContainerStore.CONTAINERS, this.updateContainers);
|
||||
},
|
||||
componentWillMount: function () {
|
||||
this._start = Date.now();
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
ContainerStore.removeChangeListener(ContainerStore.CONTAINERS, this.update);
|
||||
ContainerStore.removeChangeListener(ContainerStore.ACTIVE, this.update);
|
||||
ContainerStore.removeChangeListener(ContainerStore.CONTAINERS, this.updateContainers);
|
||||
ContainerStore.removeChangeListener(ContainerStore.ACTIVE, updateActive.update);
|
||||
},
|
||||
componentDidUpdate: function () {
|
||||
|
||||
updateActive: function () {
|
||||
if (ContainerStore.active()) {
|
||||
this.transitionTo('container', {name: ContainerStore.active()});
|
||||
}
|
||||
},
|
||||
update: function () {
|
||||
updateContainers: function () {
|
||||
// Sort by name
|
||||
var containers = _.values(ContainerStore.containers()).sort(function (a, b) {
|
||||
return a.Name.localeCompare(b.Name);
|
||||
});
|
||||
|
||||
this.setState({
|
||||
active: ContainerStore.active(),
|
||||
containers: containers
|
||||
});
|
||||
this.setState({containers: containers});
|
||||
|
||||
if (ContainerStore.active()) {
|
||||
this.transitionTo('container', {name: ContainerStore.active()});
|
||||
// Transition to the active container or set one
|
||||
var active = ContainerStore.active();
|
||||
if (!ContainerStore.container(active) && containers.length > 0) {
|
||||
ContainerStore.setActive(containers[0].Name.replace('/', ''));
|
||||
}
|
||||
},
|
||||
handleClick: function (containerId) {
|
||||
|
@ -77,13 +74,12 @@ var ContainerList = React.createClass({
|
|||
|
||||
var imageName = downloadingImage || container.Config.Image;
|
||||
|
||||
var state;
|
||||
|
||||
// Synchronize all animations
|
||||
var style = {
|
||||
WebkitAnimationDelay: (self._start - Date.now()) + 'ms'
|
||||
};
|
||||
|
||||
var state;
|
||||
if (downloading) {
|
||||
state = <div className="state state-downloading"><div style={style} className="downloading-arrow"></div></div>;
|
||||
} else if (container.State.Running && !container.State.Paused) {
|
||||
|
@ -123,52 +119,4 @@ var ContainerList = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
var Containers = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {
|
||||
sidebarOffset: 0
|
||||
};
|
||||
},
|
||||
handleScroll: function (e) {
|
||||
if (e.target.scrollTop > 0 && !this.state.sidebarOffset) {
|
||||
this.setState({
|
||||
sidebarOffset: e.target.scrollTop
|
||||
});
|
||||
} else if (e.target.scrollTop === 0 && this.state.sidebarOffset) {
|
||||
this.setState({
|
||||
sidebarOffset: 0
|
||||
});
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
var sidebarHeaderClass = 'sidebar-header';
|
||||
if (this.state.sidebarOffset) {
|
||||
sidebarHeaderClass += ' sep';
|
||||
}
|
||||
return (
|
||||
<div className="containers">
|
||||
<Header/>
|
||||
<div className="containers-body">
|
||||
<div className="sidebar">
|
||||
<section className={sidebarHeaderClass}>
|
||||
<h3>containers</h3>
|
||||
<div className="create">
|
||||
<ModalTrigger modal={<ContainerModal/>}>
|
||||
<div className="wrapper">
|
||||
<span className="icon icon-add-3"></span>
|
||||
</div>
|
||||
</ModalTrigger>
|
||||
</div>
|
||||
</section>
|
||||
<section className="sidebar-containers" onScroll={this.handleScroll}>
|
||||
<ContainerList/>
|
||||
</section>
|
||||
</div>
|
||||
<RouteHandler/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Containers;
|
||||
module.exports = ContainerList;
|
|
@ -8,6 +8,7 @@ var ContainerStore = require('./ContainerStore');
|
|||
var Navigation = Router.Navigation;
|
||||
|
||||
var ContainerModal = React.createClass({
|
||||
_searchRequest: null,
|
||||
getInitialState: function () {
|
||||
return {
|
||||
query: '',
|
||||
|
@ -19,9 +20,11 @@ var ContainerModal = React.createClass({
|
|||
},
|
||||
search: function (query) {
|
||||
var self = this;
|
||||
$.get('https://registry.hub.docker.com/v1/search?q=' + query, function (result) {
|
||||
this._searchRequest = $.get('https://registry.hub.docker.com/v1/search?q=' + query, function (result) {
|
||||
if (self.isMounted()) {
|
||||
self.setState(result);
|
||||
console.log(result);
|
||||
}
|
||||
});
|
||||
},
|
||||
handleChange: function (e) {
|
||||
|
@ -30,6 +33,11 @@ var ContainerModal = React.createClass({
|
|||
if (query === this.state.query) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._searchRequest) {
|
||||
this._searchRequest.abort();
|
||||
this._searchRequest = null;
|
||||
}
|
||||
clearTimeout(this.timeout);
|
||||
var self = this;
|
||||
this.timeout = setTimeout(function () {
|
||||
|
|
|
@ -1,89 +1,21 @@
|
|||
var EventEmitter = require('events').EventEmitter;
|
||||
var async = require('async');
|
||||
var assign = require('react/lib/Object.assign');
|
||||
var docker = require('./docker.js');
|
||||
var docker = require('./docker');
|
||||
var registry = require('./registry');
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
|
||||
// Merge our store with Node's Event Emitter
|
||||
var ContainerStore = assign(EventEmitter.prototype, {
|
||||
CONTAINERS: 'containers',
|
||||
PROGRESS: 'progress',
|
||||
LOGS: 'logs',
|
||||
ACTIVE: 'active',
|
||||
_containers: {},
|
||||
_progress: {},
|
||||
_logs: {},
|
||||
_active: null,
|
||||
init: function (callback) {
|
||||
// TODO: Load cached data from db on loading
|
||||
|
||||
// Refresh with docker & hook into events
|
||||
var self = this;
|
||||
this.update(function (err) {
|
||||
callback();
|
||||
var downloading = _.filter(_.values(self._containers), function (container) {
|
||||
var env = container.Config.Env;
|
||||
return _.indexOf(env, 'KITEMATIC_DOWNLOADING=true') !== -1;
|
||||
});
|
||||
|
||||
// Recover any pulls that were happening
|
||||
downloading.forEach(function (container) {
|
||||
var env = _.object(container.Config.Env.map(function (e) {
|
||||
return e.split('=');
|
||||
}));
|
||||
docker.client().pull(env.KITEMATIC_DOWNLOADING_IMAGE, function (err, stream) {
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', function (data) {
|
||||
console.log(data);
|
||||
});
|
||||
stream.on('end', function () {
|
||||
self._createContainer(env.KITEMATIC_DOWNLOADING_IMAGE, container.Name.replace('/', ''), function () {
|
||||
console.log('RECOVERED');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
docker.client().getEvents(function (err, stream) {
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', function (data) {
|
||||
console.log(data);
|
||||
|
||||
// TODO: Dont refresh on deleting placeholder containers
|
||||
self.update(function (err) {
|
||||
console.log('Updated container data.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
update: function (callback) {
|
||||
var self = this;
|
||||
docker.client().listContainers({all: true}, function (err, containers) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
async.map(containers, function(container, callback) {
|
||||
docker.client().getContainer(container.Id).inspect(function (err, data) {
|
||||
callback(err, data);
|
||||
});
|
||||
}, function (err, results) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
var containers = {};
|
||||
results.forEach(function (r) {
|
||||
containers[r.Name.replace('/', '')] = r;
|
||||
});
|
||||
self._containers = containers;
|
||||
_.keys(self._containers).forEach(function(c) {
|
||||
self.emit(c);
|
||||
});
|
||||
self.emit(self.CONTAINERS);
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
},
|
||||
_pullScratchImage: function (callback) {
|
||||
var image = docker.client().getImage('scratch:latest');
|
||||
image.inspect(function (err, data) {
|
||||
|
@ -163,41 +95,156 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
|||
}
|
||||
}
|
||||
},
|
||||
// Returns all containers
|
||||
containers: function() {
|
||||
return this._containers;
|
||||
init: function (callback) {
|
||||
// TODO: Load cached data from db on loading
|
||||
|
||||
// Refresh with docker & hook into events
|
||||
var self = this;
|
||||
this.update(function (err) {
|
||||
callback();
|
||||
var downloading = _.filter(_.values(self._containers), function (container) {
|
||||
var env = container.Config.Env;
|
||||
return _.indexOf(env, 'KITEMATIC_DOWNLOADING=true') !== -1;
|
||||
});
|
||||
|
||||
// Recover any pulls that were happening
|
||||
downloading.forEach(function (container) {
|
||||
var env = _.object(container.Config.Env.map(function (e) {
|
||||
return e.split('=');
|
||||
}));
|
||||
docker.client().pull(env.KITEMATIC_DOWNLOADING_IMAGE, function (err, stream) {
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', function (data) {
|
||||
console.log(data);
|
||||
});
|
||||
stream.on('end', function () {
|
||||
self._createContainer(env.KITEMATIC_DOWNLOADING_IMAGE, container.Name.replace('/', ''), function () {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
docker.client().getEvents(function (err, stream) {
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', function (data) {
|
||||
console.log(data);
|
||||
|
||||
// TODO: Dont refresh on deleting placeholder containers
|
||||
var deletingPlaceholder = data.status === 'destroy' && self.container(data.id) && self.container(data.id).Config.Env.indexOf('KITEMATIC_DOWNLOADING=true') !== -1;
|
||||
console.log(deletingPlaceholder);
|
||||
if (!deletingPlaceholder) {
|
||||
self.update(function (err) {
|
||||
console.log('Updated container data.');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
update: function (callback) {
|
||||
var self = this;
|
||||
docker.client().listContainers({all: true}, function (err, containers) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
async.map(containers, function(container, callback) {
|
||||
docker.client().getContainer(container.Id).inspect(function (err, data) {
|
||||
callback(err, data);
|
||||
});
|
||||
}, function (err, results) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
var containers = {};
|
||||
results.forEach(function (r) {
|
||||
containers[r.Name.replace('/', '')] = r;
|
||||
});
|
||||
self._containers = containers;
|
||||
self.emit(self.CONTAINERS);
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
},
|
||||
create: function (repository, tag, callback) {
|
||||
var containerName = this._generateName(repository);
|
||||
tag = tag || 'latest';
|
||||
var imageName = repository + ':' + tag;
|
||||
// Check if image is not local or already being downloaded
|
||||
console.log('Creating container.');
|
||||
var self = this;
|
||||
var imageName = repository + ':' + tag;
|
||||
var containerName = this._generateName(repository);
|
||||
var image = docker.client().getImage(imageName);
|
||||
console.log(image);
|
||||
image.inspect(function (err, data) {
|
||||
// TODO: Get image size from registry API
|
||||
/*$.get('https://registry.hub.docker.com/v1/repositories/' + repository + '/tags/' + tag, function (data) {
|
||||
|
||||
});*/
|
||||
image.inspect(function (err, data) {
|
||||
if (!data) {
|
||||
// Pull image
|
||||
self._createPlaceholderContainer(imageName, containerName, function (err, container) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
console.log('Placeholder container created.');
|
||||
registry.layers(repository, tag, function (err, layerSizes) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
}
|
||||
|
||||
// TODO: Support v2 registry API
|
||||
// TODO: clean this up- It's messy to work with pulls from both the v1 and v2 registry APIs
|
||||
// Use the per-layer pull progress % to update the total progress.
|
||||
docker.client().listImages({all: 1}, function(err, images) {
|
||||
var existingIds = new Set(images.map(function (image) {
|
||||
return image.Id.slice(0, 12);
|
||||
}));
|
||||
var layersToDownload = layerSizes.filter(function (layerSize) {
|
||||
return !existingIds.has(layerSize.Id);
|
||||
});
|
||||
|
||||
var totalBytes = layersToDownload.map(function (s) { return s.size; }).reduce(function (pv, sv) { return pv + sv; }, 0);
|
||||
docker.client().pull(imageName, function (err, stream) {
|
||||
console.log(containerName);
|
||||
callback(null, containerName);
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', function (data) {
|
||||
// TODO: update progress
|
||||
//console.log(data);
|
||||
|
||||
var layerProgress = layersToDownload.reduce(function (r, layer) {
|
||||
if (_.findWhere(images, {Id: layer.Id})) {
|
||||
r[layer.Id] = 100;
|
||||
} else {
|
||||
r[layer.Id] = 0;
|
||||
}
|
||||
return r;
|
||||
}, {});
|
||||
|
||||
self._progress[containerName] = 0;
|
||||
self.emit(containerName);
|
||||
|
||||
stream.on('data', function (str) {
|
||||
console.log(str);
|
||||
var data = JSON.parse(str);
|
||||
|
||||
if (data.status === 'Already exists') {
|
||||
layerProgress[data.id] = 1;
|
||||
} else if (data.status === 'Downloading') {
|
||||
var current = data.progressDetail.current;
|
||||
var total = data.progressDetail.total;
|
||||
var layerFraction = current / total;
|
||||
layerProgress[data.id] = layerFraction;
|
||||
}
|
||||
|
||||
var chunks = layersToDownload.map(function (s) {
|
||||
return layerProgress[s.Id] * s.size;
|
||||
});
|
||||
|
||||
var totalReceived = chunks.reduce(function (pv, sv) {
|
||||
return pv + sv;
|
||||
});
|
||||
|
||||
var totalProgress = totalReceived / totalBytes;
|
||||
self._progress[containerName] = totalProgress;
|
||||
self.emit(self.PROGRESS);
|
||||
});
|
||||
stream.on('end', function () {
|
||||
self._createContainer(imageName, containerName, function () {
|
||||
delete self._progress[containerName];
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -210,19 +257,27 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
|||
});
|
||||
}
|
||||
});
|
||||
// If so then create a container w/ kitematic-only 'downloading state'
|
||||
// Pull image
|
||||
// When image is done pulling then
|
||||
},
|
||||
setActive: function (containerName) {
|
||||
this._active = containerName;
|
||||
this.emit(self.ACTIVE);
|
||||
setActive: function (name) {
|
||||
console.log('set active');
|
||||
this._active = name;
|
||||
this.emit(this.ACTIVE);
|
||||
},
|
||||
active: function () {
|
||||
return this._active;
|
||||
},
|
||||
logs: function (containerName) {
|
||||
return logs[containerId];
|
||||
// Returns all containers
|
||||
containers: function() {
|
||||
return this._containers;
|
||||
},
|
||||
container: function (name) {
|
||||
return this._containers[name];
|
||||
},
|
||||
progress: function (name) {
|
||||
return this._progress[name];
|
||||
},
|
||||
logs: function (name) {
|
||||
return logs[name];
|
||||
},
|
||||
addChangeListener: function(eventType, callback) {
|
||||
this.on(eventType, callback);
|
||||
|
|
20
app/main.js
20
app/main.js
|
@ -8,8 +8,8 @@ var boot2docker = require('./boot2docker.js');
|
|||
var Setup = require('./Setup.react');
|
||||
var Containers = require('./Containers.react');
|
||||
var ContainerDetails = require('./ContainerDetails.react');
|
||||
var ContainerStore = require('./ContainerStore.js');
|
||||
var Radial = require('./Radial.react.js');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var Radial = require('./Radial.react');
|
||||
|
||||
var Route = Router.Route;
|
||||
var NotFoundRoute = Router.NotFoundRoute;
|
||||
|
@ -17,6 +17,14 @@ var DefaultRoute = Router.DefaultRoute;
|
|||
var Link = Router.Link;
|
||||
var RouteHandler = Router.RouteHandler;
|
||||
|
||||
var App = React.createClass({
|
||||
render: function () {
|
||||
return (
|
||||
<RouteHandler/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var NoContainers = React.createClass({
|
||||
render: function () {
|
||||
return (
|
||||
|
@ -27,14 +35,6 @@ var NoContainers = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
var App = React.createClass({
|
||||
render: function () {
|
||||
return (
|
||||
<RouteHandler/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var routes = (
|
||||
<Route name="app" path="/" handler={App}>
|
||||
<Route name="containers" handler={Containers}>
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
var async = require('async');
|
||||
var $ = require('jquery');
|
||||
|
||||
var Registry = {
|
||||
token: function(repository, callback) {
|
||||
$.ajax({
|
||||
url: 'https://registry.hub.docker.com/v1/repositories/' + repository + '/images',
|
||||
headers: {
|
||||
'X-Docker-Token': true,
|
||||
},
|
||||
success: function (res, status, xhr) {
|
||||
callback(null, xhr.getResponseHeader('X-Docker-Token'));
|
||||
},
|
||||
error: function (err) {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
ancestry: function (imageId, token, callback) {
|
||||
$.ajax({
|
||||
url: 'https://registry-1.docker.io/v1/images/' + imageId + '/ancestry',
|
||||
headers: {
|
||||
Authorization: 'Token ' + token
|
||||
},
|
||||
success: function (layers, status, xhr) {
|
||||
callback(null, layers);
|
||||
},
|
||||
error: function (err) {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
imageId: function (repository, tag, token, callback) {
|
||||
$.ajax({
|
||||
url: 'https://registry-1.docker.io/v1/repositories/' + repository + '/tags/' + tag,
|
||||
headers: {
|
||||
Authorization: 'Token ' + token
|
||||
},
|
||||
success: function (res, status, xhr) {
|
||||
callback(null, res);
|
||||
},
|
||||
error: function (err) {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Returns an array [{Id: <12 character image ID, size: size of layer in bytes}]
|
||||
layers: function (repository, tag, callback) {
|
||||
var self = this;
|
||||
this.token(repository, function (err, token) {
|
||||
self.imageId(repository, tag, token, function (err, imageId) {
|
||||
self.ancestry(imageId, token, function (err, layers) {
|
||||
async.map(layers, function (layer, callback) {
|
||||
$.ajax({
|
||||
url: 'https://registry-1.docker.io/v1/images/' + layer + '/json',
|
||||
headers: {
|
||||
Authorization: 'Token ' + token
|
||||
},
|
||||
success: function (res, status, xhr) {
|
||||
var size = xhr.getResponseHeader('X-Docker-Size');
|
||||
callback(null, {
|
||||
Id: layer.slice(0, 12),
|
||||
size: parseInt(size, 10)
|
||||
});
|
||||
},
|
||||
error: function (err) {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
}, function (err, results) {
|
||||
if (err) {
|
||||
callback('Could not sum' + err);
|
||||
return;
|
||||
}
|
||||
callback(null, results);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Registry;
|
Loading…
Reference in New Issue