Compare commits

...

9 Commits
1.2.1 ... 1.2.3

Author SHA1 Message Date
Joxit
8bd1f31c9c Release v1.2.3: Custom header supports 2019-07-04 23:09:11 +02:00
Jones Magloire
7777ff28df Merge pull request #89 from Joxit/custom-headers
Supports custom headers when the ui is used as proxy

## Background

Headers can be useful in some cases such as avoid sending credentials when you are on the UI (like #87). Or give to the registry server other properties such as `X-Forward-For` or `Server` headers for monitoring.

## How to use ?

This is compatible only with static version of the UI and used with `REGISTRY_URL` variable.
When you want to add a custom header, add to the registry ui a environment variable or entry in `/etc/nginx/.env` which looks like `NGINX_PROXY_HEADER_Custom_Header`. All underscores (`_`) will be replaced by hyphens (`-`). 

Some example of custom headers as variable:
- `NGINX_PROXY_HEADER_Authorization` for Basic auth credentials
- `NGINX_PROXY_HEADER_X_Forwarded_For` for identifying the originating IP address of a client

An example is bundled with this PR

closes: #87
2019-07-03 00:09:18 +02:00
Joxit
4fee7b44d3 feat: Supports custom headers via file /etc/nginx/.env
Remove the print of headers for security
2019-07-02 23:04:32 +02:00
Joxit
7716f8b44a feat: Supports custom headers when the ui is used as proxy 2019-06-24 23:54:21 +02:00
Joxit
1321d9b573 fix: Unable to push image on non 80 port
resolves: #88
2019-06-22 10:47:37 +02:00
Sébastien Huss
d7a19734ce Kubernetes support (#85)
* Added a kubernetes example
* Added an helm chart for kubernetes usage
* Added README for the added examples
2019-06-07 00:17:30 +02:00
Joxit
32d0df1af9 New script utils for global static functions 2019-05-21 00:18:24 +02:00
Joxit
5c1cb93a1c Add badges to README 2019-05-11 12:09:34 +02:00
Jakob Ackermann
9d97c30914 [fonts] fix bad references on the Material Icons font
Signed-off-by: Jakob Ackermann <das7pad@outlook.com>
2019-05-03 22:57:37 +02:00
44 changed files with 1136 additions and 87 deletions

View File

@@ -4,6 +4,9 @@ title: Project Page
# Docker Registry UI
![Stars](https://img.shields.io/github/stars/joxit/docker-registry-ui.svg?logo=github&maxAge=86400)
![Pulls](https://img.shields.io/docker/pulls/joxit/docker-registry-ui.svg?maxAge=86400)
## Overview
This project aims to provide a simple and complete user interface for your private docker registry.
@@ -17,7 +20,7 @@ In the **static interface**, it will connect to a single registry and will not c
This web user interface uses [Riot](https://github.com/Riot/riot) the react-like user interface micro-library and [riot-mui](https://github.com/kysonic/riot-mui) components.
## [Project Page](https://joxit.dev/docker-registry-ui) and [Live Demo](https://joxit.dev/docker-registry-ui/demo/)
## [Project Page](https://joxit.dev/docker-registry-ui), [Live Demo](https://joxit.dev/docker-registry-ui/demo/) [Examples](https://github.com/Joxit/docker-registry-ui/tree/master/examples)
![preview](https://raw.github.com/Joxit/docker-registry-ui/master/docker-registry-ui.gif "Preview of Docker Registry UI")
@@ -42,6 +45,7 @@ This web user interface uses [Riot](https://github.com/Riot/riot) the react-like
- Use `joxit/docker-registry-ui:static` as reverse proxy (with `REGISTRY_URL` environment variable) to your docker registry (This will avoid CORS) **static interface**.
- Add Title when using `REGISTRY_URL` (see [#28](https://github.com/Joxit/docker-registry-ui/issues/28)) **static interface**.
- Customise docker pull command on static registry UI (see [#71](https://github.com/Joxit/docker-registry-ui/issues/71)) **static interface**.
- Add custom header via environment variable and file (see [#89](https://github.com/Joxit/docker-registry-ui/pull/89)) **static interface**
## Getting Started
@@ -214,6 +218,8 @@ auth:
- [Use docker-registry-ui as a proxy (use REGISTRY_URL)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/ui-as-proxy)
- [Use docker-registry-ui as standalone (use URL)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/ui-as-standalone)
- [Use docker-registry-ui with traefik](https://github.com/Joxit/docker-registry-ui/tree/master/examples/traefik)
- [Use docker-registry-ui with docker registry and Amazon s3 (#75)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-75)
- [FIX revproxy to registry does not work when published under non-root url (#73)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-73)
- [Use docker-registry-ui with HTTPS (#20)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-20)
- [Use docker-registry-ui with docker registry and Amazon s3](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-75) ([#75](https://github.com/Joxit/docker-registry-ui/issues/88))
- [FIX revproxy to registry does not work when published under non-root url](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-73) ([#73](https://github.com/Joxit/docker-registry-ui/issues/73))
- [Use docker-registry-ui with HTTPS](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-20) ([#20](https://github.com/Joxit/docker-registry-ui/issues/20))
- [Unable to push image when docker-registry-ui is used as a proxy on non 80 port](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-88) ([#88](https://github.com/Joxit/docker-registry-ui/issues/88))
- [Add custom headers bases on environment variable and/or file when the ui is used as proxy](https://github.com/Joxit/docker-registry-ui/tree/master/examples/proxy-headers) ([#89](https://github.com/Joxit/docker-registry-ui/pull/89))

View File

@@ -1,5 +1,5 @@
#!/bin/sh
$@
sed -i "s,\${URL},${URL}," scripts/docker-registry-ui.js
sed -i "s,\${REGISTRY_TITLE},${REGISTRY_TITLE}," scripts/docker-registry-ui.js
sed -i "s,\${PULL_URL},${PULL_URL}," scripts/docker-registry-ui.js
@@ -8,13 +8,31 @@ if [ -z "${DELETE_IMAGES}" ] || [ "${DELETE_IMAGES}" = false ] ; then
sed -i -r "s/(isImageRemoveActivated[:=])[^,;]*/\1false/" scripts/docker-registry-ui.js
fi
get_nginx_proxy_headers() {
(
env &&
if [ -f "/etc/nginx/.env" ]; then
cat /etc/nginx/.env
# Force new line
echo ""
fi
) | while read e; do
if [ -n "$(echo $e | grep -o '^NGINX_PROXY_HEADER_')" ]; then
key=$(echo ${e%%=*} | sed 's/^NGINX_PROXY_HEADER_//' | sed 's/_/-/g')
value=${e#*=}
echo -n "proxy_set_header ${key} \"${value}\"; "
fi
done
}
if [ -n "${REGISTRY_URL}" ] ; then
sed -i "s,\${REGISTRY_URL},${REGISTRY_URL}," /etc/nginx/conf.d/default.conf
sed -i "s^\${NGINX_PROXY_HEADERS}^$(get_nginx_proxy_headers)^" /etc/nginx/conf.d/default.conf
sed -i "s,#!,," /etc/nginx/conf.d/default.conf
fi
if [ -z "$@" ]; then
nginx -g "daemon off;"
exec nginx -g "daemon off;"
else
$@
exec $@
fi

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/style.css vendored

File diff suppressed because one or more lines are too long

10
examples/README.md Normal file
View File

@@ -0,0 +1,10 @@
## Examples
- [Use docker-registry-ui as a proxy (use REGISTRY_URL)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/ui-as-proxy)
- [Use docker-registry-ui as standalone (use URL)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/ui-as-standalone)
- [Use docker-registry-ui with traefik](https://github.com/Joxit/docker-registry-ui/tree/master/examples/traefik)
- [Use docker-registry-ui with docker registry and Amazon s3](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-75) ([#75](https://github.com/Joxit/docker-registry-ui/issues/88))
- [FIX revproxy to registry does not work when published under non-root url](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-73) ([#73](https://github.com/Joxit/docker-registry-ui/issues/73))
- [Use docker-registry-ui with HTTPS](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-20) ([#20](https://github.com/Joxit/docker-registry-ui/issues/20))
- [Unable to push image when docker-registry-ui is used as a proxy on non 80 port](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-88) ([#88](https://github.com/Joxit/docker-registry-ui/issues/88))
- [Add custom headers bases on environment variable and/or file when the ui is used as proxy](https://github.com/Joxit/docker-registry-ui/tree/master/examples/proxy-headers) ([#89](https://github.com/Joxit/docker-registry-ui/pull/89))

View File

@@ -0,0 +1,22 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@@ -0,0 +1,11 @@
apiVersion: v1
appVersion: "1.2.1"
description: The simplest and most complete UI for your private registry
name: docker-registry-ui
home: https://github.com/Joxit/docker-registry-ui
keywords:
- docker
- registry
sources:
- https://github.com/Joxit/docker-registry-ui
version: 0.1.0

View File

@@ -0,0 +1,97 @@
# docker-registry-ui
[docker-registry-ui](https://joxit.dev/docker-registry-ui/) is the simplest and most complete UI for your private registry!
## TL;DR;
```bash
$ helm install .
```
## Introduction
This chart bootstraps a [docker-registry-ui](https://joxit.dev/docker-registry-ui/) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
It also may deploy the [docker registry](https://docs.docker.com/registry/) if you havent have one already.
## Prerequisites
- Kubernetes 1.9+ with Beta APIs enabled
- PV provisioner support in the underlying infrastructure
## Installing the Chart
To install the chart with the release name `my-release`:
```bash
$ helm update --install my-release .
```
The command deploys docker-registry-ui on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation.
> **Tip**: List all releases using `helm list`
## Uninstalling the Chart
To uninstall/delete the `my-release` deployment:
```bash
$ helm delete my-release
```
The command removes all the Kubernetes components associated with the chart and deletes the release.
## Configuration
The following table lists the configurable parameters of the Redmine chart and their default values.
| Parameter | Description | Default |
| --------------------------------- | ---------------------------------------- | ------------------------------------------------------- |
| `ui.title` | Title of the managed repository | `Docker registry UI` |
| `ui.delete_images` | Allow to delete image from the front-end | `false` |
| `ui.proxy` | The UI service act as a proxy of the registry | `true` |
| `ui.replicaCount` | Number of replicas to start | `1` |
| `ui.image.registry` | registry to pull the docker-registry-ui image from | `docker.io` |
| `ui.image.repository` | docker-registry-ui image name | `joxit/docker-registry-ui` |
| `ui.image.tag` | docker-registry-ui image tag (change to latest to have multi registry support) | `static` |
| `ui.image.pullPolicy` | docker-registry-ui image pull policy | `Always` |
| `ui.probe.liveness` | Ask kubernetes to check the service port for liveness | `true` |
| `ui.probe.readyness ` | Ask kubernetes to check the service port for readyness | `true` |
| `ui.service.type` | Desired service type | `ClusterIP` |
| `ui.service.port` | Service exposed port | `80` |
| `ui.ingress.enabled` | Create an ingress for docker-regstry-ui | `false` |
| `registry.external` | Use an already available registry | `false` |
| `registry.url` | URL of the existing registry | `http://localhost:5000` |
| `registry.replicaCount` | Number of replicas to start | `1` |
| `registry.image.registry` | registry to pull the docker-registry image from | `docker.io` |
| `registry.image.repository` | docker-registry-ui image name | `registry` |
| `registry.image.tag` | docker-registry-ui image tag | `2.6.2` |
| `registry.image.pullPolicy` | docker-registry-ui image pull policy | `Always` |
| `registry.probe.liveness` | Ask kubernetes to check the service port for liveness | `true` |
| `registry.probe.readyness ` | Ask kubernetes to check the service port for readyness | `true` |
| `registry.persistence.enabled` | Enable persistence using PVC for the registry | `false` |
| `registry.persistence.storageClass` | PVC Storage Class | `-` |
| `registry.persistence.size` | PVC Storage Request size | `1Gi` |
| `registry.service.type` | Desired service type | `ClusterIP` |
| `registry.service.port` | Service exposed port | `5000` |
| `registry.ingress.enabled` | Create an ingress for the regstry | `false` |
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
```bash
$ helm upgrade --install my-release \
--set registry.external=true \
--set registry.url=http://registry.example.com:5000 \
.
```
Alternatively, a YAML file that specifies the values for the above parameters can be provided while installing the chart. For example,
```bash
$ helm upgrade --install my-release -f values.yaml .
```
> **Tip**: You can use the default [values.yaml](values.yaml)

View File

@@ -0,0 +1,147 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "docker-registry-ui.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "docker-registry-ui.fullname" -}}
{{- if .Values.ui.fullnameOverride -}}
{{- .Values.ui.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- printf "%s-ui" .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-ui-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- define "docker-registry.fullname" -}}
{{- if .Values.registry.fullnameOverride -}}
{{- .Values.registry.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- printf "%s-registry" .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-registry-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "docker-registry-ui.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Common labels
*/}}
{{- define "docker-registry-ui.labels" -}}
app: registry-ui
chart: {{ include "docker-registry-ui.chart" . }}
release: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app/version: {{ .Chart.AppVersion | quote }}
{{- end }}
{{- end -}}
{{- define "docker-registry-ui.matchLabels" -}}
app: registry-ui
release: {{ .Release.Name }}
{{- end -}}
{{- define "docker-registry.labels" -}}
app: registry
chart: {{ include "docker-registry-ui.chart" . }}
release: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app/version: {{ .Chart.AppVersion | quote }}
{{- end }}
{{- end -}}
{{- define "docker-registry.matchLabels" -}}
app: registry
release: {{ .Release.Name }}
{{- end -}}
{{- define "docker-registry-ui.probes" -}}
{{- if and .Values.ui.probe.liveness (eq .Values.ui.probe.liveness true) -}}
livenessProbe:
httpGet:
path: /
port: http
{{- end -}}
{{- if and .Values.ui.probe.readiness (eq .Values.ui.probe.readiness true) }}
readinessProbe:
httpGet:
path: /
port: http
{{- end -}}
{{- end -}}
{{- define "docker-registry.probes" -}}
{{- if and .Values.registry.probe.liveness (eq .Values.registry.probe.liveness true) -}}
livenessProbe:
httpGet:
path: /v2/
port: registry
{{- end -}}
{{- if and .Values.registry.probe.readiness (eq .Values.registry.probe.readiness true) }}
readinessProbe:
httpGet:
path: /v2/
port: registry
{{- end -}}
{{- end -}}
{{- define "docker-registry-ui.url-name" -}}
{{- if eq .Values.ui.proxy true -}}
REGISTRY_URL
{{- else -}}
URL
{{- end -}}
{{- end -}}
{{- define "docker-registry-ui.url-value" -}}
{{- if eq .Values.registry.external true -}}
{{ .Values.registry.url }}
{{- else -}}
{{- $fullName := include "docker-registry.fullname" . -}}
{{ printf "http://%s.%s:%.0f" $fullName .Release.Namespace .Values.registry.service.port }}
{{- end -}}
{{- end -}}
{{- define "docker-registry-ui.pull" -}}
{{- if eq .Values.registry.external true -}}
{{ .Values.registry.url }}
{{- else -}}
{{- if eq .Values.ui.proxy true -}}
{{- if eq .Values.ui.ingress.enabled true -}}
{{- $host := index .Values.ui.ingress.hosts 0 -}}
{{ $host.host }}
{{- else -}}
{{- $fullName := include "docker-registry-ui.fullname" . -}}
{{ printf "%s.%s:%.0f" $fullName .Release.Namespace .Values.ui.service.port }}
{{- end -}}
{{- else -}}
{{- if eq .Values.registry.ingress.enabled true -}}
{{- $host := index .Values.registry.ingress.hosts 0 -}}
{{ $host.host }}
{{- else -}}
{{- $fullName := include "docker-registry.fullname" . -}}
{{ printf "%s.%s:%.0f" $fullName .Release.Namespace .Values.registry.service.port }}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}

View File

@@ -0,0 +1,31 @@
{{- if eq .Values.registry.external false -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "docker-registry.fullname" . }}
labels:
{{ include "docker-registry.labels" . | indent 4 }}
data:
config.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: ['*']
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']
{{- end -}}

View File

@@ -0,0 +1,62 @@
{{- if eq .Values.registry.external false -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "docker-registry.fullname" . }}
labels:
{{ include "docker-registry.labels" . | indent 4 }}
spec:
replicas: {{ .Values.registry.replicaCount }}
selector:
matchLabels:
{{ include "docker-registry.matchLabels" . | indent 6 }}
template:
metadata:
labels:
{{ include "docker-registry.matchLabels" . | indent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
- name: config
configMap:
defaultMode: 420
name: {{ include "docker-registry.fullname" . }}
- name: data
{{- if .Values.registry.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ include "docker-registry.fullname" . }}
{{- else }}
emptyDir: {}
{{- end }}
containers:
- name: registry
image: "{{ .Values.registry.image.registry }}/{{ .Values.registry.image.repository }}:{{ .Values.registry.image.tag }}"
imagePullPolicy: {{ .Values.registry.image.pullPolicy }}
ports:
- name: registry
containerPort: 5000
protocol: TCP
volumeMounts:
- mountPath: "/var/lib/registry"
name: "data"
- mountPath: "/etc/docker/registry"
name: "config"
{{ include "docker-registry.probes" . | indent 10 }}
resources:
{{- toYaml .Values.registry.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,34 @@
{{- if and (eq .Values.registry.external false) (and (eq .Values.ui.proxy false) .Values.registry.ingress.enabled) -}}
{{- $fullName := include "docker-registry.fullname" . -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{ include "docker-registry.labels" . | indent 4 }}
{{- with .Values.registry.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.registry.ingress.tls }}
tls:
{{- range .Values.registry.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.registry.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
- path: /
backend:
serviceName: {{ $fullName }}
servicePort: registry
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,23 @@
{{- if and (eq .Values.registry.external false) .Values.registry.persistence.enabled -}}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
{{ include "docker-registry.labels" . | indent 4 }}
name: {{ include "docker-registry.fullname" . }}
spec:
accessModes:
{{- range .Values.registry.persistence.accessModes }}
- {{ . | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.registry.persistence.size }}
{{- if .Values.registry.persistence.storageClass }}
{{- if (eq "-" .Values.registry.persistence.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: {{ .Values.registry.persistence.storageClass | quote }}
{{- end }}
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,17 @@
{{- if eq .Values.registry.external false -}}
apiVersion: v1
kind: Service
metadata:
name: {{ include "docker-registry.fullname" . }}
labels:
{{ include "docker-registry.labels" . | indent 4 }}
spec:
type: {{ .Values.registry.service.type }}
ports:
- port: {{ .Values.registry.service.port }}
targetPort: registry
protocol: TCP
name: registry
selector:
{{ include "docker-registry.matchLabels" . | indent 6 }}
{{- end -}}

View File

@@ -0,0 +1,52 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "docker-registry-ui.fullname" . }}
labels:
{{ include "docker-registry-ui.labels" . | indent 4 }}
spec:
replicas: {{ .Values.ui.replicaCount }}
selector:
matchLabels:
{{ include "docker-registry-ui.matchLabels" . | indent 6 }}
template:
metadata:
labels:
{{ include "docker-registry-ui.matchLabels" . | indent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: registry-ui
image: "{{ .Values.ui.image.registry }}/{{ .Values.ui.image.repository }}:{{ .Values.ui.image.tag }}"
imagePullPolicy: {{ .Values.ui.image.pullPolicy }}
env:
- name: REGISTRY_TITLE
value: {{ .Values.ui.title| quote }}
- name: DELETE_IMAGES
value: {{ .Values.ui.delete_images| quote }}
- name: {{ include "docker-registry-ui.url-name" . }}
value: {{ include "docker-registry-ui.url-value" . | quote }}
- name: PULL_URL
value: {{ include "docker-registry-ui.pull" . | quote }}
ports:
- name: http
containerPort: 80
protocol: TCP
{{ include "docker-registry-ui.probes" . | indent 10 }}
resources:
{{- toYaml .Values.ui.resources | nindent 12 }}
{{- with .Values.ui.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.ui.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.ui.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,34 @@
{{- if .Values.ui.ingress.enabled -}}
{{- $fullName := include "docker-registry-ui.fullname" . -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{ include "docker-registry-ui.labels" . | indent 4 }}
{{- with .Values.ui.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ui.ingress.tls }}
tls:
{{- range .Values.ui.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ui.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
- path: /
backend:
serviceName: {{ $fullName }}
servicePort: http
{{- end }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "docker-registry-ui.fullname" . }}
labels:
{{ include "docker-registry-ui.labels" . | indent 4 }}
spec:
type: {{ .Values.ui.service.type }}
ports:
- port: {{ .Values.ui.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{ include "docker-registry-ui.matchLabels" . | indent 6 }}

View File

@@ -0,0 +1,129 @@
# Default values for docker-registry-ui.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
ui:
# title of the registry
title: "Docker registry UI"
# allow delete of images
delete_images: false
# UI behave as a proxy of the registry
proxy: true
replicaCount: 1
image:
registry: docker.io
repository: joxit/docker-registry-ui
tag: static
pullPolicy: Always
probe:
liveness: true
readiness: true
resources: {}
# If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
fullnameOverride: ""
service:
type: ClusterIP
port: 80
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: docker-registry-ui.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
registry:
external: false
# URL of the registry (requiered. Note: this wont work as localhost is inside the container. Only used if the registry is external)
url: http://localhost:5000
replicaCount: 1
# Image definition for the registry (Only used if the registry is not external)
image:
registry: docker.io
repository: registry
tag: 2.6.2
pullPolicy: Always
probe:
liveness: true
readiness: true
resources: {}
# If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
fullnameOverride: ""
persistence:
## If true, use a Persistent Volume Claim, If false, use emptyDir
##
enabled: false
## Persistent Volume Storage Class
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
# storageClass: "-"
## Persistent Volume Claim annotations
##
annotations:
## Persistent Volume Access Mode
##
accessModes:
# This have to be ReadWriteMany if replicaCount>1
- ReadWriteOnce
## Persistent Volume size
##
size: 1Gi
##
service:
type: ClusterIP
port: 5000
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: docker-registry.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
imagePullSecrets: []
nameOverride: ""

View File

@@ -0,0 +1,4 @@
# Example for issue #88
When the docker registry ui is used as a proxy and its port is not 80, we can't push images.
To fix that, I added the correct Host header in the proxy_set_header.

View File

@@ -0,0 +1,23 @@
version: 0.1
log:
fields:
service: registry
storage:
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: true
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
Access-Control-Allow-Origin: ['*']
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
Access-Control-Expose-Headers: ['Docker-Content-Digest']
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3

View File

@@ -0,0 +1,34 @@
version: '2'
services:
registry-srv:
image: registry:latest
restart: always
volumes:
- storage:/var/lib/registry
- ./config.yml:/etc/docker/registry/config.yml:ro
networks:
- registry-ui-net
container_name: registry-srv
registry-ui:
image: joxit/docker-registry-ui:static
restart: always
ports:
- 8080:80
environment:
- REGISTRY_TITLE=Private Docker Registry
- REGISTRY_URL=http://registry-srv:5000
- DELETE_IMAGES=true
depends_on:
- debugproxy
networks:
- registry-ui-net
container_name: registry-ui
networks:
registry-ui-net:
volumes:
storage:
driver: local

View File

@@ -0,0 +1,19 @@
# Kubernetes installation of Docker Registry UI
## Full installation
Install a registry and docker-registry-ui as frontend of this registry to kubernetes.
```sh
kubectl apply -f *.yaml
```
Please note that you'll need a PV provisionner to be able to store the uploaded images.
## Dynamic installation
Edit the image tag in the ui-deployement.yaml file and set it to `latest`, then :
```sh
kubectl apply -f ui*.yaml
```
You'll get a docker-registry-ui pod installed inside kubernetes and you'll be able to configure it to act as a frontend to your existing registry(ies).

View File

@@ -0,0 +1,31 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: docker-registry
labels:
app: registry
release: docker-registry-ui
app/version: "1.2.1"
data:
config.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: ['*']
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']

View File

@@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: docker-registry
labels:
app: registry
release: docker-registry-ui
app/version: "1.2.1"
spec:
replicas: 1
selector:
matchLabels:
app: registry
release: docker-registry-ui
template:
metadata:
labels:
app: registry
release: docker-registry-ui
spec:
volumes:
- name: config
configMap:
defaultMode: 420
name: docker-registry
- name: data
persistentVolumeClaim:
claimName: docker-registry
containers:
- name: registry
image: "docker.io/registry:2.6.2"
imagePullPolicy: Always
ports:
- name: registry
containerPort: 5000
protocol: TCP
volumeMounts:
- mountPath: "/var/lib/registry"
name: "data"
- mountPath: "/etc/docker/registry"
name: "config"
livenessProbe:
httpGet:
path: /v2/
port: registry
readinessProbe:
httpGet:
path: /v2/
port: registry
resources:
{}

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
app: registry
release: docker-registry-ui
app/version: "1.2.1"
name: docker-registry
spec:
accessModes:
- "ReadWriteOnce"
resources:
requests:
storage: 1Gi

View File

@@ -0,0 +1,18 @@
apiVersion: v1
kind: Service
metadata:
name: docker-registry
labels:
app: registry
release: docker-registry-ui
app/version: "1.2.1"
spec:
type: ClusterIP
ports:
- port: 5000
targetPort: registry
protocol: TCP
name: registry
selector:
app: registry
release: docker-registry-ui

View File

@@ -0,0 +1,48 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: docker-registry-ui
labels:
app: registry-ui
release: docker-registry-ui
app/version: "1.2.1"
spec:
replicas: 1
selector:
matchLabels:
app: registry-ui
release: docker-registry-ui
template:
metadata:
labels:
app: registry-ui
release: docker-registry-ui
spec:
containers:
- name: registry-ui
image: "docker.io/joxit/docker-registry-ui:static"
imagePullPolicy: Always
env:
- name: REGISTRY_TITLE
value: "Docker registry UI"
- name: DELETE_IMAGES
value: "false"
- name: REGISTRY_URL
value: "http://docker-registry.default:5000"
- name: PULL_URL
value: "docker-registry-ui.default:80"
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{}

View File

@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: docker-registry-ui
labels:
app: registry-ui
release: docker-registry-ui
app/version: "1.2.1"
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: registry-ui
release: docker-registry-ui

View File

@@ -0,0 +1,19 @@
# Set custom headers to the registry example
The interface and the docker registry will be accessible with <http://localhost>.
This example highlight the usage of custom headers when the UI is used as a proxy. When you wants to use a header name with hyphens, replace them by underscores in the variable. You can put headers in environment variable or in config file `/etc/nginx/.env`. They have the same writing style.
Headers can be useful in some cases such as avoid sending credentials when you are on the UI. Or give to the registry server other properties such as X-Forward-For header.
I will set these two headers in this example. X-Forward-For by environment variable and Authorization by file.
In order to set your credentials in the header, you need to know how [Authorization](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) header works. Here we use the `Basic` authentication scheme, the credentials are constructed like this:
- The username and the password are combined with a colon (`registry:ui`).
- The resulting string is base64 encoded (`cmVnaXN0cnk6dWk=`). You can simply run `echo -n "registry:ui" | base64`.
- In your header, put this value `Basic cmVnaXN0cnk6dWk=`
- In your `/etc/nginx/.env`, the file will contains `NGINX_PROXY_HEADER_Authorization=Basic cmVnaXN0cnk6dWk=`
For X-Forward-For, replace all hyphens by underscores, and the value will be a nginx variable which is `$proxy_add_x_forwarded_for`. In your docker compose you will need to duplicate the `$` character. In your docker-compose, your environment will look like `NGINX_PROXY_HEADER_X_Forwarded_For=$$proxy_add_x_forwarded_for`
As usual, run the project with `docker-compose up -d` (for background mode)

View File

@@ -0,0 +1,28 @@
version: '2.0'
services:
registry:
image: registry:2.7
volumes:
- ./registry-data:/var/lib/registry
- ./registry-config/credentials.yml:/etc/docker/registry/config.yml
- ./registry-config/htpasswd:/etc/docker/registry/htpasswd
networks:
- registry-ui-net
ui:
image: joxit/docker-registry-ui:static
ports:
- 80:80
environment:
- REGISTRY_TITLE=My Private Docker Registry
- REGISTRY_URL=http://registry:5000
- NGINX_PROXY_HEADER_X_Forwarded_For=$$proxy_add_x_forwarded_for
volumes:
- ./nginx.env:/etc/nginx/.env
depends_on:
- registry
networks:
- registry-ui-net
networks:
registry-ui-net:

View File

@@ -0,0 +1 @@
NGINX_PROXY_HEADER_Authorization=Basic cmVnaXN0cnk6dWk=

View File

@@ -0,0 +1,25 @@
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://localhost']
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

@@ -0,0 +1 @@
registry:$2y$11$1bmuJLK8HrQl5ACS/WeqRuJLUArUZfUcP2R23asmozEpfN76.pCHy

View File

@@ -20,14 +20,16 @@ const allTags = ['src/tags/*.tag', 'src/tags/dialogs/*.tag'];
const allScripts = [
'src/scripts/http.js',
'src/scripts/script.js'
'src/scripts/script.js',
'src/scripts/utils.js'
];
const staticTags = ['src/tags/*.tag'];
const staticScripts = [
'src/scripts/http.js',
'src/scripts/static.js'
'src/scripts/static.js',
'src/scripts/utils.js'
];
function html() {

View File

@@ -24,6 +24,8 @@ server {
#! if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
#! return 404;
#! }
#! proxy_set_header Host $http_host;
#! ${NGINX_PROXY_HEADERS}
#! proxy_pass ${REGISTRY_URL};
#! }

View File

@@ -1,6 +1,6 @@
{
"name": "docker-registry-ui",
"version": "1.2.1",
"version": "1.2.3",
"scripts": {
"build": "./node_modules/gulp/bin/gulp.js build"
},
@@ -14,7 +14,7 @@
"dependencies": {},
"devDependencies": {
"del": "^3.0.0",
"gulp": "^4.0.1",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.2.0",
"gulp-concat": "^2.6.0",
"gulp-filter": "^5.1.0",

View File

@@ -61,6 +61,7 @@
<script src="tags/app.tag" type="riot/tag"></script>
<script src="scripts/http.js"></script>
<script src="scripts/script.js"></script>
<script src="scripts/utils.js"></script>
<!-- endbuild -->
</body>

View File

@@ -3,9 +3,9 @@
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(MaterialIcons-Regular.eot); /* For IE6-8 */
src: local('fonts/Material Icons'),
local('fonts/MaterialIcons-Regular'),
src: url(fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(fonts/MaterialIcons-Regular.woff2) format('woff2'),
url(fonts/MaterialIcons-Regular.woff) format('woff'),
url(fonts/MaterialIcons-Regular.ttf) format('truetype');

68
src/scripts/utils.js Normal file
View File

@@ -0,0 +1,68 @@
registryUI.bytesToSize = function (bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == undefined || isNaN(bytes)) {
return '?';
} else if (bytes == 0) {
return '0 Byte';
}
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.ceil(bytes / Math.pow(1024, i)) + ' ' + sizes[i];
};
registryUI.dateFormat = function(date) {
if (date === undefined) {
return '';
}
const labels = ['a second', 'seconds', 'a minute', 'minutes', 'an hour', 'hours', 'a day', 'days', 'a month', 'months', 'a year', 'years'];
const maxSeconds = [1, 60, 3600, 86400, 2592000, 31104000, Infinity];
const diff = (new Date() - date) / 1000;
for (var i = 0; i < maxSeconds.length - 1; i++) {
if (maxSeconds[i] * 2 >= diff) {
return labels[i * 2];
} else if (maxSeconds[i + 1] > diff) {
return Math.floor(diff / maxSeconds[i]) + ' ' + labels[i * 2 + 1];
}
}
};
registryUI.getHistoryIcon = function(attribute) {
switch (attribute) {
case 'architecture':
return 'memory';
case 'created':
return 'event';
case 'docker_version':
return '';
case 'os':
return 'developer_board';
case 'Cmd':
return 'launch';
case 'Entrypoint':
return 'input';
case 'Env':
return 'notes';
case 'Labels':
return 'label';
case 'User':
return 'face';
case 'Volumes':
return 'storage';
case 'WorkingDir':
return 'home';
case 'author':
return 'account_circle';
case 'id':
case 'digest':
return 'settings_ethernet';
case 'created_by':
return 'build';
case 'size':
return 'get_app';
case 'ExposedPorts':
return 'router';
default:
''
}
}

View File

@@ -221,17 +221,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
oReq.send();
};
registryUI.bytesToSize = function (bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == undefined || isNaN(bytes)) {
return '?';
} else if (bytes == 0) {
return '0 Byte';
}
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.ceil(bytes / Math.pow(1024, i)) + ' ' + sizes[i];
};
registryUI.taglist.go = function(image) {
route('taglist/' + image);
};

View File

@@ -15,29 +15,14 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<image-date>
<div title="Creation date { this.localDate }">{ this.dateFormat(this.date) } ago</div>
<div title="Creation date { this.localDate }">{ registryUI.dateFormat(this.date) } ago</div>
<script type="text/javascript">
const self = this;
this.dateFormat = function(date) {
if (date === undefined) {
return '';
}
const labels = ['a second', 'seconds', 'a minute', 'minutes', 'an hour', 'hours', 'a day', 'days', 'a month', 'months', 'a year', 'years'];
const maxSeconds = [1, 60, 3600, 86400, 2592000, 31104000, Infinity];
const diff = (new Date() - date) / 1000;
for (var i = 0; i < maxSeconds.length - 1; i++) {
if (maxSeconds[i] * 2 >= diff) {
return labels[i * 2];
} else if (maxSeconds[i + 1] > diff) {
return Math.floor(diff / maxSeconds[i]) + ' ' + labels[i * 2 + 1];
}
}
};
opts.image.on('creation-date', function(date) {
self.date = date;
self.localDate = date.toLocaleString()
self.update();
});
opts.image.trigger('get-date');
</script>
</image-date>

View File

@@ -15,50 +15,9 @@ 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/>.
-->
<tag-history-element class="{entry.key}">
<div class="headline"><i class="material-icons">{ this.getIcon(entry.key) }</i>
<div class="headline"><i class="material-icons">{ registryUI.getHistoryIcon(entry.key) }</i>
<p>{ entry.key.replace('_', ' ') }</p>
</div>
<div class="value" if={!(entry.value instanceof Array)}> { entry.value }</div>
<div class="value" each={ e in entry.value } if={entry.value instanceof Array}> { e }</div>
<script type="text/javascript">
this.getIcon = function(attribute) {
switch (attribute) {
case 'architecture':
return 'memory';
case 'created':
return 'event';
case 'docker_version':
return '';
case 'os':
return 'developer_board';
case 'Cmd':
return 'launch';
case 'Entrypoint':
return 'input';
case 'Env':
return 'notes';
case 'Labels':
return 'label';
case 'User':
return 'face';
case 'Volumes':
return 'storage';
case 'WorkingDir':
return 'home';
case 'author':
return 'account_circle';
case 'id':
case 'digest':
return 'settings_ethernet';
case 'created_by':
return 'build';
case 'size':
return 'get_app';
case 'ExposedPorts':
return 'router';
default:
''
}
}
</script>
</tag-history-element>