mirror of
https://github.com/Joxit/docker-registry-ui.git
synced 2026-02-17 21:19:51 +00:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf9c6c82e7 | ||
|
|
c74a9aeaa3 | ||
|
|
42bcec50df | ||
|
|
18dd5ca129 | ||
|
|
84b31f2cfb | ||
|
|
16d01d4dbf | ||
|
|
834a0ea40a | ||
|
|
c8383a9c80 | ||
|
|
ae9591c79a | ||
|
|
b88dc4567d | ||
|
|
ffb6d14baf | ||
|
|
684f82f24e | ||
|
|
9cfb6791f8 | ||
|
|
4091baa341 | ||
|
|
affb0572c9 | ||
|
|
dbfc9fe587 | ||
|
|
d2e6cdcab1 | ||
|
|
1031034bc4 | ||
|
|
398fa65fa1 | ||
|
|
b6604421bb | ||
|
|
03157d841e | ||
|
|
e7e762d6d9 | ||
|
|
8e98c1c63b | ||
|
|
aca633720a | ||
|
|
5a340291c2 | ||
|
|
9ebbbc3518 | ||
|
|
d2222bef05 | ||
|
|
c6dee14d79 | ||
|
|
92584fc3da | ||
|
|
78606e07f1 | ||
|
|
bc80050a44 | ||
|
|
8bbfc5c390 | ||
|
|
fbab517a17 | ||
|
|
34d1ed90ad | ||
|
|
a135c00866 | ||
|
|
ba322e076f | ||
|
|
a77103a2d4 | ||
|
|
b0ea4e5fb8 | ||
|
|
edb5aa97e8 | ||
|
|
ca7202d1f5 | ||
|
|
a3e987482e | ||
|
|
2b63fb725c | ||
|
|
b0811086fd | ||
|
|
43a242312c | ||
|
|
bfc901eb0b | ||
|
|
f984633bc3 | ||
|
|
ea508e6a1d | ||
|
|
4f452207c4 |
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@@ -11,8 +11,12 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
- name: Build the interface
|
||||
run: npm run build
|
||||
env:
|
||||
DEVELOPMENT_BUILD: ${{ github.sha }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
|
||||
20
.github/workflows/pull_request.yml
vendored
Normal file
20
.github/workflows/pull_request.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Run tests on PRs and branches
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore: [ main, master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
- name: Build the interface
|
||||
run: npm run build
|
||||
env:
|
||||
DEVELOPMENT_BUILD: ${{ github.event.pull_request.head.sha }}
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -2,7 +2,7 @@ name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: '*'
|
||||
tags: ['*']
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -13,6 +13,8 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
- name: Build the interface
|
||||
run: npm run build
|
||||
- name: Major tag
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ _site
|
||||
*.orig
|
||||
.serve/
|
||||
demo/v2
|
||||
.version.json
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
- Murad [@muradheydarov](https://github.com/muradheydarov)
|
||||
- Giacomo Mazzamuto [@gmazzamuto](https://github.com/gmazzamuto)
|
||||
- Joe Bureau [@jabstone](https://github.com/jabstone)
|
||||
- Artur Mostowski [@Vulwsztyn](https://github.com/Vulwsztyn)
|
||||
|
||||
## Because committers are not the only contributors
|
||||
|
||||
@@ -44,3 +45,11 @@
|
||||
- [@JKDingwall](https://github.com/JKDingwall)
|
||||
- Martin Herren [@MartinHerren](https://github.com/MartinHerren)
|
||||
- John Daktylidis [@Greek64](https://github.com/Greek64)
|
||||
- Philipp Staiger [@lippl](https://github.com/lippl)
|
||||
- [@mexaniksmirnov](https://github.com/mexaniksmirnov)
|
||||
- [@HighOnMikey](https://github.com/HighOnMikey)
|
||||
- [@logopk](https://github.com/logopk)
|
||||
- Gustaf Järgren [@GoryMoon](https://github.com/GoryMoon)
|
||||
- [@ArwynFr](https://github.com/ArwynFr)
|
||||
- Nikita Matushkin [@yourh3ro](https://github.com/yourh3ro)
|
||||
- Michael Grote [@quotengrote](https://github.com/quotengrote)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#
|
||||
# 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/>.
|
||||
FROM nginx:alpine
|
||||
FROM nginx:alpine-slim
|
||||
|
||||
LABEL maintainer="Jones MAGLOIRE @Joxit"
|
||||
|
||||
|
||||
82
README.md
82
README.md
@@ -1,13 +1,10 @@
|
||||
---
|
||||
title: Docker Registry User Interface
|
||||
---
|
||||
# Docker Registry User Interface
|
||||
|
||||
# Docker Registry UI
|
||||
|
||||

|
||||

|
||||
[](https://github.com/Joxit/docker-registry-ui/stargazers)
|
||||
[](https://hub.docker.com/r/joxit/docker-registry-ui)
|
||||
[](https://github.com/sponsors/Joxit)
|
||||
[](https://artifacthub.io/packages/search?repo=joxit)
|
||||
[](https://github.com/Joxit/docker-registry-ui/releases)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -17,12 +14,22 @@ You may need the [migration guide from 1.x to 2.x](https://github.com/Joxit/dock
|
||||
|
||||
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.
|
||||
|
||||
If you like my work and want to support it, don't hesitate to [sponsor me](https://github.com/sponsors/Joxit).
|
||||
|
||||
## Supported Docker tags
|
||||
|
||||
* `latest`: image with the latest release of Docker Registry UI based on `nginx:alpine`
|
||||
* `latest-debian`: image with the latest release of Docker Registry UI based on `nginx:debian`
|
||||
* `main`, `master`: image with the beta version of Docker Registry UI based on `nginx:alpine`
|
||||
* `main-debian`, `master-debian`: image with the beta version of Docker Registry UI based on `nginx:debian`
|
||||
* `2`: image with the latest release of Docker Registry UI v2 (includes latest minor and patch version)
|
||||
* `2.x`: image with the latest release of Docker Registry UI v2.x (includes latest patch version)
|
||||
* `2.x.y`: image with the specific release of Docker Registry UI v2.x.y
|
||||
|
||||
## [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/main/examples), [Helm Chart](https://helm.joxit.dev/)
|
||||
|
||||

|
||||
|
||||
If you like my work and want to support it, don't hesitate to [sponsor me](https://github.com/sponsors/Joxit).
|
||||
|
||||
## Hidden Features
|
||||
|
||||
- Many ways to delete multiple images at once
|
||||
@@ -38,6 +45,7 @@ If you like my work and want to support it, don't hesitate to [sponsor me](https
|
||||
- You can select the search bar with the shortcut `CRTL + F` or `F3`. When the search bar is already focused, the shortcut will fallback to the default behavior (see [#213](https://github.com/Joxit/docker-registry-ui/issues/213)). Since 2.1.0
|
||||
- Multi arch support in history page (see [#130](https://github.com/Joxit/docker-registry-ui/issues/130) and [#134](https://github.com/Joxit/docker-registry-ui/pull/134)). Since 1.5.0
|
||||
- Show the content of the dockerfile (see [#286](https://github.com/Joxit/docker-registry-ui/pull/286)). Since 2.4.0
|
||||
- The UI will cache requests from your registry, such as blobs and some manifests (URL with `sha256:`).
|
||||
|
||||
Checkout all options in [Available options](#available-options) section.
|
||||
|
||||
@@ -73,6 +81,8 @@ Checkout all options in [Available options](#available-options) section.
|
||||
- Yes but it is at your own risk using two regstry servers, check the comment [#155](https://github.com/Joxit/docker-registry-ui/issues/155#issuecomment-1286052124).
|
||||
- How to fix CORS issue on s3 bucket ?
|
||||
- You should add a CORS Policy on your bucket, check the issue [#193](https://github.com/Joxit/docker-registry-ui/issues/193).
|
||||
- Why my docker registry server is returning an error `pagination number invalid` ?
|
||||
- Since docker registry server 2.8.2 there is default limit of 1000 images in catalog. If you need more images update the configuration `REGISTRY_CATALOG_MAXENTRIES` with your max value and check the issue [#306](https://github.com/Joxit/docker-registry-ui/issues/306).
|
||||
|
||||
Need more informations ? Try my [examples](https://github.com/Joxit/docker-registry-ui/tree/main/examples) or open an issue.
|
||||
|
||||
@@ -87,19 +97,25 @@ Some env options are available for use this interface for **only one server** (w
|
||||
- `PULL_URL`: Set a custom url when you copy the `docker pull` command (see [#71](https://github.com/Joxit/docker-registry-ui/issues/71)). (default: value derived from `REGISTRY_URL`). Since 1.1.0
|
||||
- `DELETE_IMAGES`: Set if we can delete images from the UI. (default: `false`)
|
||||
- `SHOW_CONTENT_DIGEST`: Show/Hide content digest in docker tag list (see [#126](https://github.com/Joxit/docker-registry-ui/issues/126) and [#131](https://github.com/Joxit/docker-registry-ui/pull/131)). (default: `false`). Since 1.4.9
|
||||
- `CATALOG_ELEMENTS_LIMIT`: Limit the number of elements in the catalog page (see [#39](https://github.com/Joxit/docker-registry-ui/issues/39), [#127](https://github.com/Joxit/docker-registry-ui/pull/127) and [#132](https://github.com/Joxit/docker-registry-ui/pull/132)). (default: `100000`). Since 1.4.9
|
||||
- `CATALOG_ELEMENTS_LIMIT`: Limit the number of elements in the catalog page (see [#39](https://github.com/Joxit/docker-registry-ui/issues/39), [#127](https://github.com/Joxit/docker-registry-ui/pull/127), [#132](https://github.com/Joxit/docker-registry-ui/pull/132)) and [#306](https://github.com/Joxit/docker-registry-ui/issues/306). (default: `1000`). Since 1.4.9
|
||||
- `SINGLE_REGISTRY`: Remove the menu that show the dialogs to add, remove and change the endpoint of your docker registry. (default: `false`). Since 2.0.0
|
||||
- `NGINX_PROXY_PASS_URL`: Update the default Nginx configuration and set the **proxy_pass** to your backend docker registry (this avoid CORS configuration). This is usually the name of your registry container in the form `http://registry:5000`. Since 2.0.0
|
||||
- `NGINX_PROXY_HEADER_*`: Update the default Nginx configuration and **set custom headers** for your backend docker registry via environment variable and file (`/etc/nginx/.env`). Only when `NGINX_PROXY_PASS_URL` is used (see [#89](https://github.com/Joxit/docker-registry-ui/pull/89)). Since 1.2.3
|
||||
- `NGINX_PROXY_PASS_HEADER_*`: Update the default Nginx configuration and **forward custom headers** to your backend docker registry via environment variable and file (`/etc/nginx/.env`). Only when `NGINX_PROXY_PASS_URL` is used (see [#206](https://github.com/Joxit/docker-registry-ui/issues/206)). Since 2.1.0
|
||||
- `NGINX_LISTEN_PORT`: Listen on a port other than 80, you can also change the default user and set to nginx `--user nginx` (see [#224](https://github.com/Joxit/docker-registry-ui/issues/224) and [#234](https://github.com/Joxit/docker-registry-ui/pull/234)). (default: `80` when the user is root, `8080` otherwise). Since 2.2.0
|
||||
- `DEFAULT_REGISTRIES`: List of comma separated registry URLs (e.g `http://registry.example.com,http://registry:5000`), available only when `SINGLE_REGISTRY=false` (see [#219](https://github.com/Joxit/docker-registry-ui/pull/219)). (default: ` `). Since 2.1.0
|
||||
- `READ_ONLY_REGISTRIES`: Desactivate dialog for remove and add new registries, available only when `SINGLE_REGISTRY=false` (see [#219](https://github.com/Joxit/docker-registry-ui/pull/219)). (default: `false`). Since 2.1.0
|
||||
- `SHOW_CATALOG_NB_TAGS`: Show number of tags per images on catalog page. This will produce + nb images requests, not recommended on large registries (see [#161](https://github.com/Joxit/docker-registry-ui/issues/161) and [#239](https://github.com/Joxit/docker-registry-ui/pull/239)). (default: `false`). Since 2.2.0
|
||||
- `READ_ONLY_REGISTRIES`: Deactivate dialog for remove and add new registries, available only when `SINGLE_REGISTRY=false` (see [#219](https://github.com/Joxit/docker-registry-ui/pull/219)). (default: `false`). Since 2.1.0
|
||||
- `SHOW_CATALOG_NB_TAGS`: Show number of tags per images on catalog page and hide images with 0 tags. This will produce + nb images requests, **not recommended on large registries** (see [#161](https://github.com/Joxit/docker-registry-ui/issues/161) and [#239](https://github.com/Joxit/docker-registry-ui/pull/239)). (default: `false`). Since 2.2.0
|
||||
- `HISTORY_CUSTOM_LABELS`: Expose custom labels in history page, custom labels will be processed like maintainer label (see [#160](https://github.com/Joxit/docker-registry-ui/issues/160) and [#240](https://github.com/Joxit/docker-registry-ui/pull/240)). Since 2.2.0
|
||||
- `USE_CONTROL_CACHE_HEADER`: Use `Control-Cache` header and set to `no-store, no-cache`. This will avoid some issues on multi-arch images (see [#260](https://github.com/Joxit/docker-registry-ui/issues/260) and [#265](https://github.com/Joxit/docker-registry-ui/pull/265)). This option requires registry configuration: `Access-Control-Allow-Headers` with `Cache-Control`. (default: `false`). Since 2.3.0
|
||||
- `THEME`: Chose your default theme, could be `dark`, `light` or `auto` (see [#283](https://github.com/Joxit/docker-registry-ui/pull/283)). When auto is selected, you will have a switch to manually change from light to dark and vice-versa (see [#291](https://github.com/Joxit/docker-registry-ui/pull/291)). (default: `auto`). Since 2.4.0
|
||||
- `THEME_*`: See table in [Theme options](#theme-options) section (see [#283](https://github.com/Joxit/docker-registry-ui/pull/283)). Since 2.4.0
|
||||
- `TAGLIST_ORDER`: Set the default order for the taglist page, could be `num-asc;alpha-asc`, `num-desc;alpha-asc`, `num-asc;alpha-desc`, `num-desc;alpha-desc`, `alpha-asc;num-asc`, `alpha-asc;num-desc`, `alpha-desc;num-asc` or `alpha-desc;num-desc` (see [#307](https://github.com/Joxit/docker-registry-ui/pull/307)). (default: `alpha-asc;num-desc`). Since 2.5.0
|
||||
- `CATALOG_DEFAULT_EXPANDED`: Expand by default all repositories in catalog (see [#302](https://github.com/Joxit/docker-registry-ui/issues/302)). (default: `false`). Since 2.5.0
|
||||
- `CATALOG_MIN_BRANCHES`: Set the minimum repository/namespace to expand (e.g. `joxit/docker-registry-ui` `joxit/` is the repository/namespace). Can be 0 to disable branching. (see [#319](https://github.com/Joxit/docker-registry-ui/pull/319)). (default: `1`). Since 2.5.0
|
||||
- `CATALOG_MAX_BRANCHES`: Set the maximum repository/namespace to expand (e.g. `joxit/docker-registry-ui` `joxit/` is the repository/namespace). Can be 0 to disable branching. (see [#319](https://github.com/Joxit/docker-registry-ui/pull/319)). (default: `1`). Since 2.5.0
|
||||
- `TAGLIST_PAGE_SIZE`: Set the number of tags to display in one page. (default: `100`). Since 2.5.0
|
||||
- `REGISTRY_SECURED`: By default, the UI will check on every requests if your registry is secured or not (you will see `401` responses in your console). Set to `true` if your registry uses Basic Authentication and divide by two the number of call to your registry. (default `false`). Since 2.5.0
|
||||
|
||||
There are some examples with [docker-compose](https://docs.docker.com/compose/) and docker-registry-ui as proxy [here](https://github.com/Joxit/docker-registry-ui/tree/main/examples/ui-as-proxy/) or docker-registry-ui as standalone [here](https://github.com/Joxit/docker-registry-ui/tree/main/examples/ui-as-standalone/).
|
||||
|
||||
@@ -120,6 +136,48 @@ This featureswas added to version 2.4.0. See more about this in [#283](https://g
|
||||
| `THEME_FOOTER_NEUTRAL_TEXT` | `#999999` | `#999999` |
|
||||
| `THEME_FOOTER_BACKGROUND` | `#555555` | `#555555` |
|
||||
|
||||
## Recommended Docker Registry Usage
|
||||
|
||||
Here is a simple usage of Docker Registry UI with Docker Registry Server using docker-compose. This example should work for most of your use case and your UI will be on the same domain as you registry.
|
||||
|
||||
```yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
registry-ui:
|
||||
image: joxit/docker-registry-ui:main
|
||||
restart: always
|
||||
ports:
|
||||
- 80:80
|
||||
environment:
|
||||
- SINGLE_REGISTRY=true
|
||||
- REGISTRY_TITLE=Docker Registry UI
|
||||
- DELETE_IMAGES=true
|
||||
- SHOW_CONTENT_DIGEST=true
|
||||
- NGINX_PROXY_PASS_URL=http://registry-server:5000
|
||||
- SHOW_CATALOG_NB_TAGS=true
|
||||
- CATALOG_MIN_BRANCHES=1
|
||||
- CATALOG_MAX_BRANCHES=1
|
||||
- TAGLIST_PAGE_SIZE=100
|
||||
- REGISTRY_SECURED=false
|
||||
- CATALOG_ELEMENTS_LIMIT=1000
|
||||
container_name: registry-ui
|
||||
|
||||
registry-server:
|
||||
image: registry:2.8.2
|
||||
restart: always
|
||||
environment:
|
||||
REGISTRY_HTTP_HEADERS_Access-Control-Origin: '[http://registry.example.com]'
|
||||
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods: '[HEAD,GET,OPTIONS,DELETE]'
|
||||
REGISTRY_HTTP_HEADERS_Access-Control-Credentials: '[true]'
|
||||
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: '[Authorization,Accept,Cache-Control]'
|
||||
REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers: '[Docker-Content-Digest]'
|
||||
REGISTRY_STORAGE_DELETE_ENABLED: 'true'
|
||||
volumes:
|
||||
- ./registry/data:/var/lib/registry
|
||||
container_name: registry-server
|
||||
```
|
||||
|
||||
## Using CORS
|
||||
|
||||
Your server should be configured to accept CORS.
|
||||
|
||||
@@ -11,6 +11,12 @@ sed -i "s~\${READ_ONLY_REGISTRIES}~${READ_ONLY_REGISTRIES}~" index.html
|
||||
sed -i "s~\${SHOW_CATALOG_NB_TAGS}~${SHOW_CATALOG_NB_TAGS}~" index.html
|
||||
sed -i "s~\${HISTORY_CUSTOM_LABELS}~${HISTORY_CUSTOM_LABELS}~" index.html
|
||||
sed -i "s~\${USE_CONTROL_CACHE_HEADER}~${USE_CONTROL_CACHE_HEADER}~" index.html
|
||||
sed -i "s~\${TAGLIST_ORDER}~${TAGLIST_ORDER}~" index.html
|
||||
sed -i "s~\${CATALOG_DEFAULT_EXPANDED}~${CATALOG_DEFAULT_EXPANDED}~" index.html
|
||||
sed -i "s~\${CATALOG_MIN_BRANCHES}~${CATALOG_MIN_BRANCHES}~" index.html
|
||||
sed -i "s~\${CATALOG_MAX_BRANCHES}~${CATALOG_MAX_BRANCHES}~" index.html
|
||||
sed -i "s~\${TAGLIST_PAGE_SIZE}~${TAGLIST_PAGE_SIZE}~" index.html
|
||||
sed -i "s~\${REGISTRY_SECURED}~${REGISTRY_SECURED}~" index.html
|
||||
|
||||
grep -o 'THEME[A-Z_]*' index.html | while read e; do
|
||||
sed -i "s~\${$e}~$(printenv $e)~" index.html
|
||||
|
||||
@@ -12,7 +12,7 @@ for i in arm64v8 arm32v7 master latest debian main; do
|
||||
docker push 127.0.0.1:5000/joxit/docker-registry-ui:$i
|
||||
done
|
||||
|
||||
for v in 1.1 1.2 1.3 1.4 1.5 2.0 2 2.0.0 2.1 2.1.0 2.2 2.2.0 2.3 2.3.0 2.4 2.4.0; do
|
||||
for v in 1.1 1.2 1.3 1.4 1.5 2.0 2 2.0.0 2.1 2.1.0 2.2 2.2.0 2.3 2.3.0 2.4 2.4.0 2.5.0; do
|
||||
for type in "-debian" ""; do
|
||||
docker pull joxit/docker-registry-ui:$v$type
|
||||
docker tag joxit/docker-registry-ui:$v$type 127.0.0.1:5000/joxit/docker-registry-ui:$v$type
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
single-registry="false"
|
||||
default-registries="https://joxit.dev/docker-registry-demo"
|
||||
show-catalog-nb-tags="true"
|
||||
taglist-order=""
|
||||
theme="auto"
|
||||
/>
|
||||
<script>
|
||||
|
||||
2
dist/docker-registry-ui.css
vendored
2
dist/docker-registry-ui.css
vendored
File diff suppressed because one or more lines are too long
24
dist/docker-registry-ui.js
vendored
24
dist/docker-registry-ui.js
vendored
File diff suppressed because one or more lines are too long
1
dist/images/rocket.svg
vendored
Normal file
1
dist/images/rocket.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 128 128"> <path fill="#ca2c31" d="M3.77 71.73l16.34-16.1 27.82-4.93-2.75 14.56L7.57 76.82l-2.43-1.05z"/> <path fill="#a02422" d="M22.94 59.76L5.2 75.88l13.05 6.36 19.81-10.11v-4.77l4.05-10.92zm41.98 28.39l-8.57 3.72-8.09 17.15s7.12 15.77 7.44 15.77c.32 0 4.37.32 4.37.32l14.4-16.1 3.64-27.5-13.19 6.64z"/> <path d="M56.5 100.84s4.77-.97 8.17-2.59c3.4-1.62 7.6-4.04 7.6-4.04l-1.54 13.43-15.05 17.13s-.59-.73-3.09-6.17c-1.99-4.34-2.68-5.89-2.68-5.89l6.59-11.87z" fill="#ca2c31"/> <path d="M31.58 80.66s-5.74-.48-12.03 7.47c-5.74 7.26-8.43 19.08-9.47 22.12s-3.53 3.66-2.7 5.05 4.42 1.31 8.85.76 8.23-1.94 8.23-1.94-.19.48-.83 1.52c-.23.37-1.03.9-.97 1.45.14 1.31 11.36 1.34 20.32-7.88 9.68-9.95 4.98-18.11 4.98-18.11L31.58 80.66z" fill="#f7d74d"/> <path d="M33.31 85.29s-6.19.33-11.31 8.28-7.5 17.16-7.01 17.78c.48.62 10.02-2.83 12.31-2.14 1.57.48.76 2.07 1.18 2.49.35.35 4.49.94 11.19-6.32 6.71-7.26 5.12-17.46 5.12-17.46l-11.48-2.63z" fill="#fbf0b4"/> <path d="M36.35 74.44s-3.11 2.77-4.22 4.36c-1.11 1.59-1.11 1.73-1.04 2.21.07.48 1.22 5.75 6.01 10.37 5.88 5.67 11.13 6.43 11.89 6.43.76 0 5.81-5.67 5.81-5.67l-18.45-17.7z" fill="#858585"/> <path d="M50.1 91.24s5.04 3.31 13.49.47c11.55-3.88 20.02-12.56 30.51-23.52 10.12-10.58 18.61-23.71 18.61-23.71l-5.95-19.93L50.1 91.24z" fill="#437687"/> <path d="M67.99 80.33l1.39-4.32 3.48.49s2.65 1.25 4.6 2.16c1.95.91 4.46 1.6 4.46 1.6l-4.95 4.18s-2.7-1.02-4.67-1.88c-2.22-.97-4.31-2.23-4.31-2.23z" fill="#3f545f"/> <path d="M84.32 16.14s-9.62 5.58-23.41 18.63c-12.43 11.76-21.64 22.4-23.87 31.45-1.86 7.58-.87 12.18 3.36 17.15 4.47 5.26 9.71 7.87 9.71 7.87s3.94.06 20.38-12.59C91 62.86 107.43 36.42 107.43 36.42L84.32 16.14z" fill="#8dafbf"/> <path d="M104.18 41.84s-8.37-3.57-14.34-11.9c-5.93-8.27-5.46-13.86-5.46-13.86s4.96-3.89 16.11-8.34c7.5-2.99 17.71-4.52 21.07-2.03s-2.3 14.98-2.3 14.98l-10.31 19.96-4.77 1.19z" fill="#d83f22"/> <path d="M68.17 80.4s-7.23-3.69-11.83-8.94c-8.7-9.91-10.5-20.79-10.5-20.79l4.37-5.13S51.3 57.1 60.63 67.09c6.08 6.51 12.43 9.49 12.43 9.49s-1.27 1.07-2.63 2.11c-.87.67-2.26 1.71-2.26 1.71z" fill="#6896a5"/> <path d="M112.71 44.48s4.34-5.23 8.45-17.02c5.74-16.44.74-21.42.74-21.42s-1.69 7.82-7.56 18.69c-4.71 8.71-10.41 17-10.41 17s3.14 1.41 4.84 1.9c2.14.62 3.94.85 3.94.85z" fill="#a02422"/> <path d="M39.81 69.66c1.3 1.24 3.27-.06 4.56-3.1 1.3-3.04 1.28-4.74.28-5.46-1.24-.9-3.32 1.07-4.23 2.82-1 1.94-1.59 4.8-.61 5.74zm45.14-49.53s-7.61 5.47-15.73 12.91c-7.45 6.83-12.39 12.17-13.07 13.41-.72 1.33-.73 3.21-.17 4.17s1.8 1.46 2.93.62c1.13-.85 9.18-9.75 16.45-16.11 6.65-5.82 11.78-9.51 11.78-9.51s2.08-3.68 1.74-4.52c-.34-.85-3.93-.97-3.93-.97z" fill="#b3e1ee"/> <path d="M84.95 20.13s5.62-4.31 11.74-7.34c5.69-2.82 11.35-5.17 12.37-3.13.97 1.94-5.37 4.58-10.95 8.14-5.58 3.56-10.95 7.81-10.95 7.81s-.82-1.5-1.35-2.89a23.7 23.7 0 01-.86-2.59z" fill="#ed6a65"/> <path d="M89.59 39.25c-5.57-5.13-13.32-3.75-17.14.81-3.92 4.7-3.63 11.88 1 16.2 4.21 3.92 12.04 4.81 16.76-.69 4.2-4.88 3.94-12.13-.62-16.32z" fill="#e1e1e1"/> <path d="M75.33 41.87c-3.31 3.25-3.13 9.69.81 12.63 3.44 2.57 8.32 2.44 11.38-.69 3.06-3.13 3.06-8.82.19-11.76-3.3-3.37-8.59-3.9-12.38-.18z" fill="#3f545f"/> <path d="M50 76.89s6.19-6.28 6.87-5.6c.68.68.59 4.49-2.37 8.73-2.97 4.24-9.5 11.79-14.67 16.88-5.1 5.01-12.29 10.74-12.97 10.64-.53-.08-2.68-1.15-3.54-2.19-.84-1.03 1.67-5.9 2.68-7.51 1.02-1.61 24-20.95 24-20.95z" fill="#a02524"/> <path d="M21.23 101.85c-.08 1.44 2.12 3.54 2.12 3.54L56.87 71.3s-1.57-1.77-6.19 1.1c-4.66 2.9-8.74 6.38-14.76 12.21-8.39 8.14-14.61 15.8-14.69 17.24z" fill="#ca2c31"/> </svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
6
dist/index.html
vendored
6
dist/index.html
vendored
@@ -26,6 +26,11 @@
|
||||
show-catalog-nb-tags="${SHOW_CATALOG_NB_TAGS}"
|
||||
history-custom-labels="${HISTORY_CUSTOM_LABELS}"
|
||||
use-control-cache-header="${USE_CONTROL_CACHE_HEADER}"
|
||||
taglist-order="${TAGLIST_ORDER}"
|
||||
catalog-default-expanded="${CATALOG_DEFAULT_EXPANDED}"
|
||||
catalog-min-branches="${CATALOG_MIN_BRANCHES}"
|
||||
catalog-max-branches="${CATALOG_MAX_BRANCHES}"
|
||||
is-registry-secured="${REGISTRY_SECURED}"
|
||||
theme="${THEME}"
|
||||
theme-primary-text="${THEME_PRIMARY_TEXT}"
|
||||
theme-neutral-text="${THEME_NEUTRAL_TEXT}"
|
||||
@@ -37,4 +42,5 @@
|
||||
theme-footer-text="${THEME_FOOTER_TEXT}"
|
||||
theme-footer-neutra-text="${THEME_FOOTER_NEUTRAL_TEXT}"
|
||||
theme-footer-background="${THEME_FOOTER_BACKGROUND}"
|
||||
tags-per-page="${TAGLIST_PAGE_SIZE}"
|
||||
></docker-registry-ui><script src="docker-registry-ui.js"></script></body></html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 3.6 MiB |
@@ -6,5 +6,5 @@ There are two htpasswd files. `read-write.htpasswd` a read and write access to t
|
||||
|
||||
All users in `read-only.htpasswd` should be in `read-write.htpasswd`.
|
||||
|
||||
Read only user: login: `read` password: `regisrty`.
|
||||
Read and write user: login: `write` password: `regisrty`.
|
||||
Read only user: login: `read` password: `registry`.
|
||||
Read and write user: login: `write` password: `registry`.
|
||||
|
||||
@@ -32,8 +32,23 @@ I will highlight required configuration for Basic Access Authentication Protocol
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
|
||||
if ($request_method = "OPTIONS") {
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Allow-Methods "OPTIONS, GET" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Accept, Authorization" always;
|
||||
add_header Access-Control-Allow-Credentials true always;
|
||||
add_header Content-Type "text/plain charset=UTF-8";
|
||||
add_header Content-Length 0;
|
||||
return 204;
|
||||
}
|
||||
|
||||
# By default, keycloak returns 400 instead of 401, we need to change that
|
||||
if ($http_authorization = "") {
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Allow-Methods "OPTIONS, GET" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Accept, Authorization" always;
|
||||
add_header Access-Control-Allow-Credentials true always;
|
||||
add_header WWW-Authenticate 'Basic realm="Keycloak login"' always;
|
||||
return 401;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ server {
|
||||
set $registry "http://registry:5000";
|
||||
set $ui "http://ui";
|
||||
|
||||
|
||||
#charset koi8-r;
|
||||
#access_log /var/log/nginx/host.access.log main;
|
||||
|
||||
@@ -44,14 +43,35 @@ server {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
|
||||
if ($request_method = "OPTIONS") {
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Allow-Methods "OPTIONS, GET" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Accept, Authorization" always;
|
||||
add_header Access-Control-Allow-Credentials true always;
|
||||
add_header Content-Type "text/plain charset=UTF-8";
|
||||
add_header Content-Length 0;
|
||||
return 204;
|
||||
}
|
||||
|
||||
if ($http_authorization = "") {
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Allow-Methods "OPTIONS, GET" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Accept, Authorization" always;
|
||||
add_header Access-Control-Allow-Credentials true always;
|
||||
add_header WWW-Authenticate 'Basic realm="Keycloak login"' always;
|
||||
return 401;
|
||||
}
|
||||
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Allow-Methods "OPTIONS, GET" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Accept, Authorization" always;
|
||||
add_header Access-Control-Allow-Credentials true always;
|
||||
proxy_pass $keycloak;
|
||||
}
|
||||
|
||||
location /ui {
|
||||
rewrite ^/ui/(.*) /$1 break;
|
||||
proxy_pass $ui;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ bash run-swarm.sh
|
||||
|
||||
## Authentication
|
||||
|
||||
The registry is protected via __Basic authentication__ but feel free to use wathever you like.
|
||||
The registry is protected via __Basic authentication__ but feel free to use whatever you like.
|
||||
In this sample, credentials are: **admin / admin**.
|
||||
|
||||
To generate a new password for basic auth, run the command: `htpasswd -nb username password`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "docker-registry-ui",
|
||||
"version": "2.4.1",
|
||||
"version": "2.5.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"format": "npm run format-html && npm run format-js && npm run format-riot",
|
||||
@@ -9,7 +9,8 @@
|
||||
"format-riot": "find src rollup rollup.config.js -name '*.riot' -exec prettier --config .prettierrc -w --parser html {} \\;",
|
||||
"start": "rollup -c -w --environment ROLLUP_SERVE:true",
|
||||
"build": "rollup -c",
|
||||
"build:electron": "npm run build && cd examples/electron && npm install && npm run dist"
|
||||
"build:electron": "npm run build && cd examples/electron && npm install && npm run dist",
|
||||
"test": "mocha"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -31,6 +32,7 @@
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@rollup/plugin-terser": "^0.2.1",
|
||||
"core-js": "^3.27.1",
|
||||
"mocha": "^10.2.0",
|
||||
"node-sass": "^8.0.0",
|
||||
"prettier": "^2.8.1",
|
||||
"riot": "^7.1.0",
|
||||
|
||||
@@ -14,10 +14,24 @@ import copyTransform from './rollup/copy-transform.js';
|
||||
import license from './rollup/license.js';
|
||||
import checkOutput from './rollup/check-output.js';
|
||||
import importSVG from './rollup/import-svg.js';
|
||||
import fs from 'fs';
|
||||
const version = JSON.parse(fs.readFileSync('./package.json', 'utf-8')).version;
|
||||
|
||||
const useServe = process.env.ROLLUP_SERVE === 'true';
|
||||
const output = useServe ? '.serve' : 'dist';
|
||||
|
||||
const getVersion = (version) => {
|
||||
const parts = version.split('.').map((e) => parseInt(e));
|
||||
if (useServe || process.env.DEVELOPMENT_BUILD) {
|
||||
parts[1]++;
|
||||
parts[2] = 0;
|
||||
return parts.join('.') + (useServe ? '-dev' : `-${process.env.DEVELOPMENT_BUILD.slice(0, 10)}`);
|
||||
}
|
||||
return version;
|
||||
};
|
||||
|
||||
fs.writeFileSync('.version.json', JSON.stringify({ version: getVersion(version), latest: version }));
|
||||
|
||||
const plugins = [
|
||||
riot(),
|
||||
json(),
|
||||
|
||||
@@ -20,7 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
class="content"
|
||||
if="{!props.filterResults || state.nImages > 0 || matchSearch(props.filterResults, state.image)}"
|
||||
>
|
||||
<material-card class="list highlight" expanded="{state.expanded}" onclick="{ onClick }">
|
||||
<material-card
|
||||
class="list highlight"
|
||||
expanded="{state.expanded}"
|
||||
onclick="{ onClick }"
|
||||
if="{ !props.showCatalogNbTags || state.nbTags !== 0 }"
|
||||
>
|
||||
<a if="{ state.image }" href="{ router.taglist(state.image) }">
|
||||
<material-waves center="true" color="#ddd"></material-waves>
|
||||
</a>
|
||||
@@ -45,8 +50,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
on-notify="{ props.onnNotify }"
|
||||
on-authentication="{ props.onAuthentication }"
|
||||
show-catalog-nb-tags="{ props.showCatalogNbTags }"
|
||||
is-registry-secured="{ props.isRegistrySecured }"
|
||||
class="animated {!state.expanded && !props.filterResults ? 'hide' : ''} {state.expanding ? 'expanding' : ''}"
|
||||
each="{item in state.images}"
|
||||
z-index="{ props.zIndex - 1 }"
|
||||
item="{ item }"
|
||||
></catalog-element>
|
||||
</div>
|
||||
@@ -65,11 +72,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
state.images = props.item.images;
|
||||
state.repo = props.item.repo;
|
||||
state.nImages = props.item.images.length;
|
||||
state.expanded = props.catalogDefaultExpanded;
|
||||
}
|
||||
if (props.showCatalogNbTags && state.image) {
|
||||
this.getNbTags(props, state);
|
||||
}
|
||||
},
|
||||
onMounted(props, state) {
|
||||
const materialCard = this.$('material-card');
|
||||
if (materialCard) {
|
||||
materialCard.style['z-index'] = props.zIndex;
|
||||
}
|
||||
},
|
||||
onBeforeUpdate(props, state) {
|
||||
if (props.filterResults && state.images) {
|
||||
state.nImages = state.images.filter((image) => matchSearch(props.filterResults, image)).length;
|
||||
@@ -95,6 +109,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
const self = this;
|
||||
const oReq = new Http({
|
||||
onAuthentication: props.onAuthentication,
|
||||
withCredentials: props.isRegistrySecured,
|
||||
});
|
||||
oReq.addEventListener('load', function () {
|
||||
if (this.status === 200) {
|
||||
|
||||
@@ -34,10 +34,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
on-notify="{ props.onNotify }"
|
||||
on-authentication="{ props.onAuthentication }"
|
||||
show-catalog-nb-tags="{ props.showCatalogNbTags }"
|
||||
catalog-default-expanded="{ props.catalogDefaultExpanded || state.nRepositories === 1 }"
|
||||
z-index="{ props.catalogMaxBranches - props.catalogMinBranches + 2 }"
|
||||
is-registry-secured="{ props.isRegistrySecured }"
|
||||
></catalog-element>
|
||||
<script>
|
||||
import CatalogElement from './catalog-element.riot';
|
||||
import { Http } from '../../scripts/http';
|
||||
import { getBranching } from '../../scripts/repositories';
|
||||
import { getRegistryServers } from '../../scripts/utils';
|
||||
|
||||
export default {
|
||||
@@ -55,6 +59,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
onBeforeMount(props) {
|
||||
this.state.registryName = props.registryName;
|
||||
this.state.catalogElementsLimit = props.catalogElementsLimit;
|
||||
try {
|
||||
this.state.branching = getBranching(props.catalogMinBranches, props.catalogMaxBranches);
|
||||
} catch (e) {
|
||||
props.onNotify(e);
|
||||
}
|
||||
},
|
||||
onMounted(props, state) {
|
||||
this.display(props, state);
|
||||
@@ -68,33 +77,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
}
|
||||
state.registryUrl = props.registryUrl;
|
||||
let repositories = [];
|
||||
let nImages = 0;
|
||||
const self = this;
|
||||
const catalogUrl = `${props.registryUrl}/v2/_catalog?n=${state.catalogElementsLimit}`;
|
||||
const oReq = new Http({
|
||||
onAuthentication: this.props.onAuthentication,
|
||||
withCredentials: props.isRegistrySecured,
|
||||
});
|
||||
oReq.addEventListener('load', function () {
|
||||
if (this.status === 200) {
|
||||
repositories = JSON.parse(this.responseText).repositories || [];
|
||||
repositories.sort();
|
||||
repositories = repositories.reduce(function (acc, e) {
|
||||
const slash = e.indexOf('/');
|
||||
if (slash > 0) {
|
||||
const repoName = e.substring(0, slash) + '/';
|
||||
if (acc.length === 0 || acc[acc.length - 1].repo != repoName) {
|
||||
acc.push({
|
||||
repo: repoName,
|
||||
images: [],
|
||||
});
|
||||
}
|
||||
acc[acc.length - 1].images.push(e);
|
||||
return acc;
|
||||
}
|
||||
acc.push(e);
|
||||
return acc;
|
||||
}, []);
|
||||
nImages = repositories.length;
|
||||
if (typeof state.branching === 'function') {
|
||||
repositories = state.branching(repositories);
|
||||
}
|
||||
} else if (this.status === 404) {
|
||||
self.props.onNotify({ code: 'CATALOG_NOT_FOUND', url: catalogUrl }, true);
|
||||
} else if (this.status === 400) {
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(this.responseText);
|
||||
} catch (e) {}
|
||||
if (!response || !Array.isArray(response.errors)) {
|
||||
return self.props.onNotify(this.responseText, true);
|
||||
}
|
||||
self.props.onNotify({ ...response, url: catalogUrl }, true);
|
||||
} else {
|
||||
self.props.onNotify(this.responseText);
|
||||
}
|
||||
@@ -106,7 +114,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
self.update({
|
||||
repositories,
|
||||
nRepositories: repositories.length,
|
||||
nImages: repositories.reduce((acc, e) => acc + ((e.images && e.images.length) || 1), 0),
|
||||
nImages,
|
||||
loadend: true,
|
||||
});
|
||||
});
|
||||
@@ -115,4 +123,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
catalog {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
catalog > material-card {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</catalog>
|
||||
|
||||
@@ -58,10 +58,11 @@
|
||||
},
|
||||
deleteImages() {
|
||||
this.props.toDelete.forEach((image) => this.getContentDigestThenDelete(image, this.props));
|
||||
this.props.onImageDeleted();
|
||||
},
|
||||
getContentDigestThenDelete({ name, tag }, opts) {
|
||||
const { registryUrl, onNotify, onAuthentication } = opts;
|
||||
const oReq = new Http({ onAuthentication });
|
||||
const { registryUrl, onNotify, onAuthentication, isRegistrySecured } = opts;
|
||||
const oReq = new Http({ onAuthentication, withCredentials: isRegistrySecured });
|
||||
const self = this;
|
||||
oReq.addEventListener('loadend', function () {
|
||||
if (this.status === 200 || this.status === 202) {
|
||||
@@ -86,8 +87,8 @@
|
||||
oReq.send();
|
||||
},
|
||||
deleteImage({ name, tag, contentDigest }, opts) {
|
||||
const { registryUrl, ignoreError, onNotify, onAuthentication, onClick } = opts;
|
||||
const oReq = new Http({ onAuthentication });
|
||||
const { registryUrl, ignoreError, onNotify, onAuthentication, onClick, isRegistrySecured } = opts;
|
||||
const oReq = new Http({ onAuthentication, withCredentials: isRegistrySecured });
|
||||
oReq.addEventListener('loadend', function () {
|
||||
if (this.status === 200 || this.status === 202) {
|
||||
router.taglist(name);
|
||||
|
||||
@@ -17,7 +17,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
<docker-registry-ui>
|
||||
<header>
|
||||
<material-navbar>
|
||||
<span class="logo">Docker Registry UI</span>
|
||||
<span class="logo">
|
||||
<span>Docker Registry UI</span>
|
||||
<version-notification version="{ latest }" on-notify="{ notifySnackbar }"></version-notification>
|
||||
</span>
|
||||
<div class="menu">
|
||||
<search-bar on-search="{ onSearch }"></search-bar>
|
||||
<dialogs-menu
|
||||
@@ -31,6 +34,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
</material-navbar>
|
||||
</header>
|
||||
<main>
|
||||
<error-page
|
||||
if="{ state.pageError && !Array.isArray(state.pageError.errors) }"
|
||||
code="{ state.pageError.code }"
|
||||
status="{ state.pageError.status }"
|
||||
message="{ state.pageError.message }"
|
||||
url="{ state.pageError.url }"
|
||||
></error-page>
|
||||
<error-page
|
||||
if="{ state.pageError && Array.isArray(state.pageError.errors) }"
|
||||
each="{ error in (state.pageError && state.pageError.errors) }"
|
||||
code="{ error.code }"
|
||||
detail="{ error.detail }"
|
||||
message="{ error.message }"
|
||||
url="{ state.pageError.url }"
|
||||
></error-page>
|
||||
<router base="#!">
|
||||
<route path="{baseRoute}">
|
||||
<catalog
|
||||
@@ -41,6 +59,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
filter-results="{ state.filter }"
|
||||
on-authentication="{ onAuthentication }"
|
||||
show-catalog-nb-tags="{ truthy(props.showCatalogNbTags) }"
|
||||
catalog-default-expanded="{ truthy(props.catalogDefaultExpanded) }"
|
||||
catalog-min-branches="{ props.catalogMinBranches }"
|
||||
catalog-max-branches="{ props.catalogMaxBranches }"
|
||||
is-registry-secured="{ truthy(props.isRegistrySecured) }"
|
||||
></catalog>
|
||||
</route>
|
||||
<route path="{baseRoute}taglist/(.*)">
|
||||
@@ -55,6 +77,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
filter-results="{ state.filter }"
|
||||
on-authentication="{ onAuthentication }"
|
||||
use-control-cache-header="{ truthy(props.useControlCacheHeader) }"
|
||||
taglist-order="{ props.taglistOrder }"
|
||||
tags-per-page="{ props.tagsPerPage }"
|
||||
is-registry-secured="{ truthy(props.isRegistrySecured) }"
|
||||
></tag-list>
|
||||
</route>
|
||||
<route path="{baseRoute}taghistory/(.*)">
|
||||
@@ -69,6 +94,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
on-authentication="{ onAuthentication }"
|
||||
history-custom-labels="{ stringToArray(props.historyCustomLabels) }"
|
||||
use-control-cache-header="{ truthy(props.useControlCacheHeader) }"
|
||||
is-registry-secured="{ truthy(props.isRegistrySecured) }"
|
||||
></tag-history>
|
||||
</route>
|
||||
</router>
|
||||
@@ -80,13 +106,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
on-authenticated="{ state.onAuthenticated }"
|
||||
opened="{ state.authenticationDialogOpened }"
|
||||
></registry-authentication>
|
||||
<error-page
|
||||
if="{ state.pageError }"
|
||||
code="{ state.pageError.code }"
|
||||
status="{ state.pageError.status }"
|
||||
message="{ state.pageError.message }"
|
||||
url="{ state.pageError.url }"
|
||||
></error-page>
|
||||
<material-snackbar message="{ state.snackbarMessage }" is-error="{ state.snackbarIsError }"></material-snackbar>
|
||||
</main>
|
||||
<footer>
|
||||
@@ -118,7 +137,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
</material-footer>
|
||||
</footer>
|
||||
<script>
|
||||
import { version } from '../../package.json';
|
||||
import { version, latest } from '../../.version.json';
|
||||
import { Router, Route } from '@riotjs/route';
|
||||
import Catalog from './catalog/catalog.riot';
|
||||
import TagList from './tag-list/tag-list.riot';
|
||||
@@ -126,6 +145,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import DialogsMenu from './dialogs/dialogs-menu.riot';
|
||||
import SearchBar from './search-bar.riot';
|
||||
import ErrorPage from './error-page.riot';
|
||||
import VersionNotification from './version-notification.riot';
|
||||
import { stripHttps, getRegistryServers, setRegistryServers, truthy, stringToArray } from '../scripts/utils';
|
||||
import router from '../scripts/router';
|
||||
import { loadTheme } from '../scripts/theme';
|
||||
@@ -140,6 +160,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
Router,
|
||||
Route,
|
||||
ErrorPage,
|
||||
VersionNotification,
|
||||
},
|
||||
onUpdated(props, state) {
|
||||
state.snackbarIsError = false;
|
||||
@@ -154,7 +175,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
}
|
||||
|
||||
window.onselectstart = (e) => {
|
||||
if (e.target && e.target.className) {
|
||||
if (e.target && e.target.className && typeof e.target.className.indexOf === 'function') {
|
||||
return !['checkbox', 'checkmark', 'remove-tag'].find((elt) => e.target.className.indexOf(elt) >= 0);
|
||||
}
|
||||
};
|
||||
@@ -166,7 +187,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
window.location.origin + window.location.pathname.replace(/\/+$/, '');
|
||||
this.state.registryUrl = registryUrl.replace(/\/$/, '').replace(/index(\.html?)?$/, '');
|
||||
this.state.name = props.name || stripHttps(props.registryUrl);
|
||||
this.state.catalogElementsLimit = props.catalogElementsLimit || 100000;
|
||||
this.state.catalogElementsLimit = props.catalogElementsLimit || 1000;
|
||||
this.state.pullUrl = this.pullUrl(this.state.registryUrl, props.pullUrl);
|
||||
this.state.useControlCacheHeader = props.useControlCacheHeader;
|
||||
const theme = loadTheme(props, this.root.parentNode.style);
|
||||
@@ -192,6 +213,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
this.notifySnackbar(`Failed to log in: ${e.message}`, true);
|
||||
}
|
||||
});
|
||||
req.withCredentials = true;
|
||||
req.open('GET', `${realm}?service=${service}&scope=${scope}`);
|
||||
req.send();
|
||||
} else {
|
||||
@@ -213,7 +235,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
snackbarMessage: message,
|
||||
snackbarIsError: isError || false,
|
||||
});
|
||||
} else if (message && message.code) {
|
||||
} else if (message && (message.code || message.errors)) {
|
||||
this.update({
|
||||
pageError: message,
|
||||
});
|
||||
@@ -238,6 +260,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
baseRoute: '([^#]*?)/(\\?[^#]*?)?(#!)?(/?)',
|
||||
router,
|
||||
version,
|
||||
latest,
|
||||
truthy,
|
||||
stringToArray,
|
||||
};
|
||||
@@ -257,6 +280,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
material-navbar .nav-wrapper .logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
material-footer {
|
||||
color: var(--footer-neutral-text);
|
||||
background-color: var(--footer-background);
|
||||
|
||||
@@ -29,6 +29,42 @@
|
||||
<template if="{ props.code === 'INCORRECT_URL' }">
|
||||
<p>`{ props.url }` does not seems to be a correct URL, should starts with http:// or https://.</p>
|
||||
</template>
|
||||
<template if="{ props.code === 'PAGINATION_NUMBER_INVALID' }">
|
||||
<p>
|
||||
A default limit of <code>1000</code> images in catalog has been added in docker registry server
|
||||
<a href="https://github.com/distribution/distribution/releases/tag/v2.8.2">v2.8.2</a> (May 11, 2023) and we
|
||||
cannot exceed this value without configuration.
|
||||
</p>
|
||||
<p>
|
||||
The new default value for the UI is <code>1000</code> since
|
||||
<a href="https://github.com/Joxit/docker-registry-ui/milestone/6">2.5.0</a> and was <code>100000</code> from
|
||||
<a href="https://github.com/Joxit/docker-registry-ui/issues/39">0.3.6</a>.
|
||||
</p>
|
||||
<h3>Possible fixes</h3>
|
||||
<p>
|
||||
You can update the environment variable
|
||||
<code>REGISTRY_CATALOG_MAXENTRIES={ props.detail?.n || 100000 }</code> of your
|
||||
<span>docker registry server</span> or you can update your
|
||||
<code>/etc/docker/registry/config.yml</code> configuration of your <span>docker registry server</span> and add
|
||||
those lines:
|
||||
</p>
|
||||
<pre>
|
||||
<span class="keyword">catalog</span>:
|
||||
<span class="keyword">maxentries</span>: <span>{props.detail?.n || 100000}</span>
|
||||
</pre>
|
||||
<p>
|
||||
If you don't need that many images, you can reduce the number of elements fetch by the
|
||||
<span>docker registry UI</span> with the environment variable <code>CATALOG_ELEMENTS_LIMIT=1000</code>.
|
||||
</p>
|
||||
<p>
|
||||
More about this issue:
|
||||
<a href="https://github.com/Joxit/docker-registry-ui/issues/306">Joxit/docker-registry-ui#306</a>.
|
||||
</p>
|
||||
</template>
|
||||
<template if="{ props.code === 'CATALOG_BRANCHING_CONFIGURATION' }">
|
||||
<p>Wrong configuration for the branching feature: { props.message }</p>
|
||||
<p>Configuration environment variables are : CATALOG_MIN_BRANCH and CATALOG_MAX_BRANCH</p>
|
||||
</template>
|
||||
</div>
|
||||
<script>
|
||||
export default {
|
||||
@@ -37,6 +73,8 @@
|
||||
switch (props.code) {
|
||||
case 'CATALOG_NOT_FOUND':
|
||||
return '404';
|
||||
case 'PAGINATION_NUMBER_INVALID':
|
||||
return '400';
|
||||
}
|
||||
},
|
||||
URL: window.URL,
|
||||
@@ -46,7 +84,7 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 20px;
|
||||
margin: 20px 3em;
|
||||
}
|
||||
:host .content {
|
||||
margin: auto;
|
||||
@@ -65,5 +103,21 @@
|
||||
:host .content h2 {
|
||||
font-weight: 700;
|
||||
}
|
||||
:host pre,
|
||||
code {
|
||||
background-color: var(--hover-background);
|
||||
font-family: 'Roboto Mono', monospace !important;
|
||||
text-align: left;
|
||||
border-radius: 5px;
|
||||
}
|
||||
code {
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
:host pre {
|
||||
padding: 0.5em;
|
||||
}
|
||||
:host pre .keyword {
|
||||
color: var(--accent-text);
|
||||
}
|
||||
</style>
|
||||
</error-page>
|
||||
|
||||
@@ -89,6 +89,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
onNotify: props.onNotify,
|
||||
onAuthentication: props.onAuthentication,
|
||||
useControlCacheHeader: props.useControlCacheHeader,
|
||||
isRegistrySecured: props.isRegistrySecured,
|
||||
});
|
||||
state.image.fillInfo();
|
||||
},
|
||||
@@ -159,7 +160,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
return router.taglist(this.props.image);
|
||||
},
|
||||
showDockerfile() {
|
||||
console.log(this);
|
||||
this.update({ showDockerfile: true });
|
||||
},
|
||||
onDockerfileClose() {
|
||||
|
||||
54
src/components/tag-list/architectures.riot
Normal file
54
src/components/tag-list/architectures.riot
Normal file
@@ -0,0 +1,54 @@
|
||||
<architectures>
|
||||
<div class="architecture" each="{ architecture in state.architectures }">{ architecture }</div>
|
||||
<script>
|
||||
import { platformToString } from '../../scripts/docker-image';
|
||||
export default {
|
||||
onMounted(props, state) {
|
||||
this.onLoad(props, state);
|
||||
},
|
||||
onUpdated(props, state) {
|
||||
if (props.image !== state.image) {
|
||||
this.onLoad(props, state);
|
||||
}
|
||||
},
|
||||
onLoad(props, state) {
|
||||
if (props.image.manifests) {
|
||||
return this.onList(props.image.manifests, props, state);
|
||||
} else if (props.image.blobs) {
|
||||
return this.onBlobs(props.image.blobs, props, state);
|
||||
}
|
||||
props.image.on('blobs', (blobs) => this.onBlobs(blobs, props, state));
|
||||
props.image.on('list', (list) => this.onList(list, props, state));
|
||||
},
|
||||
onBlobs(blobs, props, state) {
|
||||
const architectures = [platformToString(blobs)];
|
||||
if (!props.image.manifests) {
|
||||
this.update({ architectures, image: props.image });
|
||||
}
|
||||
},
|
||||
onList(list, props, state) {
|
||||
const architectures = list.map(({ platform }) => platformToString(platform));
|
||||
this.update({ architectures, image: props.image });
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
:host architectures {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
architectures .architecture {
|
||||
background-color: var(--hover-background);
|
||||
padding: 2px 4px;
|
||||
border-radius: 12px;
|
||||
margin: 4px 0;
|
||||
text-align: center;
|
||||
}
|
||||
architectures .architecture:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
architectures .architecture:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</architectures>
|
||||
@@ -39,7 +39,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
<material-spinner></material-spinner>
|
||||
</div>
|
||||
|
||||
<pagination pages="{ getPageLabels(state.page, getNumPages(state.tags)) }" onPageUpdate="{onPageUpdate}"></pagination>
|
||||
<pagination
|
||||
pages="{ getPageLabels(state.page, getNumPages(state.tags, props.tagsPerPage)) }"
|
||||
onPageUpdate="{onPageUpdate}"
|
||||
></pagination>
|
||||
|
||||
<tag-table
|
||||
if="{ state.loadend }"
|
||||
@@ -54,18 +57,26 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
on-notify="{ props.onNotify }"
|
||||
filter-results="{ props.filterResults }"
|
||||
on-authentication="{ props.onAuthentication }"
|
||||
tags-per-page="{ props.tagsPerPage }"
|
||||
is-registry-secured="{ props.isRegistrySecured }"
|
||||
on-image-deleted="{ () => state.reload() }"
|
||||
>
|
||||
</tag-table>
|
||||
|
||||
<pagination pages="{ getPageLabels(state.page, getNumPages(state.tags)) }" onPageUpdate="{onPageUpdate}"></pagination>
|
||||
<pagination
|
||||
pages="{ getPageLabels(state.page, getNumPages(state.tags, props.tagsPerPage)) }"
|
||||
onPageUpdate="{onPageUpdate}"
|
||||
></pagination>
|
||||
|
||||
<script>
|
||||
import { Http } from '../../scripts/http';
|
||||
import { DockerImage, compare } from '../../scripts/docker-image';
|
||||
import { DockerImage } from '../../scripts/docker-image';
|
||||
import { getNumPages, getPageLabels } from '../../scripts/utils';
|
||||
import Pagination from './pagination.riot';
|
||||
import TagTable from './tag-table.riot';
|
||||
import router from '../../scripts/router';
|
||||
import { getTagComparator, taglistOrderParser } from '../../scripts/taglist-order';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Pagination,
|
||||
@@ -79,6 +90,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
asc: true,
|
||||
page: router.getPageQueryParam() || 1,
|
||||
};
|
||||
try {
|
||||
this.state.taglistOrder = taglistOrderParser(props.taglistOrder);
|
||||
} catch (e) {
|
||||
props.onNotify(e);
|
||||
}
|
||||
},
|
||||
onMounted(props, state) {
|
||||
this.display(props, state);
|
||||
@@ -86,12 +102,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// this may be run before the final document size is available, so schedule
|
||||
// a correction once everything is set up.
|
||||
window.requestAnimationFrame(this.onResize);
|
||||
this.tagComparator = getTagComparator(props.taglistOrder);
|
||||
},
|
||||
display(props, state) {
|
||||
state.reload = () => setTimeout(() => this.display(props, state), 1000);
|
||||
state.tags = [];
|
||||
const self = this;
|
||||
const oReq = new Http({
|
||||
onAuthentication: props.onAuthentication,
|
||||
withCredentials: props.isRegistrySecured,
|
||||
});
|
||||
oReq.addEventListener('load', function () {
|
||||
if (this.status === 200) {
|
||||
@@ -104,9 +123,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
onNotify: props.onNotify,
|
||||
onAuthentication: props.onAuthentication,
|
||||
useControlCacheHeader: props.useControlCacheHeader,
|
||||
isRegistrySecured: props.isRegistrySecured,
|
||||
})
|
||||
)
|
||||
.sort(compare);
|
||||
.sort(self.tagComparator);
|
||||
window.requestAnimationFrame(self.onResize);
|
||||
self.update({
|
||||
page: Math.min(state.page, getNumPages(tags)),
|
||||
@@ -168,7 +188,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
this.state.tags.reverse();
|
||||
this.state.asc = false;
|
||||
} else {
|
||||
this.state.tags.sort(compare);
|
||||
this.state.tags.sort(this.tagComparator);
|
||||
this.state.asc = true;
|
||||
}
|
||||
this.update();
|
||||
|
||||
@@ -23,6 +23,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
on-authentication="{ props.onAuthentication }"
|
||||
tags="{ props.tags }"
|
||||
to-delete="{ state.toDelete }"
|
||||
is-registry-secured="{ props.isRegistrySecured }"
|
||||
on-image-deleted="{ props.onImageDeleted }"
|
||||
></confirm-delete-image>
|
||||
<material-card class="taglist">
|
||||
<table style="border: none">
|
||||
@@ -49,6 +51,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
>
|
||||
Tag
|
||||
</th>
|
||||
<th class="architectures">Arch</th>
|
||||
<th class="show-tag-history">History</th>
|
||||
<th
|
||||
class="remove-tag { state.toDelete.size > 0 && !state.singleDeleteAction ? 'delete' : '' }"
|
||||
@@ -103,6 +106,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
on-notify="{ props.onNotify }"
|
||||
></copy-to-clipboard>
|
||||
</td>
|
||||
<td class="architectures">
|
||||
<architectures image="{ image }"></architectures>
|
||||
</td>
|
||||
<td class="show-tag-history">
|
||||
<tag-history-button image="{ image }"></tag-history-button>
|
||||
</td>
|
||||
@@ -130,6 +136,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import CopyToClipboard from './copy-to-clipboard.riot';
|
||||
import TagHistoryButton from './tag-history-button.riot';
|
||||
import RemoveImage from './remove-image.riot';
|
||||
import Architectures from './architectures.riot';
|
||||
import { matchSearch } from '../search-bar.riot';
|
||||
import ConfirmDeleteImage from '../dialogs/confirm-delete-image.riot';
|
||||
const ACTION_CHECK_TO_DELETE = 'CHECK';
|
||||
@@ -146,6 +153,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
RemoveImage,
|
||||
TagHistoryButton,
|
||||
ConfirmDeleteImage,
|
||||
Architectures,
|
||||
},
|
||||
onBeforeMount(props) {
|
||||
this.state = {
|
||||
@@ -183,31 +191,31 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
this.update({
|
||||
multiDelete: true,
|
||||
toDelete: this.state.toDelete,
|
||||
slectedImage: undefined,
|
||||
selectedImage: undefined,
|
||||
});
|
||||
} else {
|
||||
this.update({
|
||||
multiDelete: event.target.checked,
|
||||
slectedImage: undefined,
|
||||
selectedImage: undefined,
|
||||
});
|
||||
}
|
||||
},
|
||||
onRemoveImageChange(action, image, shiftKey) {
|
||||
let confirmDeleteImage = false;
|
||||
let singleDeleteAction = false;
|
||||
let slectedImage = undefined;
|
||||
let selectedImage = undefined;
|
||||
switch (action) {
|
||||
case ACTION_CHECK_TO_DELETE: {
|
||||
this.state.toDelete.add(image);
|
||||
if (shiftKey) {
|
||||
slectedImage = this.supportShiftKey(image, true);
|
||||
selectedImage = this.supportShiftKey(image, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_UNCHECK_TO_DELETE: {
|
||||
this.state.toDelete.delete(image);
|
||||
if (shiftKey) {
|
||||
slectedImage = this.supportShiftKey(image, false);
|
||||
selectedImage = this.supportShiftKey(image, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -222,18 +230,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
toDelete: this.state.toDelete,
|
||||
confirmDeleteImage,
|
||||
singleDeleteAction,
|
||||
slectedImage,
|
||||
selectedImage,
|
||||
});
|
||||
},
|
||||
supportShiftKey(selectedImage, addOrRemove) {
|
||||
if (!this.state.slectedImage) {
|
||||
if (!this.state.selectedImage) {
|
||||
return selectedImage;
|
||||
} else {
|
||||
let shouldChange = false;
|
||||
const tags = getPage(this.props.tags, this.props.page);
|
||||
const tags = this.getPage(this.props.tags, this.props.page);
|
||||
tags
|
||||
.filter((image) => {
|
||||
if (image == this.state.slectedImage || image == selectedImage) {
|
||||
if (image == this.state.selectedImage || image == selectedImage) {
|
||||
shouldChange = !shouldChange;
|
||||
return true;
|
||||
}
|
||||
@@ -261,12 +269,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
});
|
||||
},
|
||||
getPage(tags, page) {
|
||||
const sortedTags = getPage(tags, page);
|
||||
const sortedTags = getPage(tags, page, this.props.tagsPerPage);
|
||||
if (this.state.orderType === 'date') {
|
||||
sortedTags.sort((e1, e2) =>
|
||||
!this.state.desc
|
||||
? (e2.creationDate?.getTime()||0) - (e1.creationDate?.getTime()||0)
|
||||
: (e1.creationDate?.getTime()||0) - (e2.creationDate?.getTime()||0)
|
||||
? (e2.creationDate?.getTime() || 0) - (e1.creationDate?.getTime() || 0)
|
||||
: (e1.creationDate?.getTime() || 0) - (e2.creationDate?.getTime() || 0)
|
||||
);
|
||||
} else if (this.state.orderType === 'size') {
|
||||
sortedTags.sort((e1, e2) => (!this.state.desc ? e2.size - e1.size : e1.size - e2.size));
|
||||
@@ -277,4 +285,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
};
|
||||
export { ACTION_CHECK_TO_DELETE, ACTION_UNCHECK_TO_DELETE, ACTION_DELETE_IMAGE };
|
||||
</script>
|
||||
<style>
|
||||
tag-table table th.architectures {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</tag-table>
|
||||
|
||||
109
src/components/version-notification.riot
Normal file
109
src/components/version-notification.riot
Normal file
@@ -0,0 +1,109 @@
|
||||
<version-notification>
|
||||
<span if="{ state.tag_name && !isNewestVersion(props.version, state.tag_name) }" onclick="{ onClick }"></span>
|
||||
<material-popup opened="{ state.open }" onClick="{ onClose }">
|
||||
<div class="material-popup-title">Check for updates</div>
|
||||
<div class="material-popup-content">
|
||||
<p>The version <b>{ state.tag_name }</b> of Docker Registry UI now available.</p>
|
||||
<p>You can download the lastest version with docker.</p>
|
||||
<code>joxit/docker-registry-ui:{ state.tag_name }</code>
|
||||
</div>
|
||||
<div class="material-popup-action">
|
||||
<material-button
|
||||
class="dialog-button release-note"
|
||||
waves-color="var(--hover-background)"
|
||||
href="{ state.latest && state.latest.html_url }"
|
||||
color="inherit"
|
||||
text-color="var(--accent-text)"
|
||||
target="_blank"
|
||||
>
|
||||
Release Note
|
||||
</material-button>
|
||||
<material-button
|
||||
class="dialog-button"
|
||||
waves-color="var(--hover-background)"
|
||||
onClick="{ onClose }"
|
||||
color="inherit"
|
||||
text-color="var(--primary-text)"
|
||||
>
|
||||
Close
|
||||
</material-button>
|
||||
</div>
|
||||
</material-popup>
|
||||
<script>
|
||||
import rocketIcon from '../images/rocket.svg';
|
||||
import { Http } from '../scripts/http';
|
||||
import { isNewestVersion, parseJSON } from '../scripts/utils';
|
||||
const LATEST = 'version-notification:latest';
|
||||
const EXPIRES = 'version-notification:expiration-date';
|
||||
const ONE_DAY = 24 * 60 * 60 * 1000;
|
||||
export default {
|
||||
onMounted(props, state) {
|
||||
const latest = parseJSON(localStorage.getItem(LATEST));
|
||||
const expires = parseInt(localStorage.getItem(EXPIRES));
|
||||
if (latest && latest.tag_name) {
|
||||
this.update({ tag_name: latest.tag_name, latest });
|
||||
}
|
||||
if (!latest || isNaN(expires) || new Date().getTime() > expires) {
|
||||
this.checkForUpdates(props, state);
|
||||
}
|
||||
},
|
||||
onUpdated(props, state) {
|
||||
const span = this.$('span');
|
||||
if (span) {
|
||||
span.innerHTML = rocketIcon().firstElementChild.outerHTML;
|
||||
}
|
||||
},
|
||||
onClose() {
|
||||
this.update({ open: false });
|
||||
},
|
||||
onClick() {
|
||||
this.update({ open: true });
|
||||
},
|
||||
checkForUpdates(props, state) {
|
||||
const oReq = new Http();
|
||||
const self = this;
|
||||
|
||||
oReq.addEventListener('load', function () {
|
||||
if (this.status === 200) {
|
||||
const latest = parseJSON(this.responseText);
|
||||
if (latest && self.tag_name !== latest.tag_name && !isNewestVersion(props.version, latest.tag_name)) {
|
||||
props.onNotify('A new version of Docker Registry UI is available!');
|
||||
}
|
||||
localStorage.setItem(LATEST, this.responseText);
|
||||
localStorage.setItem(EXPIRES, new Date().getTime() + ONE_DAY);
|
||||
self.update({ tag_name: latest.tag_name, latest });
|
||||
} else {
|
||||
props.onNotify('Cannot check for new updates. See the browser console.');
|
||||
console.error(`Got status code ${this.status} from Github API with response ${this.responseText}`);
|
||||
}
|
||||
});
|
||||
|
||||
oReq.open('GET', 'https://api.github.com/repos/joxit/docker-registry-ui/releases/latest');
|
||||
oReq.send();
|
||||
},
|
||||
isNewestVersion,
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
:host {
|
||||
display: inline;
|
||||
}
|
||||
:host svg {
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
material-popup material-button > a:first-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
material-popup .material-popup-content code {
|
||||
background-color: var(--hover-background);
|
||||
padding: 0 5px;
|
||||
border-radius: 4px;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
material-popup .material-popup-content b {
|
||||
color: var(--accent-text);
|
||||
}
|
||||
</style>
|
||||
</version-notification>
|
||||
20
src/images/rocket.svg
Normal file
20
src/images/rocket.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 128 128">
|
||||
<path fill="#ca2c31" d="M3.77 71.73l16.34-16.1 27.82-4.93-2.75 14.56L7.57 76.82l-2.43-1.05z"/>
|
||||
<path fill="#a02422" d="M22.94 59.76L5.2 75.88l13.05 6.36 19.81-10.11v-4.77l4.05-10.92zm41.98 28.39l-8.57 3.72-8.09 17.15s7.12 15.77 7.44 15.77c.32 0 4.37.32 4.37.32l14.4-16.1 3.64-27.5-13.19 6.64z"/>
|
||||
<path d="M56.5 100.84s4.77-.97 8.17-2.59c3.4-1.62 7.6-4.04 7.6-4.04l-1.54 13.43-15.05 17.13s-.59-.73-3.09-6.17c-1.99-4.34-2.68-5.89-2.68-5.89l6.59-11.87z" fill="#ca2c31"/>
|
||||
<path d="M31.58 80.66s-5.74-.48-12.03 7.47c-5.74 7.26-8.43 19.08-9.47 22.12s-3.53 3.66-2.7 5.05 4.42 1.31 8.85.76 8.23-1.94 8.23-1.94-.19.48-.83 1.52c-.23.37-1.03.9-.97 1.45.14 1.31 11.36 1.34 20.32-7.88 9.68-9.95 4.98-18.11 4.98-18.11L31.58 80.66z" fill="#f7d74d"/>
|
||||
<path d="M33.31 85.29s-6.19.33-11.31 8.28-7.5 17.16-7.01 17.78c.48.62 10.02-2.83 12.31-2.14 1.57.48.76 2.07 1.18 2.49.35.35 4.49.94 11.19-6.32 6.71-7.26 5.12-17.46 5.12-17.46l-11.48-2.63z" fill="#fbf0b4"/>
|
||||
<path d="M36.35 74.44s-3.11 2.77-4.22 4.36c-1.11 1.59-1.11 1.73-1.04 2.21.07.48 1.22 5.75 6.01 10.37 5.88 5.67 11.13 6.43 11.89 6.43.76 0 5.81-5.67 5.81-5.67l-18.45-17.7z" fill="#858585"/>
|
||||
<path d="M50.1 91.24s5.04 3.31 13.49.47c11.55-3.88 20.02-12.56 30.51-23.52 10.12-10.58 18.61-23.71 18.61-23.71l-5.95-19.93L50.1 91.24z" fill="#437687"/>
|
||||
<path d="M67.99 80.33l1.39-4.32 3.48.49s2.65 1.25 4.6 2.16c1.95.91 4.46 1.6 4.46 1.6l-4.95 4.18s-2.7-1.02-4.67-1.88c-2.22-.97-4.31-2.23-4.31-2.23z" fill="#3f545f"/>
|
||||
<path d="M84.32 16.14s-9.62 5.58-23.41 18.63c-12.43 11.76-21.64 22.4-23.87 31.45-1.86 7.58-.87 12.18 3.36 17.15 4.47 5.26 9.71 7.87 9.71 7.87s3.94.06 20.38-12.59C91 62.86 107.43 36.42 107.43 36.42L84.32 16.14z" fill="#8dafbf"/>
|
||||
<path d="M104.18 41.84s-8.37-3.57-14.34-11.9c-5.93-8.27-5.46-13.86-5.46-13.86s4.96-3.89 16.11-8.34c7.5-2.99 17.71-4.52 21.07-2.03s-2.3 14.98-2.3 14.98l-10.31 19.96-4.77 1.19z" fill="#d83f22"/>
|
||||
<path d="M68.17 80.4s-7.23-3.69-11.83-8.94c-8.7-9.91-10.5-20.79-10.5-20.79l4.37-5.13S51.3 57.1 60.63 67.09c6.08 6.51 12.43 9.49 12.43 9.49s-1.27 1.07-2.63 2.11c-.87.67-2.26 1.71-2.26 1.71z" fill="#6896a5"/>
|
||||
<path d="M112.71 44.48s4.34-5.23 8.45-17.02c5.74-16.44.74-21.42.74-21.42s-1.69 7.82-7.56 18.69c-4.71 8.71-10.41 17-10.41 17s3.14 1.41 4.84 1.9c2.14.62 3.94.85 3.94.85z" fill="#a02422"/>
|
||||
<path d="M39.81 69.66c1.3 1.24 3.27-.06 4.56-3.1 1.3-3.04 1.28-4.74.28-5.46-1.24-.9-3.32 1.07-4.23 2.82-1 1.94-1.59 4.8-.61 5.74zm45.14-49.53s-7.61 5.47-15.73 12.91c-7.45 6.83-12.39 12.17-13.07 13.41-.72 1.33-.73 3.21-.17 4.17s1.8 1.46 2.93.62c1.13-.85 9.18-9.75 16.45-16.11 6.65-5.82 11.78-9.51 11.78-9.51s2.08-3.68 1.74-4.52c-.34-.85-3.93-.97-3.93-.97z" fill="#b3e1ee"/>
|
||||
<path d="M84.95 20.13s5.62-4.31 11.74-7.34c5.69-2.82 11.35-5.17 12.37-3.13.97 1.94-5.37 4.58-10.95 8.14-5.58 3.56-10.95 7.81-10.95 7.81s-.82-1.5-1.35-2.89a23.7 23.7 0 01-.86-2.59z" fill="#ed6a65"/>
|
||||
<path d="M89.59 39.25c-5.57-5.13-13.32-3.75-17.14.81-3.92 4.7-3.63 11.88 1 16.2 4.21 3.92 12.04 4.81 16.76-.69 4.2-4.88 3.94-12.13-.62-16.32z" fill="#e1e1e1"/>
|
||||
<path d="M75.33 41.87c-3.31 3.25-3.13 9.69.81 12.63 3.44 2.57 8.32 2.44 11.38-.69 3.06-3.13 3.06-8.82.19-11.76-3.3-3.37-8.59-3.9-12.38-.18z" fill="#3f545f"/>
|
||||
<path d="M50 76.89s6.19-6.28 6.87-5.6c.68.68.59 4.49-2.37 8.73-2.97 4.24-9.5 11.79-14.67 16.88-5.1 5.01-12.29 10.74-12.97 10.64-.53-.08-2.68-1.15-3.54-2.19-.84-1.03 1.67-5.9 2.68-7.51 1.02-1.61 24-20.95 24-20.95z" fill="#a02524"/>
|
||||
<path d="M21.23 101.85c-.08 1.44 2.12 3.54 2.12 3.54L56.87 71.3s-1.57-1.77-6.19 1.1c-4.66 2.9-8.74 6.38-14.76 12.21-8.39 8.14-14.61 15.8-14.69 17.24z" fill="#ca2c31"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -47,6 +47,11 @@
|
||||
show-catalog-nb-tags="${SHOW_CATALOG_NB_TAGS}"
|
||||
history-custom-labels="${HISTORY_CUSTOM_LABELS}"
|
||||
use-control-cache-header="${USE_CONTROL_CACHE_HEADER}"
|
||||
taglist-order="${TAGLIST_ORDER}"
|
||||
catalog-default-expanded="${CATALOG_DEFAULT_EXPANDED}"
|
||||
catalog-min-branches="${CATALOG_MIN_BRANCHES}"
|
||||
catalog-max-branches="${CATALOG_MAX_BRANCHES}"
|
||||
is-registry-secured="${REGISTRY_SECURED}"
|
||||
theme="${THEME}"
|
||||
theme-primary-text="${THEME_PRIMARY_TEXT}"
|
||||
theme-neutral-text="${THEME_NEUTRAL_TEXT}"
|
||||
@@ -58,6 +63,7 @@
|
||||
theme-footer-text="${THEME_FOOTER_TEXT}"
|
||||
theme-footer-neutra-text="${THEME_FOOTER_NEUTRAL_TEXT}"
|
||||
theme-footer-background="${THEME_FOOTER_BACKGROUND}"
|
||||
tags-per-page="${TAGLIST_PAGE_SIZE}"
|
||||
>
|
||||
</docker-registry-ui>
|
||||
<!-- endbuild -->
|
||||
@@ -73,6 +79,11 @@
|
||||
show-catalog-nb-tags="true"
|
||||
history-custom-labels="first_custom_labels,second_custom_labels"
|
||||
use-control-cache-header="false"
|
||||
taglist-order=""
|
||||
catalog-default-expanded=""
|
||||
catalog-min-branches="1"
|
||||
catalog-max-branches="1"
|
||||
is-registry-secured="false"
|
||||
theme="auto"
|
||||
theme-primary-text=""
|
||||
theme-neutral-text=""
|
||||
@@ -84,6 +95,7 @@
|
||||
theme-footer-text=""
|
||||
theme-footer-neutra-text=""
|
||||
theme-footer-background=""
|
||||
tags-per-page=""
|
||||
>
|
||||
</docker-registry-ui>
|
||||
<!-- endbuild -->
|
||||
|
||||
32
src/scripts/cache-request.js
Normal file
32
src/scripts/cache-request.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const SHA_REGEX = /(blobs|manifests)\/sha256:[a-f0-9]+$/;
|
||||
|
||||
const getSha256 = (method, url) => {
|
||||
if (method !== 'GET') {
|
||||
return;
|
||||
}
|
||||
const parts = SHA_REGEX.exec(url);
|
||||
if (!parts || !parts[0]) {
|
||||
return;
|
||||
}
|
||||
return parts[0];
|
||||
};
|
||||
|
||||
export const getFromCache = (method, url) => {
|
||||
const sha256 = getSha256(method, url);
|
||||
if (!sha256) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
return sessionStorage.getItem(sha256);
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
export const setCache = (method, url, responseText) => {
|
||||
const sha256 = getSha256(method, url);
|
||||
if (!sha256) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sessionStorage.setItem(sha256, responseText);
|
||||
} catch (e) {}
|
||||
};
|
||||
@@ -14,39 +14,35 @@
|
||||
* 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/>.
|
||||
*/
|
||||
import { Http } from './http';
|
||||
import { isDigit, eventTransfer, ERROR_CAN_NOT_READ_CONTENT_DIGEST } from './utils';
|
||||
import { Http } from './http.js';
|
||||
import { eventTransfer, ERROR_CAN_NOT_READ_CONTENT_DIGEST } from './utils.js';
|
||||
import observable from '@riotjs/observable';
|
||||
|
||||
const tagReduce = (acc, e) => {
|
||||
if (acc.length > 0 && isDigit(acc[acc.length - 1].charAt(0)) == isDigit(e)) {
|
||||
acc[acc.length - 1] += e;
|
||||
} else {
|
||||
acc.push(e);
|
||||
export const supportListManifest = (response) => {
|
||||
if (response.mediaType === 'application/vnd.docker.distribution.manifest.list.v2+json') {
|
||||
return true;
|
||||
}
|
||||
return acc;
|
||||
if (response.mediaType === 'application/vnd.oci.image.index.v1+json' && Array.isArray(response.manifests)) {
|
||||
return !response.manifests.some(({ mediaType }) => mediaType !== 'application/vnd.oci.image.manifest.v1+json');
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export function compare(e1, e2) {
|
||||
const tag1 = e1.tag.match(/./g).reduce(tagReduce, []);
|
||||
const tag2 = e2.tag.match(/./g).reduce(tagReduce, []);
|
||||
export const filterWrongManifests = (response) => {
|
||||
return response.manifests.filter(
|
||||
({ annotations }) => !annotations || annotations['vnd.docker.reference.type'] !== 'attestation-manifest'
|
||||
);
|
||||
};
|
||||
|
||||
for (var i = 0; i < tag1.length && i < tag2.length; i++) {
|
||||
const compare = tag1[i].localeCompare(tag2[i]);
|
||||
if (isDigit(tag1[i].charAt(0)) && isDigit(tag2[i].charAt(0))) {
|
||||
const diff = tag1[i] - tag2[i];
|
||||
if (diff != 0) {
|
||||
return diff;
|
||||
}
|
||||
} else if (compare != 0) {
|
||||
return compare;
|
||||
}
|
||||
export const platformToString = (platform) => {
|
||||
if (!platform || !platform.architecture) {
|
||||
return 'unknown';
|
||||
}
|
||||
return e1.tag.length - e2.tag.length;
|
||||
}
|
||||
return platform.architecture + (platform.variant ? platform.variant : '');
|
||||
};
|
||||
|
||||
export class DockerImage {
|
||||
constructor(name, tag, { list, registryUrl, onNotify, onAuthentication, useControlCacheHeader }) {
|
||||
constructor(name, tag, { list, registryUrl, onNotify, onAuthentication, useControlCacheHeader, isRegistrySecured }) {
|
||||
this.name = name;
|
||||
this.tag = tag;
|
||||
this.chars = 0;
|
||||
@@ -56,6 +52,7 @@ export class DockerImage {
|
||||
onNotify,
|
||||
onAuthentication,
|
||||
useControlCacheHeader,
|
||||
isRegistrySecured,
|
||||
};
|
||||
this.ociImage = false;
|
||||
observable(this);
|
||||
@@ -95,13 +92,18 @@ export class DockerImage {
|
||||
return;
|
||||
}
|
||||
this._fillInfoWaiting = true;
|
||||
const oReq = new Http({ onAuthentication: this.opts.onAuthentication });
|
||||
const oReq = new Http({
|
||||
onAuthentication: this.opts.onAuthentication,
|
||||
withCredentials: this.opts.isRegistrySecured,
|
||||
});
|
||||
const self = this;
|
||||
oReq.addEventListener('loadend', function () {
|
||||
if (this.status === 200 || this.status === 202) {
|
||||
const response = JSON.parse(this.responseText);
|
||||
if (response.mediaType === 'application/vnd.docker.distribution.manifest.list.v2+json' && self.opts.list) {
|
||||
self.trigger('list', response);
|
||||
if (supportListManifest(response) && self.opts.list) {
|
||||
const manifests = filterWrongManifests(response);
|
||||
self.trigger('list', manifests);
|
||||
self.manifests = manifests;
|
||||
const manifest = response.manifests[0];
|
||||
const image = new DockerImage(self.name, manifest.digest, { ...self.opts, list: false });
|
||||
eventTransfer(image, self);
|
||||
@@ -150,7 +152,10 @@ export class DockerImage {
|
||||
oReq.send();
|
||||
}
|
||||
getBlobs(blob) {
|
||||
const oReq = new Http({ onAuthentication: this.opts.onAuthentication });
|
||||
const oReq = new Http({
|
||||
onAuthentication: this.opts.onAuthentication,
|
||||
withCredentials: this.opts.isRegistrySecured,
|
||||
});
|
||||
const self = this;
|
||||
oReq.addEventListener('loadend', function () {
|
||||
if (this.status === 200 || this.status === 202) {
|
||||
|
||||
7
src/scripts/error.js
Normal file
7
src/scripts/error.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export class DockerRegistryUIError extends Error {
|
||||
constructor(msg, code) {
|
||||
super(msg);
|
||||
this.isError = true;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getFromCache, setCache } from './cache-request.js';
|
||||
|
||||
export class Http {
|
||||
constructor(opts) {
|
||||
this.oReq = new XMLHttpRequest();
|
||||
@@ -67,6 +69,8 @@ export class Http {
|
||||
}
|
||||
if (bearer && bearer.token) {
|
||||
req.setRequestHeader('Authorization', `Bearer ${bearer.token}`);
|
||||
} else if (bearer && bearer.access_token) {
|
||||
req.setRequestHeader('Authorization', `Bearer ${bearer.access_token}`);
|
||||
} else {
|
||||
req.withCredentials = true;
|
||||
}
|
||||
@@ -76,6 +80,7 @@ export class Http {
|
||||
req.send();
|
||||
});
|
||||
} else {
|
||||
this.status === 200 && setCache(self._method, self._url, this.responseText);
|
||||
f.bind(this)();
|
||||
}
|
||||
});
|
||||
@@ -114,6 +119,10 @@ export class Http {
|
||||
}
|
||||
|
||||
send() {
|
||||
const responseText = getFromCache(this._method, this._url);
|
||||
if (responseText) {
|
||||
return this._events['loadend'].bind({ status: 200, responseText })();
|
||||
}
|
||||
this.oReq.send();
|
||||
}
|
||||
}
|
||||
|
||||
70
src/scripts/repositories.js
Normal file
70
src/scripts/repositories.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import { DockerRegistryUIError } from './error.js';
|
||||
const ERROR_CODE = 'CATALOG_BRANCHING_CONFIGURATION';
|
||||
|
||||
const getRepositoryName = (split, max) => {
|
||||
let repositoryName = '';
|
||||
for (let i = 0; i < Math.min(max, split.length - 1); i++) {
|
||||
repositoryName += `${split[i]}/`;
|
||||
}
|
||||
return repositoryName;
|
||||
};
|
||||
|
||||
const getLatestRepository = (repo, repoName) => {
|
||||
if (!repo.images) {
|
||||
return;
|
||||
}
|
||||
if (repo.repo === repoName) {
|
||||
return repo;
|
||||
}
|
||||
for (let i = 0; i < repo.images.length; i++) {
|
||||
const res = getLatestRepository(repo.images[i], repoName);
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
if (repoName.startsWith(repo.repo)) {
|
||||
const newRepo = { repo: repoName, images: [] };
|
||||
repo.images.push(newRepo);
|
||||
return newRepo;
|
||||
}
|
||||
};
|
||||
|
||||
const cleanInt = (n) => (n === '' ? 1 : parseInt(n));
|
||||
|
||||
export const getBranching = (min = 1, max = 1) => {
|
||||
min = cleanInt(min);
|
||||
max = cleanInt(max);
|
||||
if (isNaN(min) || isNaN(max)) {
|
||||
throw new DockerRegistryUIError(`min and max must be integers: (min: ${min} and max: ${max}))`, ERROR_CODE);
|
||||
} else if (min > max) {
|
||||
throw new DockerRegistryUIError(`min must be inferior to max (min: ${min} <= max: ${max})`, ERROR_CODE);
|
||||
} else if (max < 0 || min < 0) {
|
||||
throw new DockerRegistryUIError(
|
||||
`min and max must be greater than equals to 0 (min: ${min} >= 0 and max: ${max} >= 0)`,
|
||||
ERROR_CODE
|
||||
);
|
||||
}
|
||||
if (max == 1) {
|
||||
min = 1;
|
||||
}
|
||||
return (repositories) =>
|
||||
repositories.sort().reduce(function (acc, image) {
|
||||
const split = image.split('/');
|
||||
if (split.length > min && min > 0) {
|
||||
const repoName = getRepositoryName(split, max);
|
||||
let repo = acc.length > 0 && getLatestRepository(acc[acc.length - 1], repoName);
|
||||
if (!repo) {
|
||||
repo = {
|
||||
repo: repoName,
|
||||
images: [],
|
||||
};
|
||||
acc.push(repo);
|
||||
}
|
||||
repo.images.push(image);
|
||||
return acc;
|
||||
}
|
||||
acc.push(image);
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
89
src/scripts/taglist-order.js
Normal file
89
src/scripts/taglist-order.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import { DockerRegistryUIError } from './error.js';
|
||||
import { isDigit } from './utils.js';
|
||||
|
||||
const TAGLIST_ORDER_REGEX = /(alpha-(asc|desc);num-(asc|desc))|(num-(asc|desc);alpha-(asc|desc))/;
|
||||
|
||||
export const taglistOrderVariants = (taglistOrder) => {
|
||||
switch (taglistOrder) {
|
||||
case 'desc':
|
||||
return 'alpha-desc;num-desc';
|
||||
case 'asc':
|
||||
return 'num-asc;alpha-asc';
|
||||
case 'alpha-desc':
|
||||
case 'alpha-asc':
|
||||
case 'num-desc':
|
||||
case 'num-asc':
|
||||
return `${taglistOrder};${taglistOrder.startsWith('num') ? 'alpha' : 'num'}-asc`;
|
||||
default:
|
||||
if (!taglistOrder) {
|
||||
return 'alpha-asc;num-desc';
|
||||
} else if (TAGLIST_ORDER_REGEX.test(taglistOrder)) {
|
||||
return taglistOrder;
|
||||
}
|
||||
throw new DockerRegistryUIError(`The taglist order \`${taglistOrder}\` is not recognized.`);
|
||||
}
|
||||
};
|
||||
|
||||
export const taglistOrderParser = (taglistOrder) => {
|
||||
const orders = taglistOrderVariants(taglistOrder)
|
||||
.split(';')
|
||||
.filter((e) => e)
|
||||
.map((e) => e.split('-').filter((e) => e))
|
||||
.reduce((acc, e, idx) => {
|
||||
if (e.length > 1) {
|
||||
acc[e[0] + 'Asc'] = e[1] === 'asc';
|
||||
}
|
||||
if (idx === 0) {
|
||||
acc.numFirst = e[0] === 'num';
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return orders;
|
||||
};
|
||||
|
||||
export const tagReduce = (acc, e) => {
|
||||
if (acc.length > 0 && isDigit(acc[acc.length - 1].charAt(0)) == isDigit(e)) {
|
||||
acc[acc.length - 1] += e;
|
||||
} else {
|
||||
acc.push(e);
|
||||
}
|
||||
return acc;
|
||||
};
|
||||
|
||||
export const splitTagToArray = (tag) =>
|
||||
tag
|
||||
.split('')
|
||||
.reduce(tagReduce, [])
|
||||
.map((e) => (isDigit(e.charAt(0)) ? parseInt(e) : e));
|
||||
|
||||
const applyOrder = (order, e1, e2) => {
|
||||
if (e1 === e2) {
|
||||
return 0;
|
||||
}
|
||||
const numFirst = order.numFirst ? 1 : -1;
|
||||
if (typeof e1 === 'number') {
|
||||
const factor = order.numAsc ? 1 : -1;
|
||||
return typeof e2 === 'number' ? (e1 - e2) * factor : -1 * numFirst;
|
||||
} else if (typeof e2 === 'number') {
|
||||
return 1 * numFirst;
|
||||
} else {
|
||||
const factor = order.alphaAsc ? 1 : -1;
|
||||
return e1.localeCompare(e2) * factor;
|
||||
}
|
||||
};
|
||||
|
||||
export const getTagComparator = (order) => {
|
||||
return (e1, e2) => {
|
||||
const tag1 = splitTagToArray(e1.tag || e1);
|
||||
const tag2 = splitTagToArray(e2.tag || e2);
|
||||
|
||||
for (var i = 0; i < tag1.length && i < tag2.length; i++) {
|
||||
const compare = applyOrder(order, tag1[i], tag2[i]);
|
||||
if (compare != 0) {
|
||||
return compare;
|
||||
}
|
||||
}
|
||||
return (e1.tag || e1).length - (e2.tag || e2).length;
|
||||
};
|
||||
};
|
||||
@@ -34,7 +34,7 @@ const normalizeKey = (k) =>
|
||||
.replace(/^theme-/, '');
|
||||
|
||||
const preferDarkMode = ({ theme }) => {
|
||||
if (theme === 'auto' || theme === "") {
|
||||
if (theme === 'auto' || theme === '') {
|
||||
switch (localStorage.getItem(LOCAL_STORAGE_THEME)) {
|
||||
case 'dark':
|
||||
return true;
|
||||
|
||||
@@ -220,3 +220,40 @@ export function truthy(value) {
|
||||
export function stringToArray(value) {
|
||||
return value && typeof value === 'string' ? value.split(',') : [];
|
||||
}
|
||||
|
||||
const compareNumbers = (a, b) => {
|
||||
const na = parseInt(a);
|
||||
const nb = parseInt(b);
|
||||
if (na > nb) return 1;
|
||||
if (nb > na) return -1;
|
||||
if (!isNaN(na) && isNaN(nb)) return 1;
|
||||
if (isNaN(na) && !isNaN(nb)) return -1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
export function isNewestVersion(current = '0.0.0', release = '0.0.0') {
|
||||
if (current === release) {
|
||||
return true;
|
||||
}
|
||||
current = current.split('.');
|
||||
release = release.split('.');
|
||||
const isDev = current[2].indexOf('-') >= 0;
|
||||
const major = compareNumbers(current[0], release[0]);
|
||||
const minor = compareNumbers(current[1], release[1]);
|
||||
const patch = compareNumbers(current[2], release[2]);
|
||||
if (!isDev && (major > 0 || (major === 0 && minor > 0) || (major === 0 && minor === 0 && patch >= 0))) {
|
||||
return true;
|
||||
} else if (isDev && (major > 0 || (major === 0 && minor > 0))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function parseJSON(json) {
|
||||
if (!json) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
@@ -69,17 +69,18 @@ material-card {
|
||||
material-card,
|
||||
material-tabs,
|
||||
pagination .container {
|
||||
max-width: 95%;
|
||||
margin: auto;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 1515px * 0.95 = 1440px */
|
||||
@media screen and (min-width: 1515px) {
|
||||
material-card,
|
||||
material-tabs,
|
||||
pagination .container {
|
||||
material-card,
|
||||
material-tabs,
|
||||
pagination .container,
|
||||
catalog {
|
||||
max-width: 95%;
|
||||
/* 1515px * 0.95 = 1440px */
|
||||
@media screen and (min-width: 1515px) {
|
||||
max-width: 1440px;
|
||||
}
|
||||
}
|
||||
@@ -390,6 +391,12 @@ taglist .creation-date {
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
material-card td.creation-date,
|
||||
material-card th.creation-date {
|
||||
text-align: left;
|
||||
max-width: 10em;
|
||||
}
|
||||
|
||||
taglist .image-size {
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
53
test/docker-image.test.js
Normal file
53
test/docker-image.test.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { supportListManifest, filterWrongManifests, platformToString } from '../src/scripts/docker-image.js';
|
||||
import { dockerManifestList } from './fixtures/docker-manifest-list.js';
|
||||
import { ociImageIndexLayer } from './fixtures/oci-image-index-layer.js';
|
||||
import { ociImageIndexManifest } from './fixtures/oci-image-index-manifest.js';
|
||||
import assert from 'assert';
|
||||
|
||||
describe('docker-image', () => {
|
||||
describe('supportListManifest', () => {
|
||||
/**
|
||||
* Manifest of an image created with:
|
||||
* docker buildx build --platform amd64,arm -t joxit/docker-registry-ui:buildx --push --provenance false .
|
||||
*/
|
||||
it('should support mediaType `application/vnd.docker.distribution.manifest.list.v2+json`', () => {
|
||||
assert.ok(supportListManifest(dockerManifestList['application/vnd.docker.distribution.manifest.list.v2+json']));
|
||||
});
|
||||
/**
|
||||
* Index of an image created with:
|
||||
* docker buildx build --platform amd64,arm -t joxit/docker-registry-ui:buildx --push --provenance true .
|
||||
*/
|
||||
it('should support mediaType `application/vnd.oci.image.index.v1+json`', () => {
|
||||
assert.ok(supportListManifest(ociImageIndexManifest['application/vnd.oci.image.index.v1+json']));
|
||||
});
|
||||
/**
|
||||
* Index of an image created with:
|
||||
* buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --export-cache type=registry,ref=joxit/docker-registry-ui:buildkit
|
||||
*/
|
||||
it('should not support mediaType `application/vnd.oci.image.index.v1+json` with layers (`application/vnd.oci.image.layer.v1.tar+gzip`)', () => {
|
||||
assert.ok(!supportListManifest(ociImageIndexLayer['application/vnd.oci.image.index.v1+json']));
|
||||
});
|
||||
});
|
||||
describe('supportListManifest', () => {
|
||||
it('should return all manifests for `application/vnd.docker.distribution.manifest.list.v2+json`', () => {
|
||||
assert.equal(
|
||||
filterWrongManifests(dockerManifestList['application/vnd.docker.distribution.manifest.list.v2+json']).length,
|
||||
2
|
||||
);
|
||||
});
|
||||
it('should return all manifests for `application/vnd.oci.image.index.v1+json`', () => {
|
||||
assert.equal(filterWrongManifests(ociImageIndexManifest['application/vnd.oci.image.index.v1+json']).length, 2);
|
||||
});
|
||||
});
|
||||
describe('platformToString', () => {
|
||||
it('should return unknown when the platform is not found', () => {
|
||||
assert.equal(platformToString(), 'unknown');
|
||||
assert.equal(platformToString({}), 'unknown');
|
||||
});
|
||||
it('should format the platform', () => {
|
||||
assert.equal(platformToString({ os: 'linux', architecture: 'amd64' }), 'amd64');
|
||||
assert.equal(platformToString({ os: 'linux', architecture: 'arm', variant: 'v7' }), 'armv7');
|
||||
assert.equal(platformToString({ architecture: 'arm', variant: 'v7' }), 'armv7');
|
||||
});
|
||||
});
|
||||
});
|
||||
542
test/fixtures/docker-manifest-list.js
vendored
Normal file
542
test/fixtures/docker-manifest-list.js
vendored
Normal file
@@ -0,0 +1,542 @@
|
||||
/**
|
||||
* Manifest of an image created with:
|
||||
* docker buildx build --platform amd64,arm -t joxit/docker-registry-ui:buildx --push --provenance false .
|
||||
*/
|
||||
const manifestList = {
|
||||
'mediaType': 'application/vnd.docker.distribution.manifest.list.v2+json',
|
||||
'schemaVersion': 2,
|
||||
'manifests': [
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.distribution.manifest.v2+json',
|
||||
'digest': 'sha256:7d9bbfa92dbd2a9c09abe924ee7cb8f164be59f25b9457fa0c593a7110dba89f',
|
||||
'size': 2850,
|
||||
'platform': { 'architecture': 'amd64', 'os': 'linux' },
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.distribution.manifest.v2+json',
|
||||
'digest': 'sha256:5ef7a7d411a524beff05c9d1a541442ff78bb1ec6b45a55434dd3e51e00292b1',
|
||||
'size': 2849,
|
||||
'platform': { 'architecture': 'arm', 'os': 'linux', 'variant': 'v7' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const manifestAmd64 = {
|
||||
'mediaType': 'application/vnd.docker.distribution.manifest.v2+json',
|
||||
'schemaVersion': 2,
|
||||
'config': {
|
||||
'mediaType': 'application/vnd.docker.container.image.v1+json',
|
||||
'digest': 'sha256:7209907f3aa39f8b259069272274f185c4e9772ea7159722728b5f648c71eaad',
|
||||
'size': 13772,
|
||||
},
|
||||
'layers': [
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:f56be85fc22e46face30e2c3de3f7fe7c15f8fd7c4e5add29d7f64b87abdaa09',
|
||||
'size': 3374563,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:2ce963c369bc5690378d31c51dc575c7035f6adfcc1e286051b5a5d9a7b0cc5c',
|
||||
'size': 1799036,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:59b9d2200e632e457f800814693b3a01adf09a244c38ebe8d3beef5c476c4c55',
|
||||
'size': 626,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:3e1e579c95fece6bbe0cb9c8c2949512a3f8caaf9dbe6219dc6495abb9902040',
|
||||
'size': 956,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:547a97583f72a32903ca1357d48fa302e91e8f83ffa18e0c40fd87adb5c06025',
|
||||
'size': 773,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:1f21f983520d9a440d410ea62eb0bda61a2b50dd79878071181b56b82efa9ef3',
|
||||
'size': 1404,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:c23b4f8cf279507bb1dd3d6eb2d15ca84fac9eac215ab5b529aa8b5a060294c8',
|
||||
'size': 11607291,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1',
|
||||
'size': 32,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:a5a652ffc299e3af7414a7c48a8b287785c0c70d7e7712cc81da43cfd18dc677',
|
||||
'size': 1023,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:ac35a311bcf31486bf05fb0a0b66dd1fb313f8ac6b631c07662f5c77a017b142',
|
||||
'size': 953,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:0bbe5bab7d19c248fd83f1d7746b306b1590118dcdab99cf49b72cec4f744a15',
|
||||
'size': 878128,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:b516cdaec2158585780cfe13e196fbd6ee4e2aed81d1007903da60df2a6f8d12',
|
||||
'size': 18327,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:c93af1909df12d053ee3b101b005c15312dedeee3d18c2f23a3aa3776693a0f5',
|
||||
'size': 900629,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const manifestArm = {
|
||||
'mediaType': 'application/vnd.docker.distribution.manifest.v2+json',
|
||||
'schemaVersion': 2,
|
||||
'config': {
|
||||
'mediaType': 'application/vnd.docker.container.image.v1+json',
|
||||
'digest': 'sha256:322f0eb73bbb441e1a0eae5dd05dfa56d7bc78b2be4682056463d919393b7d0b',
|
||||
'size': 13773,
|
||||
},
|
||||
'layers': [
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:fd4b2aeb476b6c2c0c3049dae919de20fe09e90deac95e3181d717055cbe6707',
|
||||
'size': 2868519,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:661c223f0b9c15a114f1a1cb1cfcfeea06544eb6d74e5d81bf239e3843d5963b',
|
||||
'size': 1748335,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:4edc4a7923b338722943b7ca9ab57db06e501a300e63d2694830bce5a13a8719',
|
||||
'size': 625,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:d04be6d2e446962d34ac7a76f347bf8a8bf8def411d2eb646241504f3cbc4835',
|
||||
'size': 956,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:c4eb1daa4076033e2233b6aed11887d3e6b77fbf7910ced0673fe7f2e0c218b7',
|
||||
'size': 770,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:8bdf974e3c33c7ab91a2c116867eb8007a255b8ffe7a8ff55344f0a4110eff9c',
|
||||
'size': 1403,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:b157eb398be1a483b6d89235dc72a936bc9070e13e4ce1af9e3e8101ca37f78a',
|
||||
'size': 9400541,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1',
|
||||
'size': 32,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:c4b7c519f7157460cf3fdb5bc3ef2a503e6c011cd1afb07f9c4c92df1d0d5c98',
|
||||
'size': 1023,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:5e59186ea6d831f79fb2d3b861ef2aac0e8610146f4ea86d8d0bfaff014ba168',
|
||||
'size': 954,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:4df6c8febd43820b56a4a778f4ad551395c137c42cc4c9546e642df6f7eeef9c',
|
||||
'size': 878130,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:d8b5650b9cc734d84c7f567264282cc58161d77a427c63c7380bfe8b35c916ae',
|
||||
'size': 18322,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
||||
'digest': 'sha256:803176948dabb2a0a106f58bfb423dea1da98bbe05e713649e39ef13dde1de6f',
|
||||
'size': 900626,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const blobAmd64 = {
|
||||
'architecture': 'amd64',
|
||||
'config': {
|
||||
'ExposedPorts': { '80/tcp': {} },
|
||||
'Env': [
|
||||
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
|
||||
'NGINX_VERSION=1.23.4',
|
||||
'PKG_RELEASE=1',
|
||||
'NJS_VERSION=0.7.11',
|
||||
'NGINX_PROXY_HEADER_Host=$http_host',
|
||||
'NGINX_LISTEN_PORT=80',
|
||||
'SHOW_CATALOG_NB_TAGS=false',
|
||||
],
|
||||
'Entrypoint': ['/docker-entrypoint.sh'],
|
||||
'Cmd': ['nginx', '-g', 'daemon off;'],
|
||||
'WorkingDir': '/usr/share/nginx/html/',
|
||||
'Labels': { 'maintainer': 'Jones MAGLOIRE @Joxit' },
|
||||
'StopSignal': 'SIGQUIT',
|
||||
'OnBuild': null,
|
||||
},
|
||||
'created': '2023-05-16T17:53:59.778774465Z',
|
||||
'history': [
|
||||
{
|
||||
'created': '2023-03-29T18:19:24.348438709Z',
|
||||
'created_by': '/bin/sh -c #(nop) ADD file:9a4f77dfaba7fd2aa78186e4ef0e7486ad55101cefc1fabbc1b385601bb38920 in / ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T18:19:24.45578926Z',
|
||||
'created_by': '/bin/sh -c #(nop) CMD ["/bin/sh"]',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:10.826996048Z',
|
||||
'created_by': '/bin/sh -c #(nop) LABEL maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:10.902296598Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENV NGINX_VERSION=1.23.4',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:10.975496976Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENV PKG_RELEASE=1',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.216540441Z',
|
||||
'created_by':
|
||||
'/bin/sh -c set -x \u0026\u0026 addgroup -g 101 -S nginx \u0026\u0026 adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make base \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache --virtual .gettext gettext \u0026\u0026 mv /usr/bin/envsubst /tmp/ \u0026\u0026 runDeps="$( scanelf --needed --nobanner /tmp/envsubst | awk \'{ gsub(/,/, "\\nso:", $2); print "so:" $2 }\' | sort -u | xargs -r apk info --installed | sort -u )" \u0026\u0026 apk add --no-cache $runDeps \u0026\u0026 apk del .gettext \u0026\u0026 mv /tmp/envsubst /usr/local/bin/ \u0026\u0026 apk add --no-cache tzdata \u0026\u0026 ln -sf /dev/stdout /var/log/nginx/access.log \u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log \u0026\u0026 mkdir /docker-entrypoint.d',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.339435018Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:7b307b62e82255f040c9812421a30090bf9abf3685f27b02d77fcca99f997911 in / ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.427781358Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:5c18272734349488bd0c94ec8d382c872c1a0a435cca13bd4671353d6021d2cb in /docker-entrypoint.d ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.515135125Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:abbcbf84dc17ee4454b6b2e3cf914be88e02cf84d344ec45a5b31235379d722a in /docker-entrypoint.d ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.601542115Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:e57eef017a414ca793499729d80a7b9075790c9a804f930f1417e56d506970cf in /docker-entrypoint.d ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.678093757Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENTRYPOINT ["/docker-entrypoint.sh"]',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{ 'created': '2023-03-29T22:31:16.756398749Z', 'created_by': '/bin/sh -c #(nop) EXPOSE 80', 'empty_layer': true },
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.839607817Z',
|
||||
'created_by': '/bin/sh -c #(nop) STOPSIGNAL SIGQUIT',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.921795894Z',
|
||||
'created_by': '/bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"]',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:30.252298671Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENV NJS_VERSION=0.7.11',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:35.828698308Z',
|
||||
'created_by':
|
||||
'/bin/sh -c set -x \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}.${NJS_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers libxslt-dev gd-dev geoip-dev libedit-dev bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make module-geoip module-image-filter module-njs module-xslt \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache curl ca-certificates',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.527106164Z',
|
||||
'created_by': 'LABEL maintainer=Jones MAGLOIRE @Joxit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.527106164Z',
|
||||
'created_by': 'WORKDIR /usr/share/nginx/html/',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.527106164Z',
|
||||
'created_by': 'ENV NGINX_PROXY_HEADER_Host=$http_host',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.527106164Z',
|
||||
'created_by': 'ENV NGINX_LISTEN_PORT=80',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.527106164Z',
|
||||
'created_by': 'ENV SHOW_CATALOG_NB_TAGS=false',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.549089552Z',
|
||||
'created_by': 'COPY nginx/default.conf /etc/nginx/conf.d/default.conf # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.605792395Z',
|
||||
'created_by': 'COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.627224324Z',
|
||||
'created_by': 'COPY dist/ /usr/share/nginx/html/ # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.644817137Z',
|
||||
'created_by': 'COPY favicon.ico /usr/share/nginx/html/ # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.778774465Z',
|
||||
'created_by':
|
||||
'RUN /bin/sh -c chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
],
|
||||
'moby.buildkit.buildinfo.v1':
|
||||
'eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvbmdpbng6YWxwaW5lIiwicGluIjoic2hhMjU2OjAyZmZkNDM5YjcxZDllYTk0MDhlNDQ5YjU2OGY2NWMwYmJiYjk0YmViZDg3NTBmMWQ4MDIzMWFiNjQ5NjAwOGUifV19',
|
||||
'os': 'linux',
|
||||
'rootfs': {
|
||||
'type': 'layers',
|
||||
'diff_ids': [
|
||||
'sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5',
|
||||
'sha256:1003ff723696bfd596cd65592fa26554840e90780f6937e6ddccc909b8ed1443',
|
||||
'sha256:1d54586a1706c0af48668c10cbd8246626acb4fec01287be54cd9b26d72df15d',
|
||||
'sha256:c1cd5c8c68ef2336b2504336206d58931e9215a863a35a741f66aa3f4970b0f5',
|
||||
'sha256:f0fb842dea4179a94f1b8c2ac178e72690fa2b30e25e03a7a7893794fe9520a5',
|
||||
'sha256:f9cb3f1f1d3d7c591c4ab02118816fe6761a8f2f7b2500a5ec7421a42b8a5ea2',
|
||||
'sha256:31531248c7cbf5b31a8d9695c20041b9b3749b8c04b9831331ad93333fcf1474',
|
||||
'sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef',
|
||||
'sha256:d2a2fde0ccbec9f84533eba6eae08e61456eae22cdb18cbd6770584a96de079c',
|
||||
'sha256:417b58457dd524a2ab48d3b4d124910aaff0680035f71816ee6efb5fb08c784a',
|
||||
'sha256:a8ecc6cb361d80fdf6d9a3149dcd7d3042cf1b26b45a6e591032033c98848de9',
|
||||
'sha256:f5fe999227158f3d3649ac32585d981b74774e9d3b8f8254104395470753c751',
|
||||
'sha256:3eaed6821acc2fcc0f298984d83dd9ca0317b028ed4a5b1cdcb58d4e1c6aec74',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const blobArm = {
|
||||
'architecture': 'arm',
|
||||
'config': {
|
||||
'ExposedPorts': { '80/tcp': {} },
|
||||
'Env': [
|
||||
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
|
||||
'NGINX_VERSION=1.23.4',
|
||||
'PKG_RELEASE=1',
|
||||
'NJS_VERSION=0.7.11',
|
||||
'NGINX_PROXY_HEADER_Host=$http_host',
|
||||
'NGINX_LISTEN_PORT=80',
|
||||
'SHOW_CATALOG_NB_TAGS=false',
|
||||
],
|
||||
'Entrypoint': ['/docker-entrypoint.sh'],
|
||||
'Cmd': ['nginx', '-g', 'daemon off;'],
|
||||
'WorkingDir': '/usr/share/nginx/html/',
|
||||
'Labels': { 'maintainer': 'Jones MAGLOIRE @Joxit' },
|
||||
'StopSignal': 'SIGQUIT',
|
||||
'OnBuild': null,
|
||||
},
|
||||
'created': '2023-05-18T06:18:36.698896035Z',
|
||||
'history': [
|
||||
{
|
||||
'created': '2023-03-29T18:03:38.84773971Z',
|
||||
'created_by': '/bin/sh -c #(nop) ADD file:959fa0ffb60c37c4fdc0d32ac31511f8dead32ef7f4bd848b11ff144a6b37012 in / ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T18:03:38.9602307Z',
|
||||
'created_by': '/bin/sh -c #(nop) CMD ["/bin/sh"]',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T19:27:03.894972841Z',
|
||||
'created_by': '/bin/sh -c #(nop) LABEL maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T19:27:03.977068216Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENV NGINX_VERSION=1.23.4',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T19:27:04.093650581Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENV PKG_RELEASE=1',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T19:27:44.532352816Z',
|
||||
'created_by':
|
||||
'/bin/sh -c set -x \u0026\u0026 addgroup -g 101 -S nginx \u0026\u0026 adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make base \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache --virtual .gettext gettext \u0026\u0026 mv /usr/bin/envsubst /tmp/ \u0026\u0026 runDeps="$( scanelf --needed --nobanner /tmp/envsubst | awk \'{ gsub(/,/, "\\nso:", $2); print "so:" $2 }\' | sort -u | xargs -r apk info --installed | sort -u )" \u0026\u0026 apk add --no-cache $runDeps \u0026\u0026 apk del .gettext \u0026\u0026 mv /tmp/envsubst /usr/local/bin/ \u0026\u0026 apk add --no-cache tzdata \u0026\u0026 ln -sf /dev/stdout /var/log/nginx/access.log \u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log \u0026\u0026 mkdir /docker-entrypoint.d',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T19:27:44.660812167Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:7b307b62e82255f040c9812421a30090bf9abf3685f27b02d77fcca99f997911 in / ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T19:27:44.758007971Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:5c18272734349488bd0c94ec8d382c872c1a0a435cca13bd4671353d6021d2cb in /docker-entrypoint.d ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T19:27:44.849497713Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:abbcbf84dc17ee4454b6b2e3cf914be88e02cf84d344ec45a5b31235379d722a in /docker-entrypoint.d ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T19:27:44.941848809Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:e57eef017a414ca793499729d80a7b9075790c9a804f930f1417e56d506970cf in /docker-entrypoint.d ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T19:27:45.021738784Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENTRYPOINT ["/docker-entrypoint.sh"]',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{ 'created': '2023-03-29T19:27:45.104459469Z', 'created_by': '/bin/sh -c #(nop) EXPOSE 80', 'empty_layer': true },
|
||||
{
|
||||
'created': '2023-03-29T19:27:45.186875746Z',
|
||||
'created_by': '/bin/sh -c #(nop) STOPSIGNAL SIGQUIT',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T19:27:45.267774313Z',
|
||||
'created_by': '/bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"]',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T19:29:39.602231315Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENV NJS_VERSION=0.7.11',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T19:31:21.652098356Z',
|
||||
'created_by':
|
||||
'/bin/sh -c set -x \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}.${NJS_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers libxslt-dev gd-dev geoip-dev libedit-dev bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make module-geoip module-image-filter module-njs module-xslt \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache curl ca-certificates',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-18T06:18:36.4191662Z',
|
||||
'created_by': 'LABEL maintainer=Jones MAGLOIRE @Joxit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-18T06:18:36.4191662Z',
|
||||
'created_by': 'WORKDIR /usr/share/nginx/html/',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-18T06:18:36.4191662Z',
|
||||
'created_by': 'ENV NGINX_PROXY_HEADER_Host=$http_host',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-18T06:18:36.4191662Z',
|
||||
'created_by': 'ENV NGINX_LISTEN_PORT=80',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-18T06:18:36.4191662Z',
|
||||
'created_by': 'ENV SHOW_CATALOG_NB_TAGS=false',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-18T06:18:36.433173058Z',
|
||||
'created_by': 'COPY nginx/default.conf /etc/nginx/conf.d/default.conf # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-18T06:18:36.446926669Z',
|
||||
'created_by': 'COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-18T06:18:36.464433643Z',
|
||||
'created_by': 'COPY dist/ /usr/share/nginx/html/ # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-18T06:18:36.479417246Z',
|
||||
'created_by': 'COPY favicon.ico /usr/share/nginx/html/ # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-18T06:18:36.698896035Z',
|
||||
'created_by':
|
||||
'RUN /bin/sh -c chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
],
|
||||
'moby.buildkit.buildinfo.v1':
|
||||
'eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvbmdpbng6YWxwaW5lIiwicGluIjoic2hhMjU2OjAyZmZkNDM5YjcxZDllYTk0MDhlNDQ5YjU2OGY2NWMwYmJiYjk0YmViZDg3NTBmMWQ4MDIzMWFiNjQ5NjAwOGUifV19',
|
||||
'os': 'linux',
|
||||
'rootfs': {
|
||||
'type': 'layers',
|
||||
'diff_ids': [
|
||||
'sha256:0b9ff86f9940609d912c3e621dd6adad477087fbfc4c31c61d654a22a0f11b61',
|
||||
'sha256:f60190c8cdc262062bd7d42a07cadde6791c8449f3421dff1223bfc36a9caf82',
|
||||
'sha256:e327072a6a7f45d0af7c04f57d4729a7562f5aaca9377f0ffab6d9fd120f6ec5',
|
||||
'sha256:c26ebf2a190d73b49a5e7bb779f33448d5fbf1a0a36236510621b758790ae793',
|
||||
'sha256:6af30f71b3afff8b63313da78bda5d7b7fee2216be594cb2fc7f19a3ce69a14a',
|
||||
'sha256:2bfd4c9ec145aaf73c515d20d10f1f03e7043c191134b7bcbb75013f016c68fc',
|
||||
'sha256:b1f07242859d323324ee74a7221806532d71a0cb52f1463e9c1f661d4293fabb',
|
||||
'sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef',
|
||||
'sha256:00fd9ea6d2874d964d55c800e9ea9f812c9b6a4e8e342990f79c317b96545495',
|
||||
'sha256:90eeeac365c62a487016298dfd4dae3aef0662f243b2a6f8030c0a3cefebf0aa',
|
||||
'sha256:684c2527abcba7d7b72bef753728e030093aa6ac748e0d46034c94dc50bc8780',
|
||||
'sha256:2e0de70327c6edd8680cacf8213f2dfbae8da5f2e3da03765046c220c4e56b88',
|
||||
'sha256:282631fc20d0d4585c4ff9b37ffe0bd7b9e34834c9902d1f7934ce07fe3264a3',
|
||||
],
|
||||
},
|
||||
'variant': 'v7',
|
||||
};
|
||||
|
||||
export const dockerManifestList = {
|
||||
'application/vnd.docker.distribution.manifest.list.v2+json': manifestList,
|
||||
manifestList,
|
||||
'sha256:7d9bbfa92dbd2a9c09abe924ee7cb8f164be59f25b9457fa0c593a7110dba89f': manifestAmd64,
|
||||
manifestAmd64,
|
||||
'sha256:5ef7a7d411a524beff05c9d1a541442ff78bb1ec6b45a55434dd3e51e00292b1': manifestArm,
|
||||
manifestArm,
|
||||
'sha256:7209907f3aa39f8b259069272274f185c4e9772ea7159722728b5f648c71eaad': blobAmd64,
|
||||
blobAmd64,
|
||||
'sha256:322f0eb73bbb441e1a0eae5dd05dfa56d7bc78b2be4682056463d919393b7d0b': blobArm,
|
||||
blobArm,
|
||||
};
|
||||
137
test/fixtures/oci-image-index-layer.js
vendored
Normal file
137
test/fixtures/oci-image-index-layer.js
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Index of an image created with:
|
||||
* buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --export-cache type=registry,ref=joxit/docker-registry-ui:buildkit
|
||||
*/
|
||||
const imageIndex = {
|
||||
'schemaVersion': 2,
|
||||
'mediaType': 'application/vnd.oci.image.index.v1+json',
|
||||
'manifests': [
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:1f21f983520d9a440d410ea62eb0bda61a2b50dd79878071181b56b82efa9ef3',
|
||||
'size': 1404,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:52.396770851+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:f9cb3f1f1d3d7c591c4ab02118816fe6761a8f2f7b2500a5ec7421a42b8a5ea2',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:232b62d5fcb5fc034d54170fd0e93e62b4f0eb357b23f99fc70edfd514e68688',
|
||||
'size': 878138,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:53.358118651+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:da6ab5dba3f3f7a67cee1b384e5c3e76dc6daffe31c8da7d7b8944b010146deb',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:2ce963c369bc5690378d31c51dc575c7035f6adfcc1e286051b5a5d9a7b0cc5c',
|
||||
'size': 1799036,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:52.374638832+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:1003ff723696bfd596cd65592fa26554840e90780f6937e6ddccc909b8ed1443',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:3e1e579c95fece6bbe0cb9c8c2949512a3f8caaf9dbe6219dc6495abb9902040',
|
||||
'size': 956,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:52.386713549+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:c1cd5c8c68ef2336b2504336206d58931e9215a863a35a741f66aa3f4970b0f5',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1',
|
||||
'size': 32,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:53.299566674+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:547a97583f72a32903ca1357d48fa302e91e8f83ffa18e0c40fd87adb5c06025',
|
||||
'size': 773,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:52.392007164+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:f0fb842dea4179a94f1b8c2ac178e72690fa2b30e25e03a7a7893794fe9520a5',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:59b9d2200e632e457f800814693b3a01adf09a244c38ebe8d3beef5c476c4c55',
|
||||
'size': 626,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:52.382010266+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:1d54586a1706c0af48668c10cbd8246626acb4fec01287be54cd9b26d72df15d',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:91a97e9af03b8531316a2f1418c03e329d0d1e261d6e193435263f3c7506d9ea',
|
||||
'size': 18328,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:53.375505685+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:233ce4876ce17521a29b279dfa36dc690a85faca59b64ee5b8b7aa2d1f89dfa9',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:9b5968b53397e19a4a0ba7e49f0f29fa274fe0bdb4827720a69b50a9241f7281',
|
||||
'size': 900650,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:53.54931788+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:9cf4861c81b63e36381c165dfac7c6f44877ec30d562f401b49a625e059f34d6',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:c23b4f8cf279507bb1dd3d6eb2d15ca84fac9eac215ab5b529aa8b5a060294c8',
|
||||
'size': 11607291,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:52.401595965+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:31531248c7cbf5b31a8d9695c20041b9b3749b8c04b9831331ad93333fcf1474',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:efc25abc67f8fa9710cbfbb82588504708f18bca00a2a8a6fcc09fc71e72f10b',
|
||||
'size': 1023,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:53.324178797+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:2596c2188541b87815b21a34838862ca037720785f6e150fd410213555c4aab1',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:f56be85fc22e46face30e2c3de3f7fe7c15f8fd7c4e5add29d7f64b87abdaa09',
|
||||
'size': 3374563,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:52.358761758+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:fd6b87e430ca8d0742c0cae2e0bd472ae024851526a39e3a7fe6dd15fed45224',
|
||||
'size': 954,
|
||||
'annotations': {
|
||||
'buildkit/createdat': '2023-05-17T22:25:53.33927092+02:00',
|
||||
'containerd.io/uncompressed': 'sha256:f04b4697ffd252d7a0080dcc83c0790e9962db6cf74d3450960e03e16cf1e747',
|
||||
},
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.buildkit.cacheconfig.v0',
|
||||
'digest': 'sha256:b7d18f694c1dcf673bc8e56dbdc05a3441a0ae000f05dbc3f34f42ac9f060ef9',
|
||||
'size': 2908,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const ociImageIndexLayer = {
|
||||
'application/vnd.oci.image.index.v1+json': imageIndex,
|
||||
imageIndex,
|
||||
};
|
||||
561
test/fixtures/oci-image-index-manifest.js
vendored
Normal file
561
test/fixtures/oci-image-index-manifest.js
vendored
Normal file
@@ -0,0 +1,561 @@
|
||||
/**
|
||||
* Manifest of an image created with:
|
||||
* docker buildx build --platform amd64,arm -t joxit/docker-registry-ui:buildx --push --provenance true .
|
||||
*/
|
||||
const imageIndex = {
|
||||
'mediaType': 'application/vnd.oci.image.index.v1+json',
|
||||
'schemaVersion': 2,
|
||||
'manifests': [
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.manifest.v1+json',
|
||||
'digest': 'sha256:868d96eea2ab3b0905caa746339541cef30ed4e0864da7f89e423cb50aee7857',
|
||||
'size': 2756,
|
||||
'platform': { 'architecture': 'amd64', 'os': 'linux' },
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.manifest.v1+json',
|
||||
'digest': 'sha256:ee45307ae7404ccfbe4536677095b1ad1258a261c79ecdf5640d24bec66e1257',
|
||||
'size': 2756,
|
||||
'platform': { 'architecture': 'arm64', 'os': 'linux' },
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.manifest.v1+json',
|
||||
'digest': 'sha256:2d0c98dae3c1936ef04d00554f9b2557cbdd1f2aa84226758fa77e84a7326f98',
|
||||
'size': 566,
|
||||
'annotations': {
|
||||
'vnd.docker.reference.digest': 'sha256:868d96eea2ab3b0905caa746339541cef30ed4e0864da7f89e423cb50aee7857',
|
||||
'vnd.docker.reference.type': 'attestation-manifest',
|
||||
},
|
||||
'platform': { 'architecture': 'unknown', 'os': 'unknown' },
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.manifest.v1+json',
|
||||
'digest': 'sha256:f239e839c36a0fc3bd9d4a6152d7a83522002bfbf09836f0a3b20613afe27d97',
|
||||
'size': 566,
|
||||
'annotations': {
|
||||
'vnd.docker.reference.digest': 'sha256:ee45307ae7404ccfbe4536677095b1ad1258a261c79ecdf5640d24bec66e1257',
|
||||
'vnd.docker.reference.type': 'attestation-manifest',
|
||||
},
|
||||
'platform': { 'architecture': 'unknown', 'os': 'unknown' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const manifestAmd64 = {
|
||||
'mediaType': 'application/vnd.oci.image.manifest.v1+json',
|
||||
'schemaVersion': 2,
|
||||
'config': {
|
||||
'mediaType': 'application/vnd.oci.image.config.v1+json',
|
||||
'digest': 'sha256:7209907f3aa39f8b259069272274f185c4e9772ea7159722728b5f648c71eaad',
|
||||
'size': 13772,
|
||||
},
|
||||
'layers': [
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:f56be85fc22e46face30e2c3de3f7fe7c15f8fd7c4e5add29d7f64b87abdaa09',
|
||||
'size': 3374563,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:2ce963c369bc5690378d31c51dc575c7035f6adfcc1e286051b5a5d9a7b0cc5c',
|
||||
'size': 1799036,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:59b9d2200e632e457f800814693b3a01adf09a244c38ebe8d3beef5c476c4c55',
|
||||
'size': 626,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:3e1e579c95fece6bbe0cb9c8c2949512a3f8caaf9dbe6219dc6495abb9902040',
|
||||
'size': 956,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:547a97583f72a32903ca1357d48fa302e91e8f83ffa18e0c40fd87adb5c06025',
|
||||
'size': 773,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:1f21f983520d9a440d410ea62eb0bda61a2b50dd79878071181b56b82efa9ef3',
|
||||
'size': 1404,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:c23b4f8cf279507bb1dd3d6eb2d15ca84fac9eac215ab5b529aa8b5a060294c8',
|
||||
'size': 11607291,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1',
|
||||
'size': 32,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:a5a652ffc299e3af7414a7c48a8b287785c0c70d7e7712cc81da43cfd18dc677',
|
||||
'size': 1023,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:ac35a311bcf31486bf05fb0a0b66dd1fb313f8ac6b631c07662f5c77a017b142',
|
||||
'size': 953,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:0bbe5bab7d19c248fd83f1d7746b306b1590118dcdab99cf49b72cec4f744a15',
|
||||
'size': 878128,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:b516cdaec2158585780cfe13e196fbd6ee4e2aed81d1007903da60df2a6f8d12',
|
||||
'size': 18327,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:c93af1909df12d053ee3b101b005c15312dedeee3d18c2f23a3aa3776693a0f5',
|
||||
'size': 900629,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const manifestArm = {
|
||||
'mediaType': 'application/vnd.oci.image.manifest.v1+json',
|
||||
'schemaVersion': 2,
|
||||
'config': {
|
||||
'mediaType': 'application/vnd.oci.image.config.v1+json',
|
||||
'digest': 'sha256:6d0a94a37f413ae834a226070fb042386303fd80ac79c6d4a12e986a03416710',
|
||||
'size': 13771,
|
||||
},
|
||||
'layers': [
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:c41833b44d910632b415cd89a9cdaa4d62c9725dc56c99a7ddadafd6719960f9',
|
||||
'size': 3261854,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:2c2c9b85ac58c9f389d42b1033672337110dba86c12d1b0d5c7c384a7cfe110b',
|
||||
'size': 1788526,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:40f94fa3619489012a181c2b217548ea718fe485578eec4afdef4b14b3bc536e',
|
||||
'size': 624,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:ae26f20697dc7e3b86701a83a1ed42b81b1755f0763130d7f6f816a39adaf388',
|
||||
'size': 956,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:e4fa283fba0e8150c05ba453aed98ff4f4bdd65a6248837101fc16b489d1101e',
|
||||
'size': 770,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:4c53b6cdc37bcca61cf31d3308b58fda6d7d3192ddd56559cca2f67eafcb0cc1',
|
||||
'size': 1403,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:7bcac465295e8cfefa26d0ad33a638a0415ad7c4e1afba500b9633f97e277c3c',
|
||||
'size': 11108102,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1',
|
||||
'size': 32,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:7923ad4ef26285d800ddad2f7a82d428d035aafd6d6029c4bb23b2b8ac53f699',
|
||||
'size': 1026,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:ac35a311bcf31486bf05fb0a0b66dd1fb313f8ac6b631c07662f5c77a017b142',
|
||||
'size': 953,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:cb284b84c5d00c523b11f6fa3ee3886f20b146a0f6bc70160bc6e43a3a525dcc',
|
||||
'size': 878125,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:ed35f24b7cbe4d56ab109eb77be92a68460b8f932b2a42ddde9296ee7805070a',
|
||||
'size': 18329,
|
||||
},
|
||||
{
|
||||
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
||||
'digest': 'sha256:ea6ae72ae0bc46efb9381c62a0c9f01b7d1ad2cf67dc3ec58ef8b4ff91a145f1',
|
||||
'size': 900638,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const blobAmd64 = {
|
||||
'architecture': 'amd64',
|
||||
'config': {
|
||||
'ExposedPorts': { '80/tcp': {} },
|
||||
'Env': [
|
||||
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
|
||||
'NGINX_VERSION=1.23.4',
|
||||
'PKG_RELEASE=1',
|
||||
'NJS_VERSION=0.7.11',
|
||||
'NGINX_PROXY_HEADER_Host=$http_host',
|
||||
'NGINX_LISTEN_PORT=80',
|
||||
'SHOW_CATALOG_NB_TAGS=false',
|
||||
],
|
||||
'Entrypoint': ['/docker-entrypoint.sh'],
|
||||
'Cmd': ['nginx', '-g', 'daemon off;'],
|
||||
'WorkingDir': '/usr/share/nginx/html/',
|
||||
'Labels': { 'maintainer': 'Jones MAGLOIRE @Joxit' },
|
||||
'StopSignal': 'SIGQUIT',
|
||||
'OnBuild': null,
|
||||
},
|
||||
'created': '2023-05-16T17:53:59.778774465Z',
|
||||
'history': [
|
||||
{
|
||||
'created': '2023-03-29T18:19:24.348438709Z',
|
||||
'created_by': '/bin/sh -c #(nop) ADD file:9a4f77dfaba7fd2aa78186e4ef0e7486ad55101cefc1fabbc1b385601bb38920 in / ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T18:19:24.45578926Z',
|
||||
'created_by': '/bin/sh -c #(nop) CMD ["/bin/sh"]',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:10.826996048Z',
|
||||
'created_by': '/bin/sh -c #(nop) LABEL maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:10.902296598Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENV NGINX_VERSION=1.23.4',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:10.975496976Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENV PKG_RELEASE=1',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.216540441Z',
|
||||
'created_by':
|
||||
'/bin/sh -c set -x \u0026\u0026 addgroup -g 101 -S nginx \u0026\u0026 adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make base \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache --virtual .gettext gettext \u0026\u0026 mv /usr/bin/envsubst /tmp/ \u0026\u0026 runDeps="$( scanelf --needed --nobanner /tmp/envsubst | awk \'{ gsub(/,/, "\\nso:", $2); print "so:" $2 }\' | sort -u | xargs -r apk info --installed | sort -u )" \u0026\u0026 apk add --no-cache $runDeps \u0026\u0026 apk del .gettext \u0026\u0026 mv /tmp/envsubst /usr/local/bin/ \u0026\u0026 apk add --no-cache tzdata \u0026\u0026 ln -sf /dev/stdout /var/log/nginx/access.log \u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log \u0026\u0026 mkdir /docker-entrypoint.d',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.339435018Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:7b307b62e82255f040c9812421a30090bf9abf3685f27b02d77fcca99f997911 in / ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.427781358Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:5c18272734349488bd0c94ec8d382c872c1a0a435cca13bd4671353d6021d2cb in /docker-entrypoint.d ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.515135125Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:abbcbf84dc17ee4454b6b2e3cf914be88e02cf84d344ec45a5b31235379d722a in /docker-entrypoint.d ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.601542115Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:e57eef017a414ca793499729d80a7b9075790c9a804f930f1417e56d506970cf in /docker-entrypoint.d ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.678093757Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENTRYPOINT ["/docker-entrypoint.sh"]',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{ 'created': '2023-03-29T22:31:16.756398749Z', 'created_by': '/bin/sh -c #(nop) EXPOSE 80', 'empty_layer': true },
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.839607817Z',
|
||||
'created_by': '/bin/sh -c #(nop) STOPSIGNAL SIGQUIT',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:16.921795894Z',
|
||||
'created_by': '/bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"]',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:30.252298671Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENV NJS_VERSION=0.7.11',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T22:31:35.828698308Z',
|
||||
'created_by':
|
||||
'/bin/sh -c set -x \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}.${NJS_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers libxslt-dev gd-dev geoip-dev libedit-dev bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make module-geoip module-image-filter module-njs module-xslt \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache curl ca-certificates',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.527106164Z',
|
||||
'created_by': 'LABEL maintainer=Jones MAGLOIRE @Joxit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.527106164Z',
|
||||
'created_by': 'WORKDIR /usr/share/nginx/html/',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.527106164Z',
|
||||
'created_by': 'ENV NGINX_PROXY_HEADER_Host=$http_host',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.527106164Z',
|
||||
'created_by': 'ENV NGINX_LISTEN_PORT=80',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.527106164Z',
|
||||
'created_by': 'ENV SHOW_CATALOG_NB_TAGS=false',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.549089552Z',
|
||||
'created_by': 'COPY nginx/default.conf /etc/nginx/conf.d/default.conf # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.605792395Z',
|
||||
'created_by': 'COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.627224324Z',
|
||||
'created_by': 'COPY dist/ /usr/share/nginx/html/ # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.644817137Z',
|
||||
'created_by': 'COPY favicon.ico /usr/share/nginx/html/ # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.778774465Z',
|
||||
'created_by':
|
||||
'RUN /bin/sh -c chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
],
|
||||
'moby.buildkit.buildinfo.v1':
|
||||
'eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvbmdpbng6YWxwaW5lIiwicGluIjoic2hhMjU2OjAyZmZkNDM5YjcxZDllYTk0MDhlNDQ5YjU2OGY2NWMwYmJiYjk0YmViZDg3NTBmMWQ4MDIzMWFiNjQ5NjAwOGUifV19',
|
||||
'os': 'linux',
|
||||
'rootfs': {
|
||||
'type': 'layers',
|
||||
'diff_ids': [
|
||||
'sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5',
|
||||
'sha256:1003ff723696bfd596cd65592fa26554840e90780f6937e6ddccc909b8ed1443',
|
||||
'sha256:1d54586a1706c0af48668c10cbd8246626acb4fec01287be54cd9b26d72df15d',
|
||||
'sha256:c1cd5c8c68ef2336b2504336206d58931e9215a863a35a741f66aa3f4970b0f5',
|
||||
'sha256:f0fb842dea4179a94f1b8c2ac178e72690fa2b30e25e03a7a7893794fe9520a5',
|
||||
'sha256:f9cb3f1f1d3d7c591c4ab02118816fe6761a8f2f7b2500a5ec7421a42b8a5ea2',
|
||||
'sha256:31531248c7cbf5b31a8d9695c20041b9b3749b8c04b9831331ad93333fcf1474',
|
||||
'sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef',
|
||||
'sha256:d2a2fde0ccbec9f84533eba6eae08e61456eae22cdb18cbd6770584a96de079c',
|
||||
'sha256:417b58457dd524a2ab48d3b4d124910aaff0680035f71816ee6efb5fb08c784a',
|
||||
'sha256:a8ecc6cb361d80fdf6d9a3149dcd7d3042cf1b26b45a6e591032033c98848de9',
|
||||
'sha256:f5fe999227158f3d3649ac32585d981b74774e9d3b8f8254104395470753c751',
|
||||
'sha256:3eaed6821acc2fcc0f298984d83dd9ca0317b028ed4a5b1cdcb58d4e1c6aec74',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const blobArm = {
|
||||
'architecture': 'arm64',
|
||||
'config': {
|
||||
'ExposedPorts': { '80/tcp': {} },
|
||||
'Env': [
|
||||
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
|
||||
'NGINX_VERSION=1.23.4',
|
||||
'PKG_RELEASE=1',
|
||||
'NJS_VERSION=0.7.11',
|
||||
'NGINX_PROXY_HEADER_Host=$http_host',
|
||||
'NGINX_LISTEN_PORT=80',
|
||||
'SHOW_CATALOG_NB_TAGS=false',
|
||||
],
|
||||
'Entrypoint': ['/docker-entrypoint.sh'],
|
||||
'Cmd': ['nginx', '-g', 'daemon off;'],
|
||||
'WorkingDir': '/usr/share/nginx/html/',
|
||||
'Labels': { 'maintainer': 'Jones MAGLOIRE @Joxit' },
|
||||
'StopSignal': 'SIGQUIT',
|
||||
'OnBuild': null,
|
||||
},
|
||||
'created': '2023-05-16T17:54:00.037166004Z',
|
||||
'history': [
|
||||
{
|
||||
'created': '2023-03-29T17:39:18.063622104Z',
|
||||
'created_by': '/bin/sh -c #(nop) ADD file:e51d4089e73ad6dee52b31f0c8059a00c17df6e23f6741fe11b43bd84cc99008 in / ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-29T17:39:18.167879762Z',
|
||||
'created_by': '/bin/sh -c #(nop) CMD ["/bin/sh"]',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-30T04:22:48.660484906Z',
|
||||
'created_by': '/bin/sh -c #(nop) LABEL maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-30T04:22:48.736880947Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENV NGINX_VERSION=1.23.4',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-30T04:22:48.811177358Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENV PKG_RELEASE=1',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-30T04:22:53.381662136Z',
|
||||
'created_by':
|
||||
'/bin/sh -c set -x \u0026\u0026 addgroup -g 101 -S nginx \u0026\u0026 adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make base \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache --virtual .gettext gettext \u0026\u0026 mv /usr/bin/envsubst /tmp/ \u0026\u0026 runDeps="$( scanelf --needed --nobanner /tmp/envsubst | awk \'{ gsub(/,/, "\\nso:", $2); print "so:" $2 }\' | sort -u | xargs -r apk info --installed | sort -u )" \u0026\u0026 apk add --no-cache $runDeps \u0026\u0026 apk del .gettext \u0026\u0026 mv /tmp/envsubst /usr/local/bin/ \u0026\u0026 apk add --no-cache tzdata \u0026\u0026 ln -sf /dev/stdout /var/log/nginx/access.log \u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log \u0026\u0026 mkdir /docker-entrypoint.d',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-30T04:22:53.48126766Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:7b307b62e82255f040c9812421a30090bf9abf3685f27b02d77fcca99f997911 in / ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-30T04:22:53.552453825Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:5c18272734349488bd0c94ec8d382c872c1a0a435cca13bd4671353d6021d2cb in /docker-entrypoint.d ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-30T04:22:53.624623412Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:abbcbf84dc17ee4454b6b2e3cf914be88e02cf84d344ec45a5b31235379d722a in /docker-entrypoint.d ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-30T04:22:53.696466901Z',
|
||||
'created_by':
|
||||
'/bin/sh -c #(nop) COPY file:e57eef017a414ca793499729d80a7b9075790c9a804f930f1417e56d506970cf in /docker-entrypoint.d ',
|
||||
},
|
||||
{
|
||||
'created': '2023-03-30T04:22:53.773577427Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENTRYPOINT ["/docker-entrypoint.sh"]',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{ 'created': '2023-03-30T04:22:53.855706883Z', 'created_by': '/bin/sh -c #(nop) EXPOSE 80', 'empty_layer': true },
|
||||
{
|
||||
'created': '2023-03-30T04:22:53.934315121Z',
|
||||
'created_by': '/bin/sh -c #(nop) STOPSIGNAL SIGQUIT',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-30T04:22:54.010915592Z',
|
||||
'created_by': '/bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"]',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-30T04:23:05.898706916Z',
|
||||
'created_by': '/bin/sh -c #(nop) ENV NJS_VERSION=0.7.11',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-03-30T04:23:11.319535457Z',
|
||||
'created_by':
|
||||
'/bin/sh -c set -x \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}.${NJS_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers libxslt-dev gd-dev geoip-dev libedit-dev bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make module-geoip module-image-filter module-njs module-xslt \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache curl ca-certificates',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.777374567Z',
|
||||
'created_by': 'LABEL maintainer=Jones MAGLOIRE @Joxit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.777374567Z',
|
||||
'created_by': 'WORKDIR /usr/share/nginx/html/',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.777374567Z',
|
||||
'created_by': 'ENV NGINX_PROXY_HEADER_Host=$http_host',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.777374567Z',
|
||||
'created_by': 'ENV NGINX_LISTEN_PORT=80',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.777374567Z',
|
||||
'created_by': 'ENV SHOW_CATALOG_NB_TAGS=false',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
'empty_layer': true,
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.792463797Z',
|
||||
'created_by': 'COPY nginx/default.conf /etc/nginx/conf.d/default.conf # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.806725927Z',
|
||||
'created_by': 'COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.82462658Z',
|
||||
'created_by': 'COPY dist/ /usr/share/nginx/html/ # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:53:59.839284221Z',
|
||||
'created_by': 'COPY favicon.ico /usr/share/nginx/html/ # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
{
|
||||
'created': '2023-05-16T17:54:00.037166004Z',
|
||||
'created_by':
|
||||
'RUN /bin/sh -c chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx # buildkit',
|
||||
'comment': 'buildkit.dockerfile.v0',
|
||||
},
|
||||
],
|
||||
'moby.buildkit.buildinfo.v1':
|
||||
'eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvbmdpbng6YWxwaW5lIiwicGluIjoic2hhMjU2OjAyZmZkNDM5YjcxZDllYTk0MDhlNDQ5YjU2OGY2NWMwYmJiYjk0YmViZDg3NTBmMWQ4MDIzMWFiNjQ5NjAwOGUifV19',
|
||||
'os': 'linux',
|
||||
'rootfs': {
|
||||
'type': 'layers',
|
||||
'diff_ids': [
|
||||
'sha256:26cbea5cba74143fbe6f584f5fc5321543155aedc4a434fcaa63b643877b5a74',
|
||||
'sha256:09353074bdde293a418c894712bba3f4fca2c42cef5c2061caf611794c56ce3c',
|
||||
'sha256:2749f4c7cb991ec74071f7ccec1b7907b85956709d8fccebbe77d44f01809aa9',
|
||||
'sha256:7e1b91127bea03c6bcbec75fc482f62e5e025b9b4e08f46fe11a6a8d9375d0f2',
|
||||
'sha256:3638581487963b65388fc14d1f14a1b0be2e82a48249da474047ea2e4601dc8c',
|
||||
'sha256:fbebe8ba7beddf179286606d8a627f5dd553d78645b71f2703ab114802f0bcd5',
|
||||
'sha256:363722710bd8c3005b16061d18059259edf0108644e6ab74c36c9a040a824d10',
|
||||
'sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef',
|
||||
'sha256:2cf5b89f94fd4a7ce1bc7eaad8d4fb9ae56e83b57b94537fd96682eda514dfc9',
|
||||
'sha256:417b58457dd524a2ab48d3b4d124910aaff0680035f71816ee6efb5fb08c784a',
|
||||
'sha256:b9b82eb1d5ef5a2a6ab324c70d655e27f8109959e6dcd43cf811a451c9153b3e',
|
||||
'sha256:cab4286916ebabccd5ac123b0ee58ee2eaf190bc0bbe0339f106244eb75c0355',
|
||||
'sha256:e7dc8b731ca3be139b27a043e9d21f711c5505ecfe9f9f0bfc4ed794d88e67e4',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const ociImageIndexManifest = {
|
||||
'application/vnd.oci.image.index.v1+json': imageIndex,
|
||||
imageIndex,
|
||||
'sha256:868d96eea2ab3b0905caa746339541cef30ed4e0864da7f89e423cb50aee7857': manifestAmd64,
|
||||
manifestAmd64,
|
||||
'sha256:ee45307ae7404ccfbe4536677095b1ad1258a261c79ecdf5640d24bec66e1257': manifestArm,
|
||||
manifestArm,
|
||||
'sha256:7209907f3aa39f8b259069272274f185c4e9772ea7159722728b5f648c71eaad': blobAmd64,
|
||||
blobAmd64,
|
||||
'sha256:6d0a94a37f413ae834a226070fb042386303fd80ac79c6d4a12e986a03416710': blobArm,
|
||||
blobArm,
|
||||
};
|
||||
122
test/repositories.test.js
Normal file
122
test/repositories.test.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import { getBranching } from '../src/scripts/repositories.js';
|
||||
import { DockerRegistryUIError } from '../src/scripts/error.js';
|
||||
import assert from 'assert';
|
||||
|
||||
describe('repositories', () => {
|
||||
describe('getBranching', () => {
|
||||
it('should not branch for no levels', () => {
|
||||
const branching = getBranching(0, 0);
|
||||
assert.deepEqual(branching(['alpine', 'debian', 'nginx']), ['alpine', 'debian', 'nginx']);
|
||||
assert.deepEqual(branching(['alpine', 'joxit/docker-registry-ui', 'joxit/docker-registry-ui/amd64', 'nginx']), [
|
||||
'alpine',
|
||||
'joxit/docker-registry-ui',
|
||||
'joxit/docker-registry-ui/amd64',
|
||||
'nginx',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should branch for one level', () => {
|
||||
const branching = getBranching(1, 1);
|
||||
assert.deepEqual(branching(['alpine', 'debian', 'nginx']), ['alpine', 'debian', 'nginx']);
|
||||
assert.deepEqual(branching(['alpine', 'joxit/docker-registry-ui', 'nginx']), [
|
||||
'alpine',
|
||||
{ images: ['joxit/docker-registry-ui'], repo: 'joxit/' },
|
||||
'nginx',
|
||||
]);
|
||||
assert.deepEqual(
|
||||
branching(['alpine', 'joxit/docker-registry-ui', 'joxit/kokai', 'joxit/docker-registry-ui/amd64', 'nginx']),
|
||||
[
|
||||
'alpine',
|
||||
{ images: ['joxit/docker-registry-ui', 'joxit/docker-registry-ui/amd64', 'joxit/kokai'], repo: 'joxit/' },
|
||||
'nginx',
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('should branch for two level', () => {
|
||||
const branching = getBranching(2, 2);
|
||||
assert.deepEqual(branching(['alpine', 'debian', 'nginx']), ['alpine', 'debian', 'nginx']);
|
||||
assert.deepEqual(branching(['alpine', 'joxit/docker-registry-ui', 'nginx']), [
|
||||
'alpine',
|
||||
'joxit/docker-registry-ui',
|
||||
'nginx',
|
||||
]);
|
||||
assert.deepEqual(
|
||||
branching([
|
||||
'alpine',
|
||||
'joxit/docker-registry-ui',
|
||||
'joxit/kokai',
|
||||
'joxit/docker-registry-ui/amd64',
|
||||
'joxit/docker-registry-ui/amd64/latest',
|
||||
'joxit/docker-registry-ui/armv7',
|
||||
'joxit/docker-registry-ui/armv7/latest',
|
||||
'nginx',
|
||||
]),
|
||||
[
|
||||
'alpine',
|
||||
'joxit/docker-registry-ui',
|
||||
{
|
||||
images: [
|
||||
'joxit/docker-registry-ui/amd64',
|
||||
'joxit/docker-registry-ui/amd64/latest',
|
||||
'joxit/docker-registry-ui/armv7',
|
||||
'joxit/docker-registry-ui/armv7/latest',
|
||||
],
|
||||
repo: 'joxit/docker-registry-ui/',
|
||||
},
|
||||
'joxit/kokai',
|
||||
'nginx',
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('should branch from one to two level', () => {
|
||||
const branching = getBranching(1, 2);
|
||||
assert.deepEqual(branching(['alpine', 'debian', 'nginx']), ['alpine', 'debian', 'nginx']);
|
||||
assert.deepEqual(branching(['alpine', 'joxit/docker-registry-ui', 'nginx']), [
|
||||
'alpine',
|
||||
{ images: ['joxit/docker-registry-ui'], repo: 'joxit/' },
|
||||
'nginx',
|
||||
]);
|
||||
assert.deepEqual(
|
||||
branching([
|
||||
'alpine',
|
||||
'joxit/docker-registry-ui',
|
||||
'joxit/kokai',
|
||||
'joxit/docker-registry-ui/amd64',
|
||||
'joxit/docker-registry-ui/amd64/latest',
|
||||
'joxit/docker-registry-ui/armv7',
|
||||
'joxit/docker-registry-ui/armv7/latest',
|
||||
'nginx',
|
||||
]),
|
||||
[
|
||||
'alpine',
|
||||
{
|
||||
images: [
|
||||
'joxit/docker-registry-ui',
|
||||
{
|
||||
images: [
|
||||
'joxit/docker-registry-ui/amd64',
|
||||
'joxit/docker-registry-ui/amd64/latest',
|
||||
'joxit/docker-registry-ui/armv7',
|
||||
'joxit/docker-registry-ui/armv7/latest',
|
||||
],
|
||||
repo: 'joxit/docker-registry-ui/',
|
||||
},
|
||||
'joxit/kokai',
|
||||
],
|
||||
repo: 'joxit/',
|
||||
},
|
||||
'nginx',
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should branch from one to two level', () => {
|
||||
assert.throws(() => getBranching(2, 1), DockerRegistryUIError, `Did not throw on min > max`);
|
||||
assert.throws(() => getBranching(-2, 1), DockerRegistryUIError, `Did not throw on min < 0`);
|
||||
assert.throws(() => getBranching(2, -1), DockerRegistryUIError, `Did not throw on max < 0`);
|
||||
assert.throws(() => getBranching('foo', 'bar'), DockerRegistryUIError, `Did not throw on max < 0`);
|
||||
});
|
||||
});
|
||||
163
test/taglist-order.test.js
Normal file
163
test/taglist-order.test.js
Normal file
@@ -0,0 +1,163 @@
|
||||
import { taglistOrderVariants, taglistOrderParser, splitTagToArray } from '../src/scripts/taglist-order.js';
|
||||
import { getTagComparator } from '../src/scripts/taglist-order.js';
|
||||
import { DockerRegistryUIError } from '../src/scripts/error.js';
|
||||
import assert from 'assert';
|
||||
|
||||
describe('taglist-order tests', () => {
|
||||
describe('taglistOrderVariants', () => {
|
||||
it(`should return the input when it's well formed and num first`, () => {
|
||||
const expected = ['num-asc;alpha-asc', 'num-asc;alpha-desc', 'num-desc;alpha-asc', 'num-desc;alpha-asc'];
|
||||
expected.forEach((e) => assert.deepEqual(taglistOrderVariants(e), e));
|
||||
});
|
||||
|
||||
it(`should return the input when it's well formed and alpha first`, () => {
|
||||
const expected = ['alpha-asc;num-asc', 'alpha-asc;num-desc', 'alpha-desc;num-asc', 'alpha-desc;num-asc'];
|
||||
expected.forEach((e) => assert.deepEqual(taglistOrderVariants(e), e));
|
||||
});
|
||||
|
||||
it('should return correct default order', () => {
|
||||
const expected = 'alpha-asc;num-desc';
|
||||
[undefined, ''].forEach((e) => assert.deepEqual(taglistOrderVariants(e), expected));
|
||||
});
|
||||
|
||||
it('should return correct variant of `num-asc;alpha-asc`', () => {
|
||||
const expected = 'num-asc;alpha-asc';
|
||||
['asc', 'num-asc'].forEach((e) => assert.deepEqual(taglistOrderVariants(e), expected));
|
||||
});
|
||||
|
||||
it('should return correct variant of `alpha-desc;num-desc`', () => {
|
||||
const expected = 'alpha-desc;num-desc';
|
||||
['desc'].forEach((e) => assert.deepEqual(taglistOrderVariants(e), expected));
|
||||
});
|
||||
|
||||
it('should extend correctly orders', () => {
|
||||
['alpha-desc', 'alpha-asc'].forEach((e) => assert.deepEqual(taglistOrderVariants(e), `${e};num-asc`));
|
||||
['num-desc', 'num-asc'].forEach((e) => assert.deepEqual(taglistOrderVariants(e), `${e};alpha-asc`));
|
||||
});
|
||||
|
||||
it('should throw error on incorrect values', () => {
|
||||
['alpha-desc;alpha-asc', 'foobar'].forEach((e) =>
|
||||
assert.throws(() => taglistOrderVariants(e), DockerRegistryUIError, `Did not throw on ${e}`)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('taglistOrderParser', () => {
|
||||
it('should have default configuration when empty or undefined', () => {
|
||||
const expected = { numAsc: false, alphaAsc: true, numFirst: false };
|
||||
assert.deepEqual(taglistOrderParser(), expected);
|
||||
assert.deepEqual(taglistOrderParser(''), expected);
|
||||
});
|
||||
|
||||
it('should parse correctly `num-asc;alpha-asc` and variants', () => {
|
||||
const expected = { numAsc: true, alphaAsc: true, numFirst: true };
|
||||
['asc', 'num-asc;alpha-asc', 'num-asc'].forEach((e) =>
|
||||
assert.deepEqual(taglistOrderParser(e), expected, `wrong result for ${e}`)
|
||||
);
|
||||
});
|
||||
|
||||
it('should parse correctly `alpha-desc;num-desc` and variants', () => {
|
||||
const expected = { numAsc: false, alphaAsc: false, numFirst: false };
|
||||
['desc', 'alpha-desc;num-desc'].forEach((e) =>
|
||||
assert.deepEqual(taglistOrderParser(e), expected, `wrong result for ${e}`)
|
||||
);
|
||||
});
|
||||
|
||||
it('should parse correctly `alpha-asc;num-desc` and variants', () => {
|
||||
const expected = { numAsc: false, alphaAsc: true, numFirst: false };
|
||||
assert.deepEqual(taglistOrderParser('alpha-asc;num-desc'), expected);
|
||||
});
|
||||
|
||||
it('should parse correctly `num-desc;alpha-desc` and variants', () => {
|
||||
const expected = { numAsc: false, alphaAsc: false, numFirst: true };
|
||||
assert.deepEqual(taglistOrderParser('num-desc;alpha-desc'), expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('splitTagToArray', () => {
|
||||
it('should reduce tags with numbers', () => {
|
||||
assert.deepEqual(splitTagToArray('0.2.4'), [0, '.', 2, '.', 4]);
|
||||
assert.deepEqual(splitTagToArray('1.2.3-SNAPSHOT'), [1, '.', 2, '.', 3, '-SNAPSHOT']);
|
||||
assert.deepEqual(splitTagToArray('alpine-3.2'), ['alpine-', 3, '.', 2]);
|
||||
assert.deepEqual(splitTagToArray('10.30.00'), [10, '.', 30, '.', 0]);
|
||||
assert.deepEqual(splitTagToArray('010.30.00'), [10, '.', 30, '.', 0]);
|
||||
assert.deepEqual(splitTagToArray('z010.30.00'), ['z', 10, '.', 30, '.', 0]);
|
||||
});
|
||||
|
||||
it('should reduce tags without numbers', () => {
|
||||
assert.deepEqual(splitTagToArray('main'), ['main']);
|
||||
assert.deepEqual(splitTagToArray('master'), ['master']);
|
||||
assert.deepEqual(splitTagToArray('alpine-lts'), ['alpine-lts']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTagComparator', () => {
|
||||
it('should sort tags with `num-asc;alpha-asc`', () => {
|
||||
const comparator = getTagComparator(taglistOrderParser('num-asc;alpha-asc'));
|
||||
|
||||
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['0.2.4', '0.2.5', '1.2.5']);
|
||||
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['0.2.4', 'latest', 'main']);
|
||||
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['1.0.0', '1.0.0-SNAPSHOT', 'latest']);
|
||||
});
|
||||
|
||||
it('should sort tags with `num-desc;alpha-asc`', () => {
|
||||
const comparator = getTagComparator(taglistOrderParser('num-desc;alpha-asc'));
|
||||
|
||||
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['1.2.5', '0.2.5', '0.2.4']);
|
||||
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['0.2.4', 'latest', 'main']);
|
||||
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['1.0.0', '1.0.0-SNAPSHOT', 'latest']);
|
||||
});
|
||||
|
||||
it('should sort tags with `num-asc;alpha-desc`', () => {
|
||||
const comparator = getTagComparator(taglistOrderParser('num-asc;alpha-desc'));
|
||||
|
||||
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['0.2.4', '0.2.5', '1.2.5']);
|
||||
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['0.2.4', 'main', 'latest']);
|
||||
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['1.0.0', '1.0.0-SNAPSHOT', 'latest']);
|
||||
});
|
||||
|
||||
it('should sort tags with `num-desc;alpha-desc`', () => {
|
||||
const comparator = getTagComparator(taglistOrderParser('num-desc;alpha-desc'));
|
||||
|
||||
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['1.2.5', '0.2.5', '0.2.4']);
|
||||
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['0.2.4', 'main', 'latest']);
|
||||
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['1.0.0', '1.0.0-SNAPSHOT', 'latest']);
|
||||
});
|
||||
|
||||
it('should sort tags with `alpha-asc;num-asc`', () => {
|
||||
const comparator = getTagComparator(taglistOrderParser('alpha-asc;num-asc'));
|
||||
|
||||
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['0.2.4', '0.2.5', '1.2.5']);
|
||||
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['latest', 'main', '0.2.4']);
|
||||
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['latest', '1.0.0', '1.0.0-SNAPSHOT']);
|
||||
assert.deepEqual(['latest', 'main', 'edge'].sort(comparator), ['edge', 'latest', 'main']);
|
||||
});
|
||||
|
||||
it('should sort tags with `alpha-asc;num-desc`', () => {
|
||||
const comparator = getTagComparator(taglistOrderParser('alpha-asc;num-desc'));
|
||||
|
||||
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['1.2.5', '0.2.5', '0.2.4']);
|
||||
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['latest', 'main', '0.2.4']);
|
||||
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['latest', '1.0.0', '1.0.0-SNAPSHOT']);
|
||||
assert.deepEqual(['latest', 'main', 'edge'].sort(comparator), ['edge', 'latest', 'main']);
|
||||
});
|
||||
|
||||
it('should sort tags with `alpha-desc;num-asc`', () => {
|
||||
const comparator = getTagComparator(taglistOrderParser('alpha-desc;num-asc'));
|
||||
|
||||
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['0.2.4', '0.2.5', '1.2.5']);
|
||||
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['main', 'latest', '0.2.4']);
|
||||
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['latest', '1.0.0', '1.0.0-SNAPSHOT']);
|
||||
assert.deepEqual(['latest', 'main', 'edge'].sort(comparator), ['main', 'latest', 'edge']);
|
||||
});
|
||||
|
||||
it('should sort tags with `alpha-desc;num-desc`', () => {
|
||||
const comparator = getTagComparator(taglistOrderParser('alpha-desc;num-desc'));
|
||||
|
||||
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['1.2.5', '0.2.5', '0.2.4']);
|
||||
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['main', 'latest', '0.2.4']);
|
||||
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['latest', '1.0.0', '1.0.0-SNAPSHOT']);
|
||||
assert.deepEqual(['latest', 'main', 'edge'].sort(comparator), ['main', 'latest', 'edge']);
|
||||
});
|
||||
});
|
||||
});
|
||||
52
test/utils.test.js
Normal file
52
test/utils.test.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { isNewestVersion } from '../src/scripts/utils.js';
|
||||
import assert from 'assert';
|
||||
|
||||
describe('utils tests', () => {
|
||||
describe('isNewestVersion', () => {
|
||||
it(`should return true for the same version`, () => {
|
||||
const expected = ['2.0.0', '2.4.1', '2.5.0', null, undefined];
|
||||
expected.forEach((e) => assert.ok(isNewestVersion(e, e)));
|
||||
});
|
||||
|
||||
it(`should return true with on common versions`, () => {
|
||||
assert.ok(isNewestVersion('2.5.1', '2.5.0'));
|
||||
assert.ok(isNewestVersion('2.5.0', '2.0.0'));
|
||||
assert.ok(isNewestVersion('2.15.0', '1.25.10'));
|
||||
assert.ok(isNewestVersion('10.10.10', '2.25.20'));
|
||||
});
|
||||
|
||||
it(`should return false on common versions`, () => {
|
||||
assert.equal(isNewestVersion('1.0.0', '2.5.0'), false);
|
||||
assert.equal(isNewestVersion('10.10.10', '20.20.20'), false);
|
||||
assert.equal(isNewestVersion('2.4.10', '2.5.0'), false);
|
||||
assert.equal(isNewestVersion('2.5.0', '2.6.0'), false);
|
||||
});
|
||||
|
||||
it(`should return true for -dev next versions`, () => {
|
||||
assert.ok(isNewestVersion('2.5.0-dev', '2.4.1'));
|
||||
assert.ok(isNewestVersion('2.6.0-dev', '2.5.0'));
|
||||
assert.ok(isNewestVersion('2.15.0-dev', '2.14.1'));
|
||||
assert.ok(isNewestVersion('2.15.0-dev', '1.16.0'));
|
||||
});
|
||||
|
||||
it(`should return false for -dev with current minor version`, () => {
|
||||
assert.equal(isNewestVersion('2.5.0-dev', '2.5.0'), false);
|
||||
assert.equal(isNewestVersion('2.5.0-dev', '2.5.10'), false);
|
||||
assert.equal(isNewestVersion('2.15.0-dev', '2.15.0'), false);
|
||||
assert.equal(isNewestVersion('2.0.0-dev', '2.15.0'), false);
|
||||
});
|
||||
it(`should return true for -{commit sha} next versions`, () => {
|
||||
assert.ok(isNewestVersion('2.5.0-ffb6d14baf', '2.4.1'));
|
||||
assert.ok(isNewestVersion('2.6.0-ffb6d14baf', '2.5.0'));
|
||||
assert.ok(isNewestVersion('2.15.0-ffb6d14baf', '2.14.1'));
|
||||
assert.ok(isNewestVersion('2.15.0-ffb6d14baf', '1.16.0'));
|
||||
});
|
||||
|
||||
it(`should return false for -{commit sha} with current minor version`, () => {
|
||||
assert.equal(isNewestVersion('2.5.0-ffb6d14baf', '2.5.0'), false);
|
||||
assert.equal(isNewestVersion('2.5.0-ffb6d14baf', '2.5.10'), false);
|
||||
assert.equal(isNewestVersion('2.15.0-ffb6d14baf', '2.15.0'), false);
|
||||
assert.equal(isNewestVersion('2.0.0-ffb6d14baf', '2.15.0'), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user