Merge pull request #15 from Joxit/delete-image

Delete docker images (issue #14)
This commit is contained in:
Jones Magloire
2016-10-09 21:19:38 +02:00
committed by GitHub
9 changed files with 180 additions and 16 deletions

106
README.md
View File

@@ -1,6 +1,7 @@
# Docker Registry UI
## Overview
This project aims to provide a user interface for your private docker registry v2.
There is no default registry on this UI, you should add your own with the UI.
You can manage more than one registry server.
@@ -12,13 +13,14 @@ This web user interface use [Riot](https://github.com/Riot/riot) the react-like
## Features
* List all your repositories/images.
* List all tags for a repository/image
* Sort the tag list
* One interface for many registry
* Use a secured docker registry
- List all your repositories/images.
- List all tags for a repository/image
- Sort the tag list
- One interface for many registry
- Use a secured docker registry
## Getting Started
### Basic
First you need node and npm in order to download dependencies.
@@ -30,36 +32,59 @@ npm install
```
Now you can open index.html with your browser or use a http-server
```sh
npm install -g http-server
http-server
```
### Docker
The docker contains the source code and a node webserver in order to serve the docker-registry-ui.
#### Get the docker image
You can get the image in three ways
From sources with this command :
```sh
git clone https://github.com/Joxit/docker-registry-ui.git
docker build -t joxit/docker-registry-ui docker-registry-ui
docker build -t joxit/docker-registry-ui -f docker-registry-ui/Dockerfile.static docker-registry-ui
```
Or build with the url :
```sh
```sh
docker build -t joxit/docker-registry-ui github.com/Joxit/docker-registry-ui
docker build -t joxit/docker-registry-ui -f Dockerfile.static github.com/Joxit/docker-registry-ui
```
Or pull the image from [docker hub](https://hub.docker.com/r/joxit/docker-registry-ui/) :
```sh
docker pull joxit/docker-registry-ui
docker pull joxit/docker-registry-ui:static
```
#### Run the docker
To run the docker and see the website on your 8080 port, try this :
To run the docker and see the website on your 80 port, try this :
```sh
docker run -d -p 8080:8080 joxit/docker-registry-ui
docker run -d -p 80:80 joxit/docker-registry-ui
```
#### Run the static docker
Some env options are available for use this interface for only one server.
- `URL`: set the static URL to use. (`Required`)
- `DELETE_IMAGES`: if this variable is empty or `false`, delete feature is desactivated. It is activated otherwise.
```sh
docker run -d -p 80:80 -e URL=http://127.0.0.1:5000 -e DELETE_IMAGES=true joxit/docker-registry-ui:static
```
## Using CORS
@@ -67,13 +92,66 @@ docker run -d -p 8080:8080 joxit/docker-registry-ui
Your server should be configured to accept CORS.
If your docker registry does not need credentials, you will need to send this HEADER :
```
Access-Control-Allow-Origin: '*'
```
Access-Control-Allow-Origin: '*'
If your docker registry need credentials, you will need to send these HEADERS :
```yml
http:
headers:
Access-Control-Allow-Origin: '<your docker-registry-ui url>'
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS'] # Optional
```
Access-Control-Allow-Origin: '<your docker-registry-ui url>'
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS'] # Optional
## Using delete
For deleting images, you need to activate the delete feature in your registry :
```yml
storage:
delete:
enabled: true
```
And you need to add these HEADERS :
```yml
http:
headers:
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
Access-Control-Expose-Headers: ['Docker-Content-Digest']
```
## Registry example
Example of docker registry configuration file:
```yml
version: 0.1
log:
fields:
service: registry
storage:
delete:
enabled: true
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
Access-Control-Allow-Origin: ['http://127.0.0.1:8001']
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
Access-Control-Allow-Headers: ['Authorization']
Access-Control-Max-Age: [1728000]
Access-Control-Allow-Credentials: [true]
Access-Control-Expose-Headers: ['Docker-Content-Digest']
auth:
htpasswd:
realm: basic-realm
path: /etc/docker/registry/htpasswd
```

View File

@@ -2,6 +2,10 @@
$@
sed -i "s,\${URL},${URL}," scripts/script.js
if [ -z "${DELETE_IMAGES}" ] || [ "${DELETE_IMAGES}" = false ] ; then
sed -i "s/registryUI.isImageRemoveActivated *= *[^,;]*/registryUI.isImageRemoveActivated=false/" scripts/script.js
fi
if [ -z "$@" ]; then
nginx -g "daemon off;"
else

View File

@@ -49,7 +49,7 @@ gulp.task('riot-tag', ['html'], function() {
});
gulp.task('riot-static-tag', ['html'], function() {
return gulp.src(['src/tags/catalog.tag', 'src/tags/app.tag', 'src/tags/taglist.tag'])
return gulp.src(['src/tags/catalog.tag', 'src/tags/app.tag', 'src/tags/taglist.tag', 'src/tags/remove-image.tag'])
.pipe(concat('tags-static.js'))
.pipe(riot())
.pipe(minifier({}, uglify))

View File

@@ -68,6 +68,7 @@
<!-- build:js scripts/tags.js -->
<script src="tags/catalog.tag" type="riot/tag"></script>
<script src="tags/taglist.tag" type="riot/tag"></script>
<script src="tags/remove-image.tag" type="riot/tag"></script>
<script src="tags/add.tag" type="riot/tag"></script>
<script src="tags/change.tag" type="riot/tag"></script>
<script src="tags/remove.tag" type="riot/tag"></script>

View File

@@ -17,6 +17,7 @@
function Http() {
this.oReq = new XMLHttpRequest();
this._events = {};
this._headers = {};
}
Http.prototype.addEventListener = function(e, f) {
@@ -31,6 +32,9 @@ Http.prototype.addEventListener = function(e, f) {
for (key in this.http._events) {
req.addEventListener(key, this.http._events[key]);
}
for (key in this.http._headers) {
req.setRequestHeader(key, this.http._headers[key]);
}
req.withCredentials = true;
req.open(this.http._method, this.http._url);
req.send();
@@ -50,6 +54,11 @@ Http.prototype.addEventListener = function(e, f) {
}
};
Http.prototype.setRequestHeader = function(header, value) {
this.oReq.setRequestHeader(header, value);
this._headers[header] = value;
};
Http.prototype.open = function(m, u) {
this._method = m;
this._url = u;

View File

@@ -60,6 +60,7 @@ registryUI.removeServer = function(url) {
registryServer.splice(index, 1);
localStorage.setItem('registryServer', JSON.stringify(registryServer));
}
registryUI.isImageRemoveActivated = true;
registryUI.catalog = {};
registryUI.taglist = {};

View File

@@ -17,7 +17,8 @@
var registryUI = {}
registryUI.url = function() {
return '${URL}';
}
};
registryUI.isImageRemoveActivated = true;
registryUI.catalog = {};
registryUI.taglist = {};

63
src/tags/remove-image.tag Normal file
View File

@@ -0,0 +1,63 @@
<!--
Copyright (C) 2016 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<remove-image>
<a href="#" onclick="registryUI.removeImage.remove('{ opts.name }', '{ opts.tag }')">
<i class="material-icons mdl-list__item-icon">delete</i>
</a>
<script type="text/javascript">
registryUI.removeImage = registryUI.removeImage || {}
registryUI.removeImage.update = this.update;
registryUI.removeImage.remove = function (name, tag) {
var oReq = new Http();
oReq.addEventListener('load', function () {
registryUI.taglist.refresh();
if (this.status == 200) {
if (!this.getAllResponseHeaders().includes('Docker-Content-Digest')) {
registryUI.taglist.createSnackbar('You need tu add Access-Control-Expose-Headers: [\'Docker-Content-Digest\'] in your server configuration.');
return;
}
var digest = this.getResponseHeader('Docker-Content-Digest');
var oReq = new Http();
oReq.addEventListener('load', function () {
if (this.status == 200 || this.status == 202) {
registryUI.taglist.refresh();
registryUI.taglist.createSnackbar('Deleting ' + name + ':' + tag + ' image. Run `registry garbage-collect config.yml` on your registry');
} else if (this.status == 404) {
registryUI.taglist.createSnackbar('Digest not found');
} else {
registryUI.taglist.createSnackbar(this.responseText);
}
});
oReq.open('DELETE', registryUI.url() + '/v2/' + name + '/manifests/' + digest);
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json');
oReq.addEventListener('error', function () {
registryUI.taglist.createSnackbar('An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].');
});
oReq.send();
} else if (this.status == 404) {
registryUI.taglist.createSnackbar('Manifest for' + name + ':' + tag + 'not found');
} else {
registryUI.taglist.createSnackbar(this.responseText);
}
});
oReq.open('HEAD', registryUI.url() + '/v2/' + name + '/manifests/' + tag);
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json');
oReq.send();
};
</script>
</remove-image>

View File

@@ -30,12 +30,16 @@
<tr>
<th class="mdl-data-table__cell--non-numeric">Repository</th>
<th class="{ registryUI.taglist.asc ? 'mdl-data-table__header--sorted-ascending' : 'mdl-data-table__header--sorted-descending' }" onclick="registryUI.taglist.reverse();">Tag</th>
<th show="{ registryUI.isImageRemoveActivated }" ></th>
</tr>
</thead>
<tbody>
<tr each="{ item in registryUI.taglist.tags }">
<td class="mdl-data-table__cell--non-numeric">{ registryUI.taglist.name }</td>
<td>{ item }</td>
<td show="{ registryUI.isImageRemoveActivated }" >
<remove-image name={ registryUI.taglist.name } tag={ item } />
</td>
</tr>
</tbody>
</table>
@@ -110,6 +114,9 @@
registryUI.taglist.back = function () {
rg.router.go('home');
};
registryUI.taglist.refresh = function () {
rg.router.go(rg.router.current.name, rg.router.current.params);
};
</script>
<!-- End of tag -->
</taglist>