Compare commits
110 commits
tcl/master
...
release/2.
Author | SHA1 | Date | |
---|---|---|---|
|
27206bcd3b | ||
|
110cb7538d | ||
|
2d62a4027a | ||
|
2548973b1d | ||
|
8728c52ef2 | ||
|
ab7178cc0a | ||
|
7c354a4b40 | ||
|
a173a9c625 | ||
|
4894d35ecc | ||
|
f067f66d3d | ||
|
483ad69da3 | ||
|
2b0f84df21 | ||
|
320d6a141f | ||
|
5f3ca1b2fb | ||
|
cb840f63b3 | ||
|
e884644fff | ||
|
963c19952a | ||
|
ac6c72b25f | ||
|
dcb637d6ea | ||
|
08f5645587 | ||
|
4a35c451a0 | ||
|
ae58bde985 | ||
|
3f2a4e24a7 | ||
|
9c04409fdb | ||
|
b791fdc2c6 | ||
|
3d8f3cc4a5 | ||
|
d3fac541b1 | ||
|
70db3a46d9 | ||
|
db1389e043 | ||
|
018472de2d | ||
|
19b3feb5df | ||
|
14bd72bcf8 | ||
|
2392893bcf | ||
|
092a2197ff | ||
|
22a805033a | ||
|
1d52366d2c | ||
|
521ea3d973 | ||
|
82d6c3d007 | ||
|
ad5991de09 | ||
|
dc5b207fdd | ||
|
38018aeb5d | ||
|
b5ca020cfb | ||
|
1b5f094086 | ||
|
96cc1fdb3c | ||
|
e744906f09 | ||
|
3df9fce2be | ||
|
9a0196b801 | ||
|
6736d1881a | ||
|
e4a447d0d7 | ||
|
80acbdf0a2 | ||
|
dcf66392d6 | ||
|
212b38ed22 | ||
|
359b97a75a | ||
|
d5d89a46a3 | ||
|
6241e099e1 | ||
|
1840415ca8 | ||
|
65ca39e605 | ||
|
1ddad0bad8 | ||
|
3960a560bb | ||
|
3b7b534569 | ||
|
afe85428bb | ||
|
f7365390ef | ||
|
97f6daced4 | ||
|
4313c14723 | ||
|
9a3ff11330 | ||
|
10ade61de9 | ||
|
691e62e7ef | ||
|
19b573a6f7 | ||
|
c5679da3a1 | ||
|
61e7e20823 | ||
|
d836b23fc2 | ||
|
18230b7b34 | ||
|
51636a6711 | ||
|
09109ab50a | ||
|
89e6568e34 | ||
|
3c64ff10bb | ||
|
f807afbf85 | ||
|
9142de99fa | ||
|
cc341b0110 | ||
|
cc866a5bf3 | ||
|
3fe1d67ace | ||
|
6300300270 | ||
|
f1bd655119 | ||
|
d7362d7e3a | ||
|
cf8615dedf | ||
|
70e0022e42 | ||
|
48eeac88e9 | ||
|
a45a401e97 | ||
|
e2f006ac2b | ||
|
0a1e4a57e2 | ||
|
bdf503a444 | ||
|
be75da0ef2 | ||
|
afa91463d6 | ||
|
fad36ed1a1 | ||
|
cfd1309845 | ||
|
a85caead04 | ||
|
f999f540d3 | ||
|
c636ed788a | ||
|
5883e2d935 | ||
|
269d18d9a8 | ||
|
a3c027e626 | ||
|
2461543d98 | ||
|
5b98226afe | ||
|
2eab12df9b | ||
|
445ef068dd | ||
|
cbc30be414 | ||
|
bf74e4f91d | ||
|
62994fdd12 | ||
|
e702d95cfd | ||
|
caf43bbcc2 |
1531 changed files with 241951 additions and 30802 deletions
|
@ -1,23 +0,0 @@
|
||||||
on: [pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
builds:
|
|
||||||
name: Builds
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go_versions: [ '1.21', '1.22' ]
|
|
||||||
fail-fast: false
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: '${{ matrix.go_versions }}'
|
|
||||||
|
|
||||||
- name: Build binary
|
|
||||||
run: make
|
|
||||||
|
|
||||||
- name: Check dirty suffix
|
|
||||||
run: if [[ $(make version) == *"dirty"* ]]; then echo "Version has dirty suffix" && exit 1; fi
|
|
|
@ -1,20 +0,0 @@
|
||||||
on: [pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
dco:
|
|
||||||
name: DCO
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: '1.22'
|
|
||||||
|
|
||||||
- name: Run commit format checker
|
|
||||||
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v3
|
|
||||||
with:
|
|
||||||
from: 'origin/${{ github.event.pull_request.base.ref }}'
|
|
|
@ -1,21 +0,0 @@
|
||||||
on: [pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
vulncheck:
|
|
||||||
name: Vulncheck
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: '1.22.11'
|
|
||||||
|
|
||||||
- name: Install govulncheck
|
|
||||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
|
||||||
|
|
||||||
- name: Run govulncheck
|
|
||||||
run: govulncheck ./...
|
|
48
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
48
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -1,48 +0,0 @@
|
||||||
name: Bug report
|
|
||||||
description: Create a report to help us improve
|
|
||||||
labels:
|
|
||||||
- kind/bug
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Thank you for taking the time to report a bug!
|
|
||||||
If this is a security issue please report it to the [Distributions Security Mailing List](mailto:cncf-distribution-security@lists.cncf.io).
|
|
||||||
- type: textarea
|
|
||||||
id: description
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
description: Please give a clear and concise description of the bug
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: repro
|
|
||||||
attributes:
|
|
||||||
label: Reproduce
|
|
||||||
description: Steps to reproduce the bug
|
|
||||||
placeholder: |
|
|
||||||
1. start registry version X ...
|
|
||||||
2. `docker push image:tag` ...
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: expected
|
|
||||||
attributes:
|
|
||||||
label: Expected behavior
|
|
||||||
description: What is the expected behavior?
|
|
||||||
placeholder: |
|
|
||||||
E.g. "registry returns an incorrect API error"
|
|
||||||
- type: textarea
|
|
||||||
id: version
|
|
||||||
attributes:
|
|
||||||
label: registry version
|
|
||||||
description: Output of `registry --version`. Alternatively tell us the docker image tag.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: additional
|
|
||||||
attributes:
|
|
||||||
label: Additional Info
|
|
||||||
description: Additional info you want to provide such as logs, system info, environment, etc.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
8
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,8 +0,0 @@
|
||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: Security and Vulnerabilities
|
|
||||||
url: https://github.com/distribution/distribution/blob/main/SECURITY.md
|
|
||||||
about: Please report any security issues or vulnerabilities responsibly to the distribution maintainers team. Please do not use the public issue tracker.
|
|
||||||
- name: Questions and Discussions
|
|
||||||
url: https://github.com/distribution/distribution/discussions/new/choose
|
|
||||||
about: Use Github Discussions to ask questions and/or open discussion topics.
|
|
12
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
12
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
@ -1,12 +0,0 @@
|
||||||
name: Feature request
|
|
||||||
description: Missing functionality? Come tell us about it!
|
|
||||||
labels:
|
|
||||||
- kind/feature
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: description
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
description: What is the feature you want to see?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
|
@ -1,8 +0,0 @@
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
61
.github/labeler.yml
vendored
61
.github/labeler.yml
vendored
|
@ -1,61 +0,0 @@
|
||||||
area/api:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- registry/api/**
|
|
||||||
- registry/handlers/**
|
|
||||||
area/auth:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- registry/auth/**
|
|
||||||
area/build:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- Makefile
|
|
||||||
- Dockerfile
|
|
||||||
- docker-bake.hcl
|
|
||||||
- dockerfiles/**
|
|
||||||
area/cache:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- registry/storage/cache/**
|
|
||||||
area/ci:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- .github/**
|
|
||||||
- tests/**
|
|
||||||
- testutil/**
|
|
||||||
area/config:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- configuration/**
|
|
||||||
area/docs:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- README.md
|
|
||||||
- docs/**/*.md
|
|
||||||
area/proxy:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- registry/proxy/**
|
|
||||||
area/storage:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- registry/storage/**
|
|
||||||
area/storage/azure:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- registry/storage/driver/azure/**
|
|
||||||
area/storage/gcs:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- registry/storage/driver/gcs/**
|
|
||||||
area/storage/s3:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- registry/storage/driver/s3-aws/**
|
|
||||||
dependencies:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file:
|
|
||||||
- vendor/**
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
115
.github/workflows/build.yml
vendored
115
.github/workflows/build.yml
vendored
|
@ -1,13 +1,8 @@
|
||||||
name: build
|
name: build
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
|
||||||
- 'release/*'
|
- 'release/*'
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
@ -15,138 +10,75 @@ on:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DOCKERHUB_SLUG: distribution/distribution
|
DOCKERHUB_SLUG: distribution/distribution
|
||||||
GHCR_SLUG: ghcr.io/${{ github.repository }}
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read # to fetch code (actions/checkout)
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
go:
|
|
||||||
- 1.21.8
|
|
||||||
- 1.22.1
|
|
||||||
target:
|
|
||||||
- test-coverage
|
|
||||||
- test-cloud-storage
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
-
|
|
||||||
name: Set up Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ${{ matrix.go }}
|
|
||||||
-
|
|
||||||
name: Test
|
|
||||||
run: |
|
|
||||||
make ${{ matrix.target }}
|
|
||||||
-
|
|
||||||
name: Codecov
|
|
||||||
uses: codecov/codecov-action@v4
|
|
||||||
with:
|
|
||||||
directory: ./
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
permissions:
|
|
||||||
contents: write # to create GitHub release (softprops/action-gh-release)
|
|
||||||
packages: write # so we can push the image to GHCR
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
|
||||||
- test
|
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
-
|
-
|
||||||
name: Docker meta
|
name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v3
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ env.DOCKERHUB_SLUG }}
|
${{ env.DOCKERHUB_SLUG }}
|
||||||
${{ env.GHCR_SLUG }}
|
|
||||||
### versioning strategy
|
### versioning strategy
|
||||||
### push semver tag v3.2.1 on main (default branch)
|
### push semver tag v2.9.0 on main (default branch)
|
||||||
# distribution/distribution:3.2.1
|
# distribution/distribution:2.9.0
|
||||||
# distribution/distribution:3.2
|
|
||||||
# distribution/distribution:3
|
|
||||||
# distribution/distribution:latest
|
# distribution/distribution:latest
|
||||||
### push semver prelease tag v3.0.0-beta.1 on main (default branch)
|
### push semver tag v2.8.0 on release/2.8 branch
|
||||||
# distribution/distribution:3.0.0-beta.1
|
# distribution/distribution:2.8.0
|
||||||
### push on main
|
### push on main
|
||||||
# distribution/distribution:edge
|
# distribution/distribution:edge
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
type=semver,pattern={{major}}
|
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
type=edge
|
# don't create latest tag on release/2.x
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
labels: |
|
labels: |
|
||||||
org.opencontainers.image.title=Distribution
|
org.opencontainers.image.title=Distribution
|
||||||
org.opencontainers.image.description=The toolkit to pack, ship, store, and distribute container content
|
org.opencontainers.image.description=The toolkit to pack, ship, store, and deliver container content
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v1
|
||||||
-
|
|
||||||
name: Login to DockerHub
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
-
|
|
||||||
name: Log in to GitHub Container registry
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
-
|
-
|
||||||
name: Build artifacts
|
name: Build artifacts
|
||||||
uses: docker/bake-action@v4
|
uses: docker/bake-action@v1
|
||||||
with:
|
with:
|
||||||
targets: artifact-all
|
targets: artifact-all
|
||||||
-
|
-
|
||||||
name: Rename provenance
|
name: Move artifacts
|
||||||
run: |
|
|
||||||
for pdir in ./bin/*/; do
|
|
||||||
(
|
|
||||||
cd "$pdir"
|
|
||||||
binname=$(find . -name '*.tar.gz')
|
|
||||||
filename=$(basename "${binname%.tar.gz}")
|
|
||||||
mv "provenance.json" "${filename}.provenance.json"
|
|
||||||
)
|
|
||||||
done
|
|
||||||
-
|
|
||||||
name: Move and list artifacts
|
|
||||||
run: |
|
run: |
|
||||||
mv ./bin/**/* ./bin/
|
mv ./bin/**/* ./bin/
|
||||||
tree -nh ./bin
|
|
||||||
-
|
-
|
||||||
name: Upload artifacts
|
name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4.3.0
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: registry
|
name: registry
|
||||||
path: ./bin/*
|
path: ./bin/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
-
|
||||||
|
name: Login to DockerHub
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Build image
|
name: Build image
|
||||||
uses: docker/bake-action@v4
|
uses: docker/bake-action@v1
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
./docker-bake.hcl
|
./docker-bake.hcl
|
||||||
${{ steps.meta.outputs.bake-file }}
|
${{ steps.meta.outputs.bake-file }}
|
||||||
targets: image-all
|
targets: image-all
|
||||||
push: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
|
push: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
-
|
-
|
||||||
name: GitHub Release
|
name: GitHub Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
|
@ -155,7 +87,6 @@ jobs:
|
||||||
draft: true
|
draft: true
|
||||||
files: |
|
files: |
|
||||||
bin/*.tar.gz
|
bin/*.tar.gz
|
||||||
bin/*.provenance.json
|
|
||||||
bin/*.sha256
|
bin/*.sha256
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
50
.github/workflows/ci.yml
vendored
Normal file
50
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
BUILDTAGS: "include_oss,include_gcs"
|
||||||
|
CGO_ENABLED: 1
|
||||||
|
GO111MODULE: "auto"
|
||||||
|
GOPATH: ${{ github.workspace }}
|
||||||
|
GOOS: linux
|
||||||
|
COMMIT_RANGE: ${{ github.event_name == 'pull_request' && format('{0}..{1}',github.event.pull_request.base.sha, github.event.pull_request.head.sha) || github.sha }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
path: src/github.com/docker/distribution
|
||||||
|
fetch-depth: 50
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.19.9
|
||||||
|
|
||||||
|
- name: Dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get -q update
|
||||||
|
sudo -E apt-get -yq --no-install-suggests --no-install-recommends install python2-minimal
|
||||||
|
cd /tmp && go install github.com/vbatts/git-validation@latest
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: ./src/github.com/docker/distribution
|
||||||
|
run: |
|
||||||
|
DCO_VERBOSITY=-q script/validate/dco
|
||||||
|
GO111MODULE=on script/setup/install-dev-tools
|
||||||
|
script/validate/vendor
|
||||||
|
go build .
|
||||||
|
make check
|
||||||
|
make build
|
||||||
|
make binaries
|
||||||
|
if [ "$GOOS" = "linux" ]; then make coverage ; fi
|
||||||
|
|
||||||
|
- uses: codecov/codecov-action@v1
|
||||||
|
with:
|
||||||
|
directory: ./src/github.com/docker/distribution
|
55
.github/workflows/codeql-analysis.yml
vendored
55
.github/workflows/codeql-analysis.yml
vendored
|
@ -1,55 +0,0 @@
|
||||||
name: CodeQL
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 12 * * 6'
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
- 'release/*'
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read # to fetch code (actions/checkout)
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
permissions:
|
|
||||||
contents: read # to fetch code (actions/checkout)
|
|
||||||
security-events: write # to upload SARIF results (github/codeql-action/analyze)
|
|
||||||
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language:
|
|
||||||
- go
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
-
|
|
||||||
name: Checkout HEAD on PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
git checkout HEAD^2
|
|
||||||
-
|
|
||||||
name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v3.22.12
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
-
|
|
||||||
name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v3.22.12
|
|
||||||
-
|
|
||||||
name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v3.22.12
|
|
56
.github/workflows/conformance.yml
vendored
56
.github/workflows/conformance.yml
vendored
|
@ -1,56 +0,0 @@
|
||||||
name: conformance
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read # to fetch code (actions/checkout)
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
run-conformance-test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
-
|
|
||||||
name: Build image
|
|
||||||
uses: docker/bake-action@v4
|
|
||||||
with:
|
|
||||||
targets: image-local
|
|
||||||
-
|
|
||||||
name: Start distribution server
|
|
||||||
run: |
|
|
||||||
IP=`hostname -I | awk '{print $1}'`
|
|
||||||
echo "IP=$IP" >> $GITHUB_ENV
|
|
||||||
echo "OCI_ROOT_URL=http://$IP:5000" >> $GITHUB_ENV
|
|
||||||
DISTRIBUTION_REF="registry:local"
|
|
||||||
docker run --rm -p 5000:5000 -e REGISTRY_STORAGE_DELETE_ENABLED=true -idt "registry:local"
|
|
||||||
-
|
|
||||||
name: Run OCI Distribution Spec conformance tests
|
|
||||||
uses: opencontainers/distribution-spec@v1.0.1
|
|
||||||
env:
|
|
||||||
OCI_ROOT_URL: ${{ env.OCI_ROOT_URL }}
|
|
||||||
OCI_NAMESPACE: oci-conformance/distribution-test
|
|
||||||
OCI_TEST_PULL: 1
|
|
||||||
OCI_TEST_PUSH: 1
|
|
||||||
OCI_TEST_CONTENT_DISCOVERY: 1
|
|
||||||
OCI_TEST_CONTENT_MANAGEMENT: 1
|
|
||||||
OCI_HIDE_SKIPPED_WORKFLOWS: 1
|
|
||||||
-
|
|
||||||
name: Move test results
|
|
||||||
run: mkdir -p .out/ && mv {report.html,junit.xml} .out/
|
|
||||||
-
|
|
||||||
name: Upload test results
|
|
||||||
uses: actions/upload-artifact@v4.3.0
|
|
||||||
with:
|
|
||||||
name: oci-test-results-${{ github.sha }}
|
|
||||||
path: .out/
|
|
||||||
if-no-files-found: error
|
|
35
.github/workflows/dockerhub-readme.yml
vendored
35
.github/workflows/dockerhub-readme.yml
vendored
|
@ -1,35 +0,0 @@
|
||||||
name: dockerhub-readme
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/dockerhub-readme.yml'
|
|
||||||
- 'docs/dockerhub.md'
|
|
||||||
|
|
||||||
env:
|
|
||||||
DOCKERHUB_SLUG: distribution/distribution
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
-
|
|
||||||
name: Update Docker Hub README
|
|
||||||
uses: peter-evans/dockerhub-description@v4
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
repository: ${{ env.DOCKERHUB_SLUG }}
|
|
||||||
readme-filepath: ./docs/dockerhub.md
|
|
72
.github/workflows/docs.yml
vendored
72
.github/workflows/docs.yml
vendored
|
@ -1,72 +0,0 @@
|
||||||
name: docs
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- .github/workflows/docs.yml
|
|
||||||
- dockerfiles/docs.Dockerfile
|
|
||||||
- docs/**
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
# Build job
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
# Build the site and upload artifacts using actions/upload-pages-artifact
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Setup Pages
|
|
||||||
id: pages
|
|
||||||
uses: actions/configure-pages@v4
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Build docs
|
|
||||||
uses: docker/bake-action@v4
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
docker-bake.hcl
|
|
||||||
targets: docs-export
|
|
||||||
provenance: false
|
|
||||||
set: |
|
|
||||||
*.cache-from=type=gha,scope=docs
|
|
||||||
*.cache-to=type=gha,scope=docs,mode=max
|
|
||||||
- name: Fix permissions
|
|
||||||
run: |
|
|
||||||
chmod -c -R +rX "./build/docs" | while read line; do
|
|
||||||
echo "::warning title=Invalid file permissions automatically fixed::$line"
|
|
||||||
done
|
|
||||||
- name: Upload Pages artifact
|
|
||||||
uses: actions/upload-pages-artifact@v3
|
|
||||||
with:
|
|
||||||
path: ./build/docs
|
|
||||||
|
|
||||||
# Deploy job
|
|
||||||
deploy:
|
|
||||||
# Add a dependency to the build job
|
|
||||||
needs: build
|
|
||||||
|
|
||||||
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
|
|
||||||
permissions:
|
|
||||||
pages: write # to deploy to Pages
|
|
||||||
id-token: write # to verify the deployment originates from an appropriate source
|
|
||||||
|
|
||||||
# Deploy to the github-pages environment
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
|
||||||
|
|
||||||
# Specify runner + deployment step
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v4 # or the latest "vX.X.X" version tag for this action
|
|
56
.github/workflows/e2e.yml
vendored
56
.github/workflows/e2e.yml
vendored
|
@ -1,56 +0,0 @@
|
||||||
name: e2e
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
- 'release/*'
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read # to fetch code (actions/checkout)
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
run-e2e-test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
-
|
|
||||||
name: Build image
|
|
||||||
uses: docker/bake-action@v4
|
|
||||||
with:
|
|
||||||
targets: image-local
|
|
||||||
-
|
|
||||||
name: Start distribution server
|
|
||||||
run: |
|
|
||||||
docker run --rm -p 5000:5000 -p 5001:5001 -idt "registry:local"
|
|
||||||
-
|
|
||||||
name: Tests
|
|
||||||
run: |
|
|
||||||
bash ./tests/push.sh 127.0.0.0
|
|
||||||
|
|
||||||
run-e2e-test-s3-storage:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
-
|
|
||||||
name: Start E2E environment
|
|
||||||
run: |
|
|
||||||
make start-e2e-s3-env
|
|
||||||
|
|
||||||
- name: Tests
|
|
||||||
run: |
|
|
||||||
bash ./tests/push.sh 127.0.0.0
|
|
||||||
make stop-e2e-s3-env
|
|
25
.github/workflows/fossa.yml
vendored
25
.github/workflows/fossa.yml
vendored
|
@ -1,25 +0,0 @@
|
||||||
name: FOSSA License Scanning
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
|
||||||
- pull_request
|
|
||||||
- push
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read # to fetch code (actions/checkout)
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
scan-license:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Run FOSSA scan and upload build data
|
|
||||||
uses: fossa-contrib/fossa-action@v3
|
|
||||||
with:
|
|
||||||
fossa-api-key: cac3dc8d4f2ba86142f6c0f2199a160f
|
|
19
.github/workflows/label.yaml
vendored
19
.github/workflows/label.yaml
vendored
|
@ -1,19 +0,0 @@
|
||||||
name: Pull Request Labeler
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
labeler:
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/labeler@v5
|
|
||||||
with:
|
|
||||||
dot: true
|
|
60
.github/workflows/scorecards.yml
vendored
60
.github/workflows/scorecards.yml
vendored
|
@ -1,60 +0,0 @@
|
||||||
name: Scorecards supply-chain security
|
|
||||||
on:
|
|
||||||
# Only the default branch is supported.
|
|
||||||
branch_protection_rule:
|
|
||||||
schedule:
|
|
||||||
- cron: '26 0 * * 0'
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
# Declare default permissions as read only.
|
|
||||||
permissions: read-all
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analysis:
|
|
||||||
name: Scorecards analysis
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
# Needed to upload the results to code-scanning dashboard.
|
|
||||||
security-events: write
|
|
||||||
# Used to receive a badge.
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: "Checkout code"
|
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # tag=v4.1.1
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: "Run analysis"
|
|
||||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # tag=v2.3.1
|
|
||||||
with:
|
|
||||||
results_file: results.sarif
|
|
||||||
results_format: sarif
|
|
||||||
# (Optional) Read-only PAT token. Uncomment the `repo_token` line below if:
|
|
||||||
# - you want to enable the Branch-Protection check on a *public* repository, or
|
|
||||||
# - you are installing Scorecards on a *private* repository
|
|
||||||
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
|
|
||||||
# repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
|
|
||||||
|
|
||||||
# Publish the results for public repositories to enable scorecard badges. For more details, see
|
|
||||||
# https://github.com/ossf/scorecard-action#publishing-results.
|
|
||||||
# For private repositories, `publish_results` will automatically be set to `false`, regardless
|
|
||||||
# of the value entered here.
|
|
||||||
publish_results: true
|
|
||||||
|
|
||||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
|
||||||
# format to the repository Actions tab.
|
|
||||||
- name: "Upload artifact"
|
|
||||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # tag=v4.3.0
|
|
||||||
with:
|
|
||||||
name: SARIF file
|
|
||||||
path: results.sarif
|
|
||||||
retention-days: 5
|
|
||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
|
||||||
- name: "Upload to code-scanning"
|
|
||||||
uses: github/codeql-action/upload-sarif@1500a131381b66de0c52ac28abb13cd79f4b7ecc # tag=v2.22.12
|
|
||||||
with:
|
|
||||||
sarif_file: results.sarif
|
|
||||||
|
|
38
.github/workflows/validate.yml
vendored
38
.github/workflows/validate.yml
vendored
|
@ -1,38 +0,0 @@
|
||||||
name: validate
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
- 'release/*'
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read # to fetch code (actions/checkout)
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
validate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
target:
|
|
||||||
- lint
|
|
||||||
- validate-vendor
|
|
||||||
- validate-git
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
-
|
|
||||||
name: Run
|
|
||||||
run: |
|
|
||||||
make ${{ matrix.target }}
|
|
||||||
env:
|
|
||||||
COMMIT_RANGE: ${{ format('{0}..{1}', github.sha, 'HEAD') }}
|
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -36,10 +36,3 @@ bin/*
|
||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
.idea/*
|
.idea/*
|
||||||
|
|
||||||
tests/miniodata
|
|
||||||
|
|
||||||
# Docs
|
|
||||||
**/.hugo_build.lock
|
|
||||||
docs/resources
|
|
||||||
docs/public
|
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
|
- structcheck
|
||||||
|
- varcheck
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- unconvert
|
- unconvert
|
||||||
- gofmt
|
- gofmt
|
||||||
- goimports
|
- goimports
|
||||||
- revive
|
- golint
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- govet
|
- vet
|
||||||
- unused
|
- unused
|
||||||
- misspell
|
- misspell
|
||||||
- bodyclose
|
disable:
|
||||||
- prealloc
|
|
||||||
- errcheck
|
- errcheck
|
||||||
- tparallel
|
|
||||||
|
|
||||||
linters-settings:
|
run:
|
||||||
revive:
|
deadline: 2m
|
||||||
rules:
|
skip-dirs:
|
||||||
# TODO(thaJeztah): temporarily disabled the "unused-parameter" check.
|
- vendor
|
||||||
# It produces many warnings, and some of those may need to be looked at.
|
|
||||||
- name: unused-parameter
|
|
||||||
disabled: true
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
deadline: 2m
|
exclude-rules:
|
||||||
exlude-dirs:
|
# io/ioutil is deprecated, but won't be removed until Go v2. It's safe to ignore for the release/2.8 branch.
|
||||||
- vendor
|
- text: "SA1019: \"io/ioutil\" has been deprecated since Go 1.16"
|
||||||
|
linters:
|
||||||
|
- staticcheck
|
||||||
|
|
243
.mailmap
243
.mailmap
|
@ -1,194 +1,51 @@
|
||||||
Aaron Lehmann <alehmann@netflix.com>
|
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.github.com>
|
||||||
Aaron Lehmann <alehmann@netflix.com> <aaron.lehmann@docker.com>
|
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@gmail.com>
|
||||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
|
Olivier Gambier <olivier@docker.com> Olivier Gambier <dmp42@users.noreply.github.com>
|
||||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> <suda.akihiro@lab.ntt.co.jp>
|
Brian Bland <brian.bland@docker.com> Brian Bland <r4nd0m1n4t0r@gmail.com>
|
||||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> <suda.kyoto@gmail.com>
|
Brian Bland <brian.bland@docker.com> Brian Bland <brian.t.bland@gmail.com>
|
||||||
Alexander Morozov <lk4d4math@gmail.com>
|
Josh Hawn <josh.hawn@docker.com> Josh Hawn <jlhawn@berkeley.edu>
|
||||||
Alexander Morozov <lk4d4math@gmail.com> <lk4d4@docker.com>
|
Richard Scothern <richard.scothern@docker.com> Richard <richard.scothern@gmail.com>
|
||||||
Anders Ingemann <aim@orbit.online>
|
Richard Scothern <richard.scothern@docker.com> Richard Scothern <richard.scothern@gmail.com>
|
||||||
Andrew Meredith <andymeredith@gmail.com>
|
Andrew Meredith <andymeredith@gmail.com> Andrew Meredith <kendru@users.noreply.github.com>
|
||||||
Andrew Meredith <andymeredith@gmail.com> <kendru@users.noreply.github.com>
|
harche <p.harshal@gmail.com> harche <harche@users.noreply.github.com>
|
||||||
Andrey Smirnov <andrey.smirnov@siderolabs.com>
|
Jessie Frazelle <jessie@docker.com> <jfrazelle@users.noreply.github.com>
|
||||||
Andrii Soldatenko <andrii.soldatenko@gmail.com>
|
Sharif Nassar <sharif@mrwacky.com> Sharif Nassar <mrwacky42@users.noreply.github.com>
|
||||||
Andrii Soldatenko <andrii.soldatenko@gmail.com> <andrii.soldatenko@dynatrace.com>
|
Sven Dowideit <SvenDowideit@home.org.au> Sven Dowideit <SvenDowideit@users.noreply.github.com>
|
||||||
Anthony Ramahay <thewolt@gmail.com>
|
Vincent Giersch <vincent.giersch@ovh.net> Vincent Giersch <vincent@giersch.fr>
|
||||||
Antonio Murdaca <antonio.murdaca@gmail.com>
|
davidli <wenquan.li@hp.com> davidli <wenquan.li@hpe.com>
|
||||||
Antonio Murdaca <antonio.murdaca@gmail.com> <amurdaca@redhat.com>
|
Omer Cohen <git@omer.io> Omer Cohen <git@omerc.net>
|
||||||
Antonio Murdaca <antonio.murdaca@gmail.com> <me@runcom.ninja>
|
Eric Yang <windfarer@gmail.com> Eric Yang <Windfarer@users.noreply.github.com>
|
||||||
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@linux.com>
|
Nikita Tarasov <nikita@mygento.ru> Nikita <luckyraul@users.noreply.github.com>
|
||||||
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@redhat.com>
|
Yu Wang <yuwa@microsoft.com> yuwaMSFT2 <yuwa@microsoft.com>
|
||||||
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@users.noreply.github.com>
|
|
||||||
Austin Vazquez <macedonv@amazon.com>
|
|
||||||
Benjamin Schanzel <benjamin.schanzel@bmw.de>
|
|
||||||
Brian Bland <brian.t.bland@gmail.com>
|
|
||||||
Brian Bland <brian.t.bland@gmail.com> <brian.bland@docker.com>
|
|
||||||
Brian Bland <brian.t.bland@gmail.com> <r4nd0m1n4t0r@gmail.com>
|
|
||||||
Chad Faragher <wyckster@hotmail.com>
|
|
||||||
Cory Snider <csnider@mirantis.com>
|
|
||||||
CrazyMax <github@crazymax.dev>
|
|
||||||
CrazyMax <github@crazymax.dev> <1951866+crazy-max@users.noreply.github.com>
|
|
||||||
CrazyMax <github@crazymax.dev> <crazy-max@users.noreply.github.com>
|
|
||||||
Cristian Staretu <cristian.staretu@gmail.com>
|
|
||||||
Cristian Staretu <cristian.staretu@gmail.com> <unclejack@users.noreply.github.com>
|
|
||||||
Cristian Staretu <cristian.staretu@gmail.com> <unclejacksons@gmail.com>
|
|
||||||
Daniel Nephin <dnephin@gmail.com>
|
|
||||||
Daniel Nephin <dnephin@gmail.com> <dnephin@docker.com>
|
|
||||||
David Karlsson <david.karlsson@docker.com>
|
|
||||||
David Karlsson <david.karlsson@docker.com> <35727626+dvdksn@users.noreply.github.com>
|
|
||||||
David Wu <dwu7401@gmail.com>
|
|
||||||
David Wu <dwu7401@gmail.com> <david.wu@docker.com>
|
|
||||||
Derek McGowan <derek@mcg.dev>
|
|
||||||
Derek McGowan <derek@mcg.dev> <derek@mcgstyle.net>
|
|
||||||
Dimitar Kostadinov <dimitar.kostadinov@sap.com>
|
|
||||||
Doug Davis <dug@us.ibm.com>
|
|
||||||
Doug Davis <dug@us.ibm.com> <duglin@users.noreply.github.com>
|
|
||||||
Emmanuel Ferdman <emmanuelferdman@gmail.com>
|
|
||||||
Eng Zer Jun <engzerjun@gmail.com>
|
|
||||||
Eric Yang <windfarer@gmail.com>
|
|
||||||
Eric Yang <windfarer@gmail.com> <Windfarer@users.noreply.github.com>
|
|
||||||
Eric Yang <windfarer@gmail.com> <qizhao.yang@daocloud.io>
|
|
||||||
Erica Windisch <erica@windisch.us>
|
|
||||||
Erica Windisch <erica@windisch.us> <eric@windisch.us>
|
|
||||||
Guillaume J. Charmes <charmes.guillaume@gmail.com>
|
|
||||||
Guillaume J. Charmes <charmes.guillaume@gmail.com> <guillaume.charmes@dotcloud.com>
|
|
||||||
Guillaume J. Charmes <charmes.guillaume@gmail.com> <guillaume@charmes.net>
|
|
||||||
Guillaume J. Charmes <charmes.guillaume@gmail.com> <guillaume@docker.com>
|
|
||||||
Guillaume J. Charmes <charmes.guillaume@gmail.com> <guillaume@dotcloud.com>
|
|
||||||
Hayley Swimelar <hswimelar@gmail.com>
|
|
||||||
Ismail Alidzhikov <i.alidjikov@gmail.com>
|
|
||||||
Jaime Martinez <jmartinez@gitlab.com>
|
|
||||||
James Hewitt <james.hewitt@uk.ibm.com>
|
|
||||||
Jessica Frazelle <jess@oxide.computer>
|
|
||||||
Jessica Frazelle <jess@oxide.computer> <acidburn@docker.com>
|
|
||||||
Jessica Frazelle <jess@oxide.computer> <acidburn@google.com>
|
|
||||||
Jessica Frazelle <jess@oxide.computer> <acidburn@microsoft.com>
|
|
||||||
Jessica Frazelle <jess@oxide.computer> <jess@docker.com>
|
|
||||||
Jessica Frazelle <jess@oxide.computer> <jess@mesosphere.com>
|
|
||||||
Jessica Frazelle <jess@oxide.computer> <jessfraz@google.com>
|
|
||||||
Jessica Frazelle <jess@oxide.computer> <jfrazelle@users.noreply.github.com>
|
|
||||||
Jessica Frazelle <jess@oxide.computer> <me@jessfraz.com>
|
|
||||||
Jessica Frazelle <jess@oxide.computer> <princess@docker.com>
|
|
||||||
Joao Fernandes <joaofnfernandes@gmail.com>
|
|
||||||
Joao Fernandes <joaofnfernandes@gmail.com> <joao.fernandes@docker.com>
|
|
||||||
João Pereira <484633+joaodrp@users.noreply.github.com>
|
|
||||||
Joffrey F <joffrey@docker.com>
|
|
||||||
Joffrey F <joffrey@docker.com> <f.joffrey@gmail.com>
|
|
||||||
Joffrey F <joffrey@docker.com> <joffrey@dotcloud.com>
|
|
||||||
Johan Euphrosine <proppy@google.com>
|
|
||||||
Johan Euphrosine <proppy@google.com> <proppy@aminche.com>
|
|
||||||
John Howard <github@lowenna.com>
|
|
||||||
John Howard <github@lowenna.com> <jhoward@microsoft.com>
|
|
||||||
Josh Hawn <jlhawn@berkeley.edu>
|
|
||||||
Josh Hawn <jlhawn@berkeley.edu> <josh.hawn@docker.com>
|
|
||||||
Joyce Brum <joycebrumu.u@gmail.com>
|
|
||||||
Joyce Brum <joycebrumu.u@gmail.com> <joycebrum@google.com>
|
|
||||||
Justin Cormack <justin.cormack@docker.com>
|
|
||||||
Justin Cormack <justin.cormack@docker.com> <justin.cormack@unikernel.com>
|
|
||||||
Justin Cormack <justin.cormack@docker.com> <justin@specialbusservice.com>
|
|
||||||
Kirat Singh <kirat.singh@gmail.com>
|
|
||||||
Kirat Singh <kirat.singh@gmail.com> <kirat.singh@beacon.io>
|
|
||||||
Kirat Singh <kirat.singh@gmail.com> <kirat.singh@wsq.io>
|
|
||||||
Kyle Squizzato <ksquizz@gmail.com>
|
|
||||||
Liang Zheng <zhengliang0901@gmail.com>
|
|
||||||
Luca Bruno <lucab@debian.org>
|
|
||||||
Luca Bruno <lucab@debian.org> <luca.bruno@coreos.com>
|
|
||||||
Mahmoud Kandil <47168819+MahmoudKKandil@users.noreply.github.com>
|
|
||||||
Manish Tomar <manish.tomar@docker.com>
|
|
||||||
Manish Tomar <manish.tomar@docker.com> <manishtomar@users.noreply.github.com>
|
|
||||||
Maria Bermudez <bermudez.mt@gmail.com>
|
|
||||||
Maria Bermudez <bermudez.mt@gmail.com> <bermudezmt@users.noreply.github.com>
|
|
||||||
Markus Thömmes <markusthoemmes@me.com>
|
|
||||||
Matt Linville <matt@linville.me>
|
|
||||||
Matt Linville <matt@linville.me> <misty@apache.org>
|
|
||||||
Matt Linville <matt@linville.me> <misty@docker.com>
|
|
||||||
Michael Crosby <crosbymichael@gmail.com>
|
|
||||||
Michael Crosby <crosbymichael@gmail.com> <crosby.michael@gmail.com>
|
|
||||||
Michael Crosby <crosbymichael@gmail.com> <michael@crosbymichael.com>
|
|
||||||
Michael Crosby <crosbymichael@gmail.com> <michael@docker.com>
|
|
||||||
Michael Crosby <crosbymichael@gmail.com> <michael@thepasture.io>
|
|
||||||
Michal Minar <miminar@redhat.com>
|
|
||||||
Michal Minar <miminar@redhat.com> Michal Minář <miminar@redhat.com>
|
|
||||||
Mike Brown <brownwm@us.ibm.com>
|
|
||||||
Mike Brown <brownwm@us.ibm.com> <mikebrow@users.noreply.github.com>
|
|
||||||
Mikel Rychliski <mikel@mikelr.com>
|
|
||||||
Milos Gajdos <milosthegajdos@gmail.com>
|
|
||||||
Milos Gajdos <milosthegajdos@gmail.com> <1392526+milosgajdos@users.noreply.github.com>
|
|
||||||
Milos Gajdos <milosthegajdos@gmail.com> <milosgajdos83@gmail.com>
|
|
||||||
Nikita Tarasov <nikita@mygento.ru>
|
|
||||||
Nikita Tarasov <nikita@mygento.ru> <luckyraul@users.noreply.github.com>
|
|
||||||
Oleg Bulatov <oleg@bulatov.me>
|
|
||||||
Oleg Bulatov <oleg@bulatov.me> <obulatov@redhat.com>
|
|
||||||
Olivier Gambier <olivier@docker.com>
|
|
||||||
Olivier Gambier <olivier@docker.com> <dmp42@users.noreply.github.com>
|
|
||||||
Omer Cohen <git@omer.io>
|
|
||||||
Omer Cohen <git@omer.io> <git@omerc.net>
|
|
||||||
Paul Meyer <49727155+katexochen@users.noreply.github.com>
|
|
||||||
Per Lundberg <perlun@gmail.com>
|
|
||||||
Per Lundberg <perlun@gmail.com> <per.lundberg@ecraft.com>
|
|
||||||
Peter Dave Hello <hsu@peterdavehello.org>
|
|
||||||
Peter Dave Hello <hsu@peterdavehello.org> <PeterDaveHello@users.noreply.github.com>
|
|
||||||
Phil Estes <estesp@gmail.com>
|
|
||||||
Phil Estes <estesp@gmail.com> <estesp@amazon.com>
|
|
||||||
Phil Estes <estesp@gmail.com> <estesp@linux.vnet.ibm.com>
|
|
||||||
Richard Scothern <richard.scothern@gmail.com>
|
|
||||||
Richard Scothern <richard.scothern@gmail.com> <richard.scothern@docker.com>
|
|
||||||
Rober Morales-Chaparro <rober.morales@rstor.io>
|
|
||||||
Rober Morales-Chaparro <rober.morales@rstor.io> <rober@rstor.io>
|
|
||||||
Robin Ketelbuters <robin.ketelbuters@gmail.com>
|
|
||||||
Sebastiaan van Stijn <github@gone.nl>
|
|
||||||
Sebastiaan van Stijn <github@gone.nl> <moby@example.com>
|
|
||||||
Sebastiaan van Stijn <github@gone.nl> <sebastiaan@ws-key-sebas3.dpi1.dpi>
|
|
||||||
Sebastiaan van Stijn <github@gone.nl> <thaJeztah@users.noreply.github.com>
|
|
||||||
Sharif Nassar <sharif@mrwacky.com>
|
|
||||||
Sharif Nassar <sharif@mrwacky.com> <mrwacky42@users.noreply.github.com>
|
|
||||||
Solomon Hykes <solomon@dagger.io>
|
|
||||||
Solomon Hykes <solomon@dagger.io> <s@docker.com>
|
|
||||||
Solomon Hykes <solomon@dagger.io> <solomon.hykes@dotcloud.com>
|
|
||||||
Solomon Hykes <solomon@dagger.io> <solomon@docker.com>
|
|
||||||
Solomon Hykes <solomon@dagger.io> <solomon@dotcloud.com>
|
|
||||||
Stephen Day <stevvooe@gmail.com>
|
|
||||||
Stephen Day <stevvooe@gmail.com> <stephen.day@docker.com>
|
|
||||||
Stephen Day <stevvooe@gmail.com> <stevvooe@users.noreply.github.com>
|
|
||||||
Steven Kalt <SKalt@users.noreply.github.com>
|
|
||||||
Sven Dowideit <SvenDowideit@home.org.au>
|
|
||||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@users.noreply.github.com>
|
|
||||||
Sylvain DESGRAIS <sylvain.desgrais@gmail.com>
|
|
||||||
Tadeusz Dudkiewicz <tadeusz.dudkiewicz@rtbhouse.com>
|
|
||||||
Tibor Vass <teabee89@gmail.com>
|
|
||||||
Tibor Vass <teabee89@gmail.com> <tibor@docker.com>
|
|
||||||
Tibor Vass <teabee89@gmail.com> <tiborvass@users.noreply.github.com>
|
|
||||||
Victor Vieux <victorvieux@gmail.com>
|
|
||||||
Victor Vieux <victorvieux@gmail.com> <dev@vvieux.com>
|
|
||||||
Victor Vieux <victorvieux@gmail.com> <victor.vieux@docker.com>
|
|
||||||
Victor Vieux <victorvieux@gmail.com> <victor.vieux@dotcloud.com>
|
|
||||||
Victor Vieux <victorvieux@gmail.com> <victor@docker.com>
|
|
||||||
Victor Vieux <victorvieux@gmail.com> <victor@dotcloud.com>
|
|
||||||
Victor Vieux <victorvieux@gmail.com> <victorvieux@gmail.com>
|
|
||||||
Victor Vieux <victorvieux@gmail.com> <vieux@docker.com>
|
|
||||||
Victoria Bialas <victoria.bialas@docker.com>
|
|
||||||
Victoria Bialas <victoria.bialas@docker.com> <londoncalling@users.noreply.github.com>
|
|
||||||
Vincent Batts <vbatts@redhat.com>
|
|
||||||
Vincent Batts <vbatts@redhat.com> <vbatts@hashbangbash.com>
|
|
||||||
Vincent Demeester <vincent.demeester@docker.com>
|
|
||||||
Vincent Demeester <vincent.demeester@docker.com> <vincent+github@demeester.fr>
|
|
||||||
Vincent Demeester <vincent.demeester@docker.com> <vincent@demeester.fr>
|
|
||||||
Vincent Demeester <vincent.demeester@docker.com> <vincent@sbr.pm>
|
|
||||||
Vincent Giersch <vincent@giersch.fr>
|
|
||||||
Vincent Giersch <vincent@giersch.fr> <vincent.giersch@ovh.net>
|
|
||||||
Wang Yan <wangyan@vmware.com>
|
|
||||||
Wen-Quan Li <legendarilylwq@gmail.com>
|
|
||||||
Wen-Quan Li <legendarilylwq@gmail.com> <wenquan.li@hp.com>
|
|
||||||
Wen-Quan Li <legendarilylwq@gmail.com> <wenquan.li@hpe.com>
|
|
||||||
Yu Wang <yuwa@microsoft.com>
|
|
||||||
Yu Wang <yuwa@microsoft.com> Yu Wang (UC) <yuwa@microsoft.com>
|
Yu Wang <yuwa@microsoft.com> Yu Wang (UC) <yuwa@microsoft.com>
|
||||||
baojiangnan <baojiangnan@meituan.com>
|
Olivier Gambier <olivier@docker.com> dmp <dmp@loaner.local>
|
||||||
baojiangnan <baojiangnan@meituan.com> <baojn1998@163.com>
|
Olivier Gambier <olivier@docker.com> Olivier <o+github@gambier.email>
|
||||||
erezrokah <erezrokah@users.noreply.github.com>
|
Olivier Gambier <olivier@docker.com> Olivier <dmp42@users.noreply.github.com>
|
||||||
goodactive <goodactive@qq.com>
|
Elsan Li 李楠 <elsanli@tencent.com> elsanli(李楠) <elsanli@tencent.com>
|
||||||
gotgelf <gotgelf@gmail.com>
|
Rui Cao <ruicao@alauda.io> ruicao <ruicao@alauda.io>
|
||||||
guoguangwu <guoguangwug@gmail.com>
|
Gwendolynne Barr <gwendolynne.barr@docker.com> gbarr01 <gwendolynne.barr@docker.com>
|
||||||
harche <p.harshal@gmail.com>
|
Haibing Zhou 周海兵 <zhouhaibing089@gmail.com> zhouhaibing089 <zhouhaibing089@gmail.com>
|
||||||
harche <p.harshal@gmail.com> <harche@users.noreply.github.com>
|
Feng Honglin <tifayuki@gmail.com> tifayuki <tifayuki@gmail.com>
|
||||||
icefed <zlwangel@gmail.com>
|
Helen Xie <xieyulin821@harmonycloud.cn> Helen-xie <xieyulin821@harmonycloud.cn>
|
||||||
oliver-goetz <o.goetz@sap.com>
|
Mike Brown <brownwm@us.ibm.com> Mike Brown <mikebrow@users.noreply.github.com>
|
||||||
xiaoxiangxianzi <zhaoyizheng@outlook.com>
|
Manish Tomar <manish.tomar@docker.com> Manish Tomar <manishtomar@users.noreply.github.com>
|
||||||
|
Sakeven Jiang <jc5930@sina.cn> sakeven <jc5930@sina.cn>
|
||||||
|
Milos Gajdos <milosgajdos83@gmail.com> Milos Gajdos <milosgajdos@users.noreply.github.com>
|
||||||
|
Derek McGowan <derek@mcgstyle.net> Derek McGowa <dmcgowan@users.noreply.github.com>
|
||||||
|
Adrian Plata <adrian.plata@docker.com> Adrian Plata <@users.noreply.github.com>
|
||||||
|
Sebastiaan van Stijn <github@gone.nl> Sebastiaan van Stijn <thaJeztah@users.noreply.github.com>
|
||||||
|
Vishesh Jindal <vishesh92@gmail.com> Vishesh Jindal <vishesh92@users.noreply.github.com>
|
||||||
|
Wang Yan <wangyan@vmware.com> Wang Yan <wy65701436@users.noreply.github.com>
|
||||||
|
Chris Patterson <chrispat@github.com> Chris Patterson <chrispat@users.noreply.github.com>
|
||||||
|
Eohyung Lee <liquidnuker@gmail.com> Eohyung Lee <leoh0@users.noreply.github.com>
|
||||||
|
João Pereira <484633+joaodrp@users.noreply.github.com>
|
||||||
|
Smasherr <soundcracker@gmail.com> Smasherr <Smasherr@users.noreply.github.com>
|
||||||
|
Thomas Berger <loki@lokis-chaos.de> Thomas Berger <tbe@users.noreply.github.com>
|
||||||
|
Samuel Karp <skarp@amazon.com> Samuel Karp <samuelkarp@users.noreply.github.com>
|
||||||
|
Justin Cormack <justin.cormack@docker.com>
|
||||||
|
sayboras <sayboras@yahoo.com>
|
||||||
|
CrazyMax <github@crazymax.dev> <1951866+crazy-max@users.noreply.github.com>
|
||||||
|
Hayley Swimelar <hswimelar@gmail.com>
|
||||||
|
Jose D. Gomez R <jose.gomez@suse.com>
|
||||||
|
Shengjing Zhu <zhsj@debian.org>
|
||||||
|
Silvin Lubecki <31478878+silvin-lubecki@users.noreply.github.com>
|
||||||
|
|
11
ADOPTERS.md
11
ADOPTERS.md
|
@ -1,11 +0,0 @@
|
||||||
Docker Hub https://hub.docker.com/
|
|
||||||
|
|
||||||
GitLab Container Registry https://docs.gitlab.com/ee/user/packages/container_registry/
|
|
||||||
|
|
||||||
GitHub Container Registry https://docs.github.com/en/free-pro-team@latest/packages/guides/about-github-container-registry
|
|
||||||
|
|
||||||
Harbor, CNCF Graduated project https://goharbor.io/
|
|
||||||
|
|
||||||
VMware Harbor Registry https://docs.pivotal.io/partners/vmware-harbor/index.html
|
|
||||||
|
|
||||||
DigitalOcean Container Registry https://www.digitalocean.com/products/container-registry/
|
|
530
AUTHORS
530
AUTHORS
|
@ -1,530 +0,0 @@
|
||||||
# This file lists all individuals having contributed content to the repository.
|
|
||||||
# For how it is generated, see dockerfiles/authors.Dockerfile.
|
|
||||||
|
|
||||||
a-palchikov <deemok@gmail.com>
|
|
||||||
Aaron Lehmann <alehmann@netflix.com>
|
|
||||||
Aaron Schlesinger <aschlesinger@deis.com>
|
|
||||||
Aaron Vinson <avinson.public@gmail.com>
|
|
||||||
Adam Dobrawy <ad-m@users.noreply.github.com>
|
|
||||||
Adam Duke <adam.v.duke@gmail.com>
|
|
||||||
Adam Enger <adamenger@gmail.com>
|
|
||||||
Adam Kaplan <adam.kaplan@redhat.com>
|
|
||||||
Adam Wolfe Gordon <awg@digitalocean.com>
|
|
||||||
AdamKorcz <adam@adalogics.com>
|
|
||||||
Adrian Mouat <adrian.mouat@gmail.com>
|
|
||||||
Adrian Plata <adrian.plata@docker.com>
|
|
||||||
Adrien Duermael <adrien@duermael.com>
|
|
||||||
Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
|
|
||||||
Aidan Hobson Sayers <aidanhs@cantab.net>
|
|
||||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
|
|
||||||
Aleksejs Sinicins <monder@monder.cc>
|
|
||||||
Alex <aleksandrosansan@gmail.com>
|
|
||||||
Alex Chan <alex.chan@metaswitch.com>
|
|
||||||
Alex Elman <aelman@indeed.com>
|
|
||||||
Alex Laties <agl@tumblr.com>
|
|
||||||
Alexander Larsson <alexl@redhat.com>
|
|
||||||
Alexander Morozov <lk4d4math@gmail.com>
|
|
||||||
Alexey Gladkov <gladkov.alexey@gmail.com>
|
|
||||||
Alfonso Acosta <fons@syntacticsugar.consulting>
|
|
||||||
allencloud <allen.sun@daocloud.io>
|
|
||||||
Alvin Feng <alvin4feng@yahoo.com>
|
|
||||||
amitshukla <ashukla73@hotmail.com>
|
|
||||||
Amy Lindburg <amy.lindburg@docker.com>
|
|
||||||
Andreas Hassing <andreas@famhassing.dk>
|
|
||||||
Andrew Bulford <andrew.bulford@redmatter.com>
|
|
||||||
Andrew Hsu <andrewhsu@acm.org>
|
|
||||||
Andrew Lavery <laverya@umich.edu>
|
|
||||||
Andrew Leung <anwleung@gmail.com>
|
|
||||||
Andrew Lively <andrew.lively2@gmail.com>
|
|
||||||
Andrew Meredith <andymeredith@gmail.com>
|
|
||||||
Andrew T Nguyen <andrew.nguyen@docker.com>
|
|
||||||
Andrews Medina <andrewsmedina@gmail.com>
|
|
||||||
Andrey Kostov <kostov.andrey@gmail.com>
|
|
||||||
Andrii Soldatenko <andrii.soldatenko@gmail.com>
|
|
||||||
Andy Goldstein <agoldste@redhat.com>
|
|
||||||
andyzhangx <xiazhang@microsoft.com>
|
|
||||||
Anian Z <ziegler@sicony.de>
|
|
||||||
Anil Belur <askb23@gmail.com>
|
|
||||||
Anis Elleuch <vadmeste@gmail.com>
|
|
||||||
Ankush Agarwal <ankushagarwal11@gmail.com>
|
|
||||||
Anne Henmi <41210220+ahh-docker@users.noreply.github.com>
|
|
||||||
Anton Tiurin <noxiouz@yandex.ru>
|
|
||||||
Antonio Mercado <amercado@thinknode.com>
|
|
||||||
Antonio Murdaca <antonio.murdaca@gmail.com>
|
|
||||||
Antonio Ojea <antonio.ojea.garcia@gmail.com>
|
|
||||||
Anusha Ragunathan <anusha@docker.com>
|
|
||||||
Arien Holthuizen <aholthuizen@schubergphilis.com>
|
|
||||||
Arko Dasgupta <arkodg@users.noreply.github.com>
|
|
||||||
Arnaud Porterie <arnaud.porterie@docker.com>
|
|
||||||
Arthur Baars <arthur@semmle.com>
|
|
||||||
Arthur Gautier <baloo@gandi.net>
|
|
||||||
Asuka Suzuki <hello@tanksuzuki.com>
|
|
||||||
Avi Miller <avi.miller@oracle.com>
|
|
||||||
Aviral Takkar <aviral26@users.noreply.github.com>
|
|
||||||
Ayose Cazorla <ayosec@gmail.com>
|
|
||||||
BadZen <dave.trombley@gmail.com>
|
|
||||||
baojiangnan <baojiangnan@meituan.com>
|
|
||||||
Ben Bodenmiller <bbodenmiller@hotmail.com>
|
|
||||||
Ben De St Paer-Gotch <bende@outlook.com>
|
|
||||||
Ben Emamian <ben@ictace.com>
|
|
||||||
Ben Firshman <ben@firshman.co.uk>
|
|
||||||
Ben Kochie <superq@gmail.com>
|
|
||||||
Ben Manuel <ben.manuel@procore.com>
|
|
||||||
Bhavin Gandhi <bhavin192@users.noreply.github.com>
|
|
||||||
Bill <NonCreature0714@users.noreply.github.com>
|
|
||||||
bin liu <liubin0329@gmail.com>
|
|
||||||
Bouke van der Bijl <me@bou.ke>
|
|
||||||
Bracken Dawson <abdawson@gmail.com>
|
|
||||||
Brandon Mitchell <git@bmitch.net>
|
|
||||||
Brandon Philips <brandon@ifup.co>
|
|
||||||
Brett Higgins <brhiggins@arbor.net>
|
|
||||||
Brian Bland <brian.t.bland@gmail.com>
|
|
||||||
Brian Goff <cpuguy83@gmail.com>
|
|
||||||
burnettk <burnettk@gmail.com>
|
|
||||||
Caleb Spare <cespare@gmail.com>
|
|
||||||
Carson A <ca@carsonoid.net>
|
|
||||||
Cezar Sa Espinola <cezarsa@gmail.com>
|
|
||||||
Chad Faragher <wyckster@hotmail.com>
|
|
||||||
Chaos John <chaosjohn.yjh@icloud.com>
|
|
||||||
Charles Smith <charles.smith@docker.com>
|
|
||||||
Cheng Zheng <chengzheng.apply@gmail.com>
|
|
||||||
chlins <chenyuzh@vmware.com>
|
|
||||||
Chris Aniszczyk <caniszczyk@gmail.com>
|
|
||||||
Chris Dillon <squarism@gmail.com>
|
|
||||||
Chris K. Wong <chriskw.xyz@gmail.com>
|
|
||||||
Chris Patterson <chrispat@github.com>
|
|
||||||
Christopher Yeleighton <ne01026@shark.2a.pl>
|
|
||||||
Christy Perez <christy@linux.vnet.ibm.com>
|
|
||||||
Chuanying Du <cydu@google.com>
|
|
||||||
Clayton Coleman <ccoleman@redhat.com>
|
|
||||||
Collin Shoop <cshoop@digitalocean.com>
|
|
||||||
Corey Quon <corey.quon@gmail.com>
|
|
||||||
Cory Snider <csnider@mirantis.com>
|
|
||||||
CrazyMax <github@crazymax.dev>
|
|
||||||
cressie176 <github@stephen-cresswell.net>
|
|
||||||
Cristian Staretu <cristian.staretu@gmail.com>
|
|
||||||
cui fliter <imcusg@gmail.com>
|
|
||||||
cuiwei13 <cuiwei13@pku.edu.cn>
|
|
||||||
cyli <cyli@twistedmatrix.com>
|
|
||||||
Daehyeok Mun <daehyeok@gmail.com>
|
|
||||||
Daisuke Fujita <dtanshi45@gmail.com>
|
|
||||||
Damien Mathieu <dmathieu@salesforce.com>
|
|
||||||
Dan Fredell <furtchet@gmail.com>
|
|
||||||
Dan Walsh <dwalsh@redhat.com>
|
|
||||||
Daniel Helfand <helfand.4@gmail.com>
|
|
||||||
Daniel Huhn <daniel@danielhuhn.de>
|
|
||||||
Daniel Menet <membership@sontags.ch>
|
|
||||||
Daniel Mizyrycki <mzdaniel@glidelink.net>
|
|
||||||
Daniel Nephin <dnephin@gmail.com>
|
|
||||||
Daniel, Dao Quang Minh <dqminh89@gmail.com>
|
|
||||||
Danila Fominykh <dancheg97@fmnx.su>
|
|
||||||
Darren Shepherd <darren@rancher.com>
|
|
||||||
Dave <david.warshaw@gmail.com>
|
|
||||||
Dave Trombley <dave.trombley@gmail.com>
|
|
||||||
Dave Tucker <dt@docker.com>
|
|
||||||
David Calavera <david.calavera@gmail.com>
|
|
||||||
David Justice <david@devigned.com>
|
|
||||||
David Karlsson <david.karlsson@docker.com>
|
|
||||||
David Lawrence <david.lawrence@docker.com>
|
|
||||||
David Luu <david@davidluu.info>
|
|
||||||
David Mackey <tdmackey@booleanhaiku.com>
|
|
||||||
David van der Spek <vanderspek.david@gmail.com>
|
|
||||||
David Verhasselt <david@crowdway.com>
|
|
||||||
David Wu <dwu7401@gmail.com>
|
|
||||||
David Xia <dxia@spotify.com>
|
|
||||||
Dawn W Docker <dawn.wood@users.noreply.github.com>
|
|
||||||
ddelange <14880945+ddelange@users.noreply.github.com>
|
|
||||||
Dejan Golja <dejan@golja.org>
|
|
||||||
Denis Andrejew <da.colonel@gmail.com>
|
|
||||||
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
|
||||||
Derek <crq@kernel.org>
|
|
||||||
Derek McGowan <derek@mcg.dev>
|
|
||||||
Deshi Xiao <xiaods@gmail.com>
|
|
||||||
Dimitar Kostadinov <dimitar.kostadinov@sap.com>
|
|
||||||
Diogo Mónica <diogo.monica@gmail.com>
|
|
||||||
DJ Enriquez <dj.enriquez@infospace.com>
|
|
||||||
Djibril Koné <kone.djibril@gmail.com>
|
|
||||||
dmp <dmp@loaner.local>
|
|
||||||
Don Bowman <don@agilicus.com>
|
|
||||||
Don Kjer <don.kjer@gmail.com>
|
|
||||||
Donald Huang <don.hcd@gmail.com>
|
|
||||||
Doug Davis <dug@us.ibm.com>
|
|
||||||
drornir <drornir@users.noreply.github.com>
|
|
||||||
duanhongyi <duanhongyi@doopai.com>
|
|
||||||
ducksecops <daniel@ducksecops.uk>
|
|
||||||
E. M. Bray <erik.m.bray@gmail.com>
|
|
||||||
Edgar Lee <edgar.lee@docker.com>
|
|
||||||
Elliot Pahl <elliot.pahl@gmail.com>
|
|
||||||
elsanli(李楠) <elsanli@tencent.com>
|
|
||||||
Elton Stoneman <elton@sixeyed.com>
|
|
||||||
Emmanuel Briney <emmanuel.briney@docker.com>
|
|
||||||
Eng Zer Jun <engzerjun@gmail.com>
|
|
||||||
Eohyung Lee <liquidnuker@gmail.com>
|
|
||||||
Eric Yang <windfarer@gmail.com>
|
|
||||||
Erica Windisch <erica@windisch.us>
|
|
||||||
Erik Hollensbe <github@hollensbe.org>
|
|
||||||
Etki <etki@etki.me>
|
|
||||||
Eugene Lubarsky <eug48@users.noreply.github.com>
|
|
||||||
eyjhb <eyjhbb@gmail.com>
|
|
||||||
eyjhbb@gmail.com <eyjhbb@gmail.com>
|
|
||||||
Fabio Berchtold <jamesclonk@jamesclonk.ch>
|
|
||||||
Fabio Falci <fabiofalci@gmail.com>
|
|
||||||
Fabio Huser <fabio@fh1.ch>
|
|
||||||
farmerworking <farmerworking@gmail.com>
|
|
||||||
fate-grand-order <chenjg@harmonycloud.cn>
|
|
||||||
Felix Bünemann <buenemann@louis.info>
|
|
||||||
Felix Yan <felixonmars@archlinux.org>
|
|
||||||
Feng Honglin <tifayuki@gmail.com>
|
|
||||||
Fernando Mayo Fernandez <fernando@undefinedlabs.com>
|
|
||||||
Flavian Missi <fmissi@redhat.com>
|
|
||||||
Florentin Raud <florentin.raud@gmail.com>
|
|
||||||
forkbomber <forkbomber@users.noreply.github.com>
|
|
||||||
Frank Chen <frankchn@gmail.com>
|
|
||||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
|
||||||
Gabor Nagy <mail@aigeruth.hu>
|
|
||||||
gabriell nascimento <gabriell@bluesoft.com.br>
|
|
||||||
Gaetan <gdevillele@gmail.com>
|
|
||||||
gary schaetz <gary@schaetzkc.com>
|
|
||||||
gbarr01 <gwendolynne.barr@docker.com>
|
|
||||||
Geoffrey Hausheer <rc2012@pblue.org>
|
|
||||||
ghodsizadeh <mehdi.ghodsizadeh@gmail.com>
|
|
||||||
Giovanni Toraldo <giovanni.toraldo@eng.it>
|
|
||||||
Gladkov Alexey <agladkov@redhat.com>
|
|
||||||
Gleb M Borisov <borisov.gleb@gmail.com>
|
|
||||||
Gleb Schukin <gschukin@ptsecurity.com>
|
|
||||||
glefloch <glfloch@gmail.com>
|
|
||||||
Glyn Owen Hanmer <1295698+glynternet@users.noreply.github.com>
|
|
||||||
gotgelf <gotgelf@gmail.com>
|
|
||||||
Grachev Mikhail <work@mgrachev.com>
|
|
||||||
Grant Watters <grant.watters@docker.com>
|
|
||||||
Greg Rebholz <gregrebholz@gmail.com>
|
|
||||||
Guillaume J. Charmes <charmes.guillaume@gmail.com>
|
|
||||||
Guillaume Rose <guillaume.rose@docker.com>
|
|
||||||
Gábor Lipták <gliptak@gmail.com>
|
|
||||||
harche <p.harshal@gmail.com>
|
|
||||||
hasheddan <georgedanielmangum@gmail.com>
|
|
||||||
Hayley Swimelar <hswimelar@gmail.com>
|
|
||||||
Helen-xie <xieyulin821@harmonycloud.cn>
|
|
||||||
Henri Gomez <henri.gomez@gmail.com>
|
|
||||||
Honglin Feng <tifayuki@gmail.com>
|
|
||||||
Hu Keping <hukeping@huawei.com>
|
|
||||||
Hua Wang <wanghua.humble@gmail.com>
|
|
||||||
HuKeping <hukeping@huawei.com>
|
|
||||||
Huu Nguyen <whoshuu@gmail.com>
|
|
||||||
ialidzhikov <i.alidjikov@gmail.com>
|
|
||||||
Ian Babrou <ibobrik@gmail.com>
|
|
||||||
iasoon <ilion.beyst@gmail.com>
|
|
||||||
igayoso <igayoso@gmail.com>
|
|
||||||
Igor Dolzhikov <bluesriverz@gmail.com>
|
|
||||||
Igor Morozov <igmorv@gmail.com>
|
|
||||||
Ihor Dvoretskyi <ihor@linux.com>
|
|
||||||
Ilion Beyst <ilion.beyst@gmail.com>
|
|
||||||
Ina Panova <ipanova@redhat.com>
|
|
||||||
Irene Diez <idiez@redhat.com>
|
|
||||||
Ismail Alidzhikov <i.alidjikov@gmail.com>
|
|
||||||
Jack Baines <jack.baines@uk.ibm.com>
|
|
||||||
Jack Griffin <jackpg14@gmail.com>
|
|
||||||
Jacob Atzen <jatzen@gmail.com>
|
|
||||||
Jake Moshenko <jake@devtable.com>
|
|
||||||
Jakob Ackermann <das7pad@outlook.com>
|
|
||||||
Jakub Mikulas <jakub@mikul.as>
|
|
||||||
James Findley <jfindley@fastmail.com>
|
|
||||||
James Hewitt <james.hewitt@uk.ibm.com>
|
|
||||||
James Lal <james@lightsofapollo.com>
|
|
||||||
Jason Freidman <jason.freidman@gmail.com>
|
|
||||||
Jason Heiss <jheiss@aput.net>
|
|
||||||
Javier Palomo Almena <javier.palomo.almena@gmail.com>
|
|
||||||
jdolitsky <393494+jdolitsky@users.noreply.github.com>
|
|
||||||
Jeff Nickoloff <jeff@allingeek.com>
|
|
||||||
Jeffrey van Gogh <jvg@google.com>
|
|
||||||
jerae-duffin <83294991+jerae-duffin@users.noreply.github.com>
|
|
||||||
Jeremy THERIN <jtherin@scaleway.com>
|
|
||||||
Jesse Brown <jabrown85@gmail.com>
|
|
||||||
Jesse Haka <haka.jesse@gmail.com>
|
|
||||||
Jessica Frazelle <jess@oxide.computer>
|
|
||||||
jhaohai <jhaohai@foxmail.com>
|
|
||||||
Jianqing Wang <tsing@jianqing.org>
|
|
||||||
Jihoon Chung <jihoon@gmail.com>
|
|
||||||
Jim Galasyn <jim.galasyn@docker.com>
|
|
||||||
Joao Fernandes <joaofnfernandes@gmail.com>
|
|
||||||
Joffrey F <joffrey@docker.com>
|
|
||||||
Johan Euphrosine <proppy@google.com>
|
|
||||||
John Howard <github@lowenna.com>
|
|
||||||
John Mulhausen <john@docker.com>
|
|
||||||
John Starks <jostarks@microsoft.com>
|
|
||||||
Jon Johnson <jonjohnson@google.com>
|
|
||||||
Jon Poler <jonathan.poler@apcera.com>
|
|
||||||
Jonas Hecht <jonas.hecht@codecentric.de>
|
|
||||||
Jonathan Boulle <jonathanboulle@gmail.com>
|
|
||||||
Jonathan Lee <jonjohn1232009@gmail.com>
|
|
||||||
Jonathan Rudenberg <jonathan@titanous.com>
|
|
||||||
Jordan Liggitt <jliggitt@redhat.com>
|
|
||||||
Jose D. Gomez R <jose.gomez@suse.com>
|
|
||||||
Josh Chorlton <josh.chorlton@docker.com>
|
|
||||||
Josh Dolitsky <josh@dolit.ski>
|
|
||||||
Josh Hawn <jlhawn@berkeley.edu>
|
|
||||||
Josiah Kiehl <jkiehl@riotgames.com>
|
|
||||||
Joyce Brum <joycebrumu.u@gmail.com>
|
|
||||||
João Pereira <484633+joaodrp@users.noreply.github.com>
|
|
||||||
Julien Bordellier <1444415+jstoja@users.noreply.github.com>
|
|
||||||
Julien Fernandez <julien.fernandez@gmail.com>
|
|
||||||
Justas Brazauskas <brazauskasjustas@gmail.com>
|
|
||||||
Justin Cormack <justin.cormack@docker.com>
|
|
||||||
Justin I. Nevill <JustinINevill@users.noreply.github.com>
|
|
||||||
Justin Santa Barbara <justin@fathomdb.com>
|
|
||||||
kaiwentan <kaiwentan@harmonycloud.cn>
|
|
||||||
Ke Xu <leonhartx.k@gmail.com>
|
|
||||||
Keerthan Mala <kmala@engineyard.com>
|
|
||||||
Kelsey Hightower <kelsey.hightower@gmail.com>
|
|
||||||
Ken Cochrane <KenCochrane@gmail.com>
|
|
||||||
Kenneth Lim <kennethlimcp@gmail.com>
|
|
||||||
Kenny Leung <kleung@google.com>
|
|
||||||
Kevin Lin <kevin@kelda.io>
|
|
||||||
Kevin Robatel <kevinrob2@gmail.com>
|
|
||||||
Kira <me@imkira.com>
|
|
||||||
Kirat Singh <kirat.singh@gmail.com>
|
|
||||||
L-Hudson <44844738+L-Hudson@users.noreply.github.com>
|
|
||||||
Lachlan Cooper <lachlancooper@gmail.com>
|
|
||||||
Laura Brehm <laurabrehm@hey.com>
|
|
||||||
Lei Jitang <leijitang@huawei.com>
|
|
||||||
Lenny Linux <tippexs91@googlemail.com>
|
|
||||||
Leonardo Azize Martins <lazize@users.noreply.github.com>
|
|
||||||
leonstrand <leonstrand@gmail.com>
|
|
||||||
Li Yi <denverdino@gmail.com>
|
|
||||||
Liam White <liamwhite@uk.ibm.com>
|
|
||||||
libo.huang <huanglibo2010@gmail.com>
|
|
||||||
LingFaKe <lingfake@huawei.com>
|
|
||||||
Liron Levin <liron@twistlock.com>
|
|
||||||
lisong <lisong@cdsunrise.net>
|
|
||||||
Littlemoon917 <18084421+Littlemoon917@users.noreply.github.com>
|
|
||||||
Liu Hua <sdu.liu@huawei.com>
|
|
||||||
liuchang0812 <liuchang0812@gmail.com>
|
|
||||||
liyongxin <yxli@alauda.io>
|
|
||||||
Lloyd Ramey <lnr0626@gmail.com>
|
|
||||||
lostsquirrel <lostsquirreli@hotmail.com>
|
|
||||||
Louis Kottmann <louis.kottmann@gmail.com>
|
|
||||||
Luca Bruno <lucab@debian.org>
|
|
||||||
Lucas França de Oliveira <lucasfdo@palantir.com>
|
|
||||||
Lucas Santos <lhs.santoss@gmail.com>
|
|
||||||
Luis Lobo Borobia <luislobo@gmail.com>
|
|
||||||
Luke Carpenter <x@rubynerd.net>
|
|
||||||
Ma Shimiao <mashimiao.fnst@cn.fujitsu.com>
|
|
||||||
Makoto Oda <truth_jp_4133@yahoo.co.jp>
|
|
||||||
mallchin <mallchin@mac.com>
|
|
||||||
Manish Tomar <manish.tomar@docker.com>
|
|
||||||
Marco Hennings <marco.hennings@freiheit.com>
|
|
||||||
Marcus Martins <marcus@docker.com>
|
|
||||||
Maria Bermudez <bermudez.mt@gmail.com>
|
|
||||||
Mark Sagi-Kazar <mark.sagikazar@gmail.com>
|
|
||||||
Mary Anthony <mary@docker.com>
|
|
||||||
Masataka Mizukoshi <m.mizukoshi.wakuwaku@gmail.com>
|
|
||||||
Matin Rahmanian <itsmatinx@gmail.com>
|
|
||||||
MATSUMOTO TAKEAKI <takeaki.matsumoto@linecorp.com>
|
|
||||||
Matt Bentley <mbentley@mbentley.net>
|
|
||||||
Matt Duch <matt@learnmetrics.com>
|
|
||||||
Matt Linville <matt@linville.me>
|
|
||||||
Matt Moore <mattmoor@google.com>
|
|
||||||
Matt Robenolt <matt@ydekproductions.com>
|
|
||||||
Matt Tescher <matthew.tescher@docker.com>
|
|
||||||
Matthew Balvanz <matthew.balvanz@workiva.com>
|
|
||||||
Matthew Green <greenmr@live.co.uk>
|
|
||||||
Matthew Riley <mattdr@google.com>
|
|
||||||
Maurice Sotzny <ailuridae@users.noreply.github.com>
|
|
||||||
Meaglith Ma <genedna@gmail.com>
|
|
||||||
Michael Bonfils <bonfils.michael@protonmail.com>
|
|
||||||
Michael Crosby <crosbymichael@gmail.com>
|
|
||||||
Michael Prokop <mika@grml.org>
|
|
||||||
Michael Vetter <jubalh@iodoru.org>
|
|
||||||
Michal Fojtik <mfojtik@redhat.com>
|
|
||||||
Michal Gebauer <mishak@mishak.net>
|
|
||||||
Michal Guerquin <michalg@allenai.org>
|
|
||||||
Michal Minar <miminar@redhat.com>
|
|
||||||
Mike Brown <brownwm@us.ibm.com>
|
|
||||||
Mike Lundy <mike@fluffypenguin.org>
|
|
||||||
Mike Truman <miketruman42@gmail.com>
|
|
||||||
Milos Gajdos <milosthegajdos@gmail.com>
|
|
||||||
Miquel Sabaté <msabate@suse.com>
|
|
||||||
mlmhl <409107750@qq.com>
|
|
||||||
Monika Katiyar <monika@jeavio.com>
|
|
||||||
Morgan Bauer <mbauer@us.ibm.com>
|
|
||||||
moxiegirl <mary@docker.com>
|
|
||||||
mqliang <mqliang.zju@gmail.com>
|
|
||||||
Muesli <solom.emmanuel@gmail.com>
|
|
||||||
Nan Monnand Deng <monnand@gmail.com>
|
|
||||||
Nat Zimmermann <ntzm@users.noreply.github.com>
|
|
||||||
Nathan Sullivan <nathan@nightsys.net>
|
|
||||||
Naveed Jamil <naveed.jamil@tenpearl.com>
|
|
||||||
Neil Wilson <neil@aldur.co.uk>
|
|
||||||
nevermosby <robolwq@qq.com>
|
|
||||||
Nghia Tran <tcnghia@gmail.com>
|
|
||||||
Nicolas De Loof <nicolas.deloof@gmail.com>
|
|
||||||
Nikita Tarasov <nikita@mygento.ru>
|
|
||||||
ning xie <andy.xning@gmail.com>
|
|
||||||
Nishant Totla <nishanttotla@gmail.com>
|
|
||||||
Noah Treuhaft <noah.treuhaft@docker.com>
|
|
||||||
Novak Ivanovski <novakivanovski@gmail.com>
|
|
||||||
Nuutti Kotivuori <nuutti.kotivuori@poplatek.fi>
|
|
||||||
Nycholas de Oliveira e Oliveira <nycholas@gmail.com>
|
|
||||||
Oilbeater <liumengxinfly@gmail.com>
|
|
||||||
Oleg Bulatov <oleg@bulatov.me>
|
|
||||||
olegburov <oleg.burov@outlook.com>
|
|
||||||
Olivier <o+github@gambier.email>
|
|
||||||
Olivier Gambier <olivier@docker.com>
|
|
||||||
Olivier Jacques <olivier.jacques@hp.com>
|
|
||||||
ollypom <oppomeroy@gmail.com>
|
|
||||||
Omer Cohen <git@omer.io>
|
|
||||||
Oscar Caballero <ocaballero@opensistemas.com>
|
|
||||||
Owen W. Taylor <otaylor@fishsoup.net>
|
|
||||||
paigehargrave <Paige.hargrave@docker.com>
|
|
||||||
Parth Mehrotra <parth@mehrotra.me>
|
|
||||||
Pascal Borreli <pascal@borreli.com>
|
|
||||||
Patrick Devine <patrick.devine@docker.com>
|
|
||||||
Patrick Easters <peasters@redhat.com>
|
|
||||||
Paul Cacheux <paul.cacheux@datadoghq.com>
|
|
||||||
Pavel Antonov <ddc67cd@gmail.com>
|
|
||||||
Paweł Gronowski <pawel.gronowski@docker.com>
|
|
||||||
Per Lundberg <perlun@gmail.com>
|
|
||||||
Peter Choi <reikani@Peters-MacBook-Pro.local>
|
|
||||||
Peter Dave Hello <hsu@peterdavehello.org>
|
|
||||||
Peter Kokot <peterkokot@gmail.com>
|
|
||||||
Phil Estes <estesp@gmail.com>
|
|
||||||
Philip Misiowiec <philip@atlashealth.com>
|
|
||||||
Pierre-Yves Ritschard <pyr@spootnik.org>
|
|
||||||
Pieter Scheffers <pieter.scheffers@gmail.com>
|
|
||||||
Qiang Huang <h.huangqiang@huawei.com>
|
|
||||||
Qiao Anran <qiaoanran@gmail.com>
|
|
||||||
Radon Rosborough <radon.neon@gmail.com>
|
|
||||||
Randy Barlow <randy@electronsweatshop.com>
|
|
||||||
Raphaël Enrici <raphael@root-42.com>
|
|
||||||
Ricardo Maraschini <ricardo.maraschini@gmail.com>
|
|
||||||
Richard Scothern <richard.scothern@gmail.com>
|
|
||||||
Rick Wieman <git@rickw.nl>
|
|
||||||
Rik Nijessen <rik@keefo.nl>
|
|
||||||
Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
|
|
||||||
Rober Morales-Chaparro <rober.morales@rstor.io>
|
|
||||||
Robert Kaussow <mail@geeklabor.de>
|
|
||||||
Robert Steward <speaktorob@users.noreply.github.com>
|
|
||||||
Roberto G. Hashioka <roberto.hashioka@docker.com>
|
|
||||||
Rodolfo Carvalho <rhcarvalho@gmail.com>
|
|
||||||
ROY <qqbuby@gmail.com>
|
|
||||||
Rui Cao <ruicao@alauda.io>
|
|
||||||
ruicao <ruicao@alauda.io>
|
|
||||||
Rusty Conover <rusty@luckydinosaur.com>
|
|
||||||
Ryan Abrams <rdabrams@gmail.com>
|
|
||||||
Ryan Thomas <rthomas@atlassian.com>
|
|
||||||
sakeven <jc5930@sina.cn>
|
|
||||||
Sam Alba <sam.alba@gmail.com>
|
|
||||||
Samuel Karp <skarp@amazon.com>
|
|
||||||
sangluo <sangluo@pinduoduo.com>
|
|
||||||
Santiago Torres <torresariass@gmail.com>
|
|
||||||
Sargun Dhillon <sargun@sargun.me>
|
|
||||||
sayboras <sayboras@yahoo.com>
|
|
||||||
Sean Boran <Boran@users.noreply.github.com>
|
|
||||||
Sean P. Kane <spkane00@gmail.com>
|
|
||||||
Sebastiaan van Stijn <github@gone.nl>
|
|
||||||
Sebastien Coavoux <s.coavoux@free.fr>
|
|
||||||
Serge Dubrouski <sergeyfd@gmail.com>
|
|
||||||
Sevki Hasirci <sevki@cloudflare.com>
|
|
||||||
Sharif Nassar <sharif@mrwacky.com>
|
|
||||||
Shawn Chen <chen8132@gmail.com>
|
|
||||||
Shawn Falkner-Horine <dreadpirateshawn@gmail.com>
|
|
||||||
Shawnpku <chen8132@gmail.com>
|
|
||||||
Shengjing Zhu <zhsj@debian.org>
|
|
||||||
Shiela M Parker <smp13@live.com>
|
|
||||||
Shishir Mahajan <shishir.mahajan@redhat.com>
|
|
||||||
Shreyas Karnik <karnik.shreyas@gmail.com>
|
|
||||||
Silvin Lubecki <31478878+silvin-lubecki@users.noreply.github.com>
|
|
||||||
Simon <crydotsnakegithub@gmail.com>
|
|
||||||
Simon Thulbourn <simon+github@thulbourn.com>
|
|
||||||
Simone Locci <simone.locci@eng.it>
|
|
||||||
Smasherr <soundcracker@gmail.com>
|
|
||||||
Solomon Hykes <solomon@dagger.io>
|
|
||||||
Sora Morimoto <sora@morimoto.io>
|
|
||||||
spacexnice <yaoyao.xyy@alibaba-inc.com>
|
|
||||||
Spencer Rinehart <anubis@overthemonkey.com>
|
|
||||||
srajmane <31947381+srajmane@users.noreply.github.com>
|
|
||||||
Srini Brahmaroutu <srbrahma@us.ibm.com>
|
|
||||||
Stan Hu <stanhu@gmail.com>
|
|
||||||
Stefan Lörwald <10850250+stefanloerwald@users.noreply.github.com>
|
|
||||||
Stefan Majewsky <stefan.majewsky@sap.com>
|
|
||||||
Stefan Nica <snica@suse.com>
|
|
||||||
Stefan Weil <sw@weilnetz.de>
|
|
||||||
Stephen Day <stevvooe@gmail.com>
|
|
||||||
Steve Lasker <stevenlasker@hotmail.com>
|
|
||||||
Steven Hanna <stevenhanna6@gmail.com>
|
|
||||||
Steven Kalt <SKalt@users.noreply.github.com>
|
|
||||||
Steven Taylor <steven.taylor@me.com>
|
|
||||||
stonezdj <stonezdj@gmail.com>
|
|
||||||
sun jian <cnhttpd@gmail.com>
|
|
||||||
Sungho Moon <sungho.moon@navercorp.com>
|
|
||||||
Sven Dowideit <SvenDowideit@home.org.au>
|
|
||||||
Sylvain Baubeau <sbaubeau@redhat.com>
|
|
||||||
syntaxkim <40621244+syntaxkim@users.noreply.github.com>
|
|
||||||
T N <tnir@users.noreply.github.com>
|
|
||||||
t-eimizu <t-eimizu@aim.ac>
|
|
||||||
Tariq Ibrahim <tariq181290@gmail.com>
|
|
||||||
TaylorKanper <tony_kanper@hotmail.com>
|
|
||||||
Ted Reed <ted.reed@gmail.com>
|
|
||||||
Terin Stock <terinjokes@gmail.com>
|
|
||||||
tgic <farmer1992@gmail.com>
|
|
||||||
Thomas Berger <loki@lokis-chaos.de>
|
|
||||||
Thomas Sjögren <konstruktoid@users.noreply.github.com>
|
|
||||||
Tianon Gravi <admwiggin@gmail.com>
|
|
||||||
Tibor Vass <teabee89@gmail.com>
|
|
||||||
tifayuki <tifayuki@gmail.com>
|
|
||||||
Tiger Kaovilai <tkaovila@redhat.com>
|
|
||||||
Tobias Fuhrimann <mastertinner@users.noreply.github.com>
|
|
||||||
Tobias Schwab <tobias.schwab@dynport.de>
|
|
||||||
Tom Hayward <thayward@infoblox.com>
|
|
||||||
Tom Hu <tomhu1096@gmail.com>
|
|
||||||
Tonis Tiigi <tonistiigi@gmail.com>
|
|
||||||
Tony Holdstock-Brown <tony@docker.com>
|
|
||||||
Tosone <i@tosone.cn>
|
|
||||||
Trapier Marshall <trapier@users.noreply.github.com>
|
|
||||||
Trevor Pounds <trevor.pounds@gmail.com>
|
|
||||||
Trevor Wood <Trevor.G.Wood@gmail.com>
|
|
||||||
Troels Thomsen <troels@thomsen.io>
|
|
||||||
uhayate <uhayate.gong@daocloud.io>
|
|
||||||
Usha Mandya <47779042+usha-mandya@users.noreply.github.com>
|
|
||||||
Usha Mandya <usha.mandya@docker.com>
|
|
||||||
Vaidas Jablonskis <jablonskis@gmail.com>
|
|
||||||
Vega Chou <VegeChou@users.noreply.github.com>
|
|
||||||
Veres Lajos <vlajos@gmail.com>
|
|
||||||
Victor Vieux <victorvieux@gmail.com>
|
|
||||||
Victoria Bialas <victoria.bialas@docker.com>
|
|
||||||
Vidar <vl@ez.no>
|
|
||||||
Viktor Stanchev <me@viktorstanchev.com>
|
|
||||||
Vincent Batts <vbatts@redhat.com>
|
|
||||||
Vincent Demeester <vincent.demeester@docker.com>
|
|
||||||
Vincent Giersch <vincent@giersch.fr>
|
|
||||||
Vishesh Jindal <vishesh92@gmail.com>
|
|
||||||
W. Trevor King <wking@tremily.us>
|
|
||||||
Wang Jie <wangjie5@chinaskycloud.com>
|
|
||||||
Wang Yan <wangyan@vmware.com>
|
|
||||||
Wassim Dhif <wassimdhif@gmail.com>
|
|
||||||
wayne <wayne.warren.s@gmail.com>
|
|
||||||
Wei Fu <fuweid89@gmail.com>
|
|
||||||
Wei Meng <wemeng@microsoft.com>
|
|
||||||
weiyuan.yl <weiyuan.yl@alibaba-inc.com>
|
|
||||||
Wen-Quan Li <legendarilylwq@gmail.com>
|
|
||||||
Wenkai Yin <yinw@vmware.com>
|
|
||||||
william wei <1342247033@qq.com>
|
|
||||||
xg.song <xg.song@venusource.com>
|
|
||||||
xiekeyang <xiekeyang@huawei.com>
|
|
||||||
Xueshan Feng <xueshan.feng@gmail.com>
|
|
||||||
Yann ROBERT <yann.robert@anantaplex.fr>
|
|
||||||
Yannick Fricke <YannickFricke@users.noreply.github.com>
|
|
||||||
yaoyao.xyy <yaoyao.xyy@alibaba-inc.com>
|
|
||||||
yixi zhang <yixi@memsql.com>
|
|
||||||
Yong Tang <yong.tang.github@outlook.com>
|
|
||||||
Yong Wen Chua <lawliet89@users.noreply.github.com>
|
|
||||||
Yongxin Li <yxli@alauda.io>
|
|
||||||
Yu Wang <yuwa@microsoft.com>
|
|
||||||
yuexiao-wang <wang.yuexiao@zte.com.cn>
|
|
||||||
YuJie <390282283@qq.com>
|
|
||||||
yuzou <zouyu7@huawei.com>
|
|
||||||
Zhang Wei <zhangwei555@huawei.com>
|
|
||||||
zhipengzuo <zuozhipeng@baidu.com>
|
|
||||||
zhouhaibing089 <zhouhaibing089@gmail.com>
|
|
||||||
zounengren <zounengren@cmss.chinamobile.com>
|
|
||||||
姜继忠 <jizhong.jiangjz@alibaba-inc.com>
|
|
136
BUILDING.md
136
BUILDING.md
|
@ -7,39 +7,31 @@ This is useful if you intend to actively work on the registry.
|
||||||
|
|
||||||
### Alternatives
|
### Alternatives
|
||||||
|
|
||||||
Most people should use prebuilt images, for example, the [Registry docker image](https://hub.docker.com/r/library/registry/) provided by Docker.
|
Most people should use the [official Registry docker image](https://hub.docker.com/r/library/registry/).
|
||||||
|
|
||||||
People looking for advanced operational use cases might consider rolling their own image with a custom Dockerfile inheriting `FROM registry:2`.
|
People looking for advanced operational use cases might consider rolling their own image with a custom Dockerfile inheriting `FROM registry:2`.
|
||||||
|
|
||||||
The latest updates to `main` branch are automatically pushed to [distribution Docker Hub repository](https://hub.docker.com/r/distribution/distribution) and tagged with `edge` tag.
|
OS X users who want to run natively can do so following [the instructions here](https://github.com/docker/docker.github.io/blob/master/registry/recipes/osx-setup-guide.md).
|
||||||
|
|
||||||
### Gotchas
|
### Gotchas
|
||||||
|
|
||||||
You are expected to know your way around with `go` & `git`.
|
You are expected to know your way around with go & git.
|
||||||
|
|
||||||
If you are a casual user with no development experience, and no preliminary knowledge of Go, building from source is probably not a good solution for you.
|
If you are a casual user with no development experience, and no preliminary knowledge of go, building from source is probably not a good solution for you.
|
||||||
|
|
||||||
## Configure the development environment
|
## Build the development environment
|
||||||
|
|
||||||
The first prerequisite of properly building distribution targets is to have a Go
|
The first prerequisite of properly building distribution targets is to have a Go
|
||||||
development environment setup. Please follow [How to Write Go Code](https://go.dev/doc/code) for proper setup.
|
development environment setup. Please follow [How to Write Go Code](https://golang.org/doc/code.html)
|
||||||
|
for proper setup. If done correctly, you should have a GOROOT and GOPATH set in the
|
||||||
|
environment.
|
||||||
|
|
||||||
Next, fetch the code from the repository using git:
|
If a Go development environment is setup, one can use `go get` to install the
|
||||||
|
`registry` command from the current latest:
|
||||||
|
|
||||||
git clone https://github.com/distribution/distribution
|
go get github.com/docker/distribution/cmd/registry
|
||||||
cd distribution
|
|
||||||
|
|
||||||
If you are planning to create a pull request with changes, you may want to clone directly from your [fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks).
|
The above will install the source repository into the `GOPATH`.
|
||||||
|
|
||||||
## Build and run from source
|
|
||||||
|
|
||||||
First, build the binaries:
|
|
||||||
|
|
||||||
$ make
|
|
||||||
+ bin/registry
|
|
||||||
+ bin/digest
|
|
||||||
+ bin/registry-api-descriptor-template
|
|
||||||
+ binaries
|
|
||||||
|
|
||||||
Now create the directory for the registry data (this might require you to set permissions properly)
|
Now create the directory for the registry data (this might require you to set permissions properly)
|
||||||
|
|
||||||
|
@ -50,80 +42,76 @@ Now create the directory for the registry data (this might require you to set pe
|
||||||
The `registry`
|
The `registry`
|
||||||
binary can then be run with the following:
|
binary can then be run with the following:
|
||||||
|
|
||||||
$ ./bin/registry --version
|
$ $GOPATH/bin/registry --version
|
||||||
./bin/registry github.com/distribution/distribution/v3 v2.7.0-1993-g8857a194
|
$GOPATH/bin/registry github.com/docker/distribution v2.0.0-alpha.1+unknown
|
||||||
|
|
||||||
The registry can be run with a development config using the following
|
> __NOTE:__ While you do not need to use `go get` to checkout the distribution
|
||||||
|
> project, for these build instructions to work, the project must be checked
|
||||||
|
> out in the correct location in the `GOPATH`. This should almost always be
|
||||||
|
> `$GOPATH/src/github.com/docker/distribution`.
|
||||||
|
|
||||||
|
The registry can be run with the default config using the following
|
||||||
incantation:
|
incantation:
|
||||||
|
|
||||||
$ ./bin/registry serve cmd/registry/config-dev.yml
|
$ $GOPATH/bin/registry serve $GOPATH/src/github.com/docker/distribution/cmd/registry/config-example.yml
|
||||||
INFO[0000] debug server listening :5001
|
INFO[0000] endpoint local-5003 disabled, skipping app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
|
||||||
WARN[0000] No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable. environment=development go.version=go1.18.3 instance.id=e837df62-a66c-4e04-a014-b063546e82e0 service=registry version=v2.7.0-1993-g8857a194
|
INFO[0000] endpoint local-8083 disabled, skipping app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
|
||||||
INFO[0000] endpoint local-5003 disabled, skipping environment=development go.version=go1.18.3 instance.id=e837df62-a66c-4e04-a014-b063546e82e0 service=registry version=v2.7.0-1993-g8857a194
|
INFO[0000] listening on :5000 app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
|
||||||
INFO[0000] endpoint local-8083 disabled, skipping environment=development go.version=go1.18.3 instance.id=e837df62-a66c-4e04-a014-b063546e82e0 service=registry version=v2.7.0-1993-g8857a194
|
INFO[0000] debug server listening localhost:5001
|
||||||
INFO[0000] using inmemory blob descriptor cache environment=development go.version=go1.18.3 instance.id=e837df62-a66c-4e04-a014-b063546e82e0 service=registry version=v2.7.0-1993-g8857a194
|
|
||||||
INFO[0000] providing prometheus metrics on /metrics
|
|
||||||
INFO[0000] listening on [::]:5000 environment=development go.version=go1.18.3 instance.id=e837df62-a66c-4e04-a014-b063546e82e0 service=registry version=v2.7.0-1993-g8857a194
|
|
||||||
|
|
||||||
If it is working, one should see the above log messages.
|
If it is working, one should see the above log messages.
|
||||||
|
|
||||||
### Build reference
|
### Repeatable Builds
|
||||||
|
|
||||||
The regular `go` commands, such as `go test`, should work per package.
|
For the full development experience, one should `cd` into
|
||||||
|
`$GOPATH/src/github.com/docker/distribution`. From there, the regular `go`
|
||||||
|
commands, such as `go test`, should work per package (please see
|
||||||
|
[Developing](#developing) if they don't work).
|
||||||
|
|
||||||
A `Makefile` has been provided as a convenience to support repeatable builds.
|
A `Makefile` has been provided as a convenience to support repeatable builds.
|
||||||
|
Please install the following into `GOPATH` for it to work:
|
||||||
|
|
||||||
Run `make` to build the binaries:
|
go get github.com/golang/lint/golint
|
||||||
|
|
||||||
|
Once these commands are available in the `GOPATH`, run `make` to get a full
|
||||||
|
build:
|
||||||
|
|
||||||
$ make
|
$ make
|
||||||
+ bin/registry
|
+ clean
|
||||||
+ bin/digest
|
+ fmt
|
||||||
+ bin/registry-api-descriptor-template
|
+ vet
|
||||||
|
+ lint
|
||||||
|
+ build
|
||||||
|
github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar
|
||||||
|
github.com/sirupsen/logrus
|
||||||
|
github.com/docker/libtrust
|
||||||
|
...
|
||||||
|
github.com/yvasiyarov/gorelic
|
||||||
|
github.com/docker/distribution/registry/handlers
|
||||||
|
github.com/docker/distribution/cmd/registry
|
||||||
|
+ test
|
||||||
|
...
|
||||||
|
ok github.com/docker/distribution/digest 7.875s
|
||||||
|
ok github.com/docker/distribution/manifest 0.028s
|
||||||
|
ok github.com/docker/distribution/notifications 17.322s
|
||||||
|
? github.com/docker/distribution/registry [no test files]
|
||||||
|
ok github.com/docker/distribution/registry/api/v2 0.101s
|
||||||
|
? github.com/docker/distribution/registry/auth [no test files]
|
||||||
|
ok github.com/docker/distribution/registry/auth/silly 0.011s
|
||||||
|
...
|
||||||
|
+ /Users/sday/go/src/github.com/docker/distribution/bin/registry
|
||||||
|
+ /Users/sday/go/src/github.com/docker/distribution/bin/registry-api-descriptor-template
|
||||||
+ binaries
|
+ binaries
|
||||||
|
|
||||||
The above provides a repeatable build using the contents of the vendor
|
The above provides a repeatable build using the contents of the vendor
|
||||||
directory. We can verify this worked by running
|
directory. This includes formatting, vetting, linting, building,
|
||||||
|
testing and generating tagged binaries. We can verify this worked by running
|
||||||
the registry binary generated in the "./bin" directory:
|
the registry binary generated in the "./bin" directory:
|
||||||
|
|
||||||
$ ./bin/registry --version
|
$ ./bin/registry --version
|
||||||
./bin/registry github.com/distribution/distribution v2.0.0-alpha.2-80-g16d8b2c.m
|
./bin/registry github.com/docker/distribution v2.0.0-alpha.2-80-g16d8b2c.m
|
||||||
|
|
||||||
Run `make test` to run all of the tests.
|
|
||||||
|
|
||||||
Run `make validate` to run the validators, including the linter and vendor validation. You must have docker with the buildx plugin installed to run the validators.
|
|
||||||
|
|
||||||
### Optional build tags
|
### Optional build tags
|
||||||
|
|
||||||
Optional [build tags](http://golang.org/pkg/go/build/) can be provided using
|
Optional [build tags](http://golang.org/pkg/go/build/) can be provided using
|
||||||
the environment variable `BUILDTAGS`.
|
the environment variable `BUILDTAGS`.
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>noresumabledigest</dt>
|
|
||||||
<dd>Compiles without resumable digest support</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
### Local cloud storage environment
|
|
||||||
|
|
||||||
You can run an S3 API compatible storage locally with [minio](https://min.io/).
|
|
||||||
|
|
||||||
You must have a [docker compose](https://docs.docker.com/compose/) compatible tool installed on your workstation.
|
|
||||||
|
|
||||||
Start the local cloud environment:
|
|
||||||
```
|
|
||||||
make start-cloud-storage
|
|
||||||
```
|
|
||||||
There is a sample registry configuration file that lets you point the registry to the started storage:
|
|
||||||
```
|
|
||||||
AWS_ACCESS_KEY=distribution \
|
|
||||||
AWS_SECRET_KEY=password \
|
|
||||||
AWS_REGION=us-east-1 \
|
|
||||||
S3_BUCKET=images-local \
|
|
||||||
S3_ENCRYPT=false \
|
|
||||||
REGION_ENDPOINT=http://127.0.0.1:9000 \
|
|
||||||
S3_SECURE=false \
|
|
||||||
./bin/registry serve tests/conf-local-cloud.yml
|
|
||||||
```
|
|
||||||
Stop the local storage when done:
|
|
||||||
```
|
|
||||||
make stop-cloud-storage
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
# Code of Conduct
|
|
||||||
|
|
||||||
We follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
|
|
||||||
|
|
||||||
Please contact the [CNCF Code of Conduct Committee](mailto:conduct@cncf.io) in order to report violations of the Code of Conduct.
|
|
141
CONTRIBUTING.md
141
CONTRIBUTING.md
|
@ -4,12 +4,11 @@
|
||||||
|
|
||||||
### If your problem is with...
|
### If your problem is with...
|
||||||
|
|
||||||
- automated builds or your [Docker Hub](https://hub.docker.com/) account
|
- automated builds
|
||||||
- Report it to [Hub Support](https://hub.docker.com/support/)
|
- your account on the [Docker Hub](https://hub.docker.com/)
|
||||||
- Distributions of Docker for desktop or Linux
|
- any other [Docker Hub](https://hub.docker.com/) issue
|
||||||
- Report [Mac Desktop issues](https://github.com/docker/for-mac)
|
|
||||||
- Report [Windows Desktop issues](https://github.com/docker/for-win)
|
Then please do not report your issue here - you should instead report it to [https://support.docker.com](https://support.docker.com)
|
||||||
- Report [Linux issues](https://github.com/docker/for-linux)
|
|
||||||
|
|
||||||
### If you...
|
### If you...
|
||||||
|
|
||||||
|
@ -17,16 +16,18 @@
|
||||||
- can't figure out something
|
- can't figure out something
|
||||||
- are not sure what's going on or what your problem is
|
- are not sure what's going on or what your problem is
|
||||||
|
|
||||||
Please ask first in the [#distribution](https://cloud-native.slack.com/archives/C01GVR8SY4R) channel on CNCF community slack.
|
Then please do not open an issue here yet - you should first try one of the following support forums:
|
||||||
[Click here for an invite to the CNCF community slack](https://slack.cncf.io/)
|
|
||||||
|
- irc: #docker-distribution on freenode
|
||||||
|
- mailing-list: <distribution@dockerproject.org> or https://groups.google.com/a/dockerproject.org/forum/#!forum/distribution
|
||||||
|
|
||||||
### Reporting security issues
|
### Reporting security issues
|
||||||
|
|
||||||
The maintainers take security seriously. If you discover a security
|
The Docker maintainers take security seriously. If you discover a security
|
||||||
issue, please bring it to their attention right away!
|
issue, please bring it to their attention right away!
|
||||||
|
|
||||||
Please **DO NOT** file a public issue, instead send your report privately to
|
Please **DO NOT** file a public issue, instead send your report privately to
|
||||||
[cncf-distribution-security@lists.cncf.io](mailto:cncf-distribution-security@lists.cncf.io).
|
[security@docker.com](mailto:security@docker.com).
|
||||||
|
|
||||||
## Reporting an issue properly
|
## Reporting an issue properly
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ By following these simple rules you will get better and faster feedback on your
|
||||||
1. create a new issue, with a succinct title that describes your issue:
|
1. create a new issue, with a succinct title that describes your issue:
|
||||||
- bad title: "It doesn't work with my docker"
|
- bad title: "It doesn't work with my docker"
|
||||||
- good title: "Private registry push fail: 400 error with E_INVALID_DIGEST"
|
- good title: "Private registry push fail: 400 error with E_INVALID_DIGEST"
|
||||||
2. copy the output of (or similar for other container tools):
|
2. copy the output of:
|
||||||
- `docker version`
|
- `docker version`
|
||||||
- `docker info`
|
- `docker info`
|
||||||
- `docker exec <registry-container> registry --version`
|
- `docker exec <registry-container> registry --version`
|
||||||
|
@ -58,72 +59,90 @@ By following these simple rules you will get better and faster feedback on your
|
||||||
7. provide any relevant detail about your specific Registry configuration (e.g., storage backend used)
|
7. provide any relevant detail about your specific Registry configuration (e.g., storage backend used)
|
||||||
8. indicate if you are using an enterprise proxy, Nginx, or anything else between you and your Registry
|
8. indicate if you are using an enterprise proxy, Nginx, or anything else between you and your Registry
|
||||||
|
|
||||||
## Contributing Code
|
## Contributing a patch for a known bug, or a small correction
|
||||||
|
|
||||||
Contributions should be made via pull requests. Pull requests will be reviewed
|
|
||||||
by one or more maintainers or reviewers and merged when acceptable.
|
|
||||||
|
|
||||||
You should follow the basic GitHub workflow:
|
You should follow the basic GitHub workflow:
|
||||||
|
|
||||||
1. Use your own [fork](https://help.github.com/en/articles/about-forks)
|
1. fork
|
||||||
2. Create your [change](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes)
|
2. commit a change
|
||||||
3. Test your code
|
3. make sure the tests pass
|
||||||
4. [Commit](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages) your work, always [sign your commits](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages)
|
4. PR
|
||||||
5. Push your change to your fork and create a [Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork)
|
|
||||||
|
|
||||||
Refer to [containerd's contribution guide](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes)
|
Additionally, you must [sign your commits](https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work). It's very simple:
|
||||||
for tips on creating a successful contribution.
|
|
||||||
|
|
||||||
## Sign your work
|
- configure your name with git: `git config user.name "Real Name" && git config user.email mail@example.com`
|
||||||
|
- sign your commits using `-s`: `git commit -s -m "My commit"`
|
||||||
|
|
||||||
The sign-off is a simple line at the end of the explanation for the patch. Your
|
Some simple rules to ensure quick merge:
|
||||||
signature certifies that you wrote the patch or otherwise have the right to pass
|
|
||||||
it on as an open-source patch. The rules are pretty simple: if you can certify
|
|
||||||
the below (from [developercertificate.org](http://developercertificate.org/)):
|
|
||||||
|
|
||||||
```
|
- clearly point to the issue(s) you want to fix in your PR comment (e.g., `closes #12345`)
|
||||||
Developer Certificate of Origin
|
- prefer multiple (smaller) PRs addressing individual issues over a big one trying to address multiple issues at once
|
||||||
Version 1.1
|
- if you need to amend your PR following comments, please squash instead of adding more commits
|
||||||
|
|
||||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
## Contributing new features
|
||||||
660 York Street, Suite 102,
|
|
||||||
San Francisco, CA 94110 USA
|
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies of this
|
You are heavily encouraged to first discuss what you want to do. You can do so on the irc channel, or by opening an issue that clearly describes the use case you want to fulfill, or the problem you are trying to solve.
|
||||||
license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Developer's Certificate of Origin 1.1
|
If this is a major new feature, you should then submit a proposal that describes your technical solution and reasoning.
|
||||||
|
If you did discuss it first, this will likely be greenlighted very fast. It's advisable to address all feedback on this proposal before starting actual work.
|
||||||
|
|
||||||
By making a contribution to this project, I certify that:
|
Then you should submit your implementation, clearly linking to the issue (and possible proposal).
|
||||||
|
|
||||||
(a) The contribution was created in whole or in part by me and I
|
Your PR will be reviewed by the community, then ultimately by the project maintainers, before being merged.
|
||||||
have the right to submit it under the open source license
|
|
||||||
indicated in the file; or
|
|
||||||
|
|
||||||
(b) The contribution is based upon previous work that, to the best
|
It's mandatory to:
|
||||||
of my knowledge, is covered under an appropriate open source
|
|
||||||
license and I have the right under that license to submit that
|
|
||||||
work with modifications, whether created in whole or in part
|
|
||||||
by me, under the same open source license (unless I am
|
|
||||||
permitted to submit under a different license), as indicated
|
|
||||||
in the file; or
|
|
||||||
|
|
||||||
(c) The contribution was provided directly to me by some other
|
- interact respectfully with other community members and maintainers - more generally, you are expected to abide by the [Docker community rules](https://github.com/docker/docker/blob/master/CONTRIBUTING.md#docker-community-guidelines)
|
||||||
person who certified (a), (b) or (c) and I have not modified
|
- address maintainers' comments and modify your submission accordingly
|
||||||
it.
|
- write tests for any new code
|
||||||
|
|
||||||
(d) I understand and agree that this project and the contribution
|
Complying to these simple rules will greatly accelerate the review process, and will ensure you have a pleasant experience in contributing code to the Registry.
|
||||||
are public and that a record of the contribution (including all
|
|
||||||
personal information I submit with it, including my sign-off) is
|
|
||||||
maintained indefinitely and may be redistributed consistent with
|
|
||||||
this project or the open source license(s) involved.
|
|
||||||
```
|
|
||||||
|
|
||||||
Then you just add a line to every git commit message:
|
Have a look at a great, successful contribution: the [Swift driver PR](https://github.com/docker/distribution/pull/493)
|
||||||
|
|
||||||
Signed-off-by: Joe Smith <joe.smith@email.com>
|
## Coding Style
|
||||||
|
|
||||||
Use your real name (sorry, no pseudonyms or anonymous contributions.)
|
Unless explicitly stated, we follow all coding guidelines from the Go
|
||||||
|
community. While some of these standards may seem arbitrary, they somehow seem
|
||||||
|
to result in a solid, consistent codebase.
|
||||||
|
|
||||||
If you set your `user.name` and `user.email` git configs, you can sign your
|
It is possible that the code base does not currently comply with these
|
||||||
commit automatically with `git commit -s`.
|
guidelines. We are not looking for a massive PR that fixes this, since that
|
||||||
|
goes against the spirit of the guidelines. All new contributions should make a
|
||||||
|
best effort to clean up and make the code base better than they left it.
|
||||||
|
Obviously, apply your best judgement. Remember, the goal here is to make the
|
||||||
|
code base easier for humans to navigate and understand. Always keep that in
|
||||||
|
mind when nudging others to comply.
|
||||||
|
|
||||||
|
The rules:
|
||||||
|
|
||||||
|
1. All code should be formatted with `gofmt -s`.
|
||||||
|
2. All code should pass the default levels of
|
||||||
|
[`golint`](https://github.com/golang/lint).
|
||||||
|
3. All code should follow the guidelines covered in [Effective
|
||||||
|
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
|
||||||
|
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||||
|
4. Comment the code. Tell us the why, the history and the context.
|
||||||
|
5. Document _all_ declarations and methods, even private ones. Declare
|
||||||
|
expectations, caveats and anything else that may be important. If a type
|
||||||
|
gets exported, having the comments already there will ensure it's ready.
|
||||||
|
6. Variable name length should be proportional to its context and no longer.
|
||||||
|
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
|
||||||
|
In practice, short methods will have short variable names and globals will
|
||||||
|
have longer names.
|
||||||
|
7. No underscores in package names. If you need a compound name, step back,
|
||||||
|
and re-examine why you need a compound name. If you still think you need a
|
||||||
|
compound name, lose the underscore.
|
||||||
|
8. No utils or helpers packages. If a function is not general enough to
|
||||||
|
warrant its own package, it has not been written generally enough to be a
|
||||||
|
part of a util package. Just leave it unexported and well-documented.
|
||||||
|
9. All tests should run with `go test` and outside tooling should not be
|
||||||
|
required. No, we don't need another unit testing framework. Assertion
|
||||||
|
packages are acceptable if they provide _real_ incremental value.
|
||||||
|
10. Even though we call these "rules" above, they are actually just
|
||||||
|
guidelines. Since you've read all the rules, you now know that.
|
||||||
|
|
||||||
|
If you are having trouble getting into the mood of idiomatic Go, we recommend
|
||||||
|
reading through [Effective Go](http://golang.org/doc/effective_go.html). The
|
||||||
|
[Go Blog](http://blog.golang.org/) is also a great resource. Drinking the
|
||||||
|
kool-aid is a lot easier than going thirsty.
|
||||||
|
|
18
Dockerfile
18
Dockerfile
|
@ -1,7 +1,7 @@
|
||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.22.4
|
ARG GO_VERSION=1.19.9
|
||||||
ARG ALPINE_VERSION=3.20
|
ARG ALPINE_VERSION=3.16
|
||||||
ARG XX_VERSION=1.2.1
|
ARG XX_VERSION=1.2.1
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||||
|
@ -10,20 +10,20 @@ COPY --from=xx / /
|
||||||
RUN apk add --no-cache bash coreutils file git
|
RUN apk add --no-cache bash coreutils file git
|
||||||
ENV GO111MODULE=auto
|
ENV GO111MODULE=auto
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
WORKDIR /src
|
WORKDIR /go/src/github.com/docker/distribution
|
||||||
|
|
||||||
FROM base AS version
|
FROM base AS version
|
||||||
ARG PKG=github.com/distribution/distribution/v3
|
ARG PKG="github.com/docker/distribution"
|
||||||
RUN --mount=target=. \
|
RUN --mount=target=. \
|
||||||
VERSION=$(git describe --match 'v[0-9]*' --dirty='.m' --always --tags) REVISION=$(git rev-parse HEAD)$(if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi); \
|
VERSION=$(git describe --match 'v[0-9]*' --dirty='.m' --always --tags) REVISION=$(git rev-parse HEAD)$(if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi); \
|
||||||
echo "-X ${PKG}/version.version=${VERSION#v} -X ${PKG}/version.revision=${REVISION} -X ${PKG}/version.mainpkg=${PKG}" | tee /tmp/.ldflags; \
|
echo "-X ${PKG}/version.Version=${VERSION#v} -X ${PKG}/version.Revision=${REVISION} -X ${PKG}/version.Package=${PKG}" | tee /tmp/.ldflags; \
|
||||||
echo -n "${VERSION}" | tee /tmp/.version;
|
echo -n "${VERSION}" | tee /tmp/.version;
|
||||||
|
|
||||||
FROM base AS build
|
FROM base AS build
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG LDFLAGS="-s -w"
|
ARG LDFLAGS="-s -w"
|
||||||
ARG BUILDTAGS=""
|
ARG BUILDTAGS="include_oss,include_gcs"
|
||||||
RUN --mount=type=bind,target=/src \
|
RUN --mount=type=bind,target=/go/src/github.com/docker/distribution,rw \
|
||||||
--mount=type=cache,target=/root/.cache/go-build \
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
--mount=target=/go/pkg/mod,type=cache \
|
--mount=target=/go/pkg/mod,type=cache \
|
||||||
--mount=type=bind,source=/tmp/.ldflags,target=/tmp/.ldflags,from=version \
|
--mount=type=bind,source=/tmp/.ldflags,target=/tmp/.ldflags,from=version \
|
||||||
|
@ -52,9 +52,9 @@ COPY --from=releaser /out /
|
||||||
|
|
||||||
FROM alpine:${ALPINE_VERSION}
|
FROM alpine:${ALPINE_VERSION}
|
||||||
RUN apk add --no-cache ca-certificates
|
RUN apk add --no-cache ca-certificates
|
||||||
COPY cmd/registry/config-dev.yml /etc/distribution/config.yml
|
COPY cmd/registry/config-dev.yml /etc/docker/registry/config.yml
|
||||||
COPY --from=binary /registry /bin/registry
|
COPY --from=binary /registry /bin/registry
|
||||||
VOLUME ["/var/lib/registry"]
|
VOLUME ["/var/lib/registry"]
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
ENTRYPOINT ["registry"]
|
ENTRYPOINT ["registry"]
|
||||||
CMD ["serve", "/etc/distribution/config.yml"]
|
CMD ["serve", "/etc/docker/registry/config.yml"]
|
||||||
|
|
144
GOVERNANCE.md
144
GOVERNANCE.md
|
@ -1,144 +0,0 @@
|
||||||
# distribution/distribution Project Governance
|
|
||||||
|
|
||||||
Distribution [Code of Conduct](./CODE-OF-CONDUCT.md) can be found here.
|
|
||||||
|
|
||||||
For specific guidance on practical contribution steps please
|
|
||||||
see our [CONTRIBUTING.md](./CONTRIBUTING.md) guide.
|
|
||||||
|
|
||||||
## Maintainership
|
|
||||||
|
|
||||||
There are different types of maintainers, with different responsibilities, but
|
|
||||||
all maintainers have 3 things in common:
|
|
||||||
|
|
||||||
1) They share responsibility in the project's success.
|
|
||||||
2) They have made a long-term, recurring time investment to improve the project.
|
|
||||||
3) They spend that time doing whatever needs to be done, not necessarily what
|
|
||||||
is the most interesting or fun.
|
|
||||||
|
|
||||||
Maintainers are often under-appreciated, because their work is harder to appreciate.
|
|
||||||
It's easy to appreciate a really cool and technically advanced feature. It's harder
|
|
||||||
to appreciate the absence of bugs, the slow but steady improvement in stability,
|
|
||||||
or the reliability of a release process. But those things distinguish a good
|
|
||||||
project from a great one.
|
|
||||||
|
|
||||||
## Reviewers
|
|
||||||
|
|
||||||
A reviewer is a core role within the project.
|
|
||||||
They share in reviewing issues and pull requests and their LGTM counts towards the
|
|
||||||
required LGTM count to merge a code change into the project.
|
|
||||||
|
|
||||||
Reviewers are part of the organization but do not have write access.
|
|
||||||
Becoming a reviewer is a core aspect in the journey to becoming a maintainer.
|
|
||||||
|
|
||||||
## Adding maintainers
|
|
||||||
|
|
||||||
Maintainers are first and foremost contributors that have shown they are
|
|
||||||
committed to the long term success of a project. Contributors wanting to become
|
|
||||||
maintainers are expected to be deeply involved in contributing code, pull
|
|
||||||
request review, and triage of issues in the project for more than three months.
|
|
||||||
|
|
||||||
Just contributing does not make you a maintainer, it is about building trust
|
|
||||||
with the current maintainers of the project and being a person that they can
|
|
||||||
depend on and trust to make decisions in the best interest of the project.
|
|
||||||
|
|
||||||
Periodically, the existing maintainers curate a list of contributors that have
|
|
||||||
shown regular activity on the project over the prior months. From this list,
|
|
||||||
maintainer candidates are selected and proposed in a pull request or a
|
|
||||||
maintainers communication channel.
|
|
||||||
|
|
||||||
After a candidate has been announced to the maintainers, the existing
|
|
||||||
maintainers are given five business days to discuss the candidate, raise
|
|
||||||
objections and cast their vote. Votes may take place on the communication
|
|
||||||
channel or via pull request comment. Candidates must be approved by at least 66%
|
|
||||||
of the current maintainers by adding their vote on the mailing list. The
|
|
||||||
reviewer role has the same process but only requires 33% of current maintainers.
|
|
||||||
Only maintainers of the repository that the candidate is proposed for are
|
|
||||||
allowed to vote.
|
|
||||||
|
|
||||||
If a candidate is approved, a maintainer will contact the candidate to invite
|
|
||||||
the candidate to open a pull request that adds the contributor to the
|
|
||||||
MAINTAINERS file. The voting process may take place inside a pull request if a
|
|
||||||
maintainer has already discussed the candidacy with the candidate and a
|
|
||||||
maintainer is willing to be a sponsor by opening the pull request. The candidate
|
|
||||||
becomes a maintainer once the pull request is merged.
|
|
||||||
|
|
||||||
## Stepping down policy
|
|
||||||
|
|
||||||
Life priorities, interests, and passions can change. If you're a maintainer but
|
|
||||||
feel you must remove yourself from the list, inform other maintainers that you
|
|
||||||
intend to step down, and if possible, help find someone to pick up your work.
|
|
||||||
At the very least, ensure your work can be continued where you left off.
|
|
||||||
|
|
||||||
After you've informed other maintainers, create a pull request to remove
|
|
||||||
yourself from the MAINTAINERS file.
|
|
||||||
|
|
||||||
## Removal of inactive maintainers
|
|
||||||
|
|
||||||
Similar to the procedure for adding new maintainers, existing maintainers can
|
|
||||||
be removed from the list if they do not show significant activity on the
|
|
||||||
project. Periodically, the maintainers review the list of maintainers and their
|
|
||||||
activity over the last three months.
|
|
||||||
|
|
||||||
If a maintainer has shown insufficient activity over this period, a neutral
|
|
||||||
person will contact the maintainer to ask if they want to continue being
|
|
||||||
a maintainer. If the maintainer decides to step down as a maintainer, they
|
|
||||||
open a pull request to be removed from the MAINTAINERS file.
|
|
||||||
|
|
||||||
If the maintainer wants to remain a maintainer, but is unable to perform the
|
|
||||||
required duties they can be removed with a vote of at least 66% of the current
|
|
||||||
maintainers. In this case, maintainers should first propose the change to
|
|
||||||
maintainers via the maintainers communication channel, then open a pull request
|
|
||||||
for voting. The voting period is five business days. The voting pull request
|
|
||||||
should not come as a surpise to any maintainer and any discussion related to
|
|
||||||
performance must not be discussed on the pull request.
|
|
||||||
|
|
||||||
## How are decisions made?
|
|
||||||
|
|
||||||
CNCF distribution is an open-source project with an open design philosophy.
|
|
||||||
This means that the repository is the source of truth for EVERY aspect of the
|
|
||||||
project, including its philosophy, design, road map, and APIs. *If it's part of
|
|
||||||
the project, it's in the repo. If it's in the repo, it's part of the project.*
|
|
||||||
|
|
||||||
As a result, all decisions can be expressed as changes to the repository. An
|
|
||||||
implementation change is a change to the source code. An API change is a change
|
|
||||||
to the API specification. A philosophy change is a change to the philosophy
|
|
||||||
manifesto, and so on.
|
|
||||||
|
|
||||||
All decisions affecting distribution, big and small, follow the same 3 steps:
|
|
||||||
|
|
||||||
* Step 1: Open a pull request. Anyone can do this.
|
|
||||||
|
|
||||||
* Step 2: Discuss the pull request. Anyone can do this.
|
|
||||||
|
|
||||||
* Step 3: Merge or refuse the pull request. Who does this depends on the nature
|
|
||||||
of the pull request and which areas of the project it affects.
|
|
||||||
|
|
||||||
## Helping contributors with the DCO
|
|
||||||
|
|
||||||
The [DCO or `Sign your work`](./CONTRIBUTING.md#sign-your-work)
|
|
||||||
requirement is not intended as a roadblock or speed bump.
|
|
||||||
|
|
||||||
Some contributors are not as familiar with `git`, or have used a web
|
|
||||||
based editor, and thus asking them to `git commit --amend -s` is not the best
|
|
||||||
way forward.
|
|
||||||
|
|
||||||
In this case, maintainers can update the commits based on clause (c) of the DCO.
|
|
||||||
The most trivial way for a contributor to allow the maintainer to do this, is to
|
|
||||||
add a DCO signature in a pull requests's comment, or a maintainer can simply
|
|
||||||
note that the change is sufficiently trivial that it does not substantially
|
|
||||||
change the existing contribution - i.e., a spelling change.
|
|
||||||
|
|
||||||
When you add someone's DCO, please also add your own to keep a log.
|
|
||||||
|
|
||||||
## I'm a maintainer. Should I make pull requests too?
|
|
||||||
|
|
||||||
Yes. Nobody should ever push to master directly. All changes should be
|
|
||||||
made through a pull request.
|
|
||||||
|
|
||||||
## Conflict Resolution
|
|
||||||
|
|
||||||
If you have a technical dispute that you feel has reached an impasse with a
|
|
||||||
subset of the community, any contributor may open an issue, specifically
|
|
||||||
calling for a resolution vote of the current core maintainers to resolve the
|
|
||||||
dispute. The same voting quorums required (2/3) for adding and removing
|
|
||||||
maintainers will apply to conflict resolution.
|
|
263
MAINTAINERS
263
MAINTAINERS
|
@ -1,26 +1,243 @@
|
||||||
# Distribution project maintainers & reviewers
|
# Distribution maintainers file
|
||||||
#
|
#
|
||||||
# See GOVERNANCE.md for maintainer versus reviewer roles
|
# This file describes who runs the docker/distribution project and how.
|
||||||
|
# This is a living document - if you see something out of date or missing, speak up!
|
||||||
#
|
#
|
||||||
# MAINTAINERS (cncf-distribution-maintainers@lists.cncf.io)
|
# It is structured to be consumable by both humans and programs.
|
||||||
# GitHub ID, Name, Email address
|
# To extract its contents programmatically, use any TOML-compliant parser.
|
||||||
"chrispat","Chris Patterson","chrispat@github.com"
|
|
||||||
"clarkbw","Bryan Clark","clarkbw@github.com"
|
|
||||||
"corhere","Cory Snider","csnider@mirantis.com"
|
|
||||||
"deleteriousEffect","Hayley Swimelar","hswimelar@gitlab.com"
|
|
||||||
"heww","He Weiwei","hweiwei@vmware.com"
|
|
||||||
"joaodrp","João Pereira","jpereira@gitlab.com"
|
|
||||||
"justincormack","Justin Cormack","justin.cormack@docker.com"
|
|
||||||
"squizzi","Kyle Squizzato","ksquizzato@mirantis.com"
|
|
||||||
"milosgajdos","Milos Gajdos","milosthegajdos@gmail.com"
|
|
||||||
"sargun","Sargun Dhillon","sargun@sargun.me"
|
|
||||||
"wy65701436","Wang Yan","wangyan@vmware.com"
|
|
||||||
"stevelasker","Steve Lasker","steve.lasker@microsoft.com"
|
|
||||||
#
|
#
|
||||||
# REVIEWERS
|
|
||||||
# GitHub ID, Name, Email address
|
[Rules]
|
||||||
"dmcgowan","Derek McGowan","derek@mcgstyle.net"
|
|
||||||
"stevvooe","Stephen Day","stevvooe@gmail.com"
|
[Rules.maintainers]
|
||||||
"thajeztah","Sebastiaan van Stijn","github@gone.nl"
|
|
||||||
"DavidSpek", "David van der Spek", "vanderspek.david@gmail.com"
|
title = "What is a maintainer?"
|
||||||
"Jamstah", "James Hewitt", "james.hewitt@gmail.com"
|
|
||||||
|
text = """
|
||||||
|
There are different types of maintainers, with different responsibilities, but
|
||||||
|
all maintainers have 3 things in common:
|
||||||
|
|
||||||
|
1) They share responsibility in the project's success.
|
||||||
|
2) They have made a long-term, recurring time investment to improve the project.
|
||||||
|
3) They spend that time doing whatever needs to be done, not necessarily what
|
||||||
|
is the most interesting or fun.
|
||||||
|
|
||||||
|
Maintainers are often under-appreciated, because their work is harder to appreciate.
|
||||||
|
It's easy to appreciate a really cool and technically advanced feature. It's harder
|
||||||
|
to appreciate the absence of bugs, the slow but steady improvement in stability,
|
||||||
|
or the reliability of a release process. But those things distinguish a good
|
||||||
|
project from a great one.
|
||||||
|
"""
|
||||||
|
|
||||||
|
[Rules.reviewer]
|
||||||
|
|
||||||
|
title = "What is a reviewer?"
|
||||||
|
|
||||||
|
text = """
|
||||||
|
A reviewer is a core role within the project.
|
||||||
|
They share in reviewing issues and pull requests and their LGTM count towards the
|
||||||
|
required LGTM count to merge a code change into the project.
|
||||||
|
|
||||||
|
Reviewers are part of the organization but do not have write access.
|
||||||
|
Becoming a reviewer is a core aspect in the journey to becoming a maintainer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
[Rules.adding-maintainers]
|
||||||
|
|
||||||
|
title = "How are maintainers added?"
|
||||||
|
|
||||||
|
text = """
|
||||||
|
Maintainers are first and foremost contributors that have shown they are
|
||||||
|
committed to the long term success of a project. Contributors wanting to become
|
||||||
|
maintainers are expected to be deeply involved in contributing code, pull
|
||||||
|
request review, and triage of issues in the project for more than three months.
|
||||||
|
|
||||||
|
Just contributing does not make you a maintainer, it is about building trust
|
||||||
|
with the current maintainers of the project and being a person that they can
|
||||||
|
depend on and trust to make decisions in the best interest of the project.
|
||||||
|
|
||||||
|
Periodically, the existing maintainers curate a list of contributors that have
|
||||||
|
shown regular activity on the project over the prior months. From this list,
|
||||||
|
maintainer candidates are selected and proposed on the maintainers mailing list.
|
||||||
|
|
||||||
|
After a candidate has been announced on the maintainers mailing list, the
|
||||||
|
existing maintainers are given five business days to discuss the candidate,
|
||||||
|
raise objections and cast their vote. Candidates must be approved by at least 66% of the current maintainers by adding their vote on the mailing
|
||||||
|
list. Only maintainers of the repository that the candidate is proposed for are
|
||||||
|
allowed to vote.
|
||||||
|
|
||||||
|
If a candidate is approved, a maintainer will contact the candidate to invite
|
||||||
|
the candidate to open a pull request that adds the contributor to the
|
||||||
|
MAINTAINERS file. The candidate becomes a maintainer once the pull request is
|
||||||
|
merged.
|
||||||
|
"""
|
||||||
|
|
||||||
|
[Rules.stepping-down-policy]
|
||||||
|
|
||||||
|
title = "Stepping down policy"
|
||||||
|
|
||||||
|
text = """
|
||||||
|
Life priorities, interests, and passions can change. If you're a maintainer but
|
||||||
|
feel you must remove yourself from the list, inform other maintainers that you
|
||||||
|
intend to step down, and if possible, help find someone to pick up your work.
|
||||||
|
At the very least, ensure your work can be continued where you left off.
|
||||||
|
|
||||||
|
After you've informed other maintainers, create a pull request to remove
|
||||||
|
yourself from the MAINTAINERS file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
[Rules.inactive-maintainers]
|
||||||
|
|
||||||
|
title = "Removal of inactive maintainers"
|
||||||
|
|
||||||
|
text = """
|
||||||
|
Similar to the procedure for adding new maintainers, existing maintainers can
|
||||||
|
be removed from the list if they do not show significant activity on the
|
||||||
|
project. Periodically, the maintainers review the list of maintainers and their
|
||||||
|
activity over the last three months.
|
||||||
|
|
||||||
|
If a maintainer has shown insufficient activity over this period, a neutral
|
||||||
|
person will contact the maintainer to ask if they want to continue being
|
||||||
|
a maintainer. If the maintainer decides to step down as a maintainer, they
|
||||||
|
open a pull request to be removed from the MAINTAINERS file.
|
||||||
|
|
||||||
|
If the maintainer wants to remain a maintainer, but is unable to perform the
|
||||||
|
required duties they can be removed with a vote of at least 66% of
|
||||||
|
the current maintainers. An e-mail is sent to the
|
||||||
|
mailing list, inviting maintainers of the project to vote. The voting period is
|
||||||
|
five business days. Issues related to a maintainer's performance should be
|
||||||
|
discussed with them among the other maintainers so that they are not surprised
|
||||||
|
by a pull request removing them.
|
||||||
|
"""
|
||||||
|
|
||||||
|
[Rules.decisions]
|
||||||
|
|
||||||
|
title = "How are decisions made?"
|
||||||
|
|
||||||
|
text = """
|
||||||
|
Short answer: EVERYTHING IS A PULL REQUEST.
|
||||||
|
|
||||||
|
distribution is an open-source project with an open design philosophy. This means
|
||||||
|
that the repository is the source of truth for EVERY aspect of the project,
|
||||||
|
including its philosophy, design, road map, and APIs. *If it's part of the
|
||||||
|
project, it's in the repo. If it's in the repo, it's part of the project.*
|
||||||
|
|
||||||
|
As a result, all decisions can be expressed as changes to the repository. An
|
||||||
|
implementation change is a change to the source code. An API change is a change
|
||||||
|
to the API specification. A philosophy change is a change to the philosophy
|
||||||
|
manifesto, and so on.
|
||||||
|
|
||||||
|
All decisions affecting distribution, big and small, follow the same 3 steps:
|
||||||
|
|
||||||
|
* Step 1: Open a pull request. Anyone can do this.
|
||||||
|
|
||||||
|
* Step 2: Discuss the pull request. Anyone can do this.
|
||||||
|
|
||||||
|
* Step 3: Merge or refuse the pull request. Who does this depends on the nature
|
||||||
|
of the pull request and which areas of the project it affects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
[Rules.DCO]
|
||||||
|
|
||||||
|
title = "Helping contributors with the DCO"
|
||||||
|
|
||||||
|
text = """
|
||||||
|
The [DCO or `Sign your work`](
|
||||||
|
https://github.com/moby/moby/blob/master/CONTRIBUTING.md#sign-your-work)
|
||||||
|
requirement is not intended as a roadblock or speed bump.
|
||||||
|
|
||||||
|
Some distribution contributors are not as familiar with `git`, or have used a web
|
||||||
|
based editor, and thus asking them to `git commit --amend -s` is not the best
|
||||||
|
way forward.
|
||||||
|
|
||||||
|
In this case, maintainers can update the commits based on clause (c) of the DCO.
|
||||||
|
The most trivial way for a contributor to allow the maintainer to do this, is to
|
||||||
|
add a DCO signature in a pull requests's comment, or a maintainer can simply
|
||||||
|
note that the change is sufficiently trivial that it does not substantially
|
||||||
|
change the existing contribution - i.e., a spelling change.
|
||||||
|
|
||||||
|
When you add someone's DCO, please also add your own to keep a log.
|
||||||
|
"""
|
||||||
|
|
||||||
|
[Rules."no direct push"]
|
||||||
|
|
||||||
|
title = "I'm a maintainer. Should I make pull requests too?"
|
||||||
|
|
||||||
|
text = """
|
||||||
|
Yes. Nobody should ever push to master directly. All changes should be
|
||||||
|
made through a pull request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
[Rules.tsc]
|
||||||
|
|
||||||
|
title = "Conflict Resolution and technical disputes"
|
||||||
|
|
||||||
|
text = """
|
||||||
|
distribution defers to the [Technical Steering Committee](https://github.com/moby/tsc) for escalations and resolution on disputes for technical matters."
|
||||||
|
"""
|
||||||
|
|
||||||
|
[Rules.meta]
|
||||||
|
|
||||||
|
title = "How is this process changed?"
|
||||||
|
|
||||||
|
text = "Just like everything else: by making a pull request :)"
|
||||||
|
|
||||||
|
# Current project organization
|
||||||
|
[Org]
|
||||||
|
|
||||||
|
[Org.Maintainers]
|
||||||
|
people = [
|
||||||
|
"dmcgowan",
|
||||||
|
"dmp42",
|
||||||
|
"stevvooe",
|
||||||
|
]
|
||||||
|
[Org.Reviewers]
|
||||||
|
people = [
|
||||||
|
"manishtomar",
|
||||||
|
"caervs",
|
||||||
|
"davidswu",
|
||||||
|
"RobbKistler"
|
||||||
|
]
|
||||||
|
|
||||||
|
[people]
|
||||||
|
|
||||||
|
# A reference list of all people associated with the project.
|
||||||
|
# All other sections should refer to people by their canonical key
|
||||||
|
# in the people section.
|
||||||
|
|
||||||
|
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
|
||||||
|
|
||||||
|
[people.caervs]
|
||||||
|
Name = "Ryan Abrams"
|
||||||
|
Email = "rdabrams@gmail.com"
|
||||||
|
GitHub = "caervs"
|
||||||
|
|
||||||
|
[people.davidswu]
|
||||||
|
Name = "David Wu"
|
||||||
|
Email = "dwu7401@gmail.com"
|
||||||
|
GitHub = "davidswu"
|
||||||
|
|
||||||
|
[people.dmcgowan]
|
||||||
|
Name = "Derek McGowan"
|
||||||
|
Email = "derek@mcgstyle.net"
|
||||||
|
GitHub = "dmcgowan"
|
||||||
|
|
||||||
|
[people.dmp42]
|
||||||
|
Name = "Olivier Gambier"
|
||||||
|
Email = "olivier@docker.com"
|
||||||
|
GitHub = "dmp42"
|
||||||
|
|
||||||
|
[people.manishtomar]
|
||||||
|
Name = "Manish Tomar"
|
||||||
|
Email = "manish.tomar@docker.com"
|
||||||
|
GitHub = "manishtomar"
|
||||||
|
|
||||||
|
[people.RobbKistler]
|
||||||
|
Name = "Robb Kistler"
|
||||||
|
Email = "robb.kistler@docker.com"
|
||||||
|
GitHub = "RobbKistler"
|
||||||
|
|
||||||
|
[people.stevvooe]
|
||||||
|
Name = "Stephen Day"
|
||||||
|
Email = "stephen.day@docker.com"
|
||||||
|
GitHub = "stevvooe"
|
||||||
|
|
128
Makefile
128
Makefile
|
@ -1,25 +1,18 @@
|
||||||
.DEFAULT_GOAL := help
|
|
||||||
|
|
||||||
# Root directory of the project (absolute path).
|
# Root directory of the project (absolute path).
|
||||||
ROOTDIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
ROOTDIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
# Used to populate version variable in main package.
|
# Used to populate version variable in main package.
|
||||||
VERSION ?= $(shell git describe --match 'v[0-9]*' --dirty='.m' --always)
|
VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always)
|
||||||
REVISION ?= $(shell git rev-parse HEAD)$(shell if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi)
|
REVISION=$(shell git rev-parse HEAD)$(shell if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi)
|
||||||
|
|
||||||
# default compose command
|
|
||||||
COMPOSE ?= docker compose
|
|
||||||
|
|
||||||
PKG=github.com/distribution/distribution/v3
|
PKG=github.com/docker/distribution
|
||||||
|
|
||||||
# Project packages.
|
# Project packages.
|
||||||
PACKAGES=$(shell go list -tags "${BUILDTAGS}" ./... | grep -v /vendor/)
|
PACKAGES=$(shell go list -tags "${BUILDTAGS}" ./... | grep -v /vendor/)
|
||||||
INTEGRATION_PACKAGE=${PKG}
|
INTEGRATION_PACKAGE=${PKG}
|
||||||
COVERAGE_PACKAGES=$(filter-out ${PKG}/registry/storage/driver/%,${PACKAGES})
|
COVERAGE_PACKAGES=$(filter-out ${PKG}/registry/storage/driver/%,${PACKAGES})
|
||||||
|
|
||||||
IMAGE_REPO ?= distribution/distribution
|
|
||||||
IMAGE_TAG ?= latest
|
|
||||||
IMAGE_NAME ?= $(IMAGE_REPO):$(IMAGE_TAG)
|
|
||||||
|
|
||||||
# Project binaries.
|
# Project binaries.
|
||||||
COMMANDS=registry digest registry-api-descriptor-template
|
COMMANDS=registry digest registry-api-descriptor-template
|
||||||
|
@ -37,7 +30,7 @@ WHALE = "+"
|
||||||
TESTFLAGS_RACE=
|
TESTFLAGS_RACE=
|
||||||
GOFILES=$(shell find . -type f -name '*.go')
|
GOFILES=$(shell find . -type f -name '*.go')
|
||||||
GO_TAGS=$(if $(BUILDTAGS),-tags "$(BUILDTAGS)",)
|
GO_TAGS=$(if $(BUILDTAGS),-tags "$(BUILDTAGS)",)
|
||||||
GO_LDFLAGS=-ldflags '-extldflags "-Wl,-z,now" -s -w -X $(PKG)/version.version=$(VERSION) -X $(PKG)/version.revision=$(REVISION) -X $(PKG)/version.mainpkg=$(PKG) $(EXTRA_LDFLAGS)'
|
GO_LDFLAGS=-ldflags '-s -w -X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) -X $(PKG)/version.Package=$(PKG) $(EXTRA_LDFLAGS)'
|
||||||
|
|
||||||
BINARIES=$(addprefix bin/,$(COMMANDS))
|
BINARIES=$(addprefix bin/,$(COMMANDS))
|
||||||
|
|
||||||
|
@ -45,51 +38,19 @@ BINARIES=$(addprefix bin/,$(COMMANDS))
|
||||||
TESTFLAGS ?= -v $(TESTFLAGS_RACE)
|
TESTFLAGS ?= -v $(TESTFLAGS_RACE)
|
||||||
TESTFLAGS_PARALLEL ?= 8
|
TESTFLAGS_PARALLEL ?= 8
|
||||||
|
|
||||||
.PHONY: all build binaries clean test test-race test-full integration test-coverage validate lint validate-git validate-vendor vendor mod-outdated image validate-authors authors
|
.PHONY: all build binaries check clean test test-race test-full integration coverage
|
||||||
.DEFAULT: all
|
.DEFAULT: all
|
||||||
|
|
||||||
.PHONY: FORCE
|
all: binaries
|
||||||
FORCE:
|
|
||||||
|
|
||||||
##@ Build
|
|
||||||
|
|
||||||
# This only needs to be generated by hand when cutting full releases.
|
# This only needs to be generated by hand when cutting full releases.
|
||||||
version/version.go:
|
version/version.go:
|
||||||
@echo "$(WHALE) $@"
|
@echo "$(WHALE) $@"
|
||||||
./version/version.sh > $@
|
./version/version.sh > $@
|
||||||
|
|
||||||
bin/%: cmd/% FORCE ## build individual binary
|
check: ## run all linters (TODO: enable "unused", "varcheck", "ineffassign", "unconvert", "staticheck", "goimports", "structcheck")
|
||||||
@echo "$(WHALE) $@${BINARY_SUFFIX}"
|
|
||||||
@go build -buildmode=pie ${GO_GCFLAGS} ${GO_BUILD_FLAGS} -o $@${BINARY_SUFFIX} ${GO_LDFLAGS} --ldflags '-extldflags "-Wl,-z,now" -s' ${GO_TAGS} ./$<
|
|
||||||
|
|
||||||
binaries: $(BINARIES) ## build binaries
|
|
||||||
@echo "$(WHALE) $@"
|
@echo "$(WHALE) $@"
|
||||||
|
@GO111MODULE=off golangci-lint --build-tags "${BUILDTAGS}" run
|
||||||
build: ## build go packages
|
|
||||||
@echo "$(WHALE) $@"
|
|
||||||
@go build -buildmode=pie ${GO_GCFLAGS} ${GO_BUILD_FLAGS} ${GO_LDFLAGS} --ldflags '-extldflags "-Wl,-z,now" -s' ${GO_TAGS} $(PACKAGES)
|
|
||||||
|
|
||||||
image: ## build docker image IMAGE_NAME=<name>
|
|
||||||
docker buildx bake --set "*.tags=${IMAGE_NAME}" image-local
|
|
||||||
|
|
||||||
clean: ## clean up binaries
|
|
||||||
@echo "$(WHALE) $@"
|
|
||||||
@rm -f $(BINARIES)
|
|
||||||
|
|
||||||
vendor: ## update vendor
|
|
||||||
$(eval $@_TMP_OUT := $(shell mktemp -d -t buildx-output.XXXXXXXXXX))
|
|
||||||
docker buildx bake --set "*.output=$($@_TMP_OUT)" update-vendor
|
|
||||||
rm -rf ./vendor
|
|
||||||
cp -R "$($@_TMP_OUT)"/out/* .
|
|
||||||
rm -rf $($@_TMP_OUT)/*
|
|
||||||
|
|
||||||
mod-outdated: ## check outdated dependencies
|
|
||||||
docker buildx bake $@
|
|
||||||
|
|
||||||
authors: ## generate authors
|
|
||||||
docker buildx bake $@
|
|
||||||
|
|
||||||
##@ Test
|
|
||||||
|
|
||||||
test: ## run tests, except integration test with test.short
|
test: ## run tests, except integration test with test.short
|
||||||
@echo "$(WHALE) $@"
|
@echo "$(WHALE) $@"
|
||||||
|
@ -107,7 +68,7 @@ integration: ## run integration tests
|
||||||
@echo "$(WHALE) $@"
|
@echo "$(WHALE) $@"
|
||||||
@go test ${TESTFLAGS} -parallel ${TESTFLAGS_PARALLEL} ${INTEGRATION_PACKAGE}
|
@go test ${TESTFLAGS} -parallel ${TESTFLAGS_PARALLEL} ${INTEGRATION_PACKAGE}
|
||||||
|
|
||||||
test-coverage: ## run unit tests and generate test coverprofiles
|
coverage: ## generate coverprofiles from the unit tests
|
||||||
@echo "$(WHALE) $@"
|
@echo "$(WHALE) $@"
|
||||||
@rm -f coverage.txt
|
@rm -f coverage.txt
|
||||||
@go test ${GO_TAGS} -i ${TESTFLAGS} $(filter-out ${INTEGRATION_PACKAGE},${COVERAGE_PACKAGES}) 2> /dev/null
|
@go test ${GO_TAGS} -i ${TESTFLAGS} $(filter-out ${INTEGRATION_PACKAGE},${COVERAGE_PACKAGES}) 2> /dev/null
|
||||||
|
@ -122,65 +83,20 @@ test-coverage: ## run unit tests and generate test coverprofiles
|
||||||
fi; \
|
fi; \
|
||||||
done )
|
done )
|
||||||
|
|
||||||
.PHONY: test-cloud-storage
|
FORCE:
|
||||||
test-cloud-storage: start-cloud-storage run-s3-tests stop-cloud-storage ## run cloud storage driver tests
|
|
||||||
|
|
||||||
.PHONY: start-cloud-storage
|
# Build a binary from a cmd.
|
||||||
start-cloud-storage: ## start local cloud storage (minio)
|
bin/%: cmd/% FORCE
|
||||||
$(COMPOSE) -f tests/docker-compose-storage.yml up minio minio-init -d
|
@echo "$(WHALE) $@${BINARY_SUFFIX}"
|
||||||
|
@go build ${GO_GCFLAGS} ${GO_BUILD_FLAGS} -o $@${BINARY_SUFFIX} ${GO_LDFLAGS} ${GO_TAGS} ./$<
|
||||||
|
|
||||||
.PHONY: stop-cloud-storage
|
binaries: $(BINARIES) ## build binaries
|
||||||
stop-cloud-storage: ## stop local cloud storage (minio)
|
@echo "$(WHALE) $@"
|
||||||
$(COMPOSE) -f tests/docker-compose-storage.yml down
|
|
||||||
|
|
||||||
.PHONY: reset-cloud-storage
|
build:
|
||||||
reset-cloud-storage: ## reset (stop, delete, start) local cloud storage (minio)
|
@echo "$(WHALE) $@"
|
||||||
$(COMPOSE) -f tests/docker-compose-storage.yml down
|
@go build ${GO_GCFLAGS} ${GO_BUILD_FLAGS} ${GO_LDFLAGS} ${GO_TAGS} $(PACKAGES)
|
||||||
@mkdir -p tests/miniodata/distribution
|
|
||||||
@rm -rf tests/miniodata/distribution/* tests/miniodata/.minio.sys
|
|
||||||
$(COMPOSE) -f tests/docker-compose-storage.yml up minio minio-init -d
|
|
||||||
|
|
||||||
.PHONY: run-s3-tests
|
clean: ## clean up binaries
|
||||||
run-s3-tests: start-cloud-storage ## run S3 storage driver integration tests
|
@echo "$(WHALE) $@"
|
||||||
AWS_ACCESS_KEY=distribution \
|
@rm -f $(BINARIES)
|
||||||
AWS_SECRET_KEY=password \
|
|
||||||
AWS_REGION=us-east-1 \
|
|
||||||
S3_BUCKET=images-local \
|
|
||||||
S3_ENCRYPT=false \
|
|
||||||
REGION_ENDPOINT=http://127.0.0.1:9000 \
|
|
||||||
S3_SECURE=false \
|
|
||||||
S3_ACCELERATE=false \
|
|
||||||
AWS_S3_FORCE_PATH_STYLE=true \
|
|
||||||
go test ${TESTFLAGS} -count=1 ./registry/storage/driver/s3-aws/...
|
|
||||||
|
|
||||||
.PHONY: start-e2e-s3-env
|
|
||||||
start-e2e-s3-env: ## starts E2E S3 storage test environment (S3, Redis, registry)
|
|
||||||
$(COMPOSE) -f tests/docker-compose-e2e-cloud-storage.yml up -d
|
|
||||||
|
|
||||||
.PHONY: stop-e2e-s3-env
|
|
||||||
stop-e2e-s3-env: ## stops E2E S3 storage test environment (S3, Redis, registry)
|
|
||||||
$(COMPOSE) -f tests/docker-compose-e2e-cloud-storage.yml down
|
|
||||||
|
|
||||||
##@ Validate
|
|
||||||
|
|
||||||
lint: ## run all linters
|
|
||||||
docker buildx bake $@
|
|
||||||
|
|
||||||
validate: ## run all validators
|
|
||||||
docker buildx bake $@
|
|
||||||
|
|
||||||
validate-git: ## validate git
|
|
||||||
docker buildx bake $@
|
|
||||||
|
|
||||||
validate-vendor: ## validate vendor
|
|
||||||
docker buildx bake $@
|
|
||||||
|
|
||||||
validate-authors: ## validate authors
|
|
||||||
docker buildx bake $@
|
|
||||||
|
|
||||||
.PHONY: help
|
|
||||||
help:
|
|
||||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z0-9_\/%-]+:.*?##/ { printf " \033[36m%-27s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
|
||||||
@echo ""
|
|
||||||
@echo "Go binaries: $(BINARIES)"
|
|
||||||
@echo "Docker image: $(IMAGE_NAME)"
|
|
||||||
|
|
134
README.md
134
README.md
|
@ -1,41 +1,32 @@
|
||||||
<p align="center">
|
# Distribution
|
||||||
<img style="align: center; padding-left: 10px; padding-right: 10px; padding-bottom: 10px;" width="238px" height="238px" src="./distribution-logo.svg" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[](https://github.com/distribution/distribution/actions/workflows/build.yml?query=workflow%3Abuild)
|
The Docker toolset to pack, ship, store, and deliver content.
|
||||||
[](https://pkg.go.dev/github.com/distribution/distribution)
|
|
||||||
[](LICENSE)
|
|
||||||
[](https://codecov.io/gh/distribution/distribution)
|
|
||||||
[](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Fdistribution?ref=badge_shield)
|
|
||||||
[](https://github.com/distribution/distribution/actions?query=workflow%3Aconformance)
|
|
||||||
[](https://securityscorecards.dev/viewer/?uri=github.com/distribution/distribution)
|
|
||||||
|
|
||||||
The toolset to pack, ship, store, and deliver content.
|
This repository provides the Docker Registry 2.0 implementation
|
||||||
|
for storing and distributing Docker images. It supersedes the
|
||||||
|
[docker/docker-registry](https://github.com/docker/docker-registry)
|
||||||
|
project with a new API design, focused around security and performance.
|
||||||
|
|
||||||
This repository's main product is the Open Source Registry implementation
|
<img src="https://www.docker.com/sites/default/files/oyster-registry-3.png" width=200px/>
|
||||||
for storing and distributing container images and other content using the
|
|
||||||
[OCI Distribution Specification](https://github.com/opencontainers/distribution-spec).
|
[](https://circleci.com/gh/docker/distribution/tree/master)
|
||||||
The goal of this project is to provide a simple, secure, and scalable base
|
[](https://godoc.org/github.com/docker/distribution)
|
||||||
for building a large scale registry solution or running a simple private registry.
|
|
||||||
It is a core library for many registry operators including Docker Hub, GitHub Container Registry,
|
|
||||||
GitLab Container Registry and DigitalOcean Container Registry, as well as the CNCF Harbor
|
|
||||||
Project, and VMware Harbor Registry.
|
|
||||||
|
|
||||||
This repository contains the following components:
|
This repository contains the following components:
|
||||||
|
|
||||||
|**Component** |Description |
|
|**Component** |Description |
|
||||||
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| **registry** | An implementation of the [OCI Distribution Specification](https://github.com/opencontainers/distribution-spec). |
|
| **registry** | An implementation of the [Docker Registry HTTP API V2](docs/spec/api.md) for use with docker 1.6+. |
|
||||||
| **libraries** | A rich set of libraries for interacting with distribution components. Please see [godoc](https://pkg.go.dev/github.com/distribution/distribution) for details. **Note**: The interfaces for these libraries are **unstable**. |
|
| **libraries** | A rich set of libraries for interacting with distribution components. Please see [godoc](https://godoc.org/github.com/docker/distribution) for details. **Note**: These libraries are **unstable**. |
|
||||||
| **documentation** | Full documentation is available at [https://distribution.github.io/distribution](https://distribution.github.io/distribution/).
|
| **specifications** | _Distribution_ related specifications are available in [docs/spec](docs/spec) |
|
||||||
|
| **documentation** | Docker's full documentation set is available at [docs.docker.com](https://docs.docker.com). This repository [contains the subset](docs/) related just to the registry. |
|
||||||
|
|
||||||
### How does this integrate with Docker, containerd, and other OCI client?
|
### How does this integrate with Docker engine?
|
||||||
|
|
||||||
Clients implement against the OCI specification and communicate with the
|
This project should provide an implementation to a V2 API for use in the [Docker
|
||||||
registry using HTTP. This project contains a client implementation which
|
core project](https://github.com/docker/docker). The API should be embeddable
|
||||||
is currently in use by Docker, however, it is deprecated for the
|
and simplify the process of securely pulling and pushing content from `docker`
|
||||||
[implementation in containerd](https://github.com/containerd/containerd/tree/master/remotes/docker)
|
daemons.
|
||||||
and will not support new features.
|
|
||||||
|
|
||||||
### What are the long term goals of the Distribution project?
|
### What are the long term goals of the Distribution project?
|
||||||
|
|
||||||
|
@ -52,23 +43,88 @@ system that allow users to:
|
||||||
* Implement their own home made solution through good specs, and solid
|
* Implement their own home made solution through good specs, and solid
|
||||||
extensions mechanism.
|
extensions mechanism.
|
||||||
|
|
||||||
## Contribution
|
## More about Registry 2.0
|
||||||
|
|
||||||
|
The new registry implementation provides the following benefits:
|
||||||
|
|
||||||
|
- faster push and pull
|
||||||
|
- new, more efficient implementation
|
||||||
|
- simplified deployment
|
||||||
|
- pluggable storage backend
|
||||||
|
- webhook notifications
|
||||||
|
|
||||||
|
For information on upcoming functionality, please see [ROADMAP.md](ROADMAP.md).
|
||||||
|
|
||||||
|
### Who needs to deploy a registry?
|
||||||
|
|
||||||
|
By default, Docker users pull images from Docker's public registry instance.
|
||||||
|
[Installing Docker](https://docs.docker.com/engine/installation/) gives users this
|
||||||
|
ability. Users can also push images to a repository on Docker's public registry,
|
||||||
|
if they have a [Docker Hub](https://hub.docker.com/) account.
|
||||||
|
|
||||||
|
For some users and even companies, this default behavior is sufficient. For
|
||||||
|
others, it is not.
|
||||||
|
|
||||||
|
For example, users with their own software products may want to maintain a
|
||||||
|
registry for private, company images. Also, you may wish to deploy your own
|
||||||
|
image repository for images used to test or in continuous integration. For these
|
||||||
|
use cases and others, [deploying your own registry instance](https://github.com/docker/docker.github.io/blob/master/registry/deploying.md)
|
||||||
|
may be the better choice.
|
||||||
|
|
||||||
|
### Migration to Registry 2.0
|
||||||
|
|
||||||
|
For those who have previously deployed their own registry based on the Registry
|
||||||
|
1.0 implementation and wish to deploy a Registry 2.0 while retaining images,
|
||||||
|
data migration is required. A tool to assist with migration efforts has been
|
||||||
|
created. For more information see [docker/migrator](https://github.com/docker/migrator).
|
||||||
|
|
||||||
|
## Contribute
|
||||||
|
|
||||||
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute
|
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute
|
||||||
issues, fixes, and patches to this project. If you are contributing code, see
|
issues, fixes, and patches to this project. If you are contributing code, see
|
||||||
the instructions for [building a development environment](BUILDING.md).
|
the instructions for [building a development environment](BUILDING.md).
|
||||||
|
|
||||||
## Communication
|
## Support
|
||||||
|
|
||||||
For async communication and long running discussions please use issues and pull requests on the github repo.
|
If any issues are encountered while using the _Distribution_ project, several
|
||||||
This will be the best place to discuss design and implementation.
|
avenues are available for support:
|
||||||
|
|
||||||
For sync communication we have a #distribution channel in the [CNCF Slack](https://slack.cncf.io/)
|
<table>
|
||||||
that everyone is welcome to join and chat about development.
|
<tr>
|
||||||
|
<th align="left">
|
||||||
|
IRC
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
#docker-distribution on FreeNode
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th align="left">
|
||||||
|
Issue Tracker
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
github.com/docker/distribution/issues
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th align="left">
|
||||||
|
Google Groups
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
https://groups.google.com/a/dockerproject.org/forum/#!forum/distribution
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th align="left">
|
||||||
|
Mailing List
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
docker@dockerproject.org
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
## Licenses
|
|
||||||
|
|
||||||
The distribution codebase is released under the [Apache 2.0 license](LICENSE).
|
## License
|
||||||
The README.md file, and files in the "docs" folder are licensed under the
|
|
||||||
Creative Commons Attribution 4.0 International License. You may obtain a
|
This project is distributed under [Apache License, Version 2.0](LICENSE).
|
||||||
copy of the license, titled CC-BY-4.0, at http://creativecommons.org/licenses/by/4.0/.
|
|
||||||
|
|
275
ROADMAP.md
275
ROADMAP.md
|
@ -1,16 +1,267 @@
|
||||||
# Roadmap
|
# Roadmap
|
||||||
|
|
||||||
The Distribution project aims to support the following use cases
|
The Distribution Project consists of several components, some of which are
|
||||||
|
still being defined. This document defines the high-level goals of the
|
||||||
|
project, identifies the current components, and defines the release-
|
||||||
|
relationship to the Docker Platform.
|
||||||
|
|
||||||
1. A library to support building highly scalable and reliable container registries,
|
* [Distribution Goals](#distribution-goals)
|
||||||
that can be customised for different backends and use cases. This is used by many
|
* [Distribution Components](#distribution-components)
|
||||||
of the largest registry operators, including Docker Hub, GitHub, GitLab, Harbor
|
* [Project Planning](#project-planning): release-relationship to the Docker Platform.
|
||||||
and Digital Ocean.
|
|
||||||
2. A reference implementation of the OCI registry standards, and an easy way to
|
This road map is a living document, providing an overview of the goals and
|
||||||
experiment with new propsals in the registry space as these standards change.
|
considerations made in respect of the future of the project.
|
||||||
3. Distributed registry tools, such as caching registries and local registries
|
|
||||||
that can be used within clusters for performance and locality use cases.
|
## Distribution Goals
|
||||||
|
|
||||||
|
- Replace the existing [docker registry](github.com/docker/docker-registry)
|
||||||
|
implementation as the primary implementation.
|
||||||
|
- Replace the existing push and pull code in the docker engine with the
|
||||||
|
distribution package.
|
||||||
|
- Define a strong data model for distributing docker images
|
||||||
|
- Provide a flexible distribution tool kit for use in the docker platform
|
||||||
|
- Unlock new distribution models
|
||||||
|
|
||||||
|
## Distribution Components
|
||||||
|
|
||||||
|
Components of the Distribution Project are managed via github [milestones](https://github.com/docker/distribution/milestones). Upcoming
|
||||||
|
features and bugfixes for a component will be added to the relevant milestone. If a feature or
|
||||||
|
bugfix is not part of a milestone, it is currently unscheduled for
|
||||||
|
implementation.
|
||||||
|
|
||||||
|
* [Registry](#registry)
|
||||||
|
* [Distribution Package](#distribution-package)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### Registry
|
||||||
|
|
||||||
|
The new Docker registry is the main portion of the distribution repository.
|
||||||
|
Registry 2.0 is the first release of the next-generation registry. This was
|
||||||
|
primarily focused on implementing the [new registry
|
||||||
|
API](https://github.com/docker/distribution/blob/master/docs/spec/api.md),
|
||||||
|
with a focus on security and performance.
|
||||||
|
|
||||||
|
Following from the Distribution project goals above, we have a set of goals
|
||||||
|
for registry v2 that we would like to follow in the design. New features
|
||||||
|
should be compared against these goals.
|
||||||
|
|
||||||
|
#### Data Storage and Distribution First
|
||||||
|
|
||||||
|
The registry's first goal is to provide a reliable, consistent storage
|
||||||
|
location for Docker images. The registry should only provide the minimal
|
||||||
|
amount of indexing required to fetch image data and no more.
|
||||||
|
|
||||||
|
This means we should be selective in new features and API additions, including
|
||||||
|
those that may require expensive, ever growing indexes. Requests should be
|
||||||
|
servable in "constant time".
|
||||||
|
|
||||||
|
#### Content Addressability
|
||||||
|
|
||||||
|
All data objects used in the registry API should be content addressable.
|
||||||
|
Content identifiers should be secure and verifiable. This provides a secure,
|
||||||
|
reliable base from which to build more advanced content distribution systems.
|
||||||
|
|
||||||
|
#### Content Agnostic
|
||||||
|
|
||||||
|
In the past, changes to the image format would require large changes in Docker
|
||||||
|
and the Registry. By decoupling the distribution and image format, we can
|
||||||
|
allow the formats to progress without having to coordinate between the two.
|
||||||
|
This means that we should be focused on decoupling Docker from the registry
|
||||||
|
just as much as decoupling the registry from Docker. Such an approach will
|
||||||
|
allow us to unlock new distribution models that haven't been possible before.
|
||||||
|
|
||||||
|
We can take this further by saying that the new registry should be content
|
||||||
|
agnostic. The registry provides a model of names, tags, manifests and content
|
||||||
|
addresses and that model can be used to work with content.
|
||||||
|
|
||||||
|
#### Simplicity
|
||||||
|
|
||||||
|
The new registry should be closer to a microservice component than its
|
||||||
|
predecessor. This means it should have a narrower API and a low number of
|
||||||
|
service dependencies. It should be easy to deploy.
|
||||||
|
|
||||||
|
This means that other solutions should be explored before changing the API or
|
||||||
|
adding extra dependencies. If functionality is required, can it be added as an
|
||||||
|
extension or companion service.
|
||||||
|
|
||||||
|
#### Extensibility
|
||||||
|
|
||||||
|
The registry should provide extension points to add functionality. By keeping
|
||||||
|
the scope narrow, but providing the ability to add functionality.
|
||||||
|
|
||||||
|
Features like search, indexing, synchronization and registry explorers fall
|
||||||
|
into this category. No such feature should be added unless we've found it
|
||||||
|
impossible to do through an extension.
|
||||||
|
|
||||||
|
#### Active Feature Discussions
|
||||||
|
|
||||||
|
The following are feature discussions that are currently active.
|
||||||
|
|
||||||
|
If you don't see your favorite, unimplemented feature, feel free to contact us
|
||||||
|
via IRC or the mailing list and we can talk about adding it. The goal here is
|
||||||
|
to make sure that new features go through a rigid design process before
|
||||||
|
landing in the registry.
|
||||||
|
|
||||||
|
##### Proxying to other Registries
|
||||||
|
|
||||||
|
A _pull-through caching_ mode exists for the registry, but is restricted from
|
||||||
|
within the docker client to only mirror the official Docker Hub. This functionality
|
||||||
|
can be expanded when image provenance has been specified and implemented in the
|
||||||
|
distribution project.
|
||||||
|
|
||||||
|
##### Metadata storage
|
||||||
|
|
||||||
|
Metadata for the registry is currently stored with the manifest and layer data on
|
||||||
|
the storage backend. While this is a big win for simplicity and reliably maintaining
|
||||||
|
state, it comes with the cost of consistency and high latency. The mutable registry
|
||||||
|
metadata operations should be abstracted behind an API which will allow ACID compliant
|
||||||
|
storage systems to handle metadata.
|
||||||
|
|
||||||
|
##### Peer to Peer transfer
|
||||||
|
|
||||||
|
Discussion has started here: https://docs.google.com/document/d/1rYDpSpJiQWmCQy8Cuiaa3NH-Co33oK_SC9HeXYo87QA/edit
|
||||||
|
|
||||||
|
##### Indexing, Search and Discovery
|
||||||
|
|
||||||
|
The original registry provided some implementation of search for use with
|
||||||
|
private registries. Support has been elided from V2 since we'd like to both
|
||||||
|
decouple search functionality from the registry. The makes the registry
|
||||||
|
simpler to deploy, especially in use cases where search is not needed, and
|
||||||
|
let's us decouple the image format from the registry.
|
||||||
|
|
||||||
|
There are explorations into using the catalog API and notification system to
|
||||||
|
build external indexes. The current line of thought is that we will define a
|
||||||
|
common search API to index and query docker images. Such a system could be run
|
||||||
|
as a companion to a registry or set of registries to power discovery.
|
||||||
|
|
||||||
|
The main issue with search and discovery is that there are so many ways to
|
||||||
|
accomplish it. There are two aspects to this project. The first is deciding on
|
||||||
|
how it will be done, including an API definition that can work with changing
|
||||||
|
data formats. The second is the process of integrating with `docker search`.
|
||||||
|
We expect that someone attempts to address the problem with the existing tools
|
||||||
|
and propose it as a standard search API or uses it to inform a standardization
|
||||||
|
process. Once this has been explored, we integrate with the docker client.
|
||||||
|
|
||||||
|
Please see the following for more detail:
|
||||||
|
|
||||||
|
- https://github.com/docker/distribution/issues/206
|
||||||
|
|
||||||
|
##### Deletes
|
||||||
|
|
||||||
|
> __NOTE:__ Deletes are a much asked for feature. Before requesting this
|
||||||
|
feature or participating in discussion, we ask that you read this section in
|
||||||
|
full and understand the problems behind deletes.
|
||||||
|
|
||||||
|
While, at first glance, implementing deleting seems simple, there are a number
|
||||||
|
mitigating factors that make many solutions not ideal or even pathological in
|
||||||
|
the context of a registry. The following paragraph discuss the background and
|
||||||
|
approaches that could be applied to arrive at a solution.
|
||||||
|
|
||||||
|
The goal of deletes in any system is to remove unused or unneeded data. Only
|
||||||
|
data requested for deletion should be removed and no other data. Removing
|
||||||
|
unintended data is worse than _not_ removing data that was requested for
|
||||||
|
removal but ideally, both are supported. Generally, according to this rule, we
|
||||||
|
err on holding data longer than needed, ensuring that it is only removed when
|
||||||
|
we can be certain that it can be removed. With the current behavior, we opt to
|
||||||
|
hold onto the data forever, ensuring that data cannot be incorrectly removed.
|
||||||
|
|
||||||
|
To understand the problems with implementing deletes, one must understand the
|
||||||
|
data model. All registry data is stored in a filesystem layout, implemented on
|
||||||
|
a "storage driver", effectively a _virtual file system_ (VFS). The storage
|
||||||
|
system must assume that this VFS layer will be eventually consistent and has
|
||||||
|
poor read- after-write consistency, since this is the lower common denominator
|
||||||
|
among the storage drivers. This is mitigated by writing values in reverse-
|
||||||
|
dependent order, but makes wider transactional operations unsafe.
|
||||||
|
|
||||||
|
Layered on the VFS model is a content-addressable _directed, acyclic graph_
|
||||||
|
(DAG) made up of blobs. Manifests reference layers. Tags reference manifests.
|
||||||
|
Since the same data can be referenced by multiple manifests, we only store
|
||||||
|
data once, even if it is in different repositories. Thus, we have a set of
|
||||||
|
blobs, referenced by tags and manifests. If we want to delete a blob we need
|
||||||
|
to be certain that it is no longer referenced by another manifest or tag. When
|
||||||
|
we delete a manifest, we also can try to delete the referenced blobs. Deciding
|
||||||
|
whether or not a blob has an active reference is the crux of the problem.
|
||||||
|
|
||||||
|
Conceptually, deleting a manifest and its resources is quite simple. Just find
|
||||||
|
all the manifests, enumerate the referenced blobs and delete the blobs not in
|
||||||
|
that set. An astute observer will recognize this as a garbage collection
|
||||||
|
problem. As with garbage collection in programming languages, this is very
|
||||||
|
simple when one always has a consistent view. When one adds parallelism and an
|
||||||
|
inconsistent view of data, it becomes very challenging.
|
||||||
|
|
||||||
|
A simple example can demonstrate this. Let's say we are deleting a manifest
|
||||||
|
_A_ in one process. We scan the manifest and decide that all the blobs are
|
||||||
|
ready for deletion. Concurrently, we have another process accepting a new
|
||||||
|
manifest _B_ referencing one or more blobs from the manifest _A_. Manifest _B_
|
||||||
|
is accepted and all the blobs are considered present, so the operation
|
||||||
|
proceeds. The original process then deletes the referenced blobs, assuming
|
||||||
|
they were unreferenced. The manifest _B_, which we thought had all of its data
|
||||||
|
present, can no longer be served by the registry, since the dependent data has
|
||||||
|
been deleted.
|
||||||
|
|
||||||
|
Deleting data from the registry safely requires some way to coordinate this
|
||||||
|
operation. The following approaches are being considered:
|
||||||
|
|
||||||
|
- _Reference Counting_ - Maintain a count of references to each blob. This is
|
||||||
|
challenging for a number of reasons: 1. maintaining a consistent consensus
|
||||||
|
of reference counts across a set of Registries and 2. Building the initial
|
||||||
|
list of reference counts for an existing registry. These challenges can be
|
||||||
|
met with a consensus protocol like Paxos or Raft in the first case and a
|
||||||
|
necessary but simple scan in the second..
|
||||||
|
- _Lock the World GC_ - Halt all writes to the data store. Walk the data store
|
||||||
|
and find all blob references. Delete all unreferenced blobs. This approach
|
||||||
|
is very simple but requires disabling writes for a period of time while the
|
||||||
|
service reads all data. This is slow and expensive but very accurate and
|
||||||
|
effective.
|
||||||
|
- _Generational GC_ - Do something similar to above but instead of blocking
|
||||||
|
writes, writes are sent to another storage backend while reads are broadcast
|
||||||
|
to the new and old backends. GC is then performed on the read-only portion.
|
||||||
|
Because writes land in the new backend, the data in the read-only section
|
||||||
|
can be safely deleted. The main drawbacks of this approach are complexity
|
||||||
|
and coordination.
|
||||||
|
- _Centralized Oracle_ - Using a centralized, transactional database, we can
|
||||||
|
know exactly which data is referenced at any given time. This avoids
|
||||||
|
coordination problem by managing this data in a single location. We trade
|
||||||
|
off metadata scalability for simplicity and performance. This is a very good
|
||||||
|
option for most registry deployments. This would create a bottleneck for
|
||||||
|
registry metadata. However, metadata is generally not the main bottleneck
|
||||||
|
when serving images.
|
||||||
|
|
||||||
|
Please let us know if other solutions exist that we have yet to enumerate.
|
||||||
|
Note that for any approach, implementation is a massive consideration. For
|
||||||
|
example, a mark-sweep based solution may seem simple but the amount of work in
|
||||||
|
coordination offset the extra work it might take to build a _Centralized
|
||||||
|
Oracle_. We'll accept proposals for any solution but please coordinate with us
|
||||||
|
before dropping code.
|
||||||
|
|
||||||
|
At this time, we have traded off simplicity and ease of deployment for disk
|
||||||
|
space. Simplicity and ease of deployment tend to reduce developer involvement,
|
||||||
|
which is currently the most expensive resource in software engineering. Taking
|
||||||
|
on any solution for deletes will greatly effect these factors, trading off
|
||||||
|
very cheap disk space for a complex deployment and operational story.
|
||||||
|
|
||||||
|
Please see the following issues for more detail:
|
||||||
|
|
||||||
|
- https://github.com/docker/distribution/issues/422
|
||||||
|
- https://github.com/docker/distribution/issues/461
|
||||||
|
- https://github.com/docker/distribution/issues/462
|
||||||
|
|
||||||
|
### Distribution Package
|
||||||
|
|
||||||
|
At its core, the Distribution Project is a set of Go packages that make up
|
||||||
|
Distribution Components. At this time, most of these packages make up the
|
||||||
|
Registry implementation.
|
||||||
|
|
||||||
|
The package itself is considered unstable. If you're using it, please take care to vendor the dependent version.
|
||||||
|
|
||||||
|
For feature additions, please see the Registry section. In the future, we may break out a
|
||||||
|
separate Roadmap for distribution-specific features that apply to more than
|
||||||
|
just the registry.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### Project Planning
|
||||||
|
|
||||||
|
An [Open-Source Planning Process](https://github.com/docker/distribution/wiki/Open-Source-Planning-Process) is used to define the Roadmap. [Project Pages](https://github.com/docker/distribution/wiki) define the goals for each Milestone and identify current progress.
|
||||||
|
|
||||||
As every container application needs at least one registry as part of its infrastructure,
|
|
||||||
and more cloud native artifacts are using registries as the basis of their distribution,
|
|
||||||
having a widely used and supported open source registry is important for innovation.
|
|
||||||
|
|
17
SECURITY.md
17
SECURITY.md
|
@ -1,17 +0,0 @@
|
||||||
# Security Policy
|
|
||||||
|
|
||||||
## Supported Versions
|
|
||||||
|
|
||||||
These versions are currently receiving security updates.
|
|
||||||
|
|
||||||
| Version | Supported | Notes |
|
|
||||||
| ------------ | ------------------ | ----- |
|
|
||||||
| 3.0.x (main) | :white_check_mark: | This is the next major version and has not yet been released. |
|
|
||||||
| 2.8.x | :white_check_mark: | This is the latest released version. |
|
|
||||||
| < 2.8 | :x: | |
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
The maintainers take security seriously. If you discover a security issue, please bring it to their attention right away!
|
|
||||||
|
|
||||||
Please DO NOT file a public issue, instead send your report privately to cncf-distribution-security@lists.cncf.io.
|
|
22
blobs.go
22
blobs.go
|
@ -8,7 +8,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
@ -63,13 +63,13 @@ type Descriptor struct {
|
||||||
// encoded as utf-8.
|
// encoded as utf-8.
|
||||||
MediaType string `json:"mediaType,omitempty"`
|
MediaType string `json:"mediaType,omitempty"`
|
||||||
|
|
||||||
|
// Size in bytes of content.
|
||||||
|
Size int64 `json:"size,omitempty"`
|
||||||
|
|
||||||
// Digest uniquely identifies the content. A byte stream can be verified
|
// Digest uniquely identifies the content. A byte stream can be verified
|
||||||
// against this digest.
|
// against this digest.
|
||||||
Digest digest.Digest `json:"digest,omitempty"`
|
Digest digest.Digest `json:"digest,omitempty"`
|
||||||
|
|
||||||
// Size in bytes of content.
|
|
||||||
Size int64 `json:"size,omitempty"`
|
|
||||||
|
|
||||||
// URLs contains the source URLs of this content.
|
// URLs contains the source URLs of this content.
|
||||||
URLs []string `json:"urls,omitempty"`
|
URLs []string `json:"urls,omitempty"`
|
||||||
|
|
||||||
|
@ -140,14 +140,22 @@ type BlobDescriptorServiceFactory interface {
|
||||||
BlobAccessController(svc BlobDescriptorService) BlobDescriptorService
|
BlobAccessController(svc BlobDescriptorService) BlobDescriptorService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadSeekCloser is the primary reader type for blob data, combining
|
||||||
|
// io.ReadSeeker with io.Closer.
|
||||||
|
type ReadSeekCloser interface {
|
||||||
|
io.ReadSeeker
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
// BlobProvider describes operations for getting blob data.
|
// BlobProvider describes operations for getting blob data.
|
||||||
type BlobProvider interface {
|
type BlobProvider interface {
|
||||||
// Get returns the entire blob identified by digest along with the descriptor.
|
// Get returns the entire blob identified by digest along with the descriptor.
|
||||||
Get(ctx context.Context, dgst digest.Digest) ([]byte, error)
|
Get(ctx context.Context, dgst digest.Digest) ([]byte, error)
|
||||||
|
|
||||||
// Open provides an [io.ReadSeekCloser] to the blob identified by the provided
|
// Open provides a ReadSeekCloser to the blob identified by the provided
|
||||||
// descriptor. If the blob is not known to the service, an error is returned.
|
// descriptor. If the blob is not known to the service, an error will be
|
||||||
Open(ctx context.Context, dgst digest.Digest) (io.ReadSeekCloser, error)
|
// returned.
|
||||||
|
Open(ctx context.Context, dgst digest.Digest) (ReadSeekCloser, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlobServer can serve blobs via http.
|
// BlobServer can serve blobs via http.
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3/version"
|
"github.com/docker/distribution/version"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
|
|
||||||
_ "crypto/sha256"
|
_ "crypto/sha256"
|
||||||
|
@ -62,6 +62,7 @@ func main() {
|
||||||
if flag.NArg() > 0 {
|
if flag.NArg() > 0 {
|
||||||
for _, path := range flag.Args() {
|
for _, path := range flag.Args() {
|
||||||
fp, err := os.Open(path)
|
fp, err := os.Open(path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("%s: %v", path, err)
|
log.Printf("%s: %v", path, err)
|
||||||
fail = true
|
fail = true
|
||||||
|
|
|
@ -20,13 +20,14 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
v2 "github.com/distribution/distribution/v3/registry/api/v2"
|
v2 "github.com/docker/distribution/registry/api/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var spaceRegex = regexp.MustCompile(`\n\s*`)
|
var spaceRegex = regexp.MustCompile(`\n\s*`)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
if len(os.Args) != 2 {
|
if len(os.Args) != 2 {
|
||||||
log.Fatalln("please specify a template to execute.")
|
log.Fatalln("please specify a template to execute.")
|
||||||
}
|
}
|
||||||
|
@ -126,4 +127,5 @@ end:
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,6 @@ storage:
|
||||||
maintenance:
|
maintenance:
|
||||||
uploadpurging:
|
uploadpurging:
|
||||||
enabled: false
|
enabled: false
|
||||||
tag:
|
|
||||||
concurrencylimit: 8
|
|
||||||
http:
|
http:
|
||||||
addr: :5000
|
addr: :5000
|
||||||
secret: asecretforlocaldevelopment
|
secret: asecretforlocaldevelopment
|
||||||
|
@ -22,10 +20,11 @@ http:
|
||||||
headers:
|
headers:
|
||||||
X-Content-Type-Options: [nosniff]
|
X-Content-Type-Options: [nosniff]
|
||||||
redis:
|
redis:
|
||||||
addrs: [localhost:6379]
|
addr: localhost:6379
|
||||||
maxidleconns: 16
|
pool:
|
||||||
poolsize: 64
|
maxidle: 16
|
||||||
connmaxidletime: 300s
|
maxactive: 64
|
||||||
|
idletimeout: 300s
|
||||||
dialtimeout: 10ms
|
dialtimeout: 10ms
|
||||||
readtimeout: 10ms
|
readtimeout: 10ms
|
||||||
writetimeout: 10ms
|
writetimeout: 10ms
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
version: 0.1
|
|
||||||
log:
|
|
||||||
level: debug
|
|
||||||
fields:
|
|
||||||
service: registry
|
|
||||||
environment: development
|
|
||||||
storage:
|
|
||||||
delete:
|
|
||||||
enabled: true
|
|
||||||
maintenance:
|
|
||||||
uploadpurging:
|
|
||||||
enabled: false
|
|
||||||
frostfs:
|
|
||||||
wallet:
|
|
||||||
path: /path/to/wallet.json
|
|
||||||
password: ""
|
|
||||||
peers:
|
|
||||||
0:
|
|
||||||
address: s01.frostfs.devenv:8080
|
|
||||||
weight: 1
|
|
||||||
priority: 1
|
|
||||||
1:
|
|
||||||
address: s02.frostfs.devenv:8080
|
|
||||||
weight: 1
|
|
||||||
priority: 1
|
|
||||||
2:
|
|
||||||
address: s03.frostfs.devenv:8080
|
|
||||||
weight: 1
|
|
||||||
priority: 1
|
|
||||||
3:
|
|
||||||
address: s04.frostfs.devenv:8080
|
|
||||||
weight: 1
|
|
||||||
priority: 1
|
|
||||||
# container can be nicename (rpc_endpoint is required)
|
|
||||||
container: ChzA3qeJHbAT2nyo35LofdJ7jMqVuT9h3WoRpxHRn9Uq
|
|
||||||
# the following params are optional
|
|
||||||
session_expiration_duration: 1000 # in blocks
|
|
||||||
connection_timeout: 5s
|
|
||||||
request_timeout: 5s
|
|
||||||
rebalance_interval: 30s
|
|
||||||
rpc_endpoint: http://morph-chain.frostfs.devenv:30333
|
|
||||||
http:
|
|
||||||
addr: :5000
|
|
||||||
headers:
|
|
||||||
X-Content-Type-Options: [ nosniff ]
|
|
||||||
health:
|
|
||||||
storagedriver:
|
|
||||||
enabled: true
|
|
||||||
interval: 30s
|
|
||||||
threshold: 3
|
|
|
@ -4,18 +4,30 @@ log:
|
||||||
fields:
|
fields:
|
||||||
service: registry
|
service: registry
|
||||||
environment: development
|
environment: development
|
||||||
|
hooks:
|
||||||
|
- type: mail
|
||||||
|
disabled: true
|
||||||
|
levels:
|
||||||
|
- panic
|
||||||
|
options:
|
||||||
|
smtp:
|
||||||
|
addr: mail.example.com:25
|
||||||
|
username: mailuser
|
||||||
|
password: password
|
||||||
|
insecure: true
|
||||||
|
from: sender@example.com
|
||||||
|
to:
|
||||||
|
- errors@example.com
|
||||||
storage:
|
storage:
|
||||||
delete:
|
delete:
|
||||||
enabled: true
|
enabled: true
|
||||||
cache:
|
cache:
|
||||||
blobdescriptor: inmemory
|
blobdescriptor: redis
|
||||||
filesystem:
|
filesystem:
|
||||||
rootdirectory: /var/lib/registry
|
rootdirectory: /var/lib/registry
|
||||||
maintenance:
|
maintenance:
|
||||||
uploadpurging:
|
uploadpurging:
|
||||||
enabled: false
|
enabled: false
|
||||||
tag:
|
|
||||||
concurrencylimit: 8
|
|
||||||
http:
|
http:
|
||||||
addr: :5000
|
addr: :5000
|
||||||
debug:
|
debug:
|
||||||
|
@ -25,6 +37,33 @@ http:
|
||||||
path: /metrics
|
path: /metrics
|
||||||
headers:
|
headers:
|
||||||
X-Content-Type-Options: [nosniff]
|
X-Content-Type-Options: [nosniff]
|
||||||
|
redis:
|
||||||
|
addr: localhost:6379
|
||||||
|
pool:
|
||||||
|
maxidle: 16
|
||||||
|
maxactive: 64
|
||||||
|
idletimeout: 300s
|
||||||
|
dialtimeout: 10ms
|
||||||
|
readtimeout: 10ms
|
||||||
|
writetimeout: 10ms
|
||||||
|
notifications:
|
||||||
|
events:
|
||||||
|
includereferences: true
|
||||||
|
endpoints:
|
||||||
|
- name: local-5003
|
||||||
|
url: http://localhost:5003/callback
|
||||||
|
headers:
|
||||||
|
Authorization: [Bearer <an example token>]
|
||||||
|
timeout: 1s
|
||||||
|
threshold: 10
|
||||||
|
backoff: 1s
|
||||||
|
disabled: true
|
||||||
|
- name: local-8083
|
||||||
|
url: http://localhost:8083/callback
|
||||||
|
timeout: 1s
|
||||||
|
threshold: 10
|
||||||
|
backoff: 1s
|
||||||
|
disabled: true
|
||||||
health:
|
health:
|
||||||
storagedriver:
|
storagedriver:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
|
@ -7,8 +7,6 @@ storage:
|
||||||
blobdescriptor: inmemory
|
blobdescriptor: inmemory
|
||||||
filesystem:
|
filesystem:
|
||||||
rootdirectory: /var/lib/registry
|
rootdirectory: /var/lib/registry
|
||||||
tag:
|
|
||||||
concurrencylimit: 8
|
|
||||||
http:
|
http:
|
||||||
addr: :5000
|
addr: :5000
|
||||||
headers:
|
headers:
|
||||||
|
|
|
@ -3,26 +3,22 @@ package main
|
||||||
import (
|
import (
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3/registry"
|
"github.com/docker/distribution/registry"
|
||||||
_ "github.com/distribution/distribution/v3/registry/auth/htpasswd"
|
_ "github.com/docker/distribution/registry/auth/htpasswd"
|
||||||
_ "github.com/distribution/distribution/v3/registry/auth/silly"
|
_ "github.com/docker/distribution/registry/auth/silly"
|
||||||
_ "github.com/distribution/distribution/v3/registry/auth/token"
|
_ "github.com/docker/distribution/registry/auth/token"
|
||||||
_ "github.com/distribution/distribution/v3/registry/proxy"
|
_ "github.com/docker/distribution/registry/proxy"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/azure"
|
_ "github.com/docker/distribution/registry/storage/driver/azure"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
|
_ "github.com/docker/distribution/registry/storage/driver/filesystem"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/frostfs"
|
_ "github.com/docker/distribution/registry/storage/driver/gcs"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/gcs"
|
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
_ "github.com/docker/distribution/registry/storage/driver/middleware/cloudfront"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/middleware/cloudfront"
|
_ "github.com/docker/distribution/registry/storage/driver/middleware/redirect"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/middleware/redirect"
|
_ "github.com/docker/distribution/registry/storage/driver/oss"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/middleware/rewrite"
|
_ "github.com/docker/distribution/registry/storage/driver/s3-aws"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/s3-aws"
|
_ "github.com/docker/distribution/registry/storage/driver/swift"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// NOTE(milosgajdos): if the only two commands registered
|
|
||||||
// with registry.RootCmd fail they will halt the program
|
|
||||||
// execution and exit the program with non-zero exit code.
|
|
||||||
// nolint:errcheck
|
|
||||||
registry.RootCmd.Execute()
|
registry.RootCmd.Execute()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configuration is a versioned registry configuration, intended to be provided by a yaml file, and
|
// Configuration is a versioned registry configuration, intended to be provided by a yaml file, and
|
||||||
|
@ -44,9 +43,6 @@ type Configuration struct {
|
||||||
// Hooks allows users to configure the log hooks, to enabling the
|
// Hooks allows users to configure the log hooks, to enabling the
|
||||||
// sequent handling behavior, when defined levels of log message emit.
|
// sequent handling behavior, when defined levels of log message emit.
|
||||||
Hooks []LogHook `yaml:"hooks,omitempty"`
|
Hooks []LogHook `yaml:"hooks,omitempty"`
|
||||||
|
|
||||||
// ReportCaller allows user to configure the log to report the caller
|
|
||||||
ReportCaller bool `yaml:"reportcaller,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loglevel is the level at which registry operations are logged.
|
// Loglevel is the level at which registry operations are logged.
|
||||||
|
@ -64,6 +60,9 @@ type Configuration struct {
|
||||||
// Middleware lists all middlewares to be used by the registry.
|
// Middleware lists all middlewares to be used by the registry.
|
||||||
Middleware map[string][]Middleware `yaml:"middleware,omitempty"`
|
Middleware map[string][]Middleware `yaml:"middleware,omitempty"`
|
||||||
|
|
||||||
|
// Reporting is the configuration for error reporting
|
||||||
|
Reporting Reporting `yaml:"reporting,omitempty"`
|
||||||
|
|
||||||
// HTTP contains configuration parameters for the registry's http
|
// HTTP contains configuration parameters for the registry's http
|
||||||
// interface.
|
// interface.
|
||||||
HTTP struct {
|
HTTP struct {
|
||||||
|
@ -130,10 +129,6 @@ type Configuration struct {
|
||||||
// Hosts specifies the hosts which are allowed to obtain Let's
|
// Hosts specifies the hosts which are allowed to obtain Let's
|
||||||
// Encrypt certificates.
|
// Encrypt certificates.
|
||||||
Hosts []string `yaml:"hosts,omitempty"`
|
Hosts []string `yaml:"hosts,omitempty"`
|
||||||
|
|
||||||
// DirectoryURL points to the CA directory endpoint.
|
|
||||||
// If empty, LetsEncrypt is used.
|
|
||||||
DirectoryURL string `yaml:"directoryurl,omitempty"`
|
|
||||||
} `yaml:"letsencrypt,omitempty"`
|
} `yaml:"letsencrypt,omitempty"`
|
||||||
} `yaml:"tls,omitempty"`
|
} `yaml:"tls,omitempty"`
|
||||||
|
|
||||||
|
@ -159,15 +154,9 @@ type Configuration struct {
|
||||||
// HTTP2 configuration options
|
// HTTP2 configuration options
|
||||||
HTTP2 struct {
|
HTTP2 struct {
|
||||||
// Specifies whether the registry should disallow clients attempting
|
// Specifies whether the registry should disallow clients attempting
|
||||||
// to connect via HTTP/2. If set to true, only HTTP/1.1 is supported.
|
// to connect via http2. If set to true, only http/1.1 is supported.
|
||||||
Disabled bool `yaml:"disabled,omitempty"`
|
Disabled bool `yaml:"disabled,omitempty"`
|
||||||
} `yaml:"http2,omitempty"`
|
} `yaml:"http2,omitempty"`
|
||||||
|
|
||||||
H2C struct {
|
|
||||||
// Enables H2C (HTTP/2 Cleartext). Enable to support HTTP/2 without needing to configure TLS
|
|
||||||
// Useful when deploying the registry behind a load balancer (e.g. Cloud Run)
|
|
||||||
Enabled bool `yaml:"enabled,omitempty"`
|
|
||||||
} `yaml:"h2c,omitempty"`
|
|
||||||
} `yaml:"http,omitempty"`
|
} `yaml:"http,omitempty"`
|
||||||
|
|
||||||
// Notifications specifies configuration about various endpoint to which
|
// Notifications specifies configuration about various endpoint to which
|
||||||
|
@ -175,15 +164,72 @@ type Configuration struct {
|
||||||
Notifications Notifications `yaml:"notifications,omitempty"`
|
Notifications Notifications `yaml:"notifications,omitempty"`
|
||||||
|
|
||||||
// Redis configures the redis pool available to the registry webapp.
|
// Redis configures the redis pool available to the registry webapp.
|
||||||
Redis Redis `yaml:"redis,omitempty"`
|
Redis struct {
|
||||||
|
// Addr specifies the the redis instance available to the application.
|
||||||
|
Addr string `yaml:"addr,omitempty"`
|
||||||
|
|
||||||
|
// Password string to use when making a connection.
|
||||||
|
Password string `yaml:"password,omitempty"`
|
||||||
|
|
||||||
|
// DB specifies the database to connect to on the redis instance.
|
||||||
|
DB int `yaml:"db,omitempty"`
|
||||||
|
|
||||||
|
DialTimeout time.Duration `yaml:"dialtimeout,omitempty"` // timeout for connect
|
||||||
|
ReadTimeout time.Duration `yaml:"readtimeout,omitempty"` // timeout for reads of data
|
||||||
|
WriteTimeout time.Duration `yaml:"writetimeout,omitempty"` // timeout for writes of data
|
||||||
|
|
||||||
|
// Pool configures the behavior of the redis connection pool.
|
||||||
|
Pool struct {
|
||||||
|
// MaxIdle sets the maximum number of idle connections.
|
||||||
|
MaxIdle int `yaml:"maxidle,omitempty"`
|
||||||
|
|
||||||
|
// MaxActive sets the maximum number of connections that should be
|
||||||
|
// opened before blocking a connection request.
|
||||||
|
MaxActive int `yaml:"maxactive,omitempty"`
|
||||||
|
|
||||||
|
// IdleTimeout sets the amount time to wait before closing
|
||||||
|
// inactive connections.
|
||||||
|
IdleTimeout time.Duration `yaml:"idletimeout,omitempty"`
|
||||||
|
} `yaml:"pool,omitempty"`
|
||||||
|
} `yaml:"redis,omitempty"`
|
||||||
|
|
||||||
Health Health `yaml:"health,omitempty"`
|
Health Health `yaml:"health,omitempty"`
|
||||||
Catalog Catalog `yaml:"catalog,omitempty"`
|
Catalog Catalog `yaml:"catalog,omitempty"`
|
||||||
|
|
||||||
Proxy Proxy `yaml:"proxy,omitempty"`
|
Proxy Proxy `yaml:"proxy,omitempty"`
|
||||||
|
|
||||||
|
// Compatibility is used for configurations of working with older or deprecated features.
|
||||||
|
Compatibility struct {
|
||||||
|
// Schema1 configures how schema1 manifests will be handled
|
||||||
|
Schema1 struct {
|
||||||
|
// TrustKey is the signing key to use for adding the signature to
|
||||||
|
// schema1 manifests.
|
||||||
|
TrustKey string `yaml:"signingkeyfile,omitempty"`
|
||||||
|
// Enabled determines if schema1 manifests should be pullable
|
||||||
|
Enabled bool `yaml:"enabled,omitempty"`
|
||||||
|
} `yaml:"schema1,omitempty"`
|
||||||
|
} `yaml:"compatibility,omitempty"`
|
||||||
|
|
||||||
// Validation configures validation options for the registry.
|
// Validation configures validation options for the registry.
|
||||||
Validation Validation `yaml:"validation,omitempty"`
|
Validation struct {
|
||||||
|
// Enabled enables the other options in this section. This field is
|
||||||
|
// deprecated in favor of Disabled.
|
||||||
|
Enabled bool `yaml:"enabled,omitempty"`
|
||||||
|
// Disabled disables the other options in this section.
|
||||||
|
Disabled bool `yaml:"disabled,omitempty"`
|
||||||
|
// Manifests configures manifest validation.
|
||||||
|
Manifests struct {
|
||||||
|
// URLs configures validation for URLs in pushed manifests.
|
||||||
|
URLs struct {
|
||||||
|
// Allow specifies regular expressions (https://godoc.org/regexp/syntax)
|
||||||
|
// that URLs in pushed manifests must match.
|
||||||
|
Allow []string `yaml:"allow,omitempty"`
|
||||||
|
// Deny specifies regular expressions (https://godoc.org/regexp/syntax)
|
||||||
|
// that URLs in pushed manifests must not match.
|
||||||
|
Deny []string `yaml:"deny,omitempty"`
|
||||||
|
} `yaml:"urls,omitempty"`
|
||||||
|
} `yaml:"manifests,omitempty"`
|
||||||
|
} `yaml:"validation,omitempty"`
|
||||||
|
|
||||||
// Policy configures registry policy options.
|
// Policy configures registry policy options.
|
||||||
Policy struct {
|
Policy struct {
|
||||||
|
@ -312,13 +358,6 @@ type Health struct {
|
||||||
} `yaml:"storagedriver,omitempty"`
|
} `yaml:"storagedriver,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Platform struct {
|
|
||||||
// Architecture is the architecture for this platform
|
|
||||||
Architecture string `yaml:"architecture,omitempty"`
|
|
||||||
// OS is the operating system for this platform
|
|
||||||
OS string `yaml:"os,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// v0_1Configuration is a Version 0.1 Configuration struct
|
// v0_1Configuration is a Version 0.1 Configuration struct
|
||||||
// This is currently aliased to Configuration, as it is the current version
|
// This is currently aliased to Configuration, as it is the current version
|
||||||
type v0_1Configuration Configuration
|
type v0_1Configuration Configuration
|
||||||
|
@ -394,8 +433,6 @@ func (storage Storage) Type() string {
|
||||||
// allow configuration of delete
|
// allow configuration of delete
|
||||||
case "redirect":
|
case "redirect":
|
||||||
// allow configuration of redirect
|
// allow configuration of redirect
|
||||||
case "tag":
|
|
||||||
// allow configuration of tag
|
|
||||||
default:
|
default:
|
||||||
storageType = append(storageType, k)
|
storageType = append(storageType, k)
|
||||||
}
|
}
|
||||||
|
@ -409,19 +446,6 @@ func (storage Storage) Type() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// TagParameters returns the Parameters map for a Storage tag configuration
|
|
||||||
func (storage Storage) TagParameters() Parameters {
|
|
||||||
return storage["tag"]
|
|
||||||
}
|
|
||||||
|
|
||||||
// setTagParameter changes the parameter at the provided key to the new value
|
|
||||||
func (storage Storage) setTagParameter(key string, value interface{}) {
|
|
||||||
if _, ok := storage["tag"]; !ok {
|
|
||||||
storage["tag"] = make(Parameters)
|
|
||||||
}
|
|
||||||
storage["tag"][key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parameters returns the Parameters map for a Storage configuration
|
// Parameters returns the Parameters map for a Storage configuration
|
||||||
func (storage Storage) Parameters() Parameters {
|
func (storage Storage) Parameters() Parameters {
|
||||||
return storage[storage.Type()]
|
return storage[storage.Type()]
|
||||||
|
@ -450,8 +474,6 @@ func (storage *Storage) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
// allow configuration of delete
|
// allow configuration of delete
|
||||||
case "redirect":
|
case "redirect":
|
||||||
// allow configuration of redirect
|
// allow configuration of redirect
|
||||||
case "tag":
|
|
||||||
// allow configuration of tag
|
|
||||||
default:
|
default:
|
||||||
types = append(types, k)
|
types = append(types, k)
|
||||||
}
|
}
|
||||||
|
@ -579,6 +601,35 @@ type Ignore struct {
|
||||||
Actions []string `yaml:"actions"` // ignore action types
|
Actions []string `yaml:"actions"` // ignore action types
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reporting defines error reporting methods.
|
||||||
|
type Reporting struct {
|
||||||
|
// Bugsnag configures error reporting for Bugsnag (bugsnag.com).
|
||||||
|
Bugsnag BugsnagReporting `yaml:"bugsnag,omitempty"`
|
||||||
|
// NewRelic configures error reporting for NewRelic (newrelic.com)
|
||||||
|
NewRelic NewRelicReporting `yaml:"newrelic,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BugsnagReporting configures error reporting for Bugsnag (bugsnag.com).
|
||||||
|
type BugsnagReporting struct {
|
||||||
|
// APIKey is the Bugsnag api key.
|
||||||
|
APIKey string `yaml:"apikey,omitempty"`
|
||||||
|
// ReleaseStage tracks where the registry is deployed.
|
||||||
|
// Examples: production, staging, development
|
||||||
|
ReleaseStage string `yaml:"releasestage,omitempty"`
|
||||||
|
// Endpoint is used for specifying an enterprise Bugsnag endpoint.
|
||||||
|
Endpoint string `yaml:"endpoint,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRelicReporting configures error reporting for NewRelic (newrelic.com)
|
||||||
|
type NewRelicReporting struct {
|
||||||
|
// LicenseKey is the NewRelic user license key
|
||||||
|
LicenseKey string `yaml:"licensekey,omitempty"`
|
||||||
|
// Name is the component name of the registry in NewRelic
|
||||||
|
Name string `yaml:"name,omitempty"`
|
||||||
|
// Verbose configures debug output to STDOUT
|
||||||
|
Verbose bool `yaml:"verbose,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Middleware configures named middlewares to be applied at injection points.
|
// Middleware configures named middlewares to be applied at injection points.
|
||||||
type Middleware struct {
|
type Middleware struct {
|
||||||
// Name the middleware registers itself as
|
// Name the middleware registers itself as
|
||||||
|
@ -599,67 +650,6 @@ type Proxy struct {
|
||||||
|
|
||||||
// Password of the hub user
|
// Password of the hub user
|
||||||
Password string `yaml:"password"`
|
Password string `yaml:"password"`
|
||||||
|
|
||||||
// TTL is the expiry time of the content and will be cleaned up when it expires
|
|
||||||
// if not set, defaults to 7 * 24 hours
|
|
||||||
// If set to zero, will never expire cache
|
|
||||||
TTL *time.Duration `yaml:"ttl,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Validation struct {
|
|
||||||
// Enabled enables the other options in this section. This field is
|
|
||||||
// deprecated in favor of Disabled.
|
|
||||||
Enabled bool `yaml:"enabled,omitempty"`
|
|
||||||
// Disabled disables the other options in this section.
|
|
||||||
Disabled bool `yaml:"disabled,omitempty"`
|
|
||||||
// Manifests configures manifest validation.
|
|
||||||
Manifests ValidationManifests `yaml:"manifests,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ValidationManifests struct {
|
|
||||||
// URLs configures validation for URLs in pushed manifests.
|
|
||||||
URLs struct {
|
|
||||||
// Allow specifies regular expressions (https://godoc.org/regexp/syntax)
|
|
||||||
// that URLs in pushed manifests must match.
|
|
||||||
Allow []string `yaml:"allow,omitempty"`
|
|
||||||
// Deny specifies regular expressions (https://godoc.org/regexp/syntax)
|
|
||||||
// that URLs in pushed manifests must not match.
|
|
||||||
Deny []string `yaml:"deny,omitempty"`
|
|
||||||
} `yaml:"urls,omitempty"`
|
|
||||||
// ImageIndexes configures validation of image indexes
|
|
||||||
Indexes ValidationIndexes `yaml:"indexes,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ValidationIndexes struct {
|
|
||||||
// Platforms configures the validation applies to the platform images included in an image index
|
|
||||||
Platforms Platforms `yaml:"platforms"`
|
|
||||||
// PlatformList filters the set of platforms to validate for image existence.
|
|
||||||
PlatformList []Platform `yaml:"platformlist,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Platforms configures the validation applies to the platform images included in an image index
|
|
||||||
// This can be all, none, or list
|
|
||||||
type Platforms string
|
|
||||||
|
|
||||||
// UnmarshalYAML implements the yaml.Umarshaler interface
|
|
||||||
// Unmarshals a string into a Platforms option, lowercasing the string and validating that it represents a
|
|
||||||
// valid option
|
|
||||||
func (platforms *Platforms) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
var platformsString string
|
|
||||||
err := unmarshal(&platformsString)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
platformsString = strings.ToLower(platformsString)
|
|
||||||
switch platformsString {
|
|
||||||
case "all", "none", "list":
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid platforms option %s Must be one of [all, none, list]", platformsString)
|
|
||||||
}
|
|
||||||
|
|
||||||
*platforms = Platforms(platformsString)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses an input configuration yaml document into a Configuration struct
|
// Parse parses an input configuration yaml document into a Configuration struct
|
||||||
|
@ -670,7 +660,7 @@ func (platforms *Platforms) UnmarshalYAML(unmarshal func(interface{}) error) err
|
||||||
// Configuration.Abc may be replaced by the value of REGISTRY_ABC,
|
// Configuration.Abc may be replaced by the value of REGISTRY_ABC,
|
||||||
// Configuration.Abc.Xyz may be replaced by the value of REGISTRY_ABC_XYZ, and so forth
|
// Configuration.Abc.Xyz may be replaced by the value of REGISTRY_ABC_XYZ, and so forth
|
||||||
func Parse(rd io.Reader) (*Configuration, error) {
|
func Parse(rd io.Reader) (*Configuration, error) {
|
||||||
in, err := io.ReadAll(rd)
|
in, err := ioutil.ReadAll(rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -714,172 +704,3 @@ func Parse(rd io.Reader) (*Configuration, error) {
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedisOptions = redis.UniversalOptions
|
|
||||||
|
|
||||||
type RedisTLSOptions struct {
|
|
||||||
Certificate string `yaml:"certificate,omitempty"`
|
|
||||||
Key string `yaml:"key,omitempty"`
|
|
||||||
ClientCAs []string `yaml:"clientcas,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Redis struct {
|
|
||||||
Options RedisOptions `yaml:",inline"`
|
|
||||||
TLS RedisTLSOptions `yaml:"tls,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Redis) MarshalYAML() (interface{}, error) {
|
|
||||||
fields := make(map[string]interface{})
|
|
||||||
|
|
||||||
val := reflect.ValueOf(c.Options)
|
|
||||||
typ := val.Type()
|
|
||||||
|
|
||||||
for i := 0; i < val.NumField(); i++ {
|
|
||||||
field := typ.Field(i)
|
|
||||||
fieldValue := val.Field(i)
|
|
||||||
|
|
||||||
// ignore funcs fields in redis.UniversalOptions
|
|
||||||
if fieldValue.Kind() == reflect.Func {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fields[strings.ToLower(field.Name)] = fieldValue.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add TLS fields if they're not empty
|
|
||||||
if c.TLS.Certificate != "" || c.TLS.Key != "" || len(c.TLS.ClientCAs) > 0 {
|
|
||||||
fields["tls"] = c.TLS
|
|
||||||
}
|
|
||||||
|
|
||||||
return fields, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Redis) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
var fields map[string]interface{}
|
|
||||||
err := unmarshal(&fields)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
val := reflect.ValueOf(&c.Options).Elem()
|
|
||||||
typ := val.Type()
|
|
||||||
|
|
||||||
for i := 0; i < typ.NumField(); i++ {
|
|
||||||
field := typ.Field(i)
|
|
||||||
fieldName := strings.ToLower(field.Name)
|
|
||||||
|
|
||||||
if value, ok := fields[fieldName]; ok {
|
|
||||||
fieldValue := val.Field(i)
|
|
||||||
if fieldValue.CanSet() {
|
|
||||||
switch field.Type {
|
|
||||||
case reflect.TypeOf(time.Duration(0)):
|
|
||||||
durationStr, ok := value.(string)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid duration value for field: %s", fieldName)
|
|
||||||
}
|
|
||||||
duration, err := time.ParseDuration(durationStr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse duration for field: %s, error: %v", fieldName, err)
|
|
||||||
}
|
|
||||||
fieldValue.Set(reflect.ValueOf(duration))
|
|
||||||
default:
|
|
||||||
if err := setFieldValue(fieldValue, value); err != nil {
|
|
||||||
return fmt.Errorf("failed to set value for field: %s, error: %v", fieldName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle TLS fields
|
|
||||||
if tlsData, ok := fields["tls"]; ok {
|
|
||||||
tlsMap, ok := tlsData.(map[interface{}]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid TLS data structure")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert, ok := tlsMap["certificate"]; ok {
|
|
||||||
var isString bool
|
|
||||||
c.TLS.Certificate, isString = cert.(string)
|
|
||||||
if !isString {
|
|
||||||
return fmt.Errorf("Redis TLS certificate must be a string")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if key, ok := tlsMap["key"]; ok {
|
|
||||||
var isString bool
|
|
||||||
c.TLS.Key, isString = key.(string)
|
|
||||||
if !isString {
|
|
||||||
return fmt.Errorf("Redis TLS (private) key must be a string")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cas, ok := tlsMap["clientcas"]; ok {
|
|
||||||
caList, ok := cas.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid clientcas data structure")
|
|
||||||
}
|
|
||||||
for _, ca := range caList {
|
|
||||||
if caStr, ok := ca.(string); ok {
|
|
||||||
c.TLS.ClientCAs = append(c.TLS.ClientCAs, caStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setFieldValue(field reflect.Value, value interface{}) error {
|
|
||||||
if value == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch field.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
stringValue, ok := value.(string)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("failed to convert value to string")
|
|
||||||
}
|
|
||||||
field.SetString(stringValue)
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
intValue, ok := value.(int)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("failed to convert value to integer")
|
|
||||||
}
|
|
||||||
field.SetInt(int64(intValue))
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
uintValue, ok := value.(uint)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("failed to convert value to unsigned integer")
|
|
||||||
}
|
|
||||||
field.SetUint(uint64(uintValue))
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
floatValue, ok := value.(float64)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("failed to convert value to float")
|
|
||||||
}
|
|
||||||
field.SetFloat(floatValue)
|
|
||||||
case reflect.Bool:
|
|
||||||
boolValue, ok := value.(bool)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("failed to convert value to boolean")
|
|
||||||
}
|
|
||||||
field.SetBool(boolValue)
|
|
||||||
case reflect.Slice:
|
|
||||||
slice := reflect.MakeSlice(field.Type(), 0, 0)
|
|
||||||
valueSlice, ok := value.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("failed to convert value to slice")
|
|
||||||
}
|
|
||||||
for _, item := range valueSlice {
|
|
||||||
sliceValue := reflect.New(field.Type().Elem()).Elem()
|
|
||||||
if err := setFieldValue(sliceValue, item); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
slice = reflect.Append(slice, sliceValue)
|
|
||||||
}
|
|
||||||
field.Set(slice)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported field type: %v", field.Type())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,16 +3,19 @@ package configuration
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
. "gopkg.in/check.v1"
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Hook up gocheck into the "go test" runner
|
||||||
|
func Test(t *testing.T) { TestingT(t) }
|
||||||
|
|
||||||
// configStruct is a canonical example configuration, which should map to configYamlV0_1
|
// configStruct is a canonical example configuration, which should map to configYamlV0_1
|
||||||
var configStruct = Configuration{
|
var configStruct = Configuration{
|
||||||
Version: "0.1",
|
Version: "0.1",
|
||||||
|
@ -24,24 +27,21 @@ var configStruct = Configuration{
|
||||||
Formatter string `yaml:"formatter,omitempty"`
|
Formatter string `yaml:"formatter,omitempty"`
|
||||||
Fields map[string]interface{} `yaml:"fields,omitempty"`
|
Fields map[string]interface{} `yaml:"fields,omitempty"`
|
||||||
Hooks []LogHook `yaml:"hooks,omitempty"`
|
Hooks []LogHook `yaml:"hooks,omitempty"`
|
||||||
ReportCaller bool `yaml:"reportcaller,omitempty"`
|
|
||||||
}{
|
}{
|
||||||
Level: "info",
|
Level: "info",
|
||||||
Fields: map[string]interface{}{"environment": "test"},
|
Fields: map[string]interface{}{"environment": "test"},
|
||||||
},
|
},
|
||||||
Storage: Storage{
|
Storage: Storage{
|
||||||
"somedriver": Parameters{
|
"s3": Parameters{
|
||||||
"string1": "string-value1",
|
"region": "us-east-1",
|
||||||
"string2": "string-value2",
|
"bucket": "my-bucket",
|
||||||
"bool1": true,
|
"rootdirectory": "/registry",
|
||||||
"bool2": false,
|
"encrypt": true,
|
||||||
"nil1": nil,
|
"secure": false,
|
||||||
"int1": 42,
|
"accesskey": "SAMPLEACCESSKEY",
|
||||||
"url1": "https://foo.example.com",
|
"secretkey": "SUPERSECRET",
|
||||||
"path1": "/some-path",
|
"host": nil,
|
||||||
},
|
"port": 42,
|
||||||
"tag": Parameters{
|
|
||||||
"concurrencylimit": 10,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Auth: Auth{
|
Auth: Auth{
|
||||||
|
@ -50,6 +50,11 @@ var configStruct = Configuration{
|
||||||
"service": "silly",
|
"service": "silly",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Reporting: Reporting{
|
||||||
|
Bugsnag: BugsnagReporting{
|
||||||
|
APIKey: "BugsnagApiKey",
|
||||||
|
},
|
||||||
|
},
|
||||||
Notifications: Notifications{
|
Notifications: Notifications{
|
||||||
Endpoints: []Endpoint{
|
Endpoints: []Endpoint{
|
||||||
{
|
{
|
||||||
|
@ -87,7 +92,6 @@ var configStruct = Configuration{
|
||||||
CacheFile string `yaml:"cachefile,omitempty"`
|
CacheFile string `yaml:"cachefile,omitempty"`
|
||||||
Email string `yaml:"email,omitempty"`
|
Email string `yaml:"email,omitempty"`
|
||||||
Hosts []string `yaml:"hosts,omitempty"`
|
Hosts []string `yaml:"hosts,omitempty"`
|
||||||
DirectoryURL string `yaml:"directoryurl,omitempty"`
|
|
||||||
} `yaml:"letsencrypt,omitempty"`
|
} `yaml:"letsencrypt,omitempty"`
|
||||||
} `yaml:"tls,omitempty"`
|
} `yaml:"tls,omitempty"`
|
||||||
Headers http.Header `yaml:"headers,omitempty"`
|
Headers http.Header `yaml:"headers,omitempty"`
|
||||||
|
@ -101,9 +105,6 @@ var configStruct = Configuration{
|
||||||
HTTP2 struct {
|
HTTP2 struct {
|
||||||
Disabled bool `yaml:"disabled,omitempty"`
|
Disabled bool `yaml:"disabled,omitempty"`
|
||||||
} `yaml:"http2,omitempty"`
|
} `yaml:"http2,omitempty"`
|
||||||
H2C struct {
|
|
||||||
Enabled bool `yaml:"enabled,omitempty"`
|
|
||||||
} `yaml:"h2c,omitempty"`
|
|
||||||
}{
|
}{
|
||||||
TLS: struct {
|
TLS: struct {
|
||||||
Certificate string `yaml:"certificate,omitempty"`
|
Certificate string `yaml:"certificate,omitempty"`
|
||||||
|
@ -115,7 +116,6 @@ var configStruct = Configuration{
|
||||||
CacheFile string `yaml:"cachefile,omitempty"`
|
CacheFile string `yaml:"cachefile,omitempty"`
|
||||||
Email string `yaml:"email,omitempty"`
|
Email string `yaml:"email,omitempty"`
|
||||||
Hosts []string `yaml:"hosts,omitempty"`
|
Hosts []string `yaml:"hosts,omitempty"`
|
||||||
DirectoryURL string `yaml:"directoryurl,omitempty"`
|
|
||||||
} `yaml:"letsencrypt,omitempty"`
|
} `yaml:"letsencrypt,omitempty"`
|
||||||
}{
|
}{
|
||||||
ClientCAs: []string{"/path/to/ca.pem"},
|
ClientCAs: []string{"/path/to/ca.pem"},
|
||||||
|
@ -128,59 +128,27 @@ var configStruct = Configuration{
|
||||||
}{
|
}{
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
},
|
},
|
||||||
H2C: struct {
|
|
||||||
Enabled bool `yaml:"enabled,omitempty"`
|
|
||||||
}{
|
|
||||||
Enabled: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Redis: Redis{
|
|
||||||
Options: redis.UniversalOptions{
|
|
||||||
Addrs: []string{"localhost:6379"},
|
|
||||||
Username: "alice",
|
|
||||||
Password: "123456",
|
|
||||||
DB: 1,
|
|
||||||
MaxIdleConns: 16,
|
|
||||||
PoolSize: 64,
|
|
||||||
ConnMaxIdleTime: time.Second * 300,
|
|
||||||
DialTimeout: time.Millisecond * 10,
|
|
||||||
ReadTimeout: time.Millisecond * 10,
|
|
||||||
WriteTimeout: time.Millisecond * 10,
|
|
||||||
},
|
|
||||||
TLS: RedisTLSOptions{
|
|
||||||
Certificate: "/foo/cert.crt",
|
|
||||||
Key: "/foo/key.pem",
|
|
||||||
ClientCAs: []string{"/path/to/ca.pem"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Validation: Validation{
|
|
||||||
Manifests: ValidationManifests{
|
|
||||||
Indexes: ValidationIndexes{
|
|
||||||
Platforms: "none",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// configYamlV0_1 is a Version 0.1 yaml document representing configStruct
|
// configYamlV0_1 is a Version 0.1 yaml document representing configStruct
|
||||||
const configYamlV0_1 = `
|
var configYamlV0_1 = `
|
||||||
version: 0.1
|
version: 0.1
|
||||||
log:
|
log:
|
||||||
level: info
|
level: info
|
||||||
fields:
|
fields:
|
||||||
environment: test
|
environment: test
|
||||||
storage:
|
storage:
|
||||||
somedriver:
|
s3:
|
||||||
string1: string-value1
|
region: us-east-1
|
||||||
string2: string-value2
|
bucket: my-bucket
|
||||||
bool1: true
|
rootdirectory: /registry
|
||||||
bool2: false
|
encrypt: true
|
||||||
nil1: ~
|
secure: false
|
||||||
int1: 42
|
accesskey: SAMPLEACCESSKEY
|
||||||
url1: "https://foo.example.com"
|
secretkey: SUPERSECRET
|
||||||
path1: "/some-path"
|
host: ~
|
||||||
tag:
|
port: 42
|
||||||
concurrencylimit: 10
|
|
||||||
auth:
|
auth:
|
||||||
silly:
|
silly:
|
||||||
realm: silly
|
realm: silly
|
||||||
|
@ -198,37 +166,19 @@ notifications:
|
||||||
- application/octet-stream
|
- application/octet-stream
|
||||||
actions:
|
actions:
|
||||||
- pull
|
- pull
|
||||||
|
reporting:
|
||||||
|
bugsnag:
|
||||||
|
apikey: BugsnagApiKey
|
||||||
http:
|
http:
|
||||||
tls:
|
|
||||||
clientcas:
|
clientcas:
|
||||||
- /path/to/ca.pem
|
- /path/to/ca.pem
|
||||||
headers:
|
headers:
|
||||||
X-Content-Type-Options: [nosniff]
|
X-Content-Type-Options: [nosniff]
|
||||||
redis:
|
|
||||||
tls:
|
|
||||||
certificate: /foo/cert.crt
|
|
||||||
key: /foo/key.pem
|
|
||||||
clientcas:
|
|
||||||
- /path/to/ca.pem
|
|
||||||
addrs: [localhost:6379]
|
|
||||||
username: alice
|
|
||||||
password: "123456"
|
|
||||||
db: 1
|
|
||||||
maxidleconns: 16
|
|
||||||
poolsize: 64
|
|
||||||
connmaxidletime: 300s
|
|
||||||
dialtimeout: 10ms
|
|
||||||
readtimeout: 10ms
|
|
||||||
writetimeout: 10ms
|
|
||||||
validation:
|
|
||||||
manifests:
|
|
||||||
indexes:
|
|
||||||
platforms: none
|
|
||||||
`
|
`
|
||||||
|
|
||||||
// inmemoryConfigYamlV0_1 is a Version 0.1 yaml document specifying an inmemory
|
// inmemoryConfigYamlV0_1 is a Version 0.1 yaml document specifying an inmemory
|
||||||
// storage driver with no parameters
|
// storage driver with no parameters
|
||||||
const inmemoryConfigYamlV0_1 = `
|
var inmemoryConfigYamlV0_1 = `
|
||||||
version: 0.1
|
version: 0.1
|
||||||
log:
|
log:
|
||||||
level: info
|
level: info
|
||||||
|
@ -253,204 +203,221 @@ notifications:
|
||||||
http:
|
http:
|
||||||
headers:
|
headers:
|
||||||
X-Content-Type-Options: [nosniff]
|
X-Content-Type-Options: [nosniff]
|
||||||
validation:
|
|
||||||
manifests:
|
|
||||||
indexes:
|
|
||||||
platforms: none
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type ConfigSuite struct {
|
type ConfigSuite struct {
|
||||||
suite.Suite
|
|
||||||
expectedConfig *Configuration
|
expectedConfig *Configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigSuite(t *testing.T) {
|
var _ = Suite(new(ConfigSuite))
|
||||||
suite.Run(t, new(ConfigSuite))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *ConfigSuite) SetupTest() {
|
func (suite *ConfigSuite) SetUpTest(c *C) {
|
||||||
|
os.Clearenv()
|
||||||
suite.expectedConfig = copyConfig(configStruct)
|
suite.expectedConfig = copyConfig(configStruct)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMarshalRoundtrip validates that configStruct can be marshaled and
|
// TestMarshalRoundtrip validates that configStruct can be marshaled and
|
||||||
// unmarshaled without changing any parameters
|
// unmarshaled without changing any parameters
|
||||||
func (suite *ConfigSuite) TestMarshalRoundtrip() {
|
func (suite *ConfigSuite) TestMarshalRoundtrip(c *C) {
|
||||||
configBytes, err := yaml.Marshal(suite.expectedConfig)
|
configBytes, err := yaml.Marshal(suite.expectedConfig)
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
config, err := Parse(bytes.NewReader(configBytes))
|
config, err := Parse(bytes.NewReader(configBytes))
|
||||||
suite.T().Log(string(configBytes))
|
c.Log(string(configBytes))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
suite.Require().Equal(suite.expectedConfig, config)
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseSimple validates that configYamlV0_1 can be parsed into a struct
|
// TestParseSimple validates that configYamlV0_1 can be parsed into a struct
|
||||||
// matching configStruct
|
// matching configStruct
|
||||||
func (suite *ConfigSuite) TestParseSimple() {
|
func (suite *ConfigSuite) TestParseSimple(c *C) {
|
||||||
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
suite.Require().Equal(suite.expectedConfig, config)
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseInmemory validates that configuration yaml with storage provided as
|
// TestParseInmemory validates that configuration yaml with storage provided as
|
||||||
// a string can be parsed into a Configuration struct with no storage parameters
|
// a string can be parsed into a Configuration struct with no storage parameters
|
||||||
func (suite *ConfigSuite) TestParseInmemory() {
|
func (suite *ConfigSuite) TestParseInmemory(c *C) {
|
||||||
suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
|
suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
|
||||||
|
suite.expectedConfig.Reporting = Reporting{}
|
||||||
suite.expectedConfig.Log.Fields = nil
|
suite.expectedConfig.Log.Fields = nil
|
||||||
suite.expectedConfig.HTTP.TLS.ClientCAs = nil
|
|
||||||
suite.expectedConfig.Redis = Redis{}
|
|
||||||
|
|
||||||
config, err := Parse(bytes.NewReader([]byte(inmemoryConfigYamlV0_1)))
|
config, err := Parse(bytes.NewReader([]byte(inmemoryConfigYamlV0_1)))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
suite.Require().Equal(suite.expectedConfig, config)
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseIncomplete validates that an incomplete yaml configuration cannot
|
// TestParseIncomplete validates that an incomplete yaml configuration cannot
|
||||||
// be parsed without providing environment variables to fill in the missing
|
// be parsed without providing environment variables to fill in the missing
|
||||||
// components.
|
// components.
|
||||||
func (suite *ConfigSuite) TestParseIncomplete() {
|
func (suite *ConfigSuite) TestParseIncomplete(c *C) {
|
||||||
incompleteConfigYaml := "version: 0.1"
|
incompleteConfigYaml := "version: 0.1"
|
||||||
_, err := Parse(bytes.NewReader([]byte(incompleteConfigYaml)))
|
_, err := Parse(bytes.NewReader([]byte(incompleteConfigYaml)))
|
||||||
suite.Require().Error(err)
|
c.Assert(err, NotNil)
|
||||||
|
|
||||||
suite.expectedConfig.Log.Fields = nil
|
suite.expectedConfig.Log.Fields = nil
|
||||||
suite.expectedConfig.Storage = Storage{"filesystem": Parameters{"rootdirectory": "/tmp/testroot"}}
|
suite.expectedConfig.Storage = Storage{"filesystem": Parameters{"rootdirectory": "/tmp/testroot"}}
|
||||||
suite.expectedConfig.Auth = Auth{"silly": Parameters{"realm": "silly"}}
|
suite.expectedConfig.Auth = Auth{"silly": Parameters{"realm": "silly"}}
|
||||||
|
suite.expectedConfig.Reporting = Reporting{}
|
||||||
suite.expectedConfig.Notifications = Notifications{}
|
suite.expectedConfig.Notifications = Notifications{}
|
||||||
suite.expectedConfig.HTTP.Headers = nil
|
suite.expectedConfig.HTTP.Headers = nil
|
||||||
suite.expectedConfig.HTTP.TLS.ClientCAs = nil
|
|
||||||
suite.expectedConfig.Redis = Redis{}
|
|
||||||
suite.expectedConfig.Validation.Manifests.Indexes.Platforms = ""
|
|
||||||
|
|
||||||
// Note: this also tests that REGISTRY_STORAGE and
|
// Note: this also tests that REGISTRY_STORAGE and
|
||||||
// REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY can be used together
|
// REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY can be used together
|
||||||
suite.T().Setenv("REGISTRY_STORAGE", "filesystem")
|
os.Setenv("REGISTRY_STORAGE", "filesystem")
|
||||||
suite.T().Setenv("REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY", "/tmp/testroot")
|
os.Setenv("REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY", "/tmp/testroot")
|
||||||
suite.T().Setenv("REGISTRY_AUTH", "silly")
|
os.Setenv("REGISTRY_AUTH", "silly")
|
||||||
suite.T().Setenv("REGISTRY_AUTH_SILLY_REALM", "silly")
|
os.Setenv("REGISTRY_AUTH_SILLY_REALM", "silly")
|
||||||
|
|
||||||
config, err := Parse(bytes.NewReader([]byte(incompleteConfigYaml)))
|
config, err := Parse(bytes.NewReader([]byte(incompleteConfigYaml)))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
suite.Require().Equal(suite.expectedConfig, config)
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseWithSameEnvStorage validates that providing environment variables
|
// TestParseWithSameEnvStorage validates that providing environment variables
|
||||||
// that match the given storage type will only include environment-defined
|
// that match the given storage type will only include environment-defined
|
||||||
// parameters and remove yaml-defined parameters
|
// parameters and remove yaml-defined parameters
|
||||||
func (suite *ConfigSuite) TestParseWithSameEnvStorage() {
|
func (suite *ConfigSuite) TestParseWithSameEnvStorage(c *C) {
|
||||||
suite.expectedConfig.Storage = Storage{"somedriver": Parameters{"region": "us-east-1"}}
|
suite.expectedConfig.Storage = Storage{"s3": Parameters{"region": "us-east-1"}}
|
||||||
|
|
||||||
suite.T().Setenv("REGISTRY_STORAGE", "somedriver")
|
os.Setenv("REGISTRY_STORAGE", "s3")
|
||||||
suite.T().Setenv("REGISTRY_STORAGE_SOMEDRIVER_REGION", "us-east-1")
|
os.Setenv("REGISTRY_STORAGE_S3_REGION", "us-east-1")
|
||||||
|
|
||||||
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
suite.Require().Equal(suite.expectedConfig, config)
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseWithDifferentEnvStorageParams validates that providing environment variables that change
|
// TestParseWithDifferentEnvStorageParams validates that providing environment variables that change
|
||||||
// and add to the given storage parameters will change and add parameters to the parsed
|
// and add to the given storage parameters will change and add parameters to the parsed
|
||||||
// Configuration struct
|
// Configuration struct
|
||||||
func (suite *ConfigSuite) TestParseWithDifferentEnvStorageParams() {
|
func (suite *ConfigSuite) TestParseWithDifferentEnvStorageParams(c *C) {
|
||||||
suite.expectedConfig.Storage.setParameter("string1", "us-west-1")
|
suite.expectedConfig.Storage.setParameter("region", "us-west-1")
|
||||||
suite.expectedConfig.Storage.setParameter("bool1", true)
|
suite.expectedConfig.Storage.setParameter("secure", true)
|
||||||
suite.expectedConfig.Storage.setParameter("newparam", "some Value")
|
suite.expectedConfig.Storage.setParameter("newparam", "some Value")
|
||||||
|
|
||||||
suite.T().Setenv("REGISTRY_STORAGE_SOMEDRIVER_STRING1", "us-west-1")
|
os.Setenv("REGISTRY_STORAGE_S3_REGION", "us-west-1")
|
||||||
suite.T().Setenv("REGISTRY_STORAGE_SOMEDRIVER_BOOL1", "true")
|
os.Setenv("REGISTRY_STORAGE_S3_SECURE", "true")
|
||||||
suite.T().Setenv("REGISTRY_STORAGE_SOMEDRIVER_NEWPARAM", "some Value")
|
os.Setenv("REGISTRY_STORAGE_S3_NEWPARAM", "some Value")
|
||||||
|
|
||||||
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
suite.Require().Equal(suite.expectedConfig, config)
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseWithDifferentEnvStorageType validates that providing an environment variable that
|
// TestParseWithDifferentEnvStorageType validates that providing an environment variable that
|
||||||
// changes the storage type will be reflected in the parsed Configuration struct
|
// changes the storage type will be reflected in the parsed Configuration struct
|
||||||
func (suite *ConfigSuite) TestParseWithDifferentEnvStorageType() {
|
func (suite *ConfigSuite) TestParseWithDifferentEnvStorageType(c *C) {
|
||||||
suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
|
suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
|
||||||
|
|
||||||
suite.T().Setenv("REGISTRY_STORAGE", "inmemory")
|
os.Setenv("REGISTRY_STORAGE", "inmemory")
|
||||||
|
|
||||||
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
suite.Require().Equal(suite.expectedConfig, config)
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseWithDifferentEnvStorageTypeAndParams validates that providing an environment variable
|
// TestParseWithDifferentEnvStorageTypeAndParams validates that providing an environment variable
|
||||||
// that changes the storage type will be reflected in the parsed Configuration struct and that
|
// that changes the storage type will be reflected in the parsed Configuration struct and that
|
||||||
// environment storage parameters will also be included
|
// environment storage parameters will also be included
|
||||||
func (suite *ConfigSuite) TestParseWithDifferentEnvStorageTypeAndParams() {
|
func (suite *ConfigSuite) TestParseWithDifferentEnvStorageTypeAndParams(c *C) {
|
||||||
suite.expectedConfig.Storage = Storage{"filesystem": Parameters{}}
|
suite.expectedConfig.Storage = Storage{"filesystem": Parameters{}}
|
||||||
suite.expectedConfig.Storage.setParameter("rootdirectory", "/tmp/testroot")
|
suite.expectedConfig.Storage.setParameter("rootdirectory", "/tmp/testroot")
|
||||||
|
|
||||||
suite.T().Setenv("REGISTRY_STORAGE", "filesystem")
|
os.Setenv("REGISTRY_STORAGE", "filesystem")
|
||||||
suite.T().Setenv("REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY", "/tmp/testroot")
|
os.Setenv("REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY", "/tmp/testroot")
|
||||||
|
|
||||||
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
suite.Require().Equal(suite.expectedConfig, config)
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseWithSameEnvLoglevel validates that providing an environment variable defining the log
|
// TestParseWithSameEnvLoglevel validates that providing an environment variable defining the log
|
||||||
// level to the same as the one provided in the yaml will not change the parsed Configuration struct
|
// level to the same as the one provided in the yaml will not change the parsed Configuration struct
|
||||||
func (suite *ConfigSuite) TestParseWithSameEnvLoglevel() {
|
func (suite *ConfigSuite) TestParseWithSameEnvLoglevel(c *C) {
|
||||||
suite.T().Setenv("REGISTRY_LOGLEVEL", "info")
|
os.Setenv("REGISTRY_LOGLEVEL", "info")
|
||||||
|
|
||||||
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
suite.Require().Equal(suite.expectedConfig, config)
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseWithDifferentEnvLoglevel validates that providing an environment variable defining the
|
// TestParseWithDifferentEnvLoglevel validates that providing an environment variable defining the
|
||||||
// log level will override the value provided in the yaml document
|
// log level will override the value provided in the yaml document
|
||||||
func (suite *ConfigSuite) TestParseWithDifferentEnvLoglevel() {
|
func (suite *ConfigSuite) TestParseWithDifferentEnvLoglevel(c *C) {
|
||||||
suite.expectedConfig.Log.Level = "error"
|
suite.expectedConfig.Log.Level = "error"
|
||||||
|
|
||||||
suite.T().Setenv("REGISTRY_LOG_LEVEL", "error")
|
os.Setenv("REGISTRY_LOG_LEVEL", "error")
|
||||||
|
|
||||||
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
suite.Require().Equal(suite.expectedConfig, config)
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseInvalidLoglevel validates that the parser will fail to parse a
|
// TestParseInvalidLoglevel validates that the parser will fail to parse a
|
||||||
// configuration if the loglevel is malformed
|
// configuration if the loglevel is malformed
|
||||||
func (suite *ConfigSuite) TestParseInvalidLoglevel() {
|
func (suite *ConfigSuite) TestParseInvalidLoglevel(c *C) {
|
||||||
invalidConfigYaml := "version: 0.1\nloglevel: derp\nstorage: inmemory"
|
invalidConfigYaml := "version: 0.1\nloglevel: derp\nstorage: inmemory"
|
||||||
_, err := Parse(bytes.NewReader([]byte(invalidConfigYaml)))
|
_, err := Parse(bytes.NewReader([]byte(invalidConfigYaml)))
|
||||||
suite.Require().Error(err)
|
c.Assert(err, NotNil)
|
||||||
|
|
||||||
suite.T().Setenv("REGISTRY_LOGLEVEL", "derp")
|
os.Setenv("REGISTRY_LOGLEVEL", "derp")
|
||||||
|
|
||||||
_, err = Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
_, err = Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().Error(err)
|
c.Assert(err, NotNil)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseWithDifferentEnvReporting validates that environment variables
|
||||||
|
// properly override reporting parameters
|
||||||
|
func (suite *ConfigSuite) TestParseWithDifferentEnvReporting(c *C) {
|
||||||
|
suite.expectedConfig.Reporting.Bugsnag.APIKey = "anotherBugsnagApiKey"
|
||||||
|
suite.expectedConfig.Reporting.Bugsnag.Endpoint = "localhost:8080"
|
||||||
|
suite.expectedConfig.Reporting.NewRelic.LicenseKey = "NewRelicLicenseKey"
|
||||||
|
suite.expectedConfig.Reporting.NewRelic.Name = "some NewRelic NAME"
|
||||||
|
|
||||||
|
os.Setenv("REGISTRY_REPORTING_BUGSNAG_APIKEY", "anotherBugsnagApiKey")
|
||||||
|
os.Setenv("REGISTRY_REPORTING_BUGSNAG_ENDPOINT", "localhost:8080")
|
||||||
|
os.Setenv("REGISTRY_REPORTING_NEWRELIC_LICENSEKEY", "NewRelicLicenseKey")
|
||||||
|
os.Setenv("REGISTRY_REPORTING_NEWRELIC_NAME", "some NewRelic NAME")
|
||||||
|
|
||||||
|
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseInvalidVersion validates that the parser will fail to parse a newer configuration
|
// TestParseInvalidVersion validates that the parser will fail to parse a newer configuration
|
||||||
// version than the CurrentVersion
|
// version than the CurrentVersion
|
||||||
func (suite *ConfigSuite) TestParseInvalidVersion() {
|
func (suite *ConfigSuite) TestParseInvalidVersion(c *C) {
|
||||||
suite.expectedConfig.Version = MajorMinorVersion(CurrentVersion.Major(), CurrentVersion.Minor()+1)
|
suite.expectedConfig.Version = MajorMinorVersion(CurrentVersion.Major(), CurrentVersion.Minor()+1)
|
||||||
configBytes, err := yaml.Marshal(suite.expectedConfig)
|
configBytes, err := yaml.Marshal(suite.expectedConfig)
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
_, err = Parse(bytes.NewReader(configBytes))
|
_, err = Parse(bytes.NewReader(configBytes))
|
||||||
suite.Require().Error(err)
|
c.Assert(err, NotNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseExtraneousVars validates that environment variables referring to
|
// TestParseExtraneousVars validates that environment variables referring to
|
||||||
// nonexistent variables don't cause side effects.
|
// nonexistent variables don't cause side effects.
|
||||||
func (suite *ConfigSuite) TestParseExtraneousVars() {
|
func (suite *ConfigSuite) TestParseExtraneousVars(c *C) {
|
||||||
|
suite.expectedConfig.Reporting.Bugsnag.Endpoint = "localhost:8080"
|
||||||
|
|
||||||
|
// A valid environment variable
|
||||||
|
os.Setenv("REGISTRY_REPORTING_BUGSNAG_ENDPOINT", "localhost:8080")
|
||||||
|
|
||||||
// Environment variables which shouldn't set config items
|
// Environment variables which shouldn't set config items
|
||||||
suite.T().Setenv("REGISTRY_DUCKS", "quack")
|
os.Setenv("registry_REPORTING_NEWRELIC_LICENSEKEY", "NewRelicLicenseKey")
|
||||||
suite.T().Setenv("REGISTRY_REPORTING_ASDF", "ghjk")
|
os.Setenv("REPORTING_NEWRELIC_NAME", "some NewRelic NAME")
|
||||||
|
os.Setenv("REGISTRY_DUCKS", "quack")
|
||||||
|
os.Setenv("REGISTRY_REPORTING_ASDF", "ghjk")
|
||||||
|
|
||||||
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
suite.Require().Equal(suite.expectedConfig, config)
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseEnvVarImplicitMaps validates that environment variables can set
|
// TestParseEnvVarImplicitMaps validates that environment variables can set
|
||||||
// values in maps that don't already exist.
|
// values in maps that don't already exist.
|
||||||
func (suite *ConfigSuite) TestParseEnvVarImplicitMaps() {
|
func (suite *ConfigSuite) TestParseEnvVarImplicitMaps(c *C) {
|
||||||
readonly := make(map[string]interface{})
|
readonly := make(map[string]interface{})
|
||||||
readonly["enabled"] = true
|
readonly["enabled"] = true
|
||||||
|
|
||||||
|
@ -459,63 +426,61 @@ func (suite *ConfigSuite) TestParseEnvVarImplicitMaps() {
|
||||||
|
|
||||||
suite.expectedConfig.Storage["maintenance"] = maintenance
|
suite.expectedConfig.Storage["maintenance"] = maintenance
|
||||||
|
|
||||||
suite.T().Setenv("REGISTRY_STORAGE_MAINTENANCE_READONLY_ENABLED", "true")
|
os.Setenv("REGISTRY_STORAGE_MAINTENANCE_READONLY_ENABLED", "true")
|
||||||
|
|
||||||
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
suite.Require().Equal(suite.expectedConfig, config)
|
c.Assert(config, DeepEquals, suite.expectedConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseEnvWrongTypeMap validates that incorrectly attempting to unmarshal a
|
// TestParseEnvWrongTypeMap validates that incorrectly attempting to unmarshal a
|
||||||
// string over existing map fails.
|
// string over existing map fails.
|
||||||
func (suite *ConfigSuite) TestParseEnvWrongTypeMap() {
|
func (suite *ConfigSuite) TestParseEnvWrongTypeMap(c *C) {
|
||||||
suite.T().Setenv("REGISTRY_STORAGE_SOMEDRIVER", "somestring")
|
os.Setenv("REGISTRY_STORAGE_S3", "somestring")
|
||||||
|
|
||||||
_, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
_, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().Error(err)
|
c.Assert(err, NotNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseEnvWrongTypeStruct validates that incorrectly attempting to
|
// TestParseEnvWrongTypeStruct validates that incorrectly attempting to
|
||||||
// unmarshal a string into a struct fails.
|
// unmarshal a string into a struct fails.
|
||||||
func (suite *ConfigSuite) TestParseEnvWrongTypeStruct() {
|
func (suite *ConfigSuite) TestParseEnvWrongTypeStruct(c *C) {
|
||||||
suite.T().Setenv("REGISTRY_STORAGE_LOG", "somestring")
|
os.Setenv("REGISTRY_STORAGE_LOG", "somestring")
|
||||||
|
|
||||||
_, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
_, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().Error(err)
|
c.Assert(err, NotNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseEnvWrongTypeSlice validates that incorrectly attempting to
|
// TestParseEnvWrongTypeSlice validates that incorrectly attempting to
|
||||||
// unmarshal a string into a slice fails.
|
// unmarshal a string into a slice fails.
|
||||||
func (suite *ConfigSuite) TestParseEnvWrongTypeSlice() {
|
func (suite *ConfigSuite) TestParseEnvWrongTypeSlice(c *C) {
|
||||||
suite.T().Setenv("REGISTRY_LOG_HOOKS", "somestring")
|
os.Setenv("REGISTRY_LOG_HOOKS", "somestring")
|
||||||
|
|
||||||
_, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
_, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().Error(err)
|
c.Assert(err, NotNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseEnvMany tests several environment variable overrides.
|
// TestParseEnvMany tests several environment variable overrides.
|
||||||
// The result is not checked - the goal of this test is to detect panics
|
// The result is not checked - the goal of this test is to detect panics
|
||||||
// from misuse of reflection.
|
// from misuse of reflection.
|
||||||
func (suite *ConfigSuite) TestParseEnvMany() {
|
func (suite *ConfigSuite) TestParseEnvMany(c *C) {
|
||||||
suite.T().Setenv("REGISTRY_VERSION", "0.1")
|
os.Setenv("REGISTRY_VERSION", "0.1")
|
||||||
suite.T().Setenv("REGISTRY_LOG_LEVEL", "debug")
|
os.Setenv("REGISTRY_LOG_LEVEL", "debug")
|
||||||
suite.T().Setenv("REGISTRY_LOG_FORMATTER", "json")
|
os.Setenv("REGISTRY_LOG_FORMATTER", "json")
|
||||||
suite.T().Setenv("REGISTRY_LOG_HOOKS", "json")
|
os.Setenv("REGISTRY_LOG_HOOKS", "json")
|
||||||
suite.T().Setenv("REGISTRY_LOG_FIELDS", "abc: xyz")
|
os.Setenv("REGISTRY_LOG_FIELDS", "abc: xyz")
|
||||||
suite.T().Setenv("REGISTRY_LOG_HOOKS", "- type: asdf")
|
os.Setenv("REGISTRY_LOG_HOOKS", "- type: asdf")
|
||||||
suite.T().Setenv("REGISTRY_LOGLEVEL", "debug")
|
os.Setenv("REGISTRY_LOGLEVEL", "debug")
|
||||||
suite.T().Setenv("REGISTRY_STORAGE", "somedriver")
|
os.Setenv("REGISTRY_STORAGE", "s3")
|
||||||
suite.T().Setenv("REGISTRY_AUTH_PARAMS", "param1: value1")
|
os.Setenv("REGISTRY_AUTH_PARAMS", "param1: value1")
|
||||||
suite.T().Setenv("REGISTRY_AUTH_PARAMS_VALUE2", "value2")
|
os.Setenv("REGISTRY_AUTH_PARAMS_VALUE2", "value2")
|
||||||
suite.T().Setenv("REGISTRY_AUTH_PARAMS_VALUE2", "value2")
|
os.Setenv("REGISTRY_AUTH_PARAMS_VALUE2", "value2")
|
||||||
|
|
||||||
_, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
_, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
|
||||||
suite.Require().NoError(err)
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkStructs(tt *testing.T, t reflect.Type, structsChecked map[string]struct{}) {
|
func checkStructs(c *C, t reflect.Type, structsChecked map[string]struct{}) {
|
||||||
tt.Helper()
|
|
||||||
|
|
||||||
for t.Kind() == reflect.Ptr || t.Kind() == reflect.Map || t.Kind() == reflect.Slice {
|
for t.Kind() == reflect.Ptr || t.Kind() == reflect.Map || t.Kind() == reflect.Slice {
|
||||||
t = t.Elem()
|
t = t.Elem()
|
||||||
}
|
}
|
||||||
|
@ -537,23 +502,23 @@ func checkStructs(tt *testing.T, t reflect.Type, structsChecked map[string]struc
|
||||||
// Check that the yaml tag does not contain an _.
|
// Check that the yaml tag does not contain an _.
|
||||||
yamlTag := sf.Tag.Get("yaml")
|
yamlTag := sf.Tag.Get("yaml")
|
||||||
if strings.Contains(yamlTag, "_") {
|
if strings.Contains(yamlTag, "_") {
|
||||||
tt.Fatalf("yaml field name includes _ character: %s", yamlTag)
|
c.Fatalf("yaml field name includes _ character: %s", yamlTag)
|
||||||
}
|
}
|
||||||
upper := strings.ToUpper(sf.Name)
|
upper := strings.ToUpper(sf.Name)
|
||||||
if _, present := byUpperCase[upper]; present {
|
if _, present := byUpperCase[upper]; present {
|
||||||
tt.Fatalf("field name collision in configuration object: %s", sf.Name)
|
c.Fatalf("field name collision in configuration object: %s", sf.Name)
|
||||||
}
|
}
|
||||||
byUpperCase[upper] = i
|
byUpperCase[upper] = i
|
||||||
|
|
||||||
checkStructs(tt, sf.Type, structsChecked)
|
checkStructs(c, sf.Type, structsChecked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestValidateConfigStruct makes sure that the config struct has no members
|
// TestValidateConfigStruct makes sure that the config struct has no members
|
||||||
// with yaml tags that would be ambiguous to the environment variable parser.
|
// with yaml tags that would be ambiguous to the environment variable parser.
|
||||||
func (suite *ConfigSuite) TestValidateConfigStruct() {
|
func (suite *ConfigSuite) TestValidateConfigStruct(c *C) {
|
||||||
structsChecked := make(map[string]struct{})
|
structsChecked := make(map[string]struct{})
|
||||||
checkStructs(suite.T(), reflect.TypeOf(Configuration{}), structsChecked)
|
checkStructs(c, reflect.TypeOf(Configuration{}), structsChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyConfig(config Configuration) *Configuration {
|
func copyConfig(config Configuration) *Configuration {
|
||||||
|
@ -572,8 +537,9 @@ func copyConfig(config Configuration) *Configuration {
|
||||||
for k, v := range config.Storage.Parameters() {
|
for k, v := range config.Storage.Parameters() {
|
||||||
configCopy.Storage.setParameter(k, v)
|
configCopy.Storage.setParameter(k, v)
|
||||||
}
|
}
|
||||||
for k, v := range config.Storage.TagParameters() {
|
configCopy.Reporting = Reporting{
|
||||||
configCopy.Storage.setTagParameter(k, v)
|
Bugsnag: BugsnagReporting{config.Reporting.Bugsnag.APIKey, config.Reporting.Bugsnag.ReleaseStage, config.Reporting.Bugsnag.Endpoint},
|
||||||
|
NewRelic: NewRelicReporting{config.Reporting.NewRelic.LicenseKey, config.Reporting.NewRelic.Name, config.Reporting.NewRelic.Verbose},
|
||||||
}
|
}
|
||||||
|
|
||||||
configCopy.Auth = Auth{config.Auth.Type(): Parameters{}}
|
configCopy.Auth = Auth{config.Auth.Type(): Parameters{}}
|
||||||
|
@ -588,20 +554,6 @@ func copyConfig(config Configuration) *Configuration {
|
||||||
for k, v := range config.HTTP.Headers {
|
for k, v := range config.HTTP.Headers {
|
||||||
configCopy.HTTP.Headers[k] = v
|
configCopy.HTTP.Headers[k] = v
|
||||||
}
|
}
|
||||||
configCopy.HTTP.TLS.ClientCAs = make([]string, 0, len(config.HTTP.TLS.ClientCAs))
|
|
||||||
configCopy.HTTP.TLS.ClientCAs = append(configCopy.HTTP.TLS.ClientCAs, config.HTTP.TLS.ClientCAs...)
|
|
||||||
|
|
||||||
configCopy.Redis = config.Redis
|
|
||||||
configCopy.Redis.TLS.Certificate = config.Redis.TLS.Certificate
|
|
||||||
configCopy.Redis.TLS.Key = config.Redis.TLS.Key
|
|
||||||
configCopy.Redis.TLS.ClientCAs = make([]string, 0, len(config.Redis.TLS.ClientCAs))
|
|
||||||
configCopy.Redis.TLS.ClientCAs = append(configCopy.Redis.TLS.ClientCAs, config.Redis.TLS.ClientCAs...)
|
|
||||||
|
|
||||||
configCopy.Validation = Validation{
|
|
||||||
Enabled: config.Validation.Enabled,
|
|
||||||
Disabled: config.Validation.Disabled,
|
|
||||||
Manifests: config.Validation.Manifests,
|
|
||||||
}
|
|
||||||
|
|
||||||
return configCopy
|
return configCopy
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParserFuzzer implements a fuzzer that targets Parser()
|
|
||||||
// nolint:deadcode
|
|
||||||
func FuzzConfigurationParse(f *testing.F) {
|
|
||||||
f.Fuzz(func(t *testing.T, data []byte) {
|
|
||||||
rd := bytes.NewReader(data)
|
|
||||||
_, _ = Parse(rd)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -23,7 +23,7 @@ func MajorMinorVersion(major, minor uint) Version {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (version Version) major() (uint, error) {
|
func (version Version) major() (uint, error) {
|
||||||
majorPart, _, _ := strings.Cut(string(version), ".")
|
majorPart := strings.Split(string(version), ".")[0]
|
||||||
major, err := strconv.ParseUint(majorPart, 10, 0)
|
major, err := strconv.ParseUint(majorPart, 10, 0)
|
||||||
return uint(major), err
|
return uint(major), err
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ func (version Version) Major() uint {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (version Version) minor() (uint, error) {
|
func (version Version) minor() (uint, error) {
|
||||||
_, minorPart, _ := strings.Cut(string(version), ".")
|
minorPart := strings.Split(string(version), ".")[1]
|
||||||
minor, err := strconv.ParseUint(minorPart, 10, 0)
|
minor, err := strconv.ParseUint(minorPart, 10, 0)
|
||||||
return uint(minor), err
|
return uint(minor), err
|
||||||
}
|
}
|
||||||
|
@ -89,8 +89,8 @@ func NewParser(prefix string, parseInfos []VersionedParseInfo) *Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, env := range os.Environ() {
|
for _, env := range os.Environ() {
|
||||||
k, v, _ := strings.Cut(env, "=")
|
envParts := strings.SplitN(env, "=", 2)
|
||||||
p.env = append(p.env, envVar{k, v})
|
p.env = append(p.env, envVar{envParts[0], envParts[1]})
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must sort the environment variables lexically by name so that
|
// We must sort the environment variables lexically by name so that
|
||||||
|
@ -138,7 +138,7 @@ func (p *Parser) Parse(in []byte, v interface{}) error {
|
||||||
|
|
||||||
err = p.overwriteFields(parseAs, pathStr, path[1:], envVar.value)
|
err = p.overwriteFields(parseAs, pathStr, path[1:], envVar.value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parsing environment variable %s: %v", pathStr, err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,25 +166,6 @@ func (p *Parser) overwriteFields(v reflect.Value, fullpath string, path []string
|
||||||
return p.overwriteStruct(v, fullpath, path, payload)
|
return p.overwriteStruct(v, fullpath, path, payload)
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
return p.overwriteMap(v, fullpath, path, payload)
|
return p.overwriteMap(v, fullpath, path, payload)
|
||||||
case reflect.Slice:
|
|
||||||
idx, err := strconv.Atoi(path[0])
|
|
||||||
if err != nil {
|
|
||||||
panic("non-numeric index: " + path[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if idx > v.Len() {
|
|
||||||
panic("undefined index: " + path[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there is no element or the current slice length
|
|
||||||
// is the same as the indexed variable create a new element,
|
|
||||||
// append it and then set it to the passed in env var value.
|
|
||||||
if v.Len() == 0 || idx == v.Len() {
|
|
||||||
typ := v.Type().Elem()
|
|
||||||
elem := reflect.New(typ).Elem()
|
|
||||||
v.Set(reflect.Append(v, elem))
|
|
||||||
}
|
|
||||||
return p.overwriteFields(v.Index(idx), fullpath, path[1:], payload)
|
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
if v.NumMethod() == 0 {
|
if v.NumMethod() == 0 {
|
||||||
if !v.IsNil() {
|
if !v.IsNil() {
|
||||||
|
@ -239,7 +220,7 @@ func (p *Parser) overwriteStruct(v reflect.Value, fullpath string, path []string
|
||||||
}
|
}
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
if field.IsNil() {
|
if field.IsNil() {
|
||||||
field.Set(reflect.New(field.Type().Elem()))
|
field.Set(reflect.New(sf.Type))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type localConfiguration struct {
|
|
||||||
Version Version `yaml:"version"`
|
|
||||||
Log *Log `yaml:"log"`
|
|
||||||
Notifications []Notif `yaml:"notifications,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Log struct {
|
|
||||||
Formatter string `yaml:"formatter,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Notif struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var expectedConfig = localConfiguration{
|
|
||||||
Version: "0.1",
|
|
||||||
Log: &Log{
|
|
||||||
Formatter: "json",
|
|
||||||
},
|
|
||||||
Notifications: []Notif{
|
|
||||||
{Name: "foo"},
|
|
||||||
{Name: "bar"},
|
|
||||||
{Name: "car"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const testConfig = `version: "0.1"
|
|
||||||
log:
|
|
||||||
formatter: "text"
|
|
||||||
notifications:
|
|
||||||
- name: "foo"
|
|
||||||
- name: "bar"
|
|
||||||
- name: "car"`
|
|
||||||
|
|
||||||
func TestParserOverwriteIninitializedPoiner(t *testing.T) {
|
|
||||||
config := localConfiguration{}
|
|
||||||
|
|
||||||
t.Setenv("REGISTRY_LOG_FORMATTER", "json")
|
|
||||||
|
|
||||||
p := NewParser("registry", []VersionedParseInfo{
|
|
||||||
{
|
|
||||||
Version: "0.1",
|
|
||||||
ParseAs: reflect.TypeOf(config),
|
|
||||||
ConversionFunc: func(c interface{}) (interface{}, error) {
|
|
||||||
return c, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
err := p.Parse([]byte(testConfig), &config)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedConfig, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
const testConfig2 = `version: "0.1"
|
|
||||||
log:
|
|
||||||
formatter: "text"
|
|
||||||
notifications:
|
|
||||||
- name: "val1"
|
|
||||||
- name: "val2"
|
|
||||||
- name: "car"`
|
|
||||||
|
|
||||||
func TestParseOverwriteUnininitializedPoiner(t *testing.T) {
|
|
||||||
config := localConfiguration{}
|
|
||||||
|
|
||||||
t.Setenv("REGISTRY_LOG_FORMATTER", "json")
|
|
||||||
|
|
||||||
// override only first two notificationsvalues
|
|
||||||
// in the tetConfig: leave the last value unchanged.
|
|
||||||
t.Setenv("REGISTRY_NOTIFICATIONS_0_NAME", "foo")
|
|
||||||
t.Setenv("REGISTRY_NOTIFICATIONS_1_NAME", "bar")
|
|
||||||
|
|
||||||
p := NewParser("registry", []VersionedParseInfo{
|
|
||||||
{
|
|
||||||
Version: "0.1",
|
|
||||||
ParseAs: reflect.TypeOf(config),
|
|
||||||
ConversionFunc: func(c interface{}) (interface{}, error) {
|
|
||||||
return c, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
err := p.Parse([]byte(testConfig2), &config)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedConfig, config)
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
package dcontext
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/docker/distribution/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// instanceContext is a context that provides only an instance id. It is
|
// instanceContext is a context that provides only an instance id. It is
|
||||||
|
@ -21,8 +21,8 @@ func (ic *instanceContext) Value(key interface{}) interface{} {
|
||||||
// We want to lazy initialize the UUID such that we don't
|
// We want to lazy initialize the UUID such that we don't
|
||||||
// call a random generator from the package initialization
|
// call a random generator from the package initialization
|
||||||
// code. For various reasons random could not be available
|
// code. For various reasons random could not be available
|
||||||
// https://github.com/distribution/distribution/issues/782
|
// https://github.com/docker/distribution/issues/782
|
||||||
ic.id = uuid.NewString()
|
ic.id = uuid.Generate().String()
|
||||||
})
|
})
|
||||||
return ic.id
|
return ic.id
|
||||||
}
|
}
|
|
@ -1,16 +1,16 @@
|
||||||
// Package dcontext provides several utilities for working with
|
// Package context provides several utilities for working with
|
||||||
// Go's context in http requests. Primarily, the focus is on logging relevant
|
// Go's context in http requests. Primarily, the focus is on logging relevant
|
||||||
// request information but this package is not limited to that purpose.
|
// request information but this package is not limited to that purpose.
|
||||||
//
|
//
|
||||||
// The easiest way to get started is to get the background context:
|
// The easiest way to get started is to get the background context:
|
||||||
//
|
//
|
||||||
// ctx := dcontext.Background()
|
// ctx := context.Background()
|
||||||
//
|
//
|
||||||
// The returned context should be passed around your application and be the
|
// The returned context should be passed around your application and be the
|
||||||
// root of all other context instances. If the application has a version, this
|
// root of all other context instances. If the application has a version, this
|
||||||
// line should be called before anything else:
|
// line should be called before anything else:
|
||||||
//
|
//
|
||||||
// ctx := dcontext.WithVersion(dcontext.Background(), version)
|
// ctx := context.WithVersion(context.Background(), version)
|
||||||
//
|
//
|
||||||
// The above will store the version in the context and will be available to
|
// The above will store the version in the context and will be available to
|
||||||
// the logger.
|
// the logger.
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
// the context and reported with the logger. The following example would
|
// the context and reported with the logger. The following example would
|
||||||
// return a logger that prints the version with each log message:
|
// return a logger that prints the version with each log message:
|
||||||
//
|
//
|
||||||
// ctx := context.WithValue(dcontext.Background(), "version", version)
|
// ctx := context.Context(context.Background(), "version", version)
|
||||||
// GetLogger(ctx, "version").Infof("this log message has a version field")
|
// GetLogger(ctx, "version").Infof("this log message has a version field")
|
||||||
//
|
//
|
||||||
// The above would print out a log message like this:
|
// The above would print out a log message like this:
|
||||||
|
@ -85,4 +85,4 @@
|
||||||
// can be traced in log messages. Using the fields like "http.request.id", one
|
// can be traced in log messages. Using the fields like "http.request.id", one
|
||||||
// can analyze call flow for a particular request with a simple grep of the
|
// can analyze call flow for a particular request with a simple grep of the
|
||||||
// logs.
|
// logs.
|
||||||
package dcontext
|
package context
|
|
@ -1,16 +1,17 @@
|
||||||
package dcontext
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3/internal/requestutil"
|
"github.com/docker/distribution/uuid"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Common errors used with this package.
|
// Common errors used with this package.
|
||||||
|
@ -19,6 +20,50 @@ var (
|
||||||
ErrNoResponseWriterContext = errors.New("no http response in context")
|
ErrNoResponseWriterContext = errors.New("no http response in context")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func parseIP(ipStr string) net.IP {
|
||||||
|
ip := net.ParseIP(ipStr)
|
||||||
|
if ip == nil {
|
||||||
|
log.Warnf("invalid remote IP address: %q", ipStr)
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr extracts the remote address of the request, taking into
|
||||||
|
// account proxy headers.
|
||||||
|
func RemoteAddr(r *http.Request) string {
|
||||||
|
if prior := r.Header.Get("X-Forwarded-For"); prior != "" {
|
||||||
|
proxies := strings.Split(prior, ",")
|
||||||
|
if len(proxies) > 0 {
|
||||||
|
remoteAddr := strings.Trim(proxies[0], " ")
|
||||||
|
if parseIP(remoteAddr) != nil {
|
||||||
|
return remoteAddr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// X-Real-Ip is less supported, but worth checking in the
|
||||||
|
// absence of X-Forwarded-For
|
||||||
|
if realIP := r.Header.Get("X-Real-Ip"); realIP != "" {
|
||||||
|
if parseIP(realIP) != nil {
|
||||||
|
return realIP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.RemoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteIP extracts the remote IP of the request, taking into
|
||||||
|
// account proxy headers.
|
||||||
|
func RemoteIP(r *http.Request) string {
|
||||||
|
addr := RemoteAddr(r)
|
||||||
|
|
||||||
|
// Try parsing it as "IP:port"
|
||||||
|
if ip, _, err := net.SplitHostPort(addr); err == nil {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
// WithRequest places the request on the context. The context of the request
|
// WithRequest places the request on the context. The context of the request
|
||||||
// is assigned a unique id, available at "http.request.id". The request itself
|
// is assigned a unique id, available at "http.request.id". The request itself
|
||||||
// is available at "http.request". Other common attributes are available under
|
// is available at "http.request". Other common attributes are available under
|
||||||
|
@ -35,11 +80,21 @@ func WithRequest(ctx context.Context, r *http.Request) context.Context {
|
||||||
return &httpRequestContext{
|
return &httpRequestContext{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
startedAt: time.Now(),
|
startedAt: time.Now(),
|
||||||
id: uuid.NewString(),
|
id: uuid.Generate().String(),
|
||||||
r: r,
|
r: r,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRequest returns the http request in the given context. Returns
|
||||||
|
// ErrNoRequestContext if the context does not have an http request associated
|
||||||
|
// with it.
|
||||||
|
func GetRequest(ctx context.Context) (*http.Request, error) {
|
||||||
|
if r, ok := ctx.Value("http.request").(*http.Request); r != nil && ok {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
return nil, ErrNoRequestContext
|
||||||
|
}
|
||||||
|
|
||||||
// GetRequestID attempts to resolve the current request id, if possible. An
|
// GetRequestID attempts to resolve the current request id, if possible. An
|
||||||
// error is return if it is not available on the context.
|
// error is return if it is not available on the context.
|
||||||
func GetRequestID(ctx context.Context) string {
|
func GetRequestID(ctx context.Context) string {
|
||||||
|
@ -134,37 +189,49 @@ type httpRequestContext struct {
|
||||||
// "request.<component>". For example, r.RequestURI
|
// "request.<component>". For example, r.RequestURI
|
||||||
func (ctx *httpRequestContext) Value(key interface{}) interface{} {
|
func (ctx *httpRequestContext) Value(key interface{}) interface{} {
|
||||||
if keyStr, ok := key.(string); ok {
|
if keyStr, ok := key.(string); ok {
|
||||||
switch keyStr {
|
if keyStr == "http.request" {
|
||||||
case "http.request":
|
|
||||||
return ctx.r
|
return ctx.r
|
||||||
case "http.request.uri":
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(keyStr, "http.request.") {
|
||||||
|
goto fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(keyStr, ".")
|
||||||
|
|
||||||
|
if len(parts) != 3 {
|
||||||
|
goto fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
switch parts[2] {
|
||||||
|
case "uri":
|
||||||
return ctx.r.RequestURI
|
return ctx.r.RequestURI
|
||||||
case "http.request.remoteaddr":
|
case "remoteaddr":
|
||||||
return requestutil.RemoteAddr(ctx.r)
|
return RemoteAddr(ctx.r)
|
||||||
case "http.request.method":
|
case "method":
|
||||||
return ctx.r.Method
|
return ctx.r.Method
|
||||||
case "http.request.host":
|
case "host":
|
||||||
return ctx.r.Host
|
return ctx.r.Host
|
||||||
case "http.request.referer":
|
case "referer":
|
||||||
referer := ctx.r.Referer()
|
referer := ctx.r.Referer()
|
||||||
if referer != "" {
|
if referer != "" {
|
||||||
return referer
|
return referer
|
||||||
}
|
}
|
||||||
case "http.request.useragent":
|
case "useragent":
|
||||||
return ctx.r.UserAgent()
|
return ctx.r.UserAgent()
|
||||||
case "http.request.id":
|
case "id":
|
||||||
return ctx.id
|
return ctx.id
|
||||||
case "http.request.startedat":
|
case "startedat":
|
||||||
return ctx.startedAt
|
return ctx.startedAt
|
||||||
case "http.request.contenttype":
|
case "contenttype":
|
||||||
if ct := ctx.r.Header.Get("Content-Type"); ct != "" {
|
ct := ctx.r.Header.Get("Content-Type")
|
||||||
|
if ct != "" {
|
||||||
return ct
|
return ct
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
// no match; fall back to standard behavior below
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fallback:
|
||||||
return ctx.Context.Value(key)
|
return ctx.Context.Value(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,8 +245,7 @@ func (ctx *muxVarsContext) Value(key interface{}) interface{} {
|
||||||
if keyStr == "vars" {
|
if keyStr == "vars" {
|
||||||
return ctx.vars
|
return ctx.vars
|
||||||
}
|
}
|
||||||
// TODO(thaJeztah): this considers "vars.FOO" and "FOO" to be equal.
|
|
||||||
// We need to check if that's intentional (could be a bug).
|
|
||||||
if v, ok := ctx.vars[strings.TrimPrefix(keyStr, "vars.")]; ok {
|
if v, ok := ctx.vars[strings.TrimPrefix(keyStr, "vars.")]; ok {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
@ -232,25 +298,36 @@ func (irw *instrumentedResponseWriter) Flush() {
|
||||||
|
|
||||||
func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} {
|
func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} {
|
||||||
if keyStr, ok := key.(string); ok {
|
if keyStr, ok := key.(string); ok {
|
||||||
switch keyStr {
|
if keyStr == "http.response" {
|
||||||
case "http.response":
|
|
||||||
return irw
|
return irw
|
||||||
case "http.response.written":
|
|
||||||
irw.mu.Lock()
|
|
||||||
defer irw.mu.Unlock()
|
|
||||||
return irw.written
|
|
||||||
case "http.response.status":
|
|
||||||
irw.mu.Lock()
|
|
||||||
defer irw.mu.Unlock()
|
|
||||||
return irw.status
|
|
||||||
case "http.response.contenttype":
|
|
||||||
if ct := irw.Header().Get("Content-Type"); ct != "" {
|
|
||||||
return ct
|
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
// no match; fall back to standard behavior below
|
if !strings.HasPrefix(keyStr, "http.response.") {
|
||||||
|
goto fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(keyStr, ".")
|
||||||
|
|
||||||
|
if len(parts) != 3 {
|
||||||
|
goto fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
irw.mu.Lock()
|
||||||
|
defer irw.mu.Unlock()
|
||||||
|
|
||||||
|
switch parts[2] {
|
||||||
|
case "written":
|
||||||
|
return irw.written
|
||||||
|
case "status":
|
||||||
|
return irw.status
|
||||||
|
case "contenttype":
|
||||||
|
contentType := irw.Header().Get("Content-Type")
|
||||||
|
if contentType != "" {
|
||||||
|
return contentType
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fallback:
|
||||||
return irw.Context.Value(key)
|
return irw.Context.Value(key)
|
||||||
}
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
package dcontext
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -11,7 +14,7 @@ func TestWithRequest(t *testing.T) {
|
||||||
var req http.Request
|
var req http.Request
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
req.Method = http.MethodGet
|
req.Method = "GET"
|
||||||
req.Host = "example.com"
|
req.Host = "example.com"
|
||||||
req.RequestURI = "/test-test"
|
req.RequestURI = "/test-test"
|
||||||
req.Header = make(http.Header)
|
req.Header = make(http.Header)
|
||||||
|
@ -19,7 +22,7 @@ func TestWithRequest(t *testing.T) {
|
||||||
req.Header.Set("User-Agent", "test/0.1")
|
req.Header.Set("User-Agent", "test/0.1")
|
||||||
|
|
||||||
ctx := WithRequest(Background(), &req)
|
ctx := WithRequest(Background(), &req)
|
||||||
for _, tc := range []struct {
|
for _, testcase := range []struct {
|
||||||
key string
|
key string
|
||||||
expected interface{}
|
expected interface{}
|
||||||
}{
|
}{
|
||||||
|
@ -58,18 +61,18 @@ func TestWithRequest(t *testing.T) {
|
||||||
key: "http.request.startedat",
|
key: "http.request.startedat",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
v := ctx.Value(tc.key)
|
v := ctx.Value(testcase.key)
|
||||||
|
|
||||||
if v == nil {
|
if v == nil {
|
||||||
t.Fatalf("value not found for %q", tc.key)
|
t.Fatalf("value not found for %q", testcase.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tc.expected != nil && v != tc.expected {
|
if testcase.expected != nil && v != testcase.expected {
|
||||||
t.Fatalf("%s: %v != %v", tc.key, v, tc.expected)
|
t.Fatalf("%s: %v != %v", testcase.key, v, testcase.expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key specific checks!
|
// Key specific checks!
|
||||||
switch tc.key {
|
switch testcase.key {
|
||||||
case "http.request.id":
|
case "http.request.id":
|
||||||
if _, ok := v.(string); !ok {
|
if _, ok := v.(string); !ok {
|
||||||
t.Fatalf("request id not a string: %v", v)
|
t.Fatalf("request id not a string: %v", v)
|
||||||
|
@ -192,7 +195,7 @@ func TestWithVars(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := WithVars(Background(), &req)
|
ctx := WithVars(Background(), &req)
|
||||||
for _, tc := range []struct {
|
for _, testcase := range []struct {
|
||||||
key string
|
key string
|
||||||
expected interface{}
|
expected interface{}
|
||||||
}{
|
}{
|
||||||
|
@ -209,10 +212,74 @@ func TestWithVars(t *testing.T) {
|
||||||
expected: "qwer",
|
expected: "qwer",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
v := ctx.Value(tc.key)
|
v := ctx.Value(testcase.key)
|
||||||
|
|
||||||
if !reflect.DeepEqual(v, tc.expected) {
|
if !reflect.DeepEqual(v, testcase.expected) {
|
||||||
t.Fatalf("%q: %v != %v", tc.key, v, tc.expected)
|
t.Fatalf("%q: %v != %v", testcase.key, v, testcase.expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SingleHostReverseProxy will insert an X-Forwarded-For header, and can be used to test
|
||||||
|
// RemoteAddr(). A fake RemoteAddr cannot be set on the HTTP request - it is overwritten
|
||||||
|
// at the transport layer to 127.0.0.1:<port> . However, as the X-Forwarded-For header
|
||||||
|
// just contains the IP address, it is different enough for testing.
|
||||||
|
func TestRemoteAddr(t *testing.T) {
|
||||||
|
var expectedRemote string
|
||||||
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
if r.RemoteAddr == expectedRemote {
|
||||||
|
t.Errorf("Unexpected matching remote addresses")
|
||||||
|
}
|
||||||
|
|
||||||
|
actualRemote := RemoteAddr(r)
|
||||||
|
if expectedRemote != actualRemote {
|
||||||
|
t.Errorf("Mismatching remote hosts: %v != %v", expectedRemote, actualRemote)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(200)
|
||||||
|
}))
|
||||||
|
|
||||||
|
defer backend.Close()
|
||||||
|
backendURL, err := url.Parse(backend.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(backendURL)
|
||||||
|
frontend := httptest.NewServer(proxy)
|
||||||
|
defer frontend.Close()
|
||||||
|
|
||||||
|
// X-Forwarded-For set by proxy
|
||||||
|
expectedRemote = "127.0.0.1"
|
||||||
|
proxyReq, err := http.NewRequest("GET", frontend.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = http.DefaultClient.Do(proxyReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr in X-Real-Ip
|
||||||
|
getReq, err := http.NewRequest("GET", backend.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedRemote = "1.2.3.4"
|
||||||
|
getReq.Header["X-Real-ip"] = []string{expectedRemote}
|
||||||
|
_, err = http.DefaultClient.Do(getReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid X-Real-Ip and invalid X-Forwarded-For
|
||||||
|
getReq.Header["X-forwarded-for"] = []string{"1.2.3"}
|
||||||
|
_, err = http.DefaultClient.Do(getReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,13 @@
|
||||||
package dcontext
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
defaultLogger *logrus.Entry = logrus.StandardLogger().WithField("go.version", runtime.Version())
|
|
||||||
defaultLoggerMu sync.RWMutex
|
|
||||||
)
|
|
||||||
|
|
||||||
// Logger provides a leveled-logging interface.
|
// Logger provides a leveled-logging interface.
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
// standard logger methods
|
// standard logger methods
|
||||||
|
@ -86,18 +80,6 @@ func GetLogger(ctx context.Context, keys ...interface{}) Logger {
|
||||||
return getLogrusLogger(ctx, keys...)
|
return getLogrusLogger(ctx, keys...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaultLogger sets the default logger upon which to base new loggers.
|
|
||||||
func SetDefaultLogger(logger Logger) {
|
|
||||||
entry, ok := logger.(*logrus.Entry)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultLoggerMu.Lock()
|
|
||||||
defaultLogger = entry
|
|
||||||
defaultLoggerMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLogrusLogger returns the logrus logger for the context. If one more keys
|
// GetLogrusLogger returns the logrus logger for the context. If one more keys
|
||||||
// are provided, they will be resolved on the context and included in the
|
// are provided, they will be resolved on the context and included in the
|
||||||
// logger. Only use this function if specific logrus functionality is
|
// logger. Only use this function if specific logrus functionality is
|
||||||
|
@ -122,9 +104,9 @@ func getLogrusLogger(ctx context.Context, keys ...interface{}) *logrus.Entry {
|
||||||
fields["instance.id"] = instanceID
|
fields["instance.id"] = instanceID
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultLoggerMu.RLock()
|
fields["go.version"] = runtime.Version()
|
||||||
logger = defaultLogger.WithFields(fields)
|
// If no logger is found, just return the standard logger.
|
||||||
defaultLoggerMu.RUnlock()
|
logger = logrus.StandardLogger().WithFields(fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := logrus.Fields{}
|
fields := logrus.Fields{}
|
|
@ -1,11 +1,11 @@
|
||||||
package dcontext
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/docker/distribution/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithTrace allocates a traced timing span in a new context. This allows a
|
// WithTrace allocates a traced timing span in a new context. This allows a
|
||||||
|
@ -33,7 +33,7 @@ import (
|
||||||
// If the function ran for roughly 1s, such a usage would emit a log message
|
// If the function ran for roughly 1s, such a usage would emit a log message
|
||||||
// as follows:
|
// as follows:
|
||||||
//
|
//
|
||||||
// INFO[0001] this will be the log message trace.duration=1.004575763s trace.func=github.com/distribution/distribution/context.traceOperation trace.id=<id> ...
|
// INFO[0001] this will be the log message trace.duration=1.004575763s trace.func=github.com/docker/distribution/context.traceOperation trace.id=<id> ...
|
||||||
//
|
//
|
||||||
// Notice that the function name is automatically resolved, along with the
|
// Notice that the function name is automatically resolved, along with the
|
||||||
// package and a trace id is emitted that can be linked with parent ids.
|
// package and a trace id is emitted that can be linked with parent ids.
|
||||||
|
@ -46,7 +46,7 @@ func WithTrace(ctx context.Context) (context.Context, func(format string, a ...i
|
||||||
f := runtime.FuncForPC(pc)
|
f := runtime.FuncForPC(pc)
|
||||||
ctx = &traced{
|
ctx = &traced{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
id: uuid.NewString(),
|
id: uuid.Generate().String(),
|
||||||
start: time.Now(),
|
start: time.Now(),
|
||||||
parent: GetStringValue(ctx, "trace.id"),
|
parent: GetStringValue(ctx, "trace.id"),
|
||||||
fnname: f.Name(),
|
fnname: f.Name(),
|
|
@ -1,6 +1,7 @@
|
||||||
package dcontext
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -8,7 +9,6 @@ import (
|
||||||
|
|
||||||
// TestWithTrace ensures that tracing has the expected values in the context.
|
// TestWithTrace ensures that tracing has the expected values in the context.
|
||||||
func TestWithTrace(t *testing.T) {
|
func TestWithTrace(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
pc, file, _, _ := runtime.Caller(0) // get current caller.
|
pc, file, _, _ := runtime.Caller(0) // get current caller.
|
||||||
f := runtime.FuncForPC(pc)
|
f := runtime.FuncForPC(pc)
|
||||||
|
|
||||||
|
@ -34,31 +34,14 @@ func TestWithTrace(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, done := WithTrace(Background())
|
ctx, done := WithTrace(Background())
|
||||||
t.Cleanup(func() { done("this will be emitted at end of test") })
|
defer done("this will be emitted at end of test")
|
||||||
|
|
||||||
tests := append(base, valueTestCase{
|
checkContextForValues(ctx, t, append(base, valueTestCase{
|
||||||
key: "trace.func",
|
key: "trace.func",
|
||||||
expected: f.Name(),
|
expected: f.Name(),
|
||||||
})
|
}))
|
||||||
for _, tc := range tests {
|
|
||||||
tc := tc
|
|
||||||
t.Run(tc.key, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
v := ctx.Value(tc.key)
|
|
||||||
if tc.notnilorempty {
|
|
||||||
if v == nil || v == "" {
|
|
||||||
t.Fatalf("value was nil or empty: %#v", v)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if v != tc.expected {
|
traced := func() {
|
||||||
t.Fatalf("unexpected value: %v != %v", v, tc.expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
tracedFn := func() {
|
|
||||||
parentID := ctx.Value("trace.id") // ensure the parent trace id is correct.
|
parentID := ctx.Value("trace.id") // ensure the parent trace id is correct.
|
||||||
|
|
||||||
pc, _, _, _ := runtime.Caller(0) // get current caller.
|
pc, _, _, _ := runtime.Caller(0) // get current caller.
|
||||||
|
@ -66,32 +49,15 @@ func TestWithTrace(t *testing.T) {
|
||||||
ctx, done := WithTrace(ctx)
|
ctx, done := WithTrace(ctx)
|
||||||
defer done("this should be subordinate to the other trace")
|
defer done("this should be subordinate to the other trace")
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
tests := append(base, valueTestCase{
|
checkContextForValues(ctx, t, append(base, valueTestCase{
|
||||||
key: "trace.func",
|
key: "trace.func",
|
||||||
expected: f.Name(),
|
expected: f.Name(),
|
||||||
}, valueTestCase{
|
}, valueTestCase{
|
||||||
key: "trace.parent.id",
|
key: "trace.parent.id",
|
||||||
expected: parentID,
|
expected: parentID,
|
||||||
})
|
}))
|
||||||
for _, tc := range tests {
|
|
||||||
tc := tc
|
|
||||||
t.Run(tc.key, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
v := ctx.Value(tc.key)
|
|
||||||
if tc.notnilorempty {
|
|
||||||
if v == nil || v == "" {
|
|
||||||
t.Fatalf("value was nil or empty: %#v", v)
|
|
||||||
}
|
}
|
||||||
return
|
traced()
|
||||||
}
|
|
||||||
|
|
||||||
if v != tc.expected {
|
|
||||||
t.Fatalf("unexpected value: %v != %v", v, tc.expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tracedFn()
|
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
|
@ -101,3 +67,19 @@ type valueTestCase struct {
|
||||||
expected interface{}
|
expected interface{}
|
||||||
notnilorempty bool // just check not empty/not nil
|
notnilorempty bool // just check not empty/not nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkContextForValues(ctx context.Context, t *testing.T, values []valueTestCase) {
|
||||||
|
for _, testcase := range values {
|
||||||
|
v := ctx.Value(testcase.key)
|
||||||
|
if testcase.notnilorempty {
|
||||||
|
if v == nil || v == "" {
|
||||||
|
t.Fatalf("value was nil or empty for %q: %#v", testcase.key, v)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if v != testcase.expected {
|
||||||
|
t.Fatalf("unexpected value for key %q: %v != %v", testcase.key, v, testcase.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package dcontext
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
|
@ -1,4 +1,4 @@
|
||||||
package dcontext
|
package context
|
||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package dcontext
|
package context
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
36
contrib/apache/README.MD
Normal file
36
contrib/apache/README.MD
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Apache HTTPd sample for Registry v1, v2 and mirror
|
||||||
|
|
||||||
|
3 containers involved
|
||||||
|
|
||||||
|
* Docker Registry v1 (registry 0.9.1)
|
||||||
|
* Docker Registry v2 (registry 2.0.0)
|
||||||
|
* Docker Registry v1 in mirror mode
|
||||||
|
|
||||||
|
HTTP for mirror and HTTPS for v1 & v2
|
||||||
|
|
||||||
|
* http://registry.example.com proxify Docker Registry 1.0 in Mirror mode
|
||||||
|
* https://registry.example.com proxify Docker Registry 1.0 or 2.0 in Hosting mode
|
||||||
|
|
||||||
|
## 3 Docker containers should be started
|
||||||
|
|
||||||
|
* Docker Registry 1.0 in Mirror mode : port 5001
|
||||||
|
* Docker Registry 1.0 in Hosting mode : port 5000
|
||||||
|
* Docker Registry 2.0 in Hosting mode : port 5002
|
||||||
|
|
||||||
|
### Registry v1
|
||||||
|
|
||||||
|
docker run -d -e SETTINGS_FLAVOR=dev -v /var/lib/docker-registry/storage/hosting-v1:/tmp -p 5000:5000 registry:0.9.1"
|
||||||
|
|
||||||
|
### Mirror
|
||||||
|
|
||||||
|
docker run -d -e SETTINGS_FLAVOR=dev -e STANDALONE=false -e MIRROR_SOURCE=https://registry-1.docker.io -e MIRROR_SOURCE_INDEX=https://index.docker.io \
|
||||||
|
-e MIRROR_TAGS_CACHE_TTL=172800 -v /var/lib/docker-registry/storage/mirror:/tmp -p 5001:5000 registry:0.9.1"
|
||||||
|
|
||||||
|
### Registry v2
|
||||||
|
|
||||||
|
docker run -d -e SETTINGS_FLAVOR=dev -v /var/lib/axway/docker-registry/storage/hosting2-v2:/tmp -p 5002:5000 registry:2"
|
||||||
|
|
||||||
|
# For Hosting mode access
|
||||||
|
|
||||||
|
* users should have account (valid-user) to be able to fetch images
|
||||||
|
* only users using account docker-deployer will be allowed to push images
|
127
contrib/apache/apache.conf
Normal file
127
contrib/apache/apache.conf
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
#
|
||||||
|
# Sample Apache 2.x configuration where :
|
||||||
|
#
|
||||||
|
|
||||||
|
<VirtualHost *:80>
|
||||||
|
|
||||||
|
ServerName registry.example.com
|
||||||
|
ServerAlias www.registry.example.com
|
||||||
|
|
||||||
|
ProxyRequests off
|
||||||
|
ProxyPreserveHost on
|
||||||
|
|
||||||
|
# no proxy for /error/ (Apache HTTPd errors messages)
|
||||||
|
ProxyPass /error/ !
|
||||||
|
|
||||||
|
ProxyPass /_ping http://localhost:5001/_ping
|
||||||
|
ProxyPassReverse /_ping http://localhost:5001/_ping
|
||||||
|
|
||||||
|
ProxyPass /v1 http://localhost:5001/v1
|
||||||
|
ProxyPassReverse /v1 http://localhost:5001/v1
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/mirror_error_log
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/mirror_access_log combined env=!dontlog
|
||||||
|
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
|
||||||
|
<VirtualHost *:443>
|
||||||
|
|
||||||
|
ServerName registry.example.com
|
||||||
|
ServerAlias www.registry.example.com
|
||||||
|
|
||||||
|
SSLEngine on
|
||||||
|
SSLCertificateFile /etc/apache2/ssl/registry.example.com.crt
|
||||||
|
SSLCertificateKeyFile /etc/apache2/ssl/registry.example.com.key
|
||||||
|
|
||||||
|
# Higher Strength SSL Ciphers
|
||||||
|
SSLProtocol all -SSLv2 -SSLv3 -TLSv1
|
||||||
|
SSLCipherSuite RC4-SHA:HIGH
|
||||||
|
SSLHonorCipherOrder on
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/registry_error_ssl_log
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/registry_access_ssl_log combined env=!dontlog
|
||||||
|
|
||||||
|
Header always set "Docker-Distribution-Api-Version" "registry/2.0"
|
||||||
|
Header onsuccess set "Docker-Distribution-Api-Version" "registry/2.0"
|
||||||
|
RequestHeader set X-Forwarded-Proto "https"
|
||||||
|
|
||||||
|
ProxyRequests off
|
||||||
|
ProxyPreserveHost on
|
||||||
|
|
||||||
|
# no proxy for /error/ (Apache HTTPd errors messages)
|
||||||
|
ProxyPass /error/ !
|
||||||
|
|
||||||
|
#
|
||||||
|
# Registry v1
|
||||||
|
#
|
||||||
|
|
||||||
|
ProxyPass /v1 http://localhost:5000/v1
|
||||||
|
ProxyPassReverse /v1 http://localhost:5000/v1
|
||||||
|
|
||||||
|
ProxyPass /_ping http://localhost:5000/_ping
|
||||||
|
ProxyPassReverse /_ping http://localhost:5000/_ping
|
||||||
|
|
||||||
|
# Authentication require for push
|
||||||
|
<Location /v1>
|
||||||
|
Order deny,allow
|
||||||
|
Allow from all
|
||||||
|
AuthName "Registry Authentication"
|
||||||
|
AuthType basic
|
||||||
|
AuthUserFile "/etc/apache2/htpasswd/registry-htpasswd"
|
||||||
|
|
||||||
|
# Read access to authentified users
|
||||||
|
<Limit GET HEAD>
|
||||||
|
Require valid-user
|
||||||
|
</Limit>
|
||||||
|
|
||||||
|
# Write access to docker-deployer account only
|
||||||
|
<Limit POST PUT DELETE>
|
||||||
|
Require user docker-deployer
|
||||||
|
</Limit>
|
||||||
|
|
||||||
|
</Location>
|
||||||
|
|
||||||
|
# Allow ping to run unauthenticated.
|
||||||
|
<Location /v1/_ping>
|
||||||
|
Satisfy any
|
||||||
|
Allow from all
|
||||||
|
</Location>
|
||||||
|
|
||||||
|
# Allow ping to run unauthenticated.
|
||||||
|
<Location /_ping>
|
||||||
|
Satisfy any
|
||||||
|
Allow from all
|
||||||
|
</Location>
|
||||||
|
|
||||||
|
#
|
||||||
|
# Registry v2
|
||||||
|
#
|
||||||
|
|
||||||
|
ProxyPass /v2 http://localhost:5002/v2
|
||||||
|
ProxyPassReverse /v2 http://localhost:5002/v2
|
||||||
|
|
||||||
|
<Location /v2>
|
||||||
|
Order deny,allow
|
||||||
|
Allow from all
|
||||||
|
AuthName "Registry Authentication"
|
||||||
|
AuthType basic
|
||||||
|
AuthUserFile "/etc/apache2/htpasswd/registry-htpasswd"
|
||||||
|
|
||||||
|
# Read access to authentified users
|
||||||
|
<Limit GET HEAD>
|
||||||
|
Require valid-user
|
||||||
|
</Limit>
|
||||||
|
|
||||||
|
# Write access to docker-deployer only
|
||||||
|
<Limit POST PUT DELETE>
|
||||||
|
Require user docker-deployer
|
||||||
|
</Limit>
|
||||||
|
|
||||||
|
</Location>
|
||||||
|
|
||||||
|
|
||||||
|
</VirtualHost>
|
||||||
|
|
147
contrib/compose/README.md
Normal file
147
contrib/compose/README.md
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
# Docker Compose V1 + V2 registry
|
||||||
|
|
||||||
|
This compose configuration configures a `v1` and `v2` registry behind an `nginx`
|
||||||
|
proxy. By default, you can access the combined registry at `localhost:5000`.
|
||||||
|
|
||||||
|
The configuration does not support pushing images to `v2` and pulling from `v1`.
|
||||||
|
If a `docker` client has a version less than 1.6, Nginx will route its requests
|
||||||
|
to the 1.0 registry. Requests from newer clients will route to the 2.0 registry.
|
||||||
|
|
||||||
|
### Install Docker Compose
|
||||||
|
|
||||||
|
1. Open a new terminal on the host with your `distribution` source.
|
||||||
|
|
||||||
|
2. Get the `docker-compose` binary.
|
||||||
|
|
||||||
|
$ sudo wget https://github.com/docker/compose/releases/download/1.1.0/docker-compose-`uname -s`-`uname -m` -O /usr/local/bin/docker-compose
|
||||||
|
|
||||||
|
This command installs the binary in the `/usr/local/bin` directory.
|
||||||
|
|
||||||
|
3. Add executable permissions to the binary.
|
||||||
|
|
||||||
|
$ sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
|
||||||
|
## Build and run with Compose
|
||||||
|
|
||||||
|
1. In your terminal, navigate to the `distribution/contrib/compose` directory
|
||||||
|
|
||||||
|
This directory includes a single `docker-compose.yml` configuration.
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
build: "nginx"
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
links:
|
||||||
|
- registryv1:registryv1
|
||||||
|
- registryv2:registryv2
|
||||||
|
registryv1:
|
||||||
|
image: registry
|
||||||
|
ports:
|
||||||
|
- "5000"
|
||||||
|
registryv2:
|
||||||
|
build: "../../"
|
||||||
|
ports:
|
||||||
|
- "5000"
|
||||||
|
|
||||||
|
This configuration builds a new `nginx` image as specified by the
|
||||||
|
`nginx/Dockerfile` file. The 1.0 registry comes from Docker's official
|
||||||
|
public image. Finally, the registry 2.0 image is built from the
|
||||||
|
`distribution/Dockerfile` you've used previously.
|
||||||
|
|
||||||
|
2. Get a registry 1.0 image.
|
||||||
|
|
||||||
|
$ docker pull registry:0.9.1
|
||||||
|
|
||||||
|
The Compose configuration looks for this image locally. If you don't do this
|
||||||
|
step, later steps can fail.
|
||||||
|
|
||||||
|
3. Build `nginx`, the registry 2.0 image, and
|
||||||
|
|
||||||
|
$ docker-compose build
|
||||||
|
registryv1 uses an image, skipping
|
||||||
|
Building registryv2...
|
||||||
|
Step 0 : FROM golang:1.4
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
Removing intermediate container 9f5f5068c3f3
|
||||||
|
Step 4 : COPY docker-registry-v2.conf /etc/nginx/docker-registry-v2.conf
|
||||||
|
---> 74acc70fa106
|
||||||
|
Removing intermediate container edb84c2b40cb
|
||||||
|
Successfully built 74acc70fa106
|
||||||
|
|
||||||
|
The command outputs its progress until it completes.
|
||||||
|
|
||||||
|
4. Start your configuration with compose.
|
||||||
|
|
||||||
|
$ docker-compose up
|
||||||
|
Recreating compose_registryv1_1...
|
||||||
|
Recreating compose_registryv2_1...
|
||||||
|
Recreating compose_nginx_1...
|
||||||
|
Attaching to compose_registryv1_1, compose_registryv2_1, compose_nginx_1
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
5. In another terminal, display the running configuration.
|
||||||
|
|
||||||
|
$ docker ps
|
||||||
|
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||||
|
a81ad2557702 compose_nginx:latest "nginx -g 'daemon of 8 minutes ago Up 8 minutes 80/tcp, 443/tcp, 0.0.0.0:5000->5000/tcp compose_nginx_1
|
||||||
|
0618437450dd compose_registryv2:latest "registry cmd/regist 8 minutes ago Up 8 minutes 0.0.0.0:32777->5000/tcp compose_registryv2_1
|
||||||
|
aa82b1ed8e61 registry:latest "docker-registry" 8 minutes ago Up 8 minutes 0.0.0.0:32776->5000/tcp compose_registryv1_1
|
||||||
|
|
||||||
|
### Explore a bit
|
||||||
|
|
||||||
|
1. Check for TLS on your `nginx` server.
|
||||||
|
|
||||||
|
$ curl -v https://localhost:5000
|
||||||
|
* Rebuilt URL to: https://localhost:5000/
|
||||||
|
* Hostname was NOT found in DNS cache
|
||||||
|
* Trying 127.0.0.1...
|
||||||
|
* Connected to localhost (127.0.0.1) port 5000 (#0)
|
||||||
|
* successfully set certificate verify locations:
|
||||||
|
* CAfile: none
|
||||||
|
CApath: /etc/ssl/certs
|
||||||
|
* SSLv3, TLS handshake, Client hello (1):
|
||||||
|
* SSLv3, TLS handshake, Server hello (2):
|
||||||
|
* SSLv3, TLS handshake, CERT (11):
|
||||||
|
* SSLv3, TLS alert, Server hello (2):
|
||||||
|
* SSL certificate problem: self signed certificate
|
||||||
|
* Closing connection 0
|
||||||
|
curl: (60) SSL certificate problem: self signed certificate
|
||||||
|
More details here: http://curl.haxx.se/docs/sslcerts.html
|
||||||
|
|
||||||
|
2. Tag the `v1` registry image.
|
||||||
|
|
||||||
|
$ docker tag registry:latest localhost:5000/registry_one:latest
|
||||||
|
|
||||||
|
2. Push it to the localhost.
|
||||||
|
|
||||||
|
$ docker push localhost:5000/registry_one:latest
|
||||||
|
|
||||||
|
If you are using the 1.6 Docker client, this pushes the image the `v2 `registry.
|
||||||
|
|
||||||
|
4. Use `curl` to list the image in the registry.
|
||||||
|
|
||||||
|
$ curl -v -X GET http://localhost:5000/v2/registry_one/tags/list
|
||||||
|
* Hostname was NOT found in DNS cache
|
||||||
|
* Trying 127.0.0.1...
|
||||||
|
* Connected to localhost (127.0.0.1) port 32777 (#0)
|
||||||
|
> GET /v2/registry1/tags/list HTTP/1.1
|
||||||
|
> User-Agent: curl/7.36.0
|
||||||
|
> Host: localhost:5000
|
||||||
|
> Accept: */*
|
||||||
|
>
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
< Content-Type: application/json; charset=utf-8
|
||||||
|
< Docker-Distribution-Api-Version: registry/2.0
|
||||||
|
< Date: Tue, 14 Apr 2015 22:34:13 GMT
|
||||||
|
< Content-Length: 39
|
||||||
|
<
|
||||||
|
{"name":"registry_one","tags":["latest"]}
|
||||||
|
* Connection #0 to host localhost left intact
|
||||||
|
|
||||||
|
This example refers to the specific port assigned to the 2.0 registry. You saw
|
||||||
|
this port earlier, when you used `docker ps` to show your running containers.
|
||||||
|
|
||||||
|
|
15
contrib/compose/docker-compose.yml
Normal file
15
contrib/compose/docker-compose.yml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
nginx:
|
||||||
|
build: "nginx"
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
links:
|
||||||
|
- registryv1:registryv1
|
||||||
|
- registryv2:registryv2
|
||||||
|
registryv1:
|
||||||
|
image: registry
|
||||||
|
ports:
|
||||||
|
- "5000"
|
||||||
|
registryv2:
|
||||||
|
build: "../../"
|
||||||
|
ports:
|
||||||
|
- "5000"
|
6
contrib/compose/nginx/Dockerfile
Normal file
6
contrib/compose/nginx/Dockerfile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
FROM nginx:1.7
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY registry.conf /etc/nginx/conf.d/registry.conf
|
||||||
|
COPY docker-registry.conf /etc/nginx/docker-registry.conf
|
||||||
|
COPY docker-registry-v2.conf /etc/nginx/docker-registry-v2.conf
|
9
contrib/compose/nginx/docker-registry-v2.conf
Normal file
9
contrib/compose/nginx/docker-registry-v2.conf
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
proxy_pass http://docker-registry-v2;
|
||||||
|
proxy_set_header Host $http_host; # required for docker client's sake
|
||||||
|
proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 900;
|
||||||
|
proxy_send_timeout 300;
|
||||||
|
proxy_request_buffering off; (see issue #2292 - https://github.com/moby/moby/issues/2292)
|
||||||
|
proxy_http_version 1.1;
|
7
contrib/compose/nginx/docker-registry.conf
Normal file
7
contrib/compose/nginx/docker-registry.conf
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
proxy_pass http://docker-registry;
|
||||||
|
proxy_set_header Host $http_host; # required for docker client's sake
|
||||||
|
proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Authorization ""; # For basic auth through nginx in v1 to work, please comment this line
|
||||||
|
proxy_read_timeout 900;
|
27
contrib/compose/nginx/nginx.conf
Normal file
27
contrib/compose/nginx/nginx.conf
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
user nginx;
|
||||||
|
worker_processes 1;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
}
|
||||||
|
|
41
contrib/compose/nginx/registry.conf
Normal file
41
contrib/compose/nginx/registry.conf
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# Docker registry proxy for api versions 1 and 2
|
||||||
|
|
||||||
|
upstream docker-registry {
|
||||||
|
server registryv1:5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream docker-registry-v2 {
|
||||||
|
server registryv2:5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
# No client auth or TLS
|
||||||
|
server {
|
||||||
|
listen 5000;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# disable any limits to avoid HTTP 413 for large image uploads
|
||||||
|
client_max_body_size 0;
|
||||||
|
|
||||||
|
# required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486)
|
||||||
|
chunked_transfer_encoding on;
|
||||||
|
|
||||||
|
location /v2/ {
|
||||||
|
# Do not allow connections from docker 1.5 and earlier
|
||||||
|
# docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
|
||||||
|
if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# To add basic authentication to v2 use auth_basic setting plus add_header
|
||||||
|
# auth_basic "registry.localhost";
|
||||||
|
# auth_basic_user_file test.password;
|
||||||
|
# add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always;
|
||||||
|
|
||||||
|
include docker-registry-v2.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
include docker-registry.conf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
9
contrib/docker-integration/Dockerfile
Normal file
9
contrib/docker-integration/Dockerfile
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
FROM distribution/golem:0.1
|
||||||
|
|
||||||
|
MAINTAINER Docker Distribution Team <distribution@docker.com>
|
||||||
|
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
ENV TMPDIR /var/lib/docker/tmp
|
||||||
|
|
||||||
|
WORKDIR /go/src/github.com/docker/distribution/contrib/docker-integration
|
63
contrib/docker-integration/README.md
Normal file
63
contrib/docker-integration/README.md
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# Docker Registry Integration Testing
|
||||||
|
|
||||||
|
These integration tests cover interactions between registry clients such as
|
||||||
|
the docker daemon and the registry server. All tests can be run using the
|
||||||
|
[golem integration test runner](https://github.com/docker/golem)
|
||||||
|
|
||||||
|
The integration tests configure components using docker compose
|
||||||
|
(see docker-compose.yaml) and the runner can be using the golem
|
||||||
|
configuration file (see golem.conf).
|
||||||
|
|
||||||
|
## Running integration tests
|
||||||
|
|
||||||
|
### Run using multiversion script
|
||||||
|
|
||||||
|
The integration tests in the `contrib/docker-integration` directory can be simply
|
||||||
|
run by executing the run script `./run_multiversion.sh`. If there is no running
|
||||||
|
daemon to connect to, run as `./run_multiversion.sh -d`.
|
||||||
|
|
||||||
|
This command will build the distribution image from the locally checked out
|
||||||
|
version and run against multiple versions of docker defined in the script. To
|
||||||
|
run a specific version of the registry or docker, Golem will need to be
|
||||||
|
executed manually.
|
||||||
|
|
||||||
|
### Run manually using Golem
|
||||||
|
|
||||||
|
Using the golem tool directly allows running against multiple versions of
|
||||||
|
the registry and docker. Running against multiple versions of the registry
|
||||||
|
can be useful for testing changes in the docker daemon which are not
|
||||||
|
covered by the default run script.
|
||||||
|
|
||||||
|
#### Installing Golem
|
||||||
|
|
||||||
|
Golem is distributed as an executable binary which can be installed from
|
||||||
|
the [release page](https://github.com/docker/golem/releases/tag/v0.1).
|
||||||
|
|
||||||
|
#### Running golem with docker
|
||||||
|
|
||||||
|
Additionally golem can be run as a docker image requiring no additional
|
||||||
|
installation.
|
||||||
|
|
||||||
|
`docker run --privileged -v "$GOPATH/src/github.com/docker/distribution/contrib/docker-integration:/test" -w /test distribution/golem golem -rundaemon .`
|
||||||
|
|
||||||
|
#### Golem custom images
|
||||||
|
|
||||||
|
Golem tests version of software by defining the docker image to test.
|
||||||
|
|
||||||
|
Run with registry 2.2.1 and docker 1.10.3
|
||||||
|
|
||||||
|
`golem -i golem-dind:latest,docker:1.10.3-dind,1.10.3 -i golem-distribution:latest,registry:2.2.1 .`
|
||||||
|
|
||||||
|
|
||||||
|
#### Use golem caching for developing tests
|
||||||
|
|
||||||
|
Golem allows caching image configuration to reduce test start up time.
|
||||||
|
Using this cache will allow tests with the same set of images to start
|
||||||
|
up quickly. This can be useful when developing tests and needing the
|
||||||
|
test to run quickly. If there are changes which effect the image (such as
|
||||||
|
building a new registry image), then startup time will be slower.
|
||||||
|
|
||||||
|
Run this command multiple times and after the first time test runs
|
||||||
|
should start much quicker.
|
||||||
|
`golem -cache ~/.cache/docker/golem -i golem-dind:latest,docker:1.10.3-dind,1.10.3 -i golem-distribution:latest,registry:2.2.1 .`
|
||||||
|
|
91
contrib/docker-integration/docker-compose.yml
Normal file
91
contrib/docker-integration/docker-compose.yml
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
nginx:
|
||||||
|
build: "nginx"
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
- "5002:5002"
|
||||||
|
- "5440:5440"
|
||||||
|
- "5441:5441"
|
||||||
|
- "5442:5442"
|
||||||
|
- "5443:5443"
|
||||||
|
- "5444:5444"
|
||||||
|
- "5445:5445"
|
||||||
|
- "5446:5446"
|
||||||
|
- "5447:5447"
|
||||||
|
- "5448:5448"
|
||||||
|
- "5554:5554"
|
||||||
|
- "5555:5555"
|
||||||
|
- "5556:5556"
|
||||||
|
- "5557:5557"
|
||||||
|
- "5558:5558"
|
||||||
|
- "5559:5559"
|
||||||
|
- "5600:5600"
|
||||||
|
- "6666:6666"
|
||||||
|
links:
|
||||||
|
- registryv2:registryv2
|
||||||
|
- malevolent:malevolent
|
||||||
|
- registryv2token:registryv2token
|
||||||
|
- tokenserver:tokenserver
|
||||||
|
- registryv2tokenoauth:registryv2tokenoauth
|
||||||
|
- registryv2tokenoauthnotls:registryv2tokenoauthnotls
|
||||||
|
- tokenserveroauth:tokenserveroauth
|
||||||
|
registryv2:
|
||||||
|
image: golem-distribution:latest
|
||||||
|
ports:
|
||||||
|
- "5000"
|
||||||
|
registryv2token:
|
||||||
|
image: golem-distribution:latest
|
||||||
|
ports:
|
||||||
|
- "5000"
|
||||||
|
volumes:
|
||||||
|
- ./tokenserver/registry-config.yml:/etc/docker/registry/config.yml
|
||||||
|
- ./tokenserver/certs/localregistry.cert:/etc/docker/registry/localregistry.cert
|
||||||
|
- ./tokenserver/certs/localregistry.key:/etc/docker/registry/localregistry.key
|
||||||
|
- ./tokenserver/certs/signing.cert:/etc/docker/registry/tokenbundle.pem
|
||||||
|
tokenserver:
|
||||||
|
build: "tokenserver"
|
||||||
|
command: "--debug -addr 0.0.0.0:5556 -issuer registry-test -passwd .htpasswd -tlscert tls.cert -tlskey tls.key -key sign.key -realm http://auth.localregistry:5556"
|
||||||
|
ports:
|
||||||
|
- "5556"
|
||||||
|
registryv2tokenoauth:
|
||||||
|
image: golem-distribution:latest
|
||||||
|
ports:
|
||||||
|
- "5000"
|
||||||
|
volumes:
|
||||||
|
- ./tokenserver-oauth/registry-config.yml:/etc/docker/registry/config.yml
|
||||||
|
- ./tokenserver-oauth/certs/localregistry.cert:/etc/docker/registry/localregistry.cert
|
||||||
|
- ./tokenserver-oauth/certs/localregistry.key:/etc/docker/registry/localregistry.key
|
||||||
|
- ./tokenserver-oauth/certs/signing.cert:/etc/docker/registry/tokenbundle.pem
|
||||||
|
registryv2tokenoauthnotls:
|
||||||
|
image: golem-distribution:latest
|
||||||
|
ports:
|
||||||
|
- "5000"
|
||||||
|
volumes:
|
||||||
|
- ./tokenserver-oauth/registry-config-notls.yml:/etc/docker/registry/config.yml
|
||||||
|
- ./tokenserver-oauth/certs/signing.cert:/etc/docker/registry/tokenbundle.pem
|
||||||
|
tokenserveroauth:
|
||||||
|
build: "tokenserver-oauth"
|
||||||
|
command: "--debug -addr 0.0.0.0:5559 -issuer registry-test -passwd .htpasswd -tlscert tls.cert -tlskey tls.key -key sign.key -realm http://auth.localregistry:5559 -enforce-class"
|
||||||
|
ports:
|
||||||
|
- "5559"
|
||||||
|
malevolent:
|
||||||
|
image: "dmcgowan/malevolent:0.1.0"
|
||||||
|
command: "-l 0.0.0.0:6666 -r http://registryv2:5000 -c /certs/localregistry.cert -k /certs/localregistry.key"
|
||||||
|
links:
|
||||||
|
- registryv2:registryv2
|
||||||
|
volumes:
|
||||||
|
- ./malevolent-certs:/certs:ro
|
||||||
|
ports:
|
||||||
|
- "6666"
|
||||||
|
docker:
|
||||||
|
image: golem-dind:latest
|
||||||
|
container_name: dockerdaemon
|
||||||
|
command: "docker daemon --debug -s $DOCKER_GRAPHDRIVER"
|
||||||
|
privileged: true
|
||||||
|
environment:
|
||||||
|
DOCKER_GRAPHDRIVER:
|
||||||
|
volumes:
|
||||||
|
- /etc/generated_certs.d:/etc/docker/certs.d
|
||||||
|
- /var/lib/docker
|
||||||
|
links:
|
||||||
|
- nginx:localregistry
|
||||||
|
- nginx:auth.localregistry
|
18
contrib/docker-integration/golem.conf
Normal file
18
contrib/docker-integration/golem.conf
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[[suite]]
|
||||||
|
dind=true
|
||||||
|
images=[ "nginx:1.9", "dmcgowan/token-server:simple", "dmcgowan/token-server:oauth", "dmcgowan/malevolent:0.1.0", "dmcgowan/ncat:latest" ]
|
||||||
|
|
||||||
|
[[suite.pretest]]
|
||||||
|
command="sh ./install_certs.sh /etc/generated_certs.d"
|
||||||
|
[[suite.testrunner]]
|
||||||
|
command="bats -t ."
|
||||||
|
format="tap"
|
||||||
|
env=["TEST_REPO=hello-world", "TEST_TAG=latest", "TEST_USER=testuser", "TEST_PASSWORD=passpassword", "TEST_REGISTRY=localregistry", "TEST_SKIP_PULL=true"]
|
||||||
|
[[suite.customimage]]
|
||||||
|
tag="golem-distribution:latest"
|
||||||
|
default="registry:2.2.1"
|
||||||
|
[[suite.customimage]]
|
||||||
|
tag="golem-dind:latest"
|
||||||
|
default="docker:1.10.1-dind"
|
||||||
|
version="1.10.1"
|
||||||
|
|
127
contrib/docker-integration/helpers.bash
Normal file
127
contrib/docker-integration/helpers.bash
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
# has_digest enforces the last output line is "Digest: sha256:..."
|
||||||
|
# the input is the output from a docker push cli command
|
||||||
|
function has_digest() {
|
||||||
|
filtered=$(echo "$1" |sed -rn '/[dD]igest\: sha(256|384|512)/ p')
|
||||||
|
[ "$filtered" != "" ]
|
||||||
|
# See http://wiki.alpinelinux.org/wiki/Regex#BREs before making changes to regex
|
||||||
|
digest=$(expr "$filtered" : ".*\(sha[0-9]\{3,3\}:[a-z0-9]*\)")
|
||||||
|
}
|
||||||
|
|
||||||
|
# tempImage creates a new image using the provided name
|
||||||
|
# requires bats
|
||||||
|
function tempImage() {
|
||||||
|
dir=$(mktemp -d)
|
||||||
|
run dd if=/dev/urandom of="$dir/f" bs=1024 count=512
|
||||||
|
cat <<DockerFileContent > "$dir/Dockerfile"
|
||||||
|
FROM scratch
|
||||||
|
COPY f /f
|
||||||
|
|
||||||
|
CMD []
|
||||||
|
DockerFileContent
|
||||||
|
|
||||||
|
cp_t $dir "/tmpbuild/"
|
||||||
|
exec_t "cd /tmpbuild/; docker build --no-cache -t $1 .; rm -rf /tmpbuild/"
|
||||||
|
}
|
||||||
|
|
||||||
|
# skip basic auth tests with Docker 1.6, where they don't pass due to
|
||||||
|
# certificate issues, requires bats
|
||||||
|
function basic_auth_version_check() {
|
||||||
|
run sh -c 'docker version | fgrep -q "Client version: 1.6."'
|
||||||
|
if [ "$status" -eq 0 ]; then
|
||||||
|
skip "Basic auth tests don't support 1.6.x"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
email="a@nowhere.com"
|
||||||
|
|
||||||
|
# docker_t_login calls login with email depending on version
|
||||||
|
function docker_t_login() {
|
||||||
|
# Only pass email field pre 1.11, no deprecation warning
|
||||||
|
parse_version "$GOLEM_DIND_VERSION"
|
||||||
|
v=$version
|
||||||
|
parse_version "1.11.0"
|
||||||
|
if [ "$v" -lt "$version" ]; then
|
||||||
|
run docker_t login -e $email $@
|
||||||
|
else
|
||||||
|
run docker_t login $@
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# login issues a login to docker to the provided server
|
||||||
|
# uses user, password, and email variables set outside of function
|
||||||
|
# requies bats
|
||||||
|
function login() {
|
||||||
|
rm -f /root/.docker/config.json
|
||||||
|
|
||||||
|
docker_t_login -u $user -p $password $1
|
||||||
|
if [ "$status" -ne 0 ]; then
|
||||||
|
echo $output
|
||||||
|
fi
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
# Handle different deprecation warnings
|
||||||
|
parse_version "$GOLEM_DIND_VERSION"
|
||||||
|
v=$version
|
||||||
|
parse_version "1.11.0"
|
||||||
|
if [ "$v" -lt "$version" ]; then
|
||||||
|
# First line is WARNING about credential save or email deprecation (maybe both)
|
||||||
|
[ "${lines[2]}" = "Login Succeeded" -o "${lines[1]}" = "Login Succeeded" ]
|
||||||
|
else
|
||||||
|
[ "${lines[0]}" = "Login Succeeded" ]
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function login_oauth() {
|
||||||
|
login $@
|
||||||
|
|
||||||
|
tmpFile=$(mktemp)
|
||||||
|
get_file_t /root/.docker/config.json $tmpFile
|
||||||
|
run awk -v RS="" "/\"$1\": \\{[[:space:]]+\"auth\": \"[[:alnum:]]+\",[[:space:]]+\"identitytoken\"/ {exit 3}" $tmpFile
|
||||||
|
[ "$status" -eq 3 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_version() {
|
||||||
|
version=$(echo "$1" | cut -d '-' -f1) # Strip anything after '-'
|
||||||
|
major=$(echo "$version" | cut -d . -f1)
|
||||||
|
minor=$(echo "$version" | cut -d . -f2)
|
||||||
|
rev=$(echo "$version" | cut -d . -f3)
|
||||||
|
|
||||||
|
version=$((major * 1000 * 1000 + minor * 1000 + rev))
|
||||||
|
}
|
||||||
|
|
||||||
|
function version_check() {
|
||||||
|
name=$1
|
||||||
|
checkv=$2
|
||||||
|
minv=$3
|
||||||
|
parse_version "$checkv"
|
||||||
|
v=$version
|
||||||
|
parse_version "$minv"
|
||||||
|
if [ "$v" -lt "$version" ]; then
|
||||||
|
skip "$name version \"$checkv\" does not meet required version \"$minv\""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_file_t() {
|
||||||
|
docker cp dockerdaemon:$1 $2
|
||||||
|
}
|
||||||
|
|
||||||
|
function cp_t() {
|
||||||
|
docker cp $1 dockerdaemon:$2
|
||||||
|
}
|
||||||
|
|
||||||
|
function exec_t() {
|
||||||
|
docker exec dockerdaemon sh -c "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function docker_t() {
|
||||||
|
docker exec dockerdaemon docker $@
|
||||||
|
}
|
||||||
|
|
||||||
|
# build creates a new docker image id from another image
|
||||||
|
function build() {
|
||||||
|
docker exec -i dockerdaemon docker build --no-cache -t $1 - <<DOCKERFILE
|
||||||
|
FROM $2
|
||||||
|
MAINTAINER distribution@docker.com
|
||||||
|
DOCKERFILE
|
||||||
|
}
|
50
contrib/docker-integration/install_certs.sh
Normal file
50
contrib/docker-integration/install_certs.sh
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
hostname="localregistry"
|
||||||
|
installdir="$1"
|
||||||
|
|
||||||
|
install_ca() {
|
||||||
|
mkdir -p $1/$hostname:$2
|
||||||
|
cp ./nginx/ssl/registry-ca+ca.pem $1/$hostname:$2/ca.crt
|
||||||
|
if [ "$3" != "" ]; then
|
||||||
|
cp ./nginx/ssl/registry-$3+client-cert.pem $1/$hostname:$2/client.cert
|
||||||
|
cp ./nginx/ssl/registry-$3+client-key.pem $1/$hostname:$2/client.key
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_test_certs() {
|
||||||
|
install_ca $1 5440
|
||||||
|
install_ca $1 5441
|
||||||
|
install_ca $1 5442 ca
|
||||||
|
install_ca $1 5443 noca
|
||||||
|
install_ca $1 5444 ca
|
||||||
|
install_ca $1 5447 ca
|
||||||
|
# For test remove CA
|
||||||
|
rm $1/${hostname}:5447/ca.crt
|
||||||
|
install_ca $1 5448
|
||||||
|
install_ca $1 5600
|
||||||
|
}
|
||||||
|
|
||||||
|
install_ca_file() {
|
||||||
|
mkdir -p $2
|
||||||
|
cp $1 $2/ca.crt
|
||||||
|
}
|
||||||
|
|
||||||
|
append_ca_file() {
|
||||||
|
mkdir -p $2
|
||||||
|
cat $1 >> $2/ca.crt
|
||||||
|
}
|
||||||
|
|
||||||
|
install_test_certs $installdir
|
||||||
|
|
||||||
|
# Malevolent server
|
||||||
|
install_ca_file ./malevolent-certs/ca.pem $installdir/$hostname:6666
|
||||||
|
|
||||||
|
# Token server
|
||||||
|
install_ca_file ./tokenserver/certs/ca.pem $installdir/$hostname:5554
|
||||||
|
install_ca_file ./tokenserver/certs/ca.pem $installdir/$hostname:5555
|
||||||
|
install_ca_file ./tokenserver/certs/ca.pem $installdir/$hostname:5557
|
||||||
|
install_ca_file ./tokenserver/certs/ca.pem $installdir/$hostname:5558
|
||||||
|
append_ca_file ./tokenserver/certs/ca.pem $installdir/$hostname:5600
|
||||||
|
|
18
contrib/docker-integration/malevolent-certs/ca.pem
Normal file
18
contrib/docker-integration/malevolent-certs/ca.pem
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC+TCCAeGgAwIBAgIQJMzVQNYVNTbh36kZUytWiDANBgkqhkiG9w0BAQsFADAm
|
||||||
|
MREwDwYDVQQKEwhRdWlja1RMUzERMA8GA1UEAxMIUXVpY2tUTFMwHhcNMTgwNTIx
|
||||||
|
MjI1OTA2WhcNMjgwODI2MjI1OTA2WjAmMREwDwYDVQQKEwhRdWlja1RMUzERMA8G
|
||||||
|
A1UEAxMIUXVpY2tUTFMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCe
|
||||||
|
8rEU8xHh6BMYVRz/KhFftKSxS4dxJi2LoNN4fxzY6EgHNfBACt2MhIWaUSHf2YkF
|
||||||
|
NsS/T7qZWq23NEuIJYUUwbJRAh/iQsEhCI56eV+aJX+DGd2SQQNKdx1Pt528LNws
|
||||||
|
n8Ci8rEHTe6i2/U7n/DLqa32BWF3aShsVrchRgpizXezS7GLyFmhv0hi0zRKJgDG
|
||||||
|
JebLeqe/BUtEOsS/Oa65NQTEO/5EZBzM74+4eRo5zyp9Uvw4edmOrXRXK1fK9gP3
|
||||||
|
Fq/jz9+8b5eUd9vl0e9z/xTqMdicYZOUHuUtxM3hXAkkxcaVJqqqDe6URbJHpbaN
|
||||||
|
8Vt/p/csFXMWj3oSokvDAgMBAAGjIzAhMA4GA1UdDwEB/wQEAwICpDAPBgNVHRMB
|
||||||
|
Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCC3NiX+2Qk3WB+TRNDPCtQ7Pw+
|
||||||
|
o31SSqfF8m3fevT4mdrJqFAF4qUpDwgV9/9EkU4UBoIq03S91Dk/No0jR3VAzzRA
|
||||||
|
h3+ul/7u08JriS/ZgVediodi7H8xeCz3nvZfAwCP2ZmHzDGp39Uhc3L3WFZImZuV
|
||||||
|
fCDeSWF3c5CjJbdUuCYYFy6LwSFLPoBXZaNBL19XP9btJtjbNTm77PZJ4cELTQ+U
|
||||||
|
r5Ofw9D9mCCYrapmprw7Fw9wdE+iLL9EJCHAj7L8UYshF4+7O7Jv3ZatySMWPbjS
|
||||||
|
nIa2+eKl/sfvRvLZWV9dUSObVsm/bpv8bsHIKp4bYl+IDb2aoSWnw4eZQHDJ
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,19 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDFTCCAf2gAwIBAgIQfv/raCIVnmpXY74aUyohmDANBgkqhkiG9w0BAQsFADAm
|
||||||
|
MREwDwYDVQQKEwhRdWlja1RMUzERMA8GA1UEAxMIUXVpY2tUTFMwHhcNMTgwNTIx
|
||||||
|
MjI1OTA2WhcNMjgwODI2MjI1OTA2WjArMREwDwYDVQQKEwhRdWlja1RMUzEWMBQG
|
||||||
|
A1UEAxMNbG9jYWxyZWdpc3RyeTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||||
|
ggEBALedGn6gB0Km693mvJ8yz89wtfDs+SGjJi+XmJv0PYe6j5uToXQH2naXXIOZ
|
||||||
|
lT9lmXd/RciZwn50aK4T6alu96D8yeLE13P+75rdrI9DWTNHsfx0jwRxUEXNazPI
|
||||||
|
5Knwbf2MgGJfvHE6LjQ3FStJJ9f8JzryspIAYy5PJETuzoF7GsrUhgmcgQNqQcIx
|
||||||
|
d81QwOnW3EHastTPIbUxQ3cbEKZMVmvsYSY60pQuw/syN7vGcR/uJQ6HsCUWTEpk
|
||||||
|
LWFNJYudYnRIJ/mb6bGJ0tJhdlXKQ9+89oiEWZp9p1KMfyXesp8HeW8Jyoa06+Ri
|
||||||
|
5U82r0oQgC0MI5AueueoNOmQyGsCAwEAAaM6MDgwDgYDVR0PAQH/BAQDAgWgMAwG
|
||||||
|
A1UdEwEB/wQCMAAwGAYDVR0RBBEwD4INbG9jYWxyZWdpc3RyeTANBgkqhkiG9w0B
|
||||||
|
AQsFAAOCAQEAGgUESvQoD/QGZQlY2NA4sauad/yMHVo7vs5TLiKxnAfJrnP1ycD6
|
||||||
|
sqcbwCu6B1GU7fqGjKKgzXWXHTi4MiLi5bnh5Y2JBTABksGmzNAU1LbQJJkwsPnE
|
||||||
|
GBF0RgUmcw7a+4qu3TqPJABOsl+RiUQ4VDzP3DFRbyigs2li+SjLTJepahDhAke9
|
||||||
|
11lU/r3pm1cov9m0AsKSHrU777Hv5B7gmyJ1FO1Os7/KnkdHKUwiIZx0VW6Ho5H+
|
||||||
|
IiCH7iKJ1tTxe3nkwjlkSXnx7xiLOG7QK1LtTNHzBumF4COSF1kvWvIqNhJeg482
|
||||||
|
e38+Kzctl5iVbrB+JWY6roTQ26VLIdlS7A==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEAt50afqAHQqbr3ea8nzLPz3C18Oz5IaMmL5eYm/Q9h7qPm5Oh
|
||||||
|
dAfadpdcg5mVP2WZd39FyJnCfnRorhPpqW73oPzJ4sTXc/7vmt2sj0NZM0ex/HSP
|
||||||
|
BHFQRc1rM8jkqfBt/YyAYl+8cTouNDcVK0kn1/wnOvKykgBjLk8kRO7OgXsaytSG
|
||||||
|
CZyBA2pBwjF3zVDA6dbcQdqy1M8htTFDdxsQpkxWa+xhJjrSlC7D+zI3u8ZxH+4l
|
||||||
|
DoewJRZMSmQtYU0li51idEgn+ZvpsYnS0mF2VcpD37z2iIRZmn2nUox/Jd6ynwd5
|
||||||
|
bwnKhrTr5GLlTzavShCALQwjkC5656g06ZDIawIDAQABAoIBAQCw7oKJYkucvpyq
|
||||||
|
x50bCyuVCVdJQhEPiNdTJRG5tjFUiUG4+RmrZaXugQx1A5n97TllHQ9xrjjtAd+d
|
||||||
|
XzLaQkP8rZsdGfFDpXXeFZ4irxNVhtDMJMVr0oU3vip/TCaMW1Kh8LIGGZrMwPOk
|
||||||
|
/S849tWeGyzycMwCRL1N8pVQl44G1aexTmlt/tjpGyQAUcGt3MtKaUhhr8mLttfL
|
||||||
|
2r6wfZgvSqReURBMdn/bf+sMKnJrYnZLRv/iPz+YWhdk4v1OXPO3D4OlYwR8HwSo
|
||||||
|
a9mOpPuC6lWBqzq8eCBU474aQw4FXaFwN08YkJKa4DqUrmadnd4o+ajvOIA4MdF5
|
||||||
|
7OOsHQaBAoGBANcVQIM6vndN2MFwODGnF8RfeLhEf46VlANkZadOOa0/igyra865
|
||||||
|
7IR4dREFFkSdte8bj6/iEAPeDzXgS4TRsZfr2gkhdXuc2NW4jTVeiYfWW3cgKfW+
|
||||||
|
7BQiHXsXCDeoZ1gXq/F5RmD8ue0TkP+IclWR52AM5e1MzfAuZzaIFNJFAoGBANqL
|
||||||
|
Q925GxuDamcbuloxQUBarXPJgBDfTWUAXAJVISy80N3av45Y0gyoNjPaU7wHNtU9
|
||||||
|
ppnYvM47o1W4qe9AkTtuU79T1WwXFr5T+4Ehm5I8WDHQwkzWGd+WlWkDidLWuvlx
|
||||||
|
ZkzwQGp3KOTJhO20lpOtCbnOa627Op/zLhCBQzLvAoGAFF4A0+x2KNoIUpkL2TfX
|
||||||
|
elMIHXrvEVN8xq11KtivgYZozjZVaSgWC51UiJ4Qs8KzfccAXklr9tHKYvGwdQ1e
|
||||||
|
YeKFrSOr+l6p8eMeDBW9tE1KMAetsYW42Vc5r3RI5OxfjOoA8EbpsTl9acPWkTwc
|
||||||
|
h5nfbSsLguMpBTt/rpxITHkCgYEAnKwwSBj25P+OXULUkuoytDcNmC+Bnxbm/hyG
|
||||||
|
2ak78j2eox26LAti8m35Ba1kUCz/01myQSLPIC5DByYutXWdaHTMlyI7o5Td2i6M
|
||||||
|
5GM6i1i1hWj6kmj+/XqPvEwsFzmXq1HvnAK0u16Xs4UAxgSr2ky35zujmFXcTmTg
|
||||||
|
xjZU/YMCgYEAqF93h8WfckZxSUUMBgxTkNfu4MJlbsVBzIHv6TJY95VA49RcRYEK
|
||||||
|
b7Xg+RiNQ42QGd8JBXZ50zQrIDhdd/yJ0KcytvW7WdiEEaF3ANO2QesygmI50611
|
||||||
|
R76F8Bj0xnoQUCbyPuMOLRfTwEaS1jBG7TKWQXTaN0fm4DxUU0KazxU=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
192
contrib/docker-integration/malevolent.bats
Normal file
192
contrib/docker-integration/malevolent.bats
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
# This tests various expected error scenarios when pulling bad content
|
||||||
|
|
||||||
|
load helpers
|
||||||
|
|
||||||
|
host="localregistry:6666"
|
||||||
|
base="malevolent-test"
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
tempImage $base:latest
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test malevolent proxy pass through" {
|
||||||
|
docker_t tag $base:latest $host/$base/nochange:latest
|
||||||
|
run docker_t push $host/$base/nochange:latest
|
||||||
|
echo $output
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
|
||||||
|
run docker_t pull $host/$base/nochange:latest
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test malevolent image name change" {
|
||||||
|
imagename="$host/$base/rename"
|
||||||
|
image="$imagename:lastest"
|
||||||
|
docker_t tag $base:latest $image
|
||||||
|
run docker_t push $image
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
|
||||||
|
# Pull attempt should fail to verify manifest digest
|
||||||
|
run docker_t pull "$imagename@$digest"
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test malevolent altered layer" {
|
||||||
|
image="$host/$base/addfile:latest"
|
||||||
|
tempImage $image
|
||||||
|
run docker_t push $image
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
|
||||||
|
# Remove image to ensure layer is pulled and digest verified
|
||||||
|
docker_t rmi -f $image
|
||||||
|
|
||||||
|
run docker_t pull $image
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test malevolent altered layer (by digest)" {
|
||||||
|
imagename="$host/$base/addfile"
|
||||||
|
image="$imagename:latest"
|
||||||
|
tempImage $image
|
||||||
|
run docker_t push $image
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
|
||||||
|
# Remove image to ensure layer is pulled and digest verified
|
||||||
|
docker_t rmi -f $image
|
||||||
|
|
||||||
|
run docker_t pull "$imagename@$digest"
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test malevolent poisoned images" {
|
||||||
|
truncid="777cf9284131"
|
||||||
|
poison="${truncid}d77ca0863fb7f054c0a276d7e227b5e9a5d62b497979a481fa32"
|
||||||
|
image1="$host/$base/image1/poison:$poison"
|
||||||
|
tempImage $image1
|
||||||
|
run docker_t push $image1
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
|
||||||
|
image2="$host/$base/image2/poison:$poison"
|
||||||
|
tempImage $image2
|
||||||
|
run docker_t push $image2
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
|
||||||
|
|
||||||
|
# Remove image to ensure layer is pulled and digest verified
|
||||||
|
docker_t rmi -f $image1
|
||||||
|
docker_t rmi -f $image2
|
||||||
|
|
||||||
|
run docker_t pull $image1
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_t pull $image2
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
# Test if there are multiple images
|
||||||
|
run docker_t images
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
# Test images have same ID and not the poison
|
||||||
|
id1=$(docker_t inspect --format="{{.Id}}" $image1)
|
||||||
|
id2=$(docker_t inspect --format="{{.Id}}" $image2)
|
||||||
|
|
||||||
|
# Remove old images
|
||||||
|
docker_t rmi -f $image1
|
||||||
|
docker_t rmi -f $image2
|
||||||
|
|
||||||
|
[ "$id1" != "$id2" ]
|
||||||
|
|
||||||
|
[ "$id1" != "$truncid" ]
|
||||||
|
|
||||||
|
[ "$id2" != "$truncid" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test malevolent altered identical images" {
|
||||||
|
truncid1="777cf9284131"
|
||||||
|
poison1="${truncid1}d77ca0863fb7f054c0a276d7e227b5e9a5d62b497979a481fa32"
|
||||||
|
truncid2="888cf9284131"
|
||||||
|
poison2="${truncid2}d77ca0863fb7f054c0a276d7e227b5e9a5d62b497979a481fa64"
|
||||||
|
|
||||||
|
image1="$host/$base/image1/alteredid:$poison1"
|
||||||
|
tempImage $image1
|
||||||
|
run docker_t push $image1
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
|
||||||
|
image2="$host/$base/image2/alteredid:$poison2"
|
||||||
|
docker_t tag $image1 $image2
|
||||||
|
run docker_t push $image2
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
|
||||||
|
|
||||||
|
# Remove image to ensure layer is pulled and digest verified
|
||||||
|
docker_t rmi -f $image1
|
||||||
|
docker_t rmi -f $image2
|
||||||
|
|
||||||
|
run docker_t pull $image1
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run docker_t pull $image2
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
# Test if there are multiple images
|
||||||
|
run docker_t images
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
# Test images have same ID and not the poison
|
||||||
|
id1=$(docker_t inspect --format="{{.Id}}" $image1)
|
||||||
|
id2=$(docker_t inspect --format="{{.Id}}" $image2)
|
||||||
|
|
||||||
|
# Remove old images
|
||||||
|
docker_t rmi -f $image1
|
||||||
|
docker_t rmi -f $image2
|
||||||
|
|
||||||
|
[ "$id1" == "$id2" ]
|
||||||
|
|
||||||
|
[ "$id1" != "$truncid1" ]
|
||||||
|
|
||||||
|
[ "$id2" != "$truncid2" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test malevolent resumeable pull" {
|
||||||
|
version_check docker "$GOLEM_DIND_VERSION" "1.11.0"
|
||||||
|
version_check registry "$GOLEM_DISTRIBUTION_VERSION" "2.3.0"
|
||||||
|
|
||||||
|
imagename="$host/$base/resumeable"
|
||||||
|
image="$imagename:latest"
|
||||||
|
tempImage $image
|
||||||
|
run docker_t push $image
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
|
||||||
|
# Remove image to ensure layer is pulled and digest verified
|
||||||
|
docker_t rmi -f $image
|
||||||
|
|
||||||
|
run docker_t pull "$imagename@$digest"
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
10
contrib/docker-integration/nginx/Dockerfile
Normal file
10
contrib/docker-integration/nginx/Dockerfile
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
FROM nginx:1.9
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY registry.conf /etc/nginx/conf.d/registry.conf
|
||||||
|
COPY docker-registry-v2.conf /etc/nginx/docker-registry-v2.conf
|
||||||
|
COPY registry-noauth.conf /etc/nginx/registry-noauth.conf
|
||||||
|
COPY registry-basic.conf /etc/nginx/registry-basic.conf
|
||||||
|
COPY test.passwd /etc/nginx/test.passwd
|
||||||
|
COPY ssl /etc/nginx/ssl
|
||||||
|
COPY v1 /var/www/html/v1
|
6
contrib/docker-integration/nginx/docker-registry-v2.conf
Normal file
6
contrib/docker-integration/nginx/docker-registry-v2.conf
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
proxy_pass http://docker-registry-v2;
|
||||||
|
proxy_set_header Host $http_host; # required for docker client's sake
|
||||||
|
proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 900;
|
61
contrib/docker-integration/nginx/nginx.conf
Normal file
61
contrib/docker-integration/nginx/nginx.conf
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
user nginx;
|
||||||
|
worker_processes 1;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup TCP proxies
|
||||||
|
stream {
|
||||||
|
# Malevolent proxy
|
||||||
|
server {
|
||||||
|
listen 6666;
|
||||||
|
proxy_pass malevolent:6666;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Registry configured for token server
|
||||||
|
server {
|
||||||
|
listen 5554;
|
||||||
|
listen 5555;
|
||||||
|
proxy_pass registryv2token:5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Token server
|
||||||
|
server {
|
||||||
|
listen 5556;
|
||||||
|
proxy_pass tokenserver:5556;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Registry configured for token server with oauth
|
||||||
|
server {
|
||||||
|
listen 5557;
|
||||||
|
listen 5558;
|
||||||
|
proxy_pass registryv2tokenoauth:5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Token server with oauth
|
||||||
|
server {
|
||||||
|
listen 5559;
|
||||||
|
proxy_pass tokenserveroauth:5559;
|
||||||
|
}
|
||||||
|
}
|
8
contrib/docker-integration/nginx/registry-basic.conf
Normal file
8
contrib/docker-integration/nginx/registry-basic.conf
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
client_max_body_size 0;
|
||||||
|
chunked_transfer_encoding on;
|
||||||
|
location /v2/ {
|
||||||
|
auth_basic "registry.localhost";
|
||||||
|
auth_basic_user_file test.passwd;
|
||||||
|
add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always;
|
||||||
|
include docker-registry-v2.conf;
|
||||||
|
}
|
5
contrib/docker-integration/nginx/registry-noauth.conf
Normal file
5
contrib/docker-integration/nginx/registry-noauth.conf
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
client_max_body_size 0;
|
||||||
|
chunked_transfer_encoding on;
|
||||||
|
location /v2/ {
|
||||||
|
include docker-registry-v2.conf;
|
||||||
|
}
|
260
contrib/docker-integration/nginx/registry.conf
Normal file
260
contrib/docker-integration/nginx/registry.conf
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
# Docker registry proxy for api version 2
|
||||||
|
|
||||||
|
upstream docker-registry-v2 {
|
||||||
|
server registryv2:5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
# No client auth or TLS
|
||||||
|
server {
|
||||||
|
listen 5000;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# disable any limits to avoid HTTP 413 for large image uploads
|
||||||
|
client_max_body_size 0;
|
||||||
|
|
||||||
|
# required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486)
|
||||||
|
chunked_transfer_encoding on;
|
||||||
|
|
||||||
|
location /v2/ {
|
||||||
|
# Do not allow connections from docker 1.5 and earlier
|
||||||
|
# docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
|
||||||
|
if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
include docker-registry-v2.conf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# No client auth or TLS (V2 Only)
|
||||||
|
server {
|
||||||
|
listen 5002;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# disable any limits to avoid HTTP 413 for large image uploads
|
||||||
|
client_max_body_size 0;
|
||||||
|
|
||||||
|
# required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486)
|
||||||
|
chunked_transfer_encoding on;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
include docker-registry-v2.conf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# TLS Configuration chart
|
||||||
|
# Username/Password: testuser/passpassword
|
||||||
|
# | ca | client | basic | notes
|
||||||
|
# 5440 | yes | no | no | Tests CA certificate
|
||||||
|
# 5441 | yes | no | yes | Tests basic auth over TLS
|
||||||
|
# 5442 | yes | yes | no | Tests client auth with client CA
|
||||||
|
# 5443 | yes | yes | no | Tests client auth without client CA
|
||||||
|
# 5444 | yes | yes | yes | Tests using basic auth + tls auth
|
||||||
|
# 5445 | no | no | no | Tests insecure using TLS
|
||||||
|
# 5446 | no | no | yes | Tests sending credentials to server with insecure TLS
|
||||||
|
# 5447 | no | yes | no | Tests client auth to insecure
|
||||||
|
# 5448 | yes | no | no | Bad SSL version
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5440;
|
||||||
|
server_name localhost;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-ca+localhost-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-ca+localhost-key.pem;
|
||||||
|
include registry-noauth.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5441;
|
||||||
|
server_name localhost;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-ca+localhost-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-ca+localhost-key.pem;
|
||||||
|
include registry-basic.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5442;
|
||||||
|
listen 5443;
|
||||||
|
server_name localhost;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-ca+localhost-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-ca+localhost-key.pem;
|
||||||
|
ssl_client_certificate /etc/nginx/ssl/registry-ca+ca.pem;
|
||||||
|
ssl_verify_client on;
|
||||||
|
include registry-noauth.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5444;
|
||||||
|
server_name localhost;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-ca+localhost-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-ca+localhost-key.pem;
|
||||||
|
ssl_client_certificate /etc/nginx/ssl/registry-ca+ca.pem;
|
||||||
|
ssl_verify_client on;
|
||||||
|
include registry-basic.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5445;
|
||||||
|
server_name localhost;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-noca+localhost-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-noca+localhost-key.pem;
|
||||||
|
include registry-noauth.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5446;
|
||||||
|
server_name localhost;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-noca+localhost-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-noca+localhost-key.pem;
|
||||||
|
include registry-basic.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5447;
|
||||||
|
server_name localhost;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-noca+localhost-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-noca+localhost-key.pem;
|
||||||
|
ssl_client_certificate /etc/nginx/ssl/registry-ca+ca.pem;
|
||||||
|
ssl_verify_client on;
|
||||||
|
include registry-noauth.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5448;
|
||||||
|
server_name localhost;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-ca+localhost-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-ca+localhost-key.pem;
|
||||||
|
ssl_protocols SSLv3;
|
||||||
|
include registry-noauth.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add configuration for localregistry server_name
|
||||||
|
# Requires configuring /etc/hosts to use
|
||||||
|
# Set /etc/hosts entry to external IP, not 127.0.0.1 for testing
|
||||||
|
# Docker secure/insecure registry features
|
||||||
|
server {
|
||||||
|
listen 5440;
|
||||||
|
server_name localregistry;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-ca+localregistry-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-ca+localregistry-key.pem;
|
||||||
|
include registry-noauth.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5441;
|
||||||
|
server_name localregistry;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-ca+localregistry-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-ca+localregistry-key.pem;
|
||||||
|
include registry-basic.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5442;
|
||||||
|
listen 5443;
|
||||||
|
server_name localregistry;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-ca+localregistry-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-ca+localregistry-key.pem;
|
||||||
|
ssl_client_certificate /etc/nginx/ssl/registry-ca+ca.pem;
|
||||||
|
ssl_verify_client on;
|
||||||
|
include registry-noauth.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5444;
|
||||||
|
server_name localregistry;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-ca+localregistry-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-ca+localregistry-key.pem;
|
||||||
|
ssl_client_certificate /etc/nginx/ssl/registry-ca+ca.pem;
|
||||||
|
ssl_verify_client on;
|
||||||
|
include registry-basic.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5445;
|
||||||
|
server_name localregistry;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-noca+localregistry-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-noca+localregistry-key.pem;
|
||||||
|
include registry-noauth.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5446;
|
||||||
|
server_name localregistry;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-noca+localregistry-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-noca+localregistry-key.pem;
|
||||||
|
include registry-basic.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5447;
|
||||||
|
server_name localregistry;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-noca+localregistry-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-noca+localregistry-key.pem;
|
||||||
|
ssl_client_certificate /etc/nginx/ssl/registry-ca+ca.pem;
|
||||||
|
ssl_verify_client on;
|
||||||
|
include registry-noauth.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5448;
|
||||||
|
server_name localregistry;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-ca+localregistry-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-ca+localregistry-key.pem;
|
||||||
|
ssl_protocols SSLv3;
|
||||||
|
include registry-noauth.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# V1 search test
|
||||||
|
# Registry configured with token auth and no tls
|
||||||
|
# TLS termination done by nginx, search results
|
||||||
|
# served by nginx
|
||||||
|
|
||||||
|
upstream docker-registry-v2-oauth {
|
||||||
|
server registryv2tokenoauthnotls:5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5600;
|
||||||
|
server_name localregistry;
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/nginx/ssl/registry-ca+localregistry-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/registry-ca+localregistry-key.pem;
|
||||||
|
|
||||||
|
root /var/www/html;
|
||||||
|
|
||||||
|
client_max_body_size 0;
|
||||||
|
chunked_transfer_encoding on;
|
||||||
|
location /v2/ {
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_pass http://docker-registry-v2-oauth;
|
||||||
|
proxy_set_header Host $http_host; # required for docker client's sake
|
||||||
|
proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /v1/search {
|
||||||
|
if ($http_authorization !~ "Bearer [a-zA-Z0-9\._-]+") {
|
||||||
|
return 401;
|
||||||
|
}
|
||||||
|
try_files /v1/search.json =404;
|
||||||
|
add_header Content-Type application/json;
|
||||||
|
}
|
||||||
|
}
|
18
contrib/docker-integration/nginx/ssl/registry-ca+ca.pem
Normal file
18
contrib/docker-integration/nginx/ssl/registry-ca+ca.pem
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC+TCCAeGgAwIBAgIQVhmtXJ4fG4BkISUkyZ65ITANBgkqhkiG9w0BAQsFADAm
|
||||||
|
MREwDwYDVQQKEwhRdWlja1RMUzERMA8GA1UEAxMIUXVpY2tUTFMwHhcNMTgwNTIx
|
||||||
|
MjI1MjMwWhcNMjgwODI2MjI1MjMwWjAmMREwDwYDVQQKEwhRdWlja1RMUzERMA8G
|
||||||
|
A1UEAxMIUXVpY2tUTFMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDK
|
||||||
|
J/SLv0dL7UXaNSEAdTMV8+rOFMcQNov/xLWa1mO+7zNZXHIdM+i1uQTHTdhuta6R
|
||||||
|
wfqkruPMZ9sqK7G9UIPi11ynkdTiZKRCvCr2VMc/uf5WuIsZE1JXXknSNee1TMmV
|
||||||
|
Je8TUJsRjEyQDbxn5qUAJLi8yj/O7W8wsnVHdySKMbaLN6v75151TxiIuOoncCHQ
|
||||||
|
yzz10DzjXfXYajuheu+MLy/rjNGDj0gys4yQZAHlQWY9Lsiiix9rBdXQjVc3q2QT
|
||||||
|
VM5v3pMjXcPweaIbTWJnbOgmy+267kX6kQpUfZRE55dQt6mPtPQ2idPvqPP3TXwa
|
||||||
|
AFH39cz/pPifIZApDfZFAgMBAAGjIzAhMA4GA1UdDwEB/wQEAwICpDAPBgNVHRMB
|
||||||
|
Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB93GckXcLcfNdg9C0xMkvByPQJ
|
||||||
|
dcy0GT991eZ/bNC39AXrmCSfn6a1FRlWoiCOSOW1NIZWQQ7jDep/T585vq2jN7KX
|
||||||
|
hT/z3iIdNWR+Amvo4pyJ93u2D3uG/bmmguAr62jyIgrJudQ3+Mnd+bj/J33XzAgc
|
||||||
|
d4ZGPvCmKtn8cTKzyS8rjy1oPSUm6pZnfk41MgMWrGuS5HkC3Aa7jo/4RdgGOJpm
|
||||||
|
nUdz2FGfW/+gwXRy2e94V7ijjz+YwpzL0wHPyXyAm7GwJ7mfvPOZrQOLLw4Z9OaK
|
||||||
|
R76t4NZBo5TmtvW5zQVsv3sPRnuqcmR0q6WR/fEuMafVtRVOVuDrZlSy0EtA
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,18 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC+TCCAeGgAwIBAgIRAMGmTKEnobjz4ymIziTsFuMwDQYJKoZIhvcNAQELBQAw
|
||||||
|
JjERMA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE4MDUy
|
||||||
|
MTIyNTIzMVoXDTI4MDgyNjIyNTIzMVowEzERMA8GA1UEChMIUXVpY2tUTFMwggEi
|
||||||
|
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCaFrwVi+BAvng9TebwOLg2Juzg
|
||||||
|
wnW2Lv2EOqpSYmlZLLub46/W+ktqrcb+nBMBwnbON0woCbMArONuiRk7BATnmLH8
|
||||||
|
1e6I9Rax1nCaEpKhhH/b3T9PjwvzrXC+NIqeC46E7AEneAdBa4L/x27F/npLJy7X
|
||||||
|
PAwcH9ImvACJ9csIObjFnGXNTwtGA2SMIOCiNv3lpyb/Yx20EqBcj+etz8XBjAIS
|
||||||
|
46z0JDAtYAbJgIs7Ek2XQSrUud18jopzK9mrT9YvA4tDu9Woj70IXdZfOeb0W6Y+
|
||||||
|
aBbEoHvqFtyeG7BStNszM7n6CTcJAqpHOMlYQPeRjtMwb2Ffw86NvxkfrjoNAgMB
|
||||||
|
AAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDAjAMBgNV
|
||||||
|
HRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQBv1MfAYTymtDeA62N86QFOwASq
|
||||||
|
ah2BQqfHvUzcM0U/H6YDEYUEKX2RFOtGwOwSBXr6v7JmU4KuE6tA6s+XWjD/lLr7
|
||||||
|
CqWvJfZNP6zARL+MqbZjSmyymtuXaXH4eNVgN0aaGifhUSMDsg0qyZwG8isMN4hG
|
||||||
|
kd0y1nNCn+Q3V7oe3NfjfdjviLU9PNNBQFbKRJJRQ6y267lFoWwlaHwtNyvDupVi
|
||||||
|
f+JFMiuG3o+upqBF8UFUV8Of4VL6UcJI0OoF4ngTFzn3gRYaYKmkYawUmIr9vvg7
|
||||||
|
oQccajcN1iNArnZwXK3lKSERybrUEiUZ4uZ69wVlXzE2TemhW1iYfrTU1cya
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEAmha8FYvgQL54PU3m8Di4Nibs4MJ1ti79hDqqUmJpWSy7m+Ov
|
||||||
|
1vpLaq3G/pwTAcJ2zjdMKAmzAKzjbokZOwQE55ix/NXuiPUWsdZwmhKSoYR/290/
|
||||||
|
T48L861wvjSKnguOhOwBJ3gHQWuC/8duxf56Sycu1zwMHB/SJrwAifXLCDm4xZxl
|
||||||
|
zU8LRgNkjCDgojb95acm/2MdtBKgXI/nrc/FwYwCEuOs9CQwLWAGyYCLOxJNl0Eq
|
||||||
|
1LndfI6KcyvZq0/WLwOLQ7vVqI+9CF3WXznm9FumPmgWxKB76hbcnhuwUrTbMzO5
|
||||||
|
+gk3CQKqRzjJWED3kY7TMG9hX8POjb8ZH646DQIDAQABAoIBAE2SfnOWbHoLqXqr
|
||||||
|
WkS7OTnB1OS94Qarl2NXKWG6O3DyTSyIroBal1cITzLkncj3/lmIiyVo5J3Fa+W8
|
||||||
|
zV/hgRqay5gOlzyJrjgvTZazHPCFRN0KABJsYEb3nNeUmehAxynxqg8VpQlxN4zO
|
||||||
|
+NxiZWyqODGRAEO0XVa0tMy/Wcw0guD18+U9GYiYQi3d7NEHTt5d8CX9VKY/bHKR
|
||||||
|
+ecC/lr7URnA/8FM60mKI6MAiHPxyUjJ7/6dq1goG8dDHcAtOEEIawECQtRfQ+Dn
|
||||||
|
RL55nDPRYNviXRgr8u61TFm8zgkTUQy2MLRkHAyP0IBLUiMpqDdmXB4LNMQQSrsY
|
||||||
|
0FyinIECgYEAy3eT5ZUb/ijGsWUT/DizUoetFfg8X4LV+HRLXdlxfcOYB3Elbeks
|
||||||
|
JPC+Tdm33nB0lqo3hLVNPB9yqJiPOOaWQPpWBImOeitpmDRAagjwUewJwLY9RmKT
|
||||||
|
RD0+YyCC0SwvSDFDsWF+ncW/8XpobvetCSC6mmjX6Wr070yHkhDeeC0CgYEAwd9v
|
||||||
|
P+TjgWVyL5YRiOJ+wjR7ZKpHCiSSxSTjIhq40hs5LtHddSk9e/+AU0otcMExzCqN
|
||||||
|
E4f/e05a6TD5CFAgmUMK7nb49ept3ENVoD+M13K3tTaTyeZghwYNNK56osDtdCgc
|
||||||
|
c68jQAy81gU7iRt30xbLVV6HgGdrSrWN8D8DFWECgYABkV1RYpHBppzJVycNRX6U
|
||||||
|
PzllNvF4JvDxJixCf99xAaXVQNjx/N77NeOxg+D31NQBKTSeUCtVMETY6bwIyzYT
|
||||||
|
MBqjlE/FvznkE1r/tivr5a65jm3wcegCmZo2d1SqufVvT/nejwrDunddK/1MBZqO
|
||||||
|
vHLTp8UqJknW4jcVOA4OzQKBgG7BdozJ9i62BcWptdq9iizoTpXzsSHaQv7dU+Tn
|
||||||
|
3y4o30IgIqQMK1PrYyQx/EOuGwTISlAeIZYP7V/K2nolTHpCEryouxHCG4D59rDV
|
||||||
|
nWB36PtdcpClS//XNTQjeWwBS6ZQQ/DS3RB6NmcOFjT9vDabjw32MvLoIiNMFQpq
|
||||||
|
9RgBAoGARQnQ94oH98m/iheJpzaM9NhQhAoXSi4w19FySCtnyZTYTd0A7hjRzsSl
|
||||||
|
DeoAkIGDHyy33RPK/kPtA6dxM/DQ00IkkwH4soaDDbnCmagdw4NnY8eA1Y/KSbd+
|
||||||
|
XNNm+sDafoVyCojtsTA7bripKB8q5vPYo3qRLfQ7dwMeRPYblPI=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,19 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDDTCCAfWgAwIBAgIQfzdVwVz4igfdJPd6SW/ENTANBgkqhkiG9w0BAQsFADAm
|
||||||
|
MREwDwYDVQQKEwhRdWlja1RMUzERMA8GA1UEAxMIUXVpY2tUTFMwHhcNMTgwNTIx
|
||||||
|
MjI1MjMwWhcNMjgwODI2MjI1MjMwWjAnMREwDwYDVQQKEwhRdWlja1RMUzESMBAG
|
||||||
|
A1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
||||||
|
v+H3BTOGLRYjyPx+JQQcP5r8HHBmjknflE6VcrbRD5VGx8192hwsjAdlL0kz1CEq
|
||||||
|
FW2KQidJieDi8iIh9BWB8lsTQ51xZGnry6CbVXxTbv1Ss8ci9r8Cm3GPjWy5gqTi
|
||||||
|
DTUUQez8xq29gUod4ZvRoJ8jl/eI7gF7MBFakv7tZQ40SHcogjQoG7nKMXG1VOhX
|
||||||
|
D4kM120E+hW9x0U3j0SaCIYl6bG2RHIvUMlrVnj4es6JBVzqItkhAwugE6ytneOh
|
||||||
|
VxWQ/7e8qKW2+lVsPnH/zjNES0j/9XYgVCjwkgirxjs2eZRIS5Mg14DdYqfQ9MRQ
|
||||||
|
EoyQxl3xcDxjqPocMgGYHwIDAQABozYwNDAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0T
|
||||||
|
AQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEB
|
||||||
|
ACU0E2BAdqjVvO06ZyHplxxQ4TQxK9voBCTheC2G7oFaM4VLFf48GgoMkvbsMGyd
|
||||||
|
1JqIACCDuSJ5UVjmWm6VIDZrnRsf/BbQCTZXKQd4ONLL5DU/OPjAFKGeCpAK51yj
|
||||||
|
OMHdw3cQmMCEpMH9HHJ+iB3XWLcDKPAxTkcsBytC9VLUgU7Q4+3eYIT/j/ug+y4G
|
||||||
|
W4A0cmdDDuozwBAPXj7ZLKdVlkUFka8WjQAJesHTIifS1bfahGiSNVJbYjXbGoML
|
||||||
|
d0IeGMd1lXlc2M+ygqZsSM2ErzndNdvDs7S6u/FIICm7uW6P2naPeMtedb2orO6Q
|
||||||
|
5O3gRtj/UQjegI0XV4YO2TQ=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAv+H3BTOGLRYjyPx+JQQcP5r8HHBmjknflE6VcrbRD5VGx819
|
||||||
|
2hwsjAdlL0kz1CEqFW2KQidJieDi8iIh9BWB8lsTQ51xZGnry6CbVXxTbv1Ss8ci
|
||||||
|
9r8Cm3GPjWy5gqTiDTUUQez8xq29gUod4ZvRoJ8jl/eI7gF7MBFakv7tZQ40SHco
|
||||||
|
gjQoG7nKMXG1VOhXD4kM120E+hW9x0U3j0SaCIYl6bG2RHIvUMlrVnj4es6JBVzq
|
||||||
|
ItkhAwugE6ytneOhVxWQ/7e8qKW2+lVsPnH/zjNES0j/9XYgVCjwkgirxjs2eZRI
|
||||||
|
S5Mg14DdYqfQ9MRQEoyQxl3xcDxjqPocMgGYHwIDAQABAoIBABbp0ueqGXG03R0Z
|
||||||
|
Ga8t6Hmn9kcnHPgM1kgNgkcqkZh8yPD/FvI+vwsRrwGQikHgm/fnFsWDj4KJelBT
|
||||||
|
xx4wm03nlktSt8G37FJqoWH58LSmR4P0WbaBZLxPOUc4Hob9TYkqN3sP47eN871G
|
||||||
|
rn7MbqHxnvx8sLtLLfy1dc1r58lTTZB7YL1OPV7B/VYhYFDtpkUBvadV+WJ7SJ5G
|
||||||
|
UHrBsshOUJbUI4ahmc8izi40yDw+A0LRhtj3i7aFr2Og+vCq9M8NXDjhdOu9VBkI
|
||||||
|
fvniC6worJk/GnQDJ/KT5Uqfejdd3Pq7eHp11riqwua8+/wi726zRz9perFh/3gJ
|
||||||
|
pYjaY+ECgYEA+ssW+vJRZNHEzdf8zzIJxHqq9tOjbQK9yyIPQP5O4q9zKvDJIpnX
|
||||||
|
T31aZTLGy0op+XA9GJ7X0/d1tqo3G2wNBsFYWPn3gmVVth/7iHxRznorNfmsuea7
|
||||||
|
1gFm19StL2+q8PaZ4fx9vUcWwDHlALYTYlTaazms6z9FWD/KbB8kiWkCgYEAw93H
|
||||||
|
Pp12ND3f6p2rYbXPfHJ0aAUbrQR4wRG3ipVWXGjvn2h/CbrLAt5W1wB3iwnWwatX
|
||||||
|
opdbfzjxgb0wRQHSPNVj3/SOHr8E5zH/mw+eV7mOea4xlCLTSIAJNzW1320hwsbw
|
||||||
|
FrEC5qe41PrbMUu+4LvXPkHCKVxRXaV4QX4YHEcCgYEAurjegTRM+X1cw81dwn4E
|
||||||
|
265g/6iO8qip2kWficpNvWTXoE7p0cMslVhFJzdo3w52teqk8mHBW2XQ1JFiuh32
|
||||||
|
jOMC/iwN5Z3A9PpW8kVtOwemiGc9/KMXkrw0b9k+oCTJ5uITrDeq/nOhMrNzRtZJ
|
||||||
|
FFsMy+yDHBtda9kCwwFk2JECgYBQUpbu+qwK6IT3NgmeXGzmYBmUvuOGpJrQsm9O
|
||||||
|
iceMxgvel3/hgZTXbE64hRyBDFvhuF6L8v42widoSSmOYxzQjcITibruqO9d0Ic+
|
||||||
|
E72fxBzFkcYLNezngnpFBeW75ok900+KPrUt2gJWdTmGkcWJa/7tLRJu28kSWlVi
|
||||||
|
pk9E6QKBgDH2Uh61ToeNq8Gbnue3pnhUddHELRFQfwHHaa4tFrXBHuPLKqkVefKT
|
||||||
|
A58awVoPpKTECROeyqe2DJXg9EdSVzKyhg217N/07NRaunfCJ9/TSpFy+5Xls7Rl
|
||||||
|
U7zK25S1/13KZ6rGVHpmP6Q82VSnsHkPtUfDo3A29llqIQ8je43Y
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,19 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDFTCCAf2gAwIBAgIQM3khHYh+82EC0qR1Pelk2DANBgkqhkiG9w0BAQsFADAm
|
||||||
|
MREwDwYDVQQKEwhRdWlja1RMUzERMA8GA1UEAxMIUXVpY2tUTFMwHhcNMTgwNTIx
|
||||||
|
MjI1MjMxWhcNMjgwODI2MjI1MjMxWjArMREwDwYDVQQKEwhRdWlja1RMUzEWMBQG
|
||||||
|
A1UEAxMNbG9jYWxyZWdpc3RyeTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||||
|
ggEBAKA8e9cUSyasRtEHw3yGW5lFCnnZIN+SSvykAOynt9LLKzU5G5ge3ekBtzsl
|
||||||
|
HE1ndeYjy/dK7XkECQBQ0csF+KSacU5QiZek8g6btH94HDwltCq1I8f1E8LQFP6k
|
||||||
|
483MKZUDeNNnHzbuK9xsMjYOCrJWGysLHnKjzK/+yfVPwTm9tmUVRqd4xjw1oYY6
|
||||||
|
C7iCffIWn7+dQKDjHrn+KyheIy244v5y63AaxgPfjHrtvJtz1vPqxi+FyzDM7RfZ
|
||||||
|
GIjklC6KaKHmxvUsB0hO4WNb9kt8FBvnxOxuDKf+rUYKTg6JK72O3TaUauiEvE2X
|
||||||
|
SKT0vYpLoep5hc9ns/yh3BuuznECAwEAAaM6MDgwDgYDVR0PAQH/BAQDAgWgMAwG
|
||||||
|
A1UdEwEB/wQCMAAwGAYDVR0RBBEwD4INbG9jYWxyZWdpc3RyeTANBgkqhkiG9w0B
|
||||||
|
AQsFAAOCAQEAMt/lnR3Wy99X/knvjtg7wsPz5T9sZ5hGy/9sIm8sFdsqt5NZi9IY
|
||||||
|
vS+eyij1yHvOU+pqOxsYQ2NG26CS0CKM3JWLJTo/w8GyiSwxL8a1/UxHmTxDnSMH
|
||||||
|
cYZRsuPtdkTiAuZhoT5I1ZTsOa7MQF25HiFBL6Ei88FFhcQQjJ7+xYDNhSoddMtz
|
||||||
|
U8mUY6NOENmvE86QMjWjaj1PXPLO8PxPIqw482Ln/95pHzuaxAYMvxhs2aQlBS1/
|
||||||
|
9+vi6VOkbQna9+crmzniXjZDx5QdvMN2QwzFL4hCgqbebVg0zwjhByOwQIjtNEXE
|
||||||
|
gqxjLkTNOdSva6Fkk/z8BD2XSZ4L+nM3Mw==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEAoDx71xRLJqxG0QfDfIZbmUUKedkg35JK/KQA7Ke30ssrNTkb
|
||||||
|
mB7d6QG3OyUcTWd15iPL90rteQQJAFDRywX4pJpxTlCJl6TyDpu0f3gcPCW0KrUj
|
||||||
|
x/UTwtAU/qTjzcwplQN402cfNu4r3GwyNg4KslYbKwsecqPMr/7J9U/BOb22ZRVG
|
||||||
|
p3jGPDWhhjoLuIJ98hafv51AoOMeuf4rKF4jLbji/nLrcBrGA9+Meu28m3PW8+rG
|
||||||
|
L4XLMMztF9kYiOSULopooebG9SwHSE7hY1v2S3wUG+fE7G4Mp/6tRgpODokrvY7d
|
||||||
|
NpRq6IS8TZdIpPS9ikuh6nmFz2ez/KHcG67OcQIDAQABAoIBABNXmb9ZtMSjUR0U
|
||||||
|
adWTRmVW/y+8NQqn1yNuDKqEiF0Kp1mSXjFbsH/a9CpQjX0Oex3fvlRImCfeg9Ok
|
||||||
|
7d4rB1ufRQQmFqXWhF2dEAm/DvF3v6rUGNCfVdZTVeVzNAh4l6BkPeaO8SapU2QV
|
||||||
|
L250/XePi1ID0pYWDbRE9k4FZZa5je3mTctn3s1PHp6xxQdyDHfxZmCZImwZcErj
|
||||||
|
joBoQldvUUfjqXCY9SgRJ/MQSNeJoJvPwXmYokpqxfv2sP+JlQgXEcO3Ihj9IkGx
|
||||||
|
avMFR3yGdWWLxmE3zzypXvFI+My0E035fEjcObspVOgqxJJUCWLSwWtVAo9shFgO
|
||||||
|
fPnfv70CgYEAxqVNQ4eEf8HRDN7Ygr9yruqN5NxXKJKBqOT+OlTAiCtrm6iRFkR/
|
||||||
|
WOFA3Ewjk5dxnVBvXHhTZoS2yfIVj8Pz7wbcoigfT+ia4JcAW8xQTs1CV/Xz8JsN
|
||||||
|
ChUH3ee2POue/AAxf25yDBGH3fKq34aqL9WNDmaUz+hDCo4r3/hfVZ8CgYEAzoAv
|
||||||
|
tBxwE/VUwkmWzv40WI9J4GSh7lYo4d8Z2TR6FRSxgb0Uf3C3GiGKuLf9EMilL3ae
|
||||||
|
i/Dsb0CVn2sfLdSNFlxj1l8V4R8JfXST2Tn4g1pv6Hs3LEXJtlncg5/1DiMtfrqW
|
||||||
|
quJtKuv8xO+5rbfqtmMYduf4ELkwg1uJJBc/we8CgYBZkUMrRbl6mXuXIAvjuEsP
|
||||||
|
j3b3UFqEUrrf2pC+4GQHgfx9LR5uOehpvPcv3azU6Z4y3oe33BFO0lxQ5jTOo/4j
|
||||||
|
Mqbc/tZPg4QB7FQfEBrNzUMywhWB0Yepmh338nh7M4p1+ehXmwcVZforGzXsn52w
|
||||||
|
/8sgSSSkMge4hK5HyIfD5QKBgHVr6rROH2UZ8dJwqfKWFgntoKKaVoICOEkH5dje
|
||||||
|
wDTQiYcuj0NQQq33OLyE0sACd/ufRdRpcOhqHyqBbT9QR9HZQ2QYuYZDcdAGxDOX
|
||||||
|
hTqb6FqYBe2E2Yh5XKzz/hLF6g7P5vDQxCbN/fO2JS0lEbAYdUbX7PUFeRKYsEj3
|
||||||
|
d2e9AoGAMrejS2Ic64k2I8VyYapEJ1SUaCeNCj7yR67QVtXJWvmYeu9tsUy9bxGC
|
||||||
|
FmZuEIUnQV5KZUCKG26GKq/0NiT0Umc38zlUSJzDVM9LUHEt5K066RhVEBp3Fds5
|
||||||
|
VIGgI1BkHeMKfhve0wwAbFECL+rzC9ihb6uNxZywlfeyfKN6ga8=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,18 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC+DCCAeCgAwIBAgIQTCXTJncsLpgueaMqQF6AiTANBgkqhkiG9w0BAQsFADAm
|
||||||
|
MREwDwYDVQQKEwhRdWlja1RMUzERMA8GA1UEAxMIUXVpY2tUTFMwHhcNMTgwNTIx
|
||||||
|
MjI1NzI4WhcNMjgwODI2MjI1NzI4WjATMREwDwYDVQQKEwhRdWlja1RMUzCCASIw
|
||||||
|
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL0fYn9wE7phMA6CFT6gv7mDpzSB
|
||||||
|
LkebCxj3LfU/isdgXvtXUn+BKIolvav7oJyTyz1R0NzX5uXxEERMBUW89KWvPLPK
|
||||||
|
o3d47MWMcAgiYx2+FeGZo1cjq3IRVKyg3WRVw2rO0YNL3K1QCS93A+IdA/05muwt
|
||||||
|
346XJ2FV0tPmETn6t+So2e9ZXh+uJjcCHq4XpJAJznCwemzzRpDe7nG5sYZqq+Oz
|
||||||
|
zBQ/bTC8rOdqW5woH/GhQHYHcKf1taPLmDLczVPQCqS3LAEK5EOUElfpQykfkZI4
|
||||||
|
clOZBhJ0e5zNEBTB/XRd7uuUA57Ig58l7hbX0fUPHgS9MF1z9CXJ40BSm/sCAwEA
|
||||||
|
AaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1Ud
|
||||||
|
EwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAHKH54KZdpvcLRIJK4yeSqwOigYp
|
||||||
|
0NHM9U8RlHjmf5Tp9lCtZpVrkfUtg9rXytekAXfd6GaNex7swTMNPnJBGgaQ2vA8
|
||||||
|
0jdtKfe6AcHTYQV1rs0qunlR8i26cNhYblKPJjYYA6FBzTTtybXhHYG9xvYpSVpo
|
||||||
|
XcrsC81DYK6nMiQMRYuT7RO/rtI4Tzx+laYc0lYgBzf6pXUjXycgAuJ5+cWT8DDn
|
||||||
|
OxPXbfAxfzc6jYfsigwzdOCnuIomFogm8ad47ApTTTLFrVtqCNJAKCu7HufEbB2G
|
||||||
|
OKWvl9NmTPYetS6MO5LqLAWcf/uRPn+lufHeTfBWIDD5zbJ2+ATP+mQQ2d0=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAvR9if3ATumEwDoIVPqC/uYOnNIEuR5sLGPct9T+Kx2Be+1dS
|
||||||
|
f4EoiiW9q/ugnJPLPVHQ3Nfm5fEQREwFRbz0pa88s8qjd3jsxYxwCCJjHb4V4Zmj
|
||||||
|
VyOrchFUrKDdZFXDas7Rg0vcrVAJL3cD4h0D/Tma7C3fjpcnYVXS0+YROfq35KjZ
|
||||||
|
71leH64mNwIerhekkAnOcLB6bPNGkN7ucbmxhmqr47PMFD9tMLys52pbnCgf8aFA
|
||||||
|
dgdwp/W1o8uYMtzNU9AKpLcsAQrkQ5QSV+lDKR+RkjhyU5kGEnR7nM0QFMH9dF3u
|
||||||
|
65QDnsiDnyXuFtfR9Q8eBL0wXXP0JcnjQFKb+wIDAQABAoIBAGQFxk1KFFT9c7Io
|
||||||
|
oF3IHL5b38HIFJbwbBUfHaJYoehCktlxXINs5ujxfvgHk/FbxSDANaunUEoKjaTh
|
||||||
|
Y+R3RBigroUURhI41VjBprrWnP8s+lufqyC6D8G7YsIOLikTps/FZE+Bfsv2yXTe
|
||||||
|
CCK9X8+8eLAyrsq2LLCw+Fjzk+bKRj+zE1bUR2MqNYtRNOFizDR0DCy/f+OltmhR
|
||||||
|
MVTQgA4hAWyCXc3c07zJ3YMiVMHBIGX3hiwEGhzgKtS8vQ7isW21StGLsMQlvUgt
|
||||||
|
AjrVzzsacCSzuL+QZoZtZl3E7V/Mko0bKNeOz2ouoWTKxInlzget+b+zE39+1WZO
|
||||||
|
T/X54gkCgYEAx5sI73letGuk9DOopwKLokj0Qdj3f5VRb3yJqbp3YkLTeayyRAwD
|
||||||
|
3KY+NwSDGLqj/IcG5DN/ZtLbbhiI2F3oPcJG8QyVqmsfzF7aW3RaBBt6gFN6IdQ9
|
||||||
|
SO0pS28bj3PVLqPqx3gXHZ3l9WRgj5mbl6yvoICiymMMKajOgKi0sTcCgYEA8o4j
|
||||||
|
+0HFhxcLvPz8GCynSarMXaZe/mEImURq8ObH2KSgBogD5mCA3IHL4kQSiRyxNoAt
|
||||||
|
crGr1idsR28UYfX4xprMp3okA9ujAw0hkiNhUh3jf3ZywvQXFkOoSbtwnfAFK83c
|
||||||
|
CmHy+c4OL9BAXsHvhsRHDCVjfKupqJQwux+9HV0CgYB+FSMmyX6V7qzqiDsPC5+S
|
||||||
|
Kg0IDvn/QB2Jk5wNdzhz/AxC/mA4dXJ3DRedfx8kHrj5CX3D5feixqxOtfay3VaW
|
||||||
|
tEJFfxKG7FXQrVW2kR9PGuBdcN1jwwHXL992w78f9SYC6Q2jY+sODTA1umr4KipL
|
||||||
|
O4xQkRDDUJ9dLUELqgVBLwKBgQC+/CLizQgOdZv9hCmvk0FppP3j44M6wwa1QAUA
|
||||||
|
iIblU8LZQbHobSYp+l2iXL1HjvsOkeC3RaSrLEF7AcDH3Zi0MOFiIa9IBmIVnfpI
|
||||||
|
Cmmv8e7Wx1pXnUCsfDt/SwLCqWI4+o/+8N8TySasiUqWEhhbQiM7Mhli6fvdzEmO
|
||||||
|
ndAX1QKBgCKJA25iPkLKw4mFVxAaPIAZnenJXJpuHF9tGzjjcFfioGtvI/1mrePs
|
||||||
|
PhwoO1qpjzY9brtf47l+vVMSY9KrA1LvudPvTqBtyjQvG5SqsWZSLuyJL30HKeFy
|
||||||
|
hv9FCsGVcF6wu3S8wXaGC/H8kityxTqFgZQW5whl2D9axJavygKj
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,19 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDDjCCAfagAwIBAgIRAI0Dt8LVd8cJPc0dv5aW+wcwDQYJKoZIhvcNAQELBQAw
|
||||||
|
JjERMA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE4MDUy
|
||||||
|
MTIyNTcyN1oXDTI4MDgyNjIyNTcyN1owJzERMA8GA1UEChMIUXVpY2tUTFMxEjAQ
|
||||||
|
BgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||||
|
ANr32CUXFUCW1c2oPoHjq76T8jUTH/cxPiR5NabJ1y4gMCBko2rIe+TblW9UclxH
|
||||||
|
911gjfpSAxFtNf+lX5kwmAMHhU8pcCc+Mjp3Ax9acFXSXvzzTDg+xj0NGig6OBk3
|
||||||
|
jyPuO92lM8A4qs0mBZ/T04iLkawLmdRXViRoGK/T7Df8HN+hm7UsG0VO3GxFgSST
|
||||||
|
YhhKTu6JMTADszbIFPOvBxGCUNhffXiLNyviO4AiBdcAv2v0SUadEPmSGm5Jb1DK
|
||||||
|
tfKY0jWi1k1zNSqzit/bhML/EHbVkYJ00QmH50MBTunpz60gIgHjt48nzJarLDML
|
||||||
|
oRFMppG9XIBQlUn3lo0gVwcCAwEAAaM2MDQwDgYDVR0PAQH/BAQDAgWgMAwGA1Ud
|
||||||
|
EwEB/wQCMAAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IB
|
||||||
|
AQAb388owui+O9vUle+A99FXwcMDEb0OILc0lBXVWx8q5ZE73vcanxyAcfOsZYRY
|
||||||
|
Lh7G6VtJwC9xVjAdNwJ1gd+ak1l0/Rhs1V0JZ12/wOvAOQ7+9g2lRc1IedOh3EIh
|
||||||
|
d3BMI4RdDB/BnnK3XjkggYQZK3yiAOavmmsZxAOl/apzjF+5u8XjuydMmotE2NYw
|
||||||
|
IpM93zE5wWXqzYs/Kmyy7zAcHKfvq9xej/gMCFEvO6lopmwyslBLPpPNHwyfIVtA
|
||||||
|
mspm2OZhdmpRJYGzkR4wK5NjoRl2O11uzlMRDckp0GSZ0x6TGxmb7ot5HK27p3ep
|
||||||
|
6LPZM1wJIwuYHIP74eH0ctQP
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEA2vfYJRcVQJbVzag+geOrvpPyNRMf9zE+JHk1psnXLiAwIGSj
|
||||||
|
ash75NuVb1RyXEf3XWCN+lIDEW01/6VfmTCYAweFTylwJz4yOncDH1pwVdJe/PNM
|
||||||
|
OD7GPQ0aKDo4GTePI+473aUzwDiqzSYFn9PTiIuRrAuZ1FdWJGgYr9PsN/wc36Gb
|
||||||
|
tSwbRU7cbEWBJJNiGEpO7okxMAOzNsgU868HEYJQ2F99eIs3K+I7gCIF1wC/a/RJ
|
||||||
|
Rp0Q+ZIabklvUMq18pjSNaLWTXM1KrOK39uEwv8QdtWRgnTRCYfnQwFO6enPrSAi
|
||||||
|
AeO3jyfMlqssMwuhEUymkb1cgFCVSfeWjSBXBwIDAQABAoIBAGQMCf4oZdV1FYs5
|
||||||
|
7BV86OPSxT/q1Rgkr7gKibEDWAYDPvoOAXywzarriYOsmfQADc3kZ/qPrkcwFxQP
|
||||||
|
g3aC9XGs5gQdctj7WgfMiOiycdFEpZH9uD2asQkEC4eF0kvzTrukBkZnTRXuzlud
|
||||||
|
m8RDDMu+uXhadJbIsNtBlMYBllSdS+LFxXcAYm+IsvTYzmwg4+bnjvOwMHO9SMSb
|
||||||
|
1dfgOLkg/A++/GTjD/kUyCV5dc4lv2I0i2pXJkD2V0Dr6Yra1U/MRKcOwTGC2q/8
|
||||||
|
hZuKm9DgvGXvZsG0+yT5fsexGRwTxmByvfj+QMF3LCTDCknD4d/mmEEX0EEGPlW2
|
||||||
|
I7OgKEECgYEA/LkdwnXy7ymis1Rgjumc3ydcLoCqV3ExaxXrvO50EkRpgRX/TLEk
|
||||||
|
j98iVYyksiaJuMhqnxNttT6GwWJvwIXFPP9WpIGmzi4GKyqYGEX4WbyPoY9hjt/G
|
||||||
|
muR67cTXg6ssiSssUCoQnWIHyuGDJfzRWqnoei0dIA2GobOwFJtXeV0CgYEA3c6u
|
||||||
|
utbNtmbyp17Jffx01ee8Wprhnoz7Nh/dJMLngpIx3i8qQqpFB8TPNUTu+GLgGcol
|
||||||
|
n9BDzZszoVhsxybn7Lgm/OjS/jQL4hosFoqztThkg28L8UD7QB0TyCucwgk2lgOe
|
||||||
|
VxyX25kNSXzxdCYaKr1+6g2gtBAb0zPj2E+5t7MCgYEAimoA6J6dHWwaVkmiUOOW
|
||||||
|
LYprLHT/1sCCJnptEJ8xJ0gc2LxphWGH+txk+6H6GjCNQY1TCCkl7xx9xbDaMAGU
|
||||||
|
E2Jt28++wjHm4wGDJ9g6uztRF1VmQ1BAgFkfEta6irzXuZDRxl4jl283gWCd6dJb
|
||||||
|
/2ILl87ZotKFqE6347Fo6WkCgYEAyDNyMMALIzTelkUO1wFUL3If5yPeuy4C3IJ8
|
||||||
|
J18oeQkdq66klVF8RxvT7v/ONjGAlqaHuSzQ1jbcrifS3xp1wYsh3asELl+pziXT
|
||||||
|
X3FH7Sz+REep3tLJNMBKB6WdsuF//H09oOD1DEej342/nhd6DNPHRtiQEZZslwBC
|
||||||
|
Cg9D0NMCgYEArNksPSQJSxXqxZsw17OTqQJnf3kNBI0SP9q6Wc8gN69r5YQcIHcr
|
||||||
|
KgtfdiL4LawZFie6gcNu398ng7VYUzzkYR9j+G5qPetcqllQZeVc6cieUyR7Eul0
|
||||||
|
WvtlUECCfweLFUsIhuHyEsGu1PrFYd98SlOzt24utguFss1539cEC3A=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,19 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDFTCCAf2gAwIBAgIQTyBNJlm7fS0yutwdLbhG9zANBgkqhkiG9w0BAQsFADAm
|
||||||
|
MREwDwYDVQQKEwhRdWlja1RMUzERMA8GA1UEAxMIUXVpY2tUTFMwHhcNMTgwNTIx
|
||||||
|
MjI1NzI4WhcNMjgwODI2MjI1NzI4WjArMREwDwYDVQQKEwhRdWlja1RMUzEWMBQG
|
||||||
|
A1UEAxMNbG9jYWxyZWdpc3RyeTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||||
|
ggEBANSMT7auGdwF63fFdQM9O/EqrX++gnuBQgFa4cZzC7GqsvS90uKTOLuWIA2U
|
||||||
|
ehgF548EDkZu1z6nRAvoFh5L6B5f1VjiVknzLEPlR+5uPD22kbcxgCrMCRZn+5mK
|
||||||
|
vJhTUpx18yeBXMhxtPhkGnKaKwGcgeW8O69KM7Mo4HBQqg5656pa+4wkUo7GX2v0
|
||||||
|
R4ZqmrS1tlwOgpld8KZKVJ1FNyGEeKQkIYGJKHqgC2/JrXsbzuSZ/4pqf8BHc6Mb
|
||||||
|
AHU85RlBFVDHFPMtQ7Rg1vrhYzgInKeqXtc2kEAe63nqyYyHxPOUd3vIQX/N4tdB
|
||||||
|
aH41ffs68Pdtp9GeocTiYyj7KuUCAwEAAaM6MDgwDgYDVR0PAQH/BAQDAgWgMAwG
|
||||||
|
A1UdEwEB/wQCMAAwGAYDVR0RBBEwD4INbG9jYWxyZWdpc3RyeTANBgkqhkiG9w0B
|
||||||
|
AQsFAAOCAQEAkjfZvcd5WysbfqGfhPErG7ADWAFJ1bsIDlHVUaEn2/Asr68iJpfF
|
||||||
|
fqb0fhBkBExPhiLDS+jmL1L86QRNIgyM+7zGCCagKJkl9uNBGXPdS6KxZtY8W8rV
|
||||||
|
bF/GIYnYUL5pnyrhX4pH2ZnDJpKIAJl8CAZ1VHwErQ5VqnJAX/gGO/eKgiyCciZv
|
||||||
|
WmmQkhcOo60FwLW+Wi9sLOYD+YAT+VnFrGfak/SDfT78wrmmfg5v05tvFXgJaZLh
|
||||||
|
JSxRET9D5iT3DIxb+m5GyQAqIH1djh02ybrPJ9j6/+qRQDojIe5qJUL90qIvhwO+
|
||||||
|
aSbIL/p+I6//AUMWJvcR7GbXy3xywgmaYw==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA1IxPtq4Z3AXrd8V1Az078Sqtf76Ce4FCAVrhxnMLsaqy9L3S
|
||||||
|
4pM4u5YgDZR6GAXnjwQORm7XPqdEC+gWHkvoHl/VWOJWSfMsQ+VH7m48PbaRtzGA
|
||||||
|
KswJFmf7mYq8mFNSnHXzJ4FcyHG0+GQacporAZyB5bw7r0ozsyjgcFCqDnrnqlr7
|
||||||
|
jCRSjsZfa/RHhmqatLW2XA6CmV3wpkpUnUU3IYR4pCQhgYkoeqALb8mtexvO5Jn/
|
||||||
|
imp/wEdzoxsAdTzlGUEVUMcU8y1DtGDW+uFjOAicp6pe1zaQQB7reerJjIfE85R3
|
||||||
|
e8hBf83i10FofjV9+zrw922n0Z6hxOJjKPsq5QIDAQABAoIBAQCLj3Xn5XllVx29
|
||||||
|
jxG+Br8NI5C4iEb1AXJtoVcODwxmpEbNHLcTvsdJpNF3GT7x9y6MYYVeCfmbUgkE
|
||||||
|
KGgdjInlJ9fWfQdblyhBjJMmo4s6ml4jg4U8lKyC4dP6hXZALrXXtjrqfa6GjuLd
|
||||||
|
Fh2nkkMa08EXL/mgp4A662QzW0POLQIo1lMJc49FFPrVQneLedUdsJDowNz/HU/q
|
||||||
|
oD4/SsKw6inUh/A1MfSKvEhnJcVH4fiQhFQU5CdSzAHPmAYcoBeg6LjY+WScJAAs
|
||||||
|
Hu5kgunbCsB5vw9WbFDQzM1HYtW1CvJj1cjNp662b06D7VQugjtawhHNImkq1/65
|
||||||
|
H2ZTglchAoGBAPu0OX3tEvtic4f8VLRv/TeI9NSC3EgRAtIDncDo+nwVjR54AXID
|
||||||
|
aePceImGUsDd5xfLuQTiYp50z0cEB5CGsWYbnjm0hliF8YXz/tpqi0V0Cr8fLLA8
|
||||||
|
/jG3tajbZ8xu/3p1iEcIPevYT/44bjbOyDp5peQIHhr32LZ1gZfQDRt7AoGBANgt
|
||||||
|
AIid1rPIyEzhhznpWVjw/ZIrtgaP0HDgKaUUCsEqEDoOJEaFS7WG4G7m8/iS4f8v
|
||||||
|
XGgcoYf4TjfIwYtRQy2Bp9g4oOMiUbQKukF1DuFJpsw69y3hNNoZoUm7r2jpv3Q8
|
||||||
|
/NY+O+BNaTVdmbOjNHmKo99MYGh1cPUPVGxuP1UfAoGBAOJ9fe5OUfJa2NLYv+/N
|
||||||
|
hfFfD8/aIRXIGN2Z224nNp5JVj7AhaxuXe5oCR7W+8gI5VWIP+ihPVSQj6O7gIMQ
|
||||||
|
cLkMyQfr5afqfzamJAGuNbw9ex4Xk0LS33klchWLuI9Aoiszb3lbdTyv3OtJJAO1
|
||||||
|
dn8Hz7qtg0mJFDy65+4PjHvZAoGAXtKmmEZ75hKdYbPPiCSGT5At+g74Yjp1GP4K
|
||||||
|
5mE7Mm3L/lszqEdR5UdLbPobbB6pyTCyHOzqIeVWEfwagYzcpbposFxunhLwucO2
|
||||||
|
3X2GUGXpJ056HALcFwsFB32vPJrDoy4ZTbSwuPvbuU/cWsKtAt9AcHNlGozhRm05
|
||||||
|
//IAD8sCgYAUs6ibNtUqCFjekr10FBGFuA2ZQg+9bQYw3ti+S6uFMsxIDqYRC2bG
|
||||||
|
yvKhEYym/W7RwfzPWjGzuvFbZWzJnnb81WLfcI4DnrJe3h8THlnaBQhcsEObu84O
|
||||||
|
XS/sYeVo5c6l0kTNp0I8vXbn05bExZlsLAIICMTsm5bSQZI/iCRyEw==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
1
contrib/docker-integration/nginx/test.passwd
Normal file
1
contrib/docker-integration/nginx/test.passwd
Normal file
|
@ -0,0 +1 @@
|
||||||
|
testuser:$apr1$YmLhHjm6$AjP4z8J1WgcUNxU8J4ue5.
|
1
contrib/docker-integration/nginx/v1/search.json
Normal file
1
contrib/docker-integration/nginx/v1/search.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"num_pages":1,"num_results":2,"page":1,"page_size": 25,"query":"testsearch","results":[{"description":"","is_automated":false,"is_official":false,"is_trusted":false, "name":"dmcgowan/testsearch-1","star_count":1000},{"description":"Some automated build","is_automated":true,"is_official":false,"is_trusted":false,"name":"dmcgowan/testsearch-2","star_count":10}]}
|
103
contrib/docker-integration/plugins.bats
Normal file
103
contrib/docker-integration/plugins.bats
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
# This tests pushing and pulling plugins
|
||||||
|
|
||||||
|
load helpers
|
||||||
|
|
||||||
|
user="testuser"
|
||||||
|
password="testpassword"
|
||||||
|
base="hello-world"
|
||||||
|
|
||||||
|
#TODO: Create plugin image
|
||||||
|
function create_plugin() {
|
||||||
|
plugindir=$(mktemp -d)
|
||||||
|
|
||||||
|
cat - > $plugindir/config.json <<CONFIGJSON
|
||||||
|
{
|
||||||
|
"manifestVersion": "v0",
|
||||||
|
"description": "A test plugin for integration tests",
|
||||||
|
"entrypoint": ["/usr/bin/ncat", "-l", "-U", "//run/docker/plugins/plugin.sock"],
|
||||||
|
"interface" : {
|
||||||
|
"types": ["docker.volumedriver/1.0"],
|
||||||
|
"socket": "plugin.sock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CONFIGJSON
|
||||||
|
|
||||||
|
cid=$(docker create dmcgowan/ncat:latest /bin/sh)
|
||||||
|
|
||||||
|
mkdir $plugindir/rootfs
|
||||||
|
|
||||||
|
docker export $cid | tar -x -C $plugindir/rootfs
|
||||||
|
|
||||||
|
docker rm $cid
|
||||||
|
|
||||||
|
daemontmp=$(docker exec dockerdaemon mktemp -d)
|
||||||
|
|
||||||
|
tar -c -C $plugindir . | docker exec -i dockerdaemon tar -x -C $daemontmp
|
||||||
|
|
||||||
|
docker exec dockerdaemon docker plugin create $1 $daemontmp
|
||||||
|
|
||||||
|
docker exec dockerdaemon rm -rf $daemontmp
|
||||||
|
|
||||||
|
rm -rf $plugindir
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test plugin push and pull" {
|
||||||
|
version_check docker "$GOLEM_DIND_VERSION" "1.13.0-rc3"
|
||||||
|
version_check docker "$GOLEM_DISTRIBUTION_VERSION" "2.6.0"
|
||||||
|
|
||||||
|
login_oauth localregistry:5558
|
||||||
|
image="localregistry:5558/testuser/plugin1"
|
||||||
|
|
||||||
|
create_plugin $image
|
||||||
|
|
||||||
|
run docker_t plugin push $image
|
||||||
|
echo $output
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
docker_t plugin rm $image
|
||||||
|
|
||||||
|
docker_t plugin install --grant-all-permissions $image
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test plugin push and failed image pull" {
|
||||||
|
version_check docker "$GOLEM_DIND_VERSION" "1.13.0-rc3"
|
||||||
|
version_check docker "$GOLEM_DISTRIBUTION_VERSION" "2.6.0"
|
||||||
|
|
||||||
|
|
||||||
|
login_oauth localregistry:5558
|
||||||
|
image="localregistry:5558/testuser/plugin-not-image"
|
||||||
|
|
||||||
|
create_plugin $image
|
||||||
|
|
||||||
|
run docker_t plugin push $image
|
||||||
|
echo $output
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
docker_t plugin rm $image
|
||||||
|
|
||||||
|
run docker_t pull $image
|
||||||
|
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test image push and failed plugin pull" {
|
||||||
|
version_check docker "$GOLEM_DIND_VERSION" "1.13.0-rc3"
|
||||||
|
version_check docker "$GOLEM_DISTRIBUTION_VERSION" "2.6.0"
|
||||||
|
|
||||||
|
login_oauth localregistry:5558
|
||||||
|
image="localregistry:5558/testuser/image-not-plugin"
|
||||||
|
|
||||||
|
build $image "$base:latest"
|
||||||
|
|
||||||
|
run docker_t push $image
|
||||||
|
echo $output
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
docker_t rmi $image
|
||||||
|
|
||||||
|
run docker_t plugin install --grant-all-permissions $image
|
||||||
|
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
67
contrib/docker-integration/run_multiversion.sh
Executable file
67
contrib/docker-integration/run_multiversion.sh
Executable file
|
@ -0,0 +1,67 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Run the integration tests with multiple versions of the Docker engine
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
|
||||||
|
|
||||||
|
if [ "$TMPDIR" != "" ] && [ ! -d "$TMPDIR" ]; then
|
||||||
|
mkdir -p $TMPDIR
|
||||||
|
fi
|
||||||
|
|
||||||
|
cachedir=`mktemp -t -d golem-cache.XXXXXX`
|
||||||
|
trap "rm -rf $cachedir" EXIT
|
||||||
|
|
||||||
|
if [ "$1" == "-d" ]; then
|
||||||
|
# Drivers to use for Docker engines the tests are going to create.
|
||||||
|
STORAGE_DRIVER=${STORAGE_DRIVER:-overlay}
|
||||||
|
|
||||||
|
docker daemon --log-level=panic --storage-driver="$STORAGE_DRIVER" &
|
||||||
|
DOCKER_PID=$!
|
||||||
|
|
||||||
|
# Wait for it to become reachable.
|
||||||
|
tries=10
|
||||||
|
until docker version &> /dev/null; do
|
||||||
|
(( tries-- ))
|
||||||
|
if [ $tries -le 0 ]; then
|
||||||
|
echo >&2 "error: daemon failed to start"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
trap "kill $DOCKER_PID" EXIT
|
||||||
|
fi
|
||||||
|
|
||||||
|
distimage=$(docker build -q $DIR/../..)
|
||||||
|
fullversion=$(git describe --match 'v[0-9]*' --dirty='.m' --always)
|
||||||
|
distversion=${fullversion:1}
|
||||||
|
|
||||||
|
echo "Testing image $distimage with distribution version $distversion"
|
||||||
|
|
||||||
|
# Pull needed images before invoking golem to get pull time
|
||||||
|
# These images are defined in golem.conf
|
||||||
|
time docker pull nginx:1.9
|
||||||
|
time docker pull golang:1.6
|
||||||
|
time docker pull dmcgowan/token-server:simple
|
||||||
|
time docker pull dmcgowan/token-server:oauth
|
||||||
|
time docker pull distribution/golem-runner:0.1-bats
|
||||||
|
|
||||||
|
time docker pull docker:1.9.1-dind
|
||||||
|
time docker pull docker:1.10.3-dind
|
||||||
|
time docker pull docker:1.11.1-dind
|
||||||
|
time docker pull docker:1.12.3-dind
|
||||||
|
time docker pull docker:1.13.0-rc5-dind
|
||||||
|
|
||||||
|
golem -cache $cachedir \
|
||||||
|
-i "golem-distribution:latest,$distimage,$distversion" \
|
||||||
|
-i "golem-dind:latest,docker:1.9.1-dind,1.9.1" \
|
||||||
|
-i "golem-dind:latest,docker:1.10.3-dind,1.10.3" \
|
||||||
|
-i "golem-dind:latest,docker:1.11.1-dind,1.11.1" \
|
||||||
|
-i "golem-dind:latest,docker:1.12.3-dind,1.12.3" \
|
||||||
|
-i "golem-dind:latest,docker:1.13.0-rc5-dind,1.13.0" \
|
||||||
|
$DIR
|
||||||
|
|
108
contrib/docker-integration/tls.bats
Normal file
108
contrib/docker-integration/tls.bats
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
# Registry host name, should be set to non-localhost address and match
|
||||||
|
# DNS name in nginx/ssl certificates and what is installed in /etc/docker/cert.d
|
||||||
|
|
||||||
|
load helpers
|
||||||
|
|
||||||
|
hostname="localregistry"
|
||||||
|
base="hello-world"
|
||||||
|
image="${base}:latest"
|
||||||
|
|
||||||
|
# Login information, should match values in nginx/test.passwd
|
||||||
|
user=${TEST_USER:-"testuser"}
|
||||||
|
password=${TEST_PASSWORD:-"passpassword"}
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
tempImage $image
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test valid certificates" {
|
||||||
|
docker_t tag $image $hostname:5440/$image
|
||||||
|
run docker_t push $hostname:5440/$image
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test basic auth" {
|
||||||
|
basic_auth_version_check
|
||||||
|
login $hostname:5441
|
||||||
|
docker_t tag $image $hostname:5441/$image
|
||||||
|
run docker_t push $hostname:5441/$image
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test basic auth with build" {
|
||||||
|
basic_auth_version_check
|
||||||
|
login $hostname:5441
|
||||||
|
|
||||||
|
image1=$hostname:5441/$image-build
|
||||||
|
image2=$hostname:5441/$image-build-2
|
||||||
|
|
||||||
|
tempImage $image1
|
||||||
|
|
||||||
|
run docker_t push $image1
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
|
||||||
|
docker_t rmi $image1
|
||||||
|
|
||||||
|
run build $image2 $image1
|
||||||
|
echo $output
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
run docker_t push $image2
|
||||||
|
echo $output
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test TLS client auth" {
|
||||||
|
docker_t tag $image $hostname:5442/$image
|
||||||
|
run docker_t push $hostname:5442/$image
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test TLS client with invalid certificate authority fails" {
|
||||||
|
docker_t tag $image $hostname:5443/$image
|
||||||
|
run docker_t push $hostname:5443/$image
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test basic auth with TLS client auth" {
|
||||||
|
basic_auth_version_check
|
||||||
|
login $hostname:5444
|
||||||
|
docker_t tag $image $hostname:5444/$image
|
||||||
|
run docker_t push $hostname:5444/$image
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
has_digest "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test unknown certificate authority fails" {
|
||||||
|
docker_t tag $image $hostname:5445/$image
|
||||||
|
run docker_t push $hostname:5445/$image
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test basic auth with unknown certificate authority fails" {
|
||||||
|
run login $hostname:5446
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
docker_t tag $image $hostname:5446/$image
|
||||||
|
run docker_t push $hostname:5446/$image
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test TLS client auth to server with unknown certificate authority fails" {
|
||||||
|
docker_t tag $image $hostname:5447/$image
|
||||||
|
run docker_t push $hostname:5447/$image
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Test failure to connect to server fails to fallback to SSLv3" {
|
||||||
|
docker_t tag $image $hostname:5448/$image
|
||||||
|
run docker_t push $hostname:5448/$image
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue