Compare commits

..

24 commits

Author SHA1 Message Date
Stephen Day
0bae7512b2 Merge pull request #2344 from stevvooe/prepare-2.5.2
[release/2.5] release: prepare for 2.5.2 release
2017-07-20 14:21:03 -07:00
Stephen J Day
48cb60af7c
release: prepare for 2.5.2 release
Signed-off-by: Stephen J Day <stephen.day@docker.com>
2017-07-20 14:10:39 -07:00
Stephen Day
2b0952dca1 Merge pull request #2342 from stevvooe/limit-payload-size-25
[release/2.5] registry/{storage,handlers}: limit content sizes
2017-07-20 13:55:19 -07:00
Stephen J Day
58d239d723
registry/{storage,handlers}: limit content sizes
Under certain circumstances, the use of `StorageDriver.GetContent` can
result in unbounded memory allocations. In particualr, this happens when
accessing a layer through the manifests endpoint.

This problem is mitigated by setting a 4MB limit when using to access
content that may have been accepted from a user. In practice, this means
setting the limit with the use of `BlobProvider.Get` by wrapping
`StorageDriver.GetContent` in a helper that uses `StorageDriver.Reader`
with a `limitReader` that returns an error.

When mitigating this security issue, we also noticed that the size of
manifests uploaded to the registry is also unlimited. We apply similar
logic to the request body of payloads that are full buffered.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
(cherry picked from commit 55ea440428)
Signed-off-by: Stephen J Day <stephen.day@docker.com>
2017-07-20 13:39:13 -07:00
Derek McGowan
9bc9d212ec Merge pull request #2122 from mstanleyjones/configuration_changes_backport
Backport #2116 to releases/2.5
2017-01-03 15:43:27 -08:00
Derek McGowan
fcbea606cb Improve formatting of configuration.md
Backported from master to release/2.5

Signed-off-by: Misty Stanley-Jones <misty@docker.com>
2017-01-03 14:44:46 -08:00
Derek McGowan
6b114e6d8f Merge pull request #2081 from Windfarer/release/2.5
fix panic when using storage redirect middleware
2017-01-03 10:19:19 -08:00
Eric Yang
6c985f7f63 Update main.go
Signed-off-by: Eric Yang <windfarer@gmail.com>
2016-11-24 20:41:49 +08:00
Derek McGowan
2c3b616fee Merge pull request #2054 from mstanleyjones/2.5_metadata_fixes
2.5 metadata fixes
2016-11-14 15:23:15 -08:00
Derek McGowan
5adfbe34db
Remove newlines from end of error strings
Golint now checks for new lines at the end of go error strings,
remove these unneeded new lines.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
2016-11-14 14:10:23 -08:00
Richard Scothern
cfe7079300
Satisfy the latest go lint rules
Signed-off-by: Richard Scothern <richard.scothern@docker.com>
2016-11-14 13:48:06 -08:00
Misty Stanley-Jones
abd2d765ac Metadata and formatting fixes needed for Jekyll build
Signed-off-by: Misty Stanley-Jones <misty@docker.com>
(cherry picked from commit 49d6706ce9d952718725350d82d9ea7deb4f7326)
Signed-off-by: Misty Stanley-Jones <misty@docker.com>
2016-11-11 11:58:31 -08:00
Misty Stanley-Jones
6b3ccf9640 Convert Markdown frontmatter to YAML
Some frontmatter such as the weights, menu stuff, etc is no longer used
'draft=true' becomes 'published: false'

Signed-off-by: Misty Stanley-Jones <misty@docker.com>
(cherry picked from commit f180e9a934)
Signed-off-by: Misty Stanley-Jones <misty@docker.com>
(cherry picked from commit c5a8e74c562cd62db83df69ec71d9cee3e346317)
Signed-off-by: Misty Stanley-Jones <misty@docker.com>
2016-11-11 11:58:31 -08:00
Misty Stanley-Jones
a8402a2253 Merge pull request #1985 from johndmulhausen/master
Remove old documentation source, add README on migration
(cherry picked from commit c372264f17)

Signed-off-by: Misty Stanley-Jones <misty@docker.com>
(cherry picked from commit f1219102a421c15f5c6fc437c1e1ec951424d9b5)
Signed-off-by: Misty Stanley-Jones <misty@docker.com>
2016-11-11 11:58:31 -08:00
Derek McGowan
0a22649f66 Update to fix lint errors
Context should use type values instead of strings.
Updated direct calls to WithValue, but still other uses of string keys.
Update Acl to ACL in s3 driver.

Cherry-picked to release/2.5 branch

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
Signed-off-by: Misty Stanley-Jones <misty@docker.com>
2016-11-11 11:58:28 -08:00
Edgar Lee
12acdf0a6c Stop ErrFinishedWalk from escaping from Repositories walk
Signed-off-by: Edgar Lee <edgar.lee@docker.com>
2016-08-26 11:20:37 -07:00
Edgar Lee
45b84c9512 Use typecast over reflect for error type checking
Signed-off-by: Edgar Lee <edgar.lee@docker.com>
2016-08-26 11:19:18 -07:00
Edgar Lee
8160a430be Handle new errors returned from catalog repository listing
Signed-off-by: Edgar Lee <edgar.lee@docker.com>
2016-08-26 11:19:06 -07:00
Edgar Lee
a405d3e88b Improve catalog enumerate runtime by an order of magnitude
Signed-off-by: Edgar Lee <edgar.lee@docker.com>
2016-08-26 11:10:53 -07:00
Stephen J Day
2aa09ff9a8 registry/storage: more efficient path compare in catalog
Previous component-wise path comparison is recursive and generates a
large amount of garbage. This more efficient version simply replaces the
path comparison with the zero-value to sort before everything. We do
this by replacing the byte-wise comparison that swaps a single character
inline for the separator comparison, such that separators sort first.

The resulting implementation provides component-wise path comparison
with no cost incurred for allocation or stack frame.

Direction of the comparison is also reversed to match Go style.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
2016-08-26 11:08:59 -07:00
Edgar Lee
fdc51bb1f2 Stop ErrFinishedWalk from escaping from Repositories walk
Signed-off-by: Edgar Lee <edgar.lee@docker.com>
2016-08-26 11:08:41 -07:00
Sebastien Coavoux
0567fa3c2a Fix: Compare path properly when list repository in catalog. #1854
Signed-off-by: Sebastien Coavoux <alignak@pyseb.cx>
2016-08-26 11:07:41 -07:00
Edgar Lee
22a59f2512 Refactor errVal named parameter for catalog repositories to err
Signed-off-by: Edgar Lee <edgar.lee@docker.com>
2016-08-26 11:07:32 -07:00
Edgar Lee
734caef0f4 Fix storage drivers dropping non EOF errors when listing repositories
This fixes errors other than io.EOF from being dropped when a storage driver
lists repositories. For example, filesystem driver may point to a missing
directory and errors, which then gets subsequently dropped.

Signed-off-by: Edgar Lee <edgar.lee@docker.com>
2016-08-26 11:07:24 -07:00
2740 changed files with 89167 additions and 770594 deletions

View file

@ -1 +0,0 @@
bin/

View file

@ -1,134 +0,0 @@
name: build
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- 'main'
- 'release/*'
tags:
- 'v*'
pull_request:
env:
DOCKERHUB_SLUG: distribution/distribution
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go:
- 1.18
- 1.19.10
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Set up Go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
-
name: Test
run: |
make coverage
-
name: Codecov
uses: codecov/codecov-action@v3
with:
directory: ./
build:
permissions:
contents: write # to create GitHub release (softprops/action-gh-release)
runs-on: ubuntu-latest
needs:
- test
steps:
-
name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
-
name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.DOCKERHUB_SLUG }}
### versioning strategy
### push semver tag v3.2.1 on main (default branch)
# distribution/distribution:3.2.1
# distribution/distribution:3.2
# distribution/distribution:3
# distribution/distribution:latest
### push semver prelease tag v3.0.0-beta.1 on main (default branch)
# distribution/distribution:3.0.0-beta.1
### push on main
# distribution/distribution:edge
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=ref,event=pr
type=edge
labels: |
org.opencontainers.image.title=Distribution
org.opencontainers.image.description=The toolkit to pack, ship, store, and deliver container content
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build artifacts
uses: docker/bake-action@v2
with:
targets: artifact-all
-
name: Move artifacts
run: |
mv ./bin/**/* ./bin/
-
name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: registry
path: ./bin/*
if-no-files-found: error
-
name: Build image
uses: docker/bake-action@v2
with:
files: |
./docker-bake.hcl
${{ steps.meta.outputs.bake-file }}
targets: image-all
push: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
-
name: GitHub Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
draft: true
files: |
bin/*.tar.gz
bin/*.sha256
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -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@v3
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@v2
with:
languages: ${{ matrix.language }}
-
name: Autobuild
uses: github/codeql-action/autobuild@v2
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View file

@ -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@v3
with:
fetch-depth: 0
-
name: Build image
uses: docker/bake-action@v2
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@v3
with:
name: oci-test-results-${{ github.sha }}
path: .out/
if-no-files-found: error

View file

@ -1,42 +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@v3
with:
fetch-depth: 0
-
name: Build image
uses: docker/bake-action@v2
with:
targets: image-local
-
name: Start distribution server
run: |
IP=`hostname -I | awk '{print $1}'`
echo "IP=$IP" >> $GITHUB_ENV
echo '{"insecure-registries" : ["'$IP':5000"]}' | sudo tee /etc/docker/daemon.json
sudo service docker restart
docker run --rm -p 5000:5000 -p 5001:5001 -idt "registry:local"
-
name: Tests
run: |
bash ./tests/push.sh $IP

View file

@ -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@v3
- name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@v2
with:
fossa-api-key: cac3dc8d4f2ba86142f6c0f2199a160f

View file

@ -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@a12a3943b4bdde767164f792f33f40b04645d846 # tag=v3.0.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # tag=v2.0.6
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@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # tag=v3.0.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@5f532563584d71fdef14ee64d17bafb34f751ce5 # tag=v1.0.26
with:
sarif_file: results.sarif

View file

@ -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@v3
-
name: Run
run: |
make ${{ matrix.target }}
env:
COMMIT_RANGE: ${{ format('{0}..{1}', github.sha, 'HEAD') }}

1
.gitignore vendored
View file

@ -35,4 +35,3 @@ bin/*
# Editor/IDE specific files.
*.sublime-project
*.sublime-workspace
.idea/*

View file

@ -1,27 +0,0 @@
linters:
enable:
- staticcheck
- unconvert
- gofmt
- goimports
- revive
- ineffassign
- vet
- unused
- misspell
- bodyclose
disable:
- errcheck
linters-settings:
revive:
rules:
# TODO(thaJeztah): temporarily disabled the "unused-parameter" check.
# It produces many warnings, and some of those may need to be looked at.
- name: unused-parameter
disabled: true
run:
deadline: 2m
skip-dirs:
- vendor

View file

@ -1,9 +1,9 @@
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.github.com>
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@gmail.com>
Olivier Gambier <olivier@docker.com> Olivier Gambier <dmp42@users.noreply.github.com>
Brian Bland <brian.bland@docker.com> Brian Bland <r4nd0m1n4t0r@gmail.com>
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.github.com>
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@gmail.com>
Olivier Gambier <olivier@docker.com> Olivier Gambier <dmp42@users.noreply.github.com>
Brian Bland <brian.bland@docker.com> Brian Bland <r4nd0m1n4t0r@gmail.com>
Brian Bland <brian.bland@docker.com> Brian Bland <brian.t.bland@gmail.com>
Josh Hawn <josh.hawn@docker.com> Josh Hawn <jlhawn@berkeley.edu>
Josh Hawn <josh.hawn@docker.com> Josh Hawn <jlhawn@berkeley.edu>
Richard Scothern <richard.scothern@docker.com> Richard <richard.scothern@gmail.com>
Richard Scothern <richard.scothern@docker.com> Richard Scothern <richard.scothern@gmail.com>
Andrew Meredith <andymeredith@gmail.com> Andrew Meredith <kendru@users.noreply.github.com>
@ -16,17 +16,4 @@ davidli <wenquan.li@hp.com> davidli <wenquan.li@hpe.com>
Omer Cohen <git@omer.io> Omer Cohen <git@omerc.net>
Eric Yang <windfarer@gmail.com> Eric Yang <Windfarer@users.noreply.github.com>
Nikita Tarasov <nikita@mygento.ru> Nikita <luckyraul@users.noreply.github.com>
Yu Wang <yuwa@microsoft.com> yuwaMSFT2 <yuwa@microsoft.com>
Yu Wang <yuwa@microsoft.com> Yu Wang (UC) <yuwa@microsoft.com>
Olivier Gambier <olivier@docker.com> dmp <dmp@loaner.local>
Olivier Gambier <olivier@docker.com> Olivier <o+github@gambier.email>
Olivier Gambier <olivier@docker.com> Olivier <dmp42@users.noreply.github.com>
Elsan Li 李楠 <elsanli@tencent.com> elsanli(李楠) <elsanli@tencent.com>
Rui Cao <ruicao@alauda.io> ruicao <ruicao@alauda.io>
Gwendolynne Barr <gwendolynne.barr@docker.com> gbarr01 <gwendolynne.barr@docker.com>
Haibing Zhou 周海兵 <zhouhaibing089@gmail.com> zhouhaibing089 <zhouhaibing089@gmail.com>
Feng Honglin <tifayuki@gmail.com> tifayuki <tifayuki@gmail.com>
Helen Xie <xieyulin821@harmonycloud.cn> Helen-xie <xieyulin821@harmonycloud.cn>
Mike Brown <brownwm@us.ibm.com> Mike Brown <mikebrow@users.noreply.github.com>
Manish Tomar <manish.tomar@docker.com> Manish Tomar <manishtomar@users.noreply.github.com>
Sakeven Jiang <jc5930@sina.cn> sakeven <jc5930@sina.cn>
Misty Stanley-Jones <misty@docker.com> Misty Stanley-Jones <misty@apache.org>

View file

@ -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/

153
AUTHORS Normal file
View file

@ -0,0 +1,153 @@
Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Schlesinger <aschlesinger@deis.com>
Aaron Vinson <avinson.public@gmail.com>
Adam Enger <adamenger@gmail.com>
Adrian Mouat <adrian.mouat@gmail.com>
Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
Alex Chan <alex.chan@metaswitch.com>
Alex Elman <aelman@indeed.com>
Alexey Gladkov <gladkov.alexey@gmail.com>
allencloud <allen.sun@daocloud.io>
amitshukla <ashukla73@hotmail.com>
Amy Lindburg <amy.lindburg@docker.com>
Andrew Hsu <andrewhsu@acm.org>
Andrew Meredith <andymeredith@gmail.com>
Andrew T Nguyen <andrew.nguyen@docker.com>
Andrey Kostov <kostov.andrey@gmail.com>
Andy Goldstein <agoldste@redhat.com>
Anis Elleuch <vadmeste@gmail.com>
Antonio Mercado <amercado@thinknode.com>
Antonio Murdaca <runcom@redhat.com>
Anton Tiurin <noxiouz@yandex.ru>
Arien Holthuizen <aholthuizen@schubergphilis.com>
Arnaud Porterie <arnaud.porterie@docker.com>
Arthur Baars <arthur@semmle.com>
Asuka Suzuki <hello@tanksuzuki.com>
Avi Miller <avi.miller@oracle.com>
Ayose Cazorla <ayosec@gmail.com>
BadZen <dave.trombley@gmail.com>
Ben Firshman <ben@firshman.co.uk>
bin liu <liubin0329@gmail.com>
Brian Bland <brian.bland@docker.com>
burnettk <burnettk@gmail.com>
Carson A <ca@carsonoid.net>
Cezar Sa Espinola <cezarsa@gmail.com>
Charles Smith <charles.smith@docker.com>
Chris Dillon <squarism@gmail.com>
cyli <cyli@twistedmatrix.com>
Daisuke Fujita <dtanshi45@gmail.com>
Daniel Huhn <daniel@danielhuhn.de>
Darren Shepherd <darren@rancher.com>
Dave Trombley <dave.trombley@gmail.com>
Dave Tucker <dt@docker.com>
David Lawrence <david.lawrence@docker.com>
davidli <wenquan.li@hp.com>
David Verhasselt <david@crowdway.com>
David Xia <dxia@spotify.com>
Dejan Golja <dejan@golja.org>
Derek McGowan <derek@mcgstyle.net>
Diogo Mónica <diogo.monica@gmail.com>
DJ Enriquez <dj.enriquez@infospace.com>
Donald Huang <don.hcd@gmail.com>
Doug Davis <dug@us.ibm.com>
Edgar Lee <edgar.lee@docker.com>
Eric Yang <windfarer@gmail.com>
Fabio Huser <fabio@fh1.ch>
farmerworking <farmerworking@gmail.com>
Felix Yan <felixonmars@archlinux.org>
Florentin Raud <florentin.raud@gmail.com>
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
gabriell nascimento <gabriell@bluesoft.com.br>
Gleb Schukin <gschukin@ptsecurity.com>
harche <p.harshal@gmail.com>
Henri Gomez <henri.gomez@gmail.com>
Hua Wang <wanghua.humble@gmail.com>
Hu Keping <hukeping@huawei.com>
HuKeping <hukeping@huawei.com>
Ian Babrou <ibobrik@gmail.com>
igayoso <igayoso@gmail.com>
Jack Griffin <jackpg14@gmail.com>
Jason Freidman <jason.freidman@gmail.com>
Jeff Nickoloff <jeff@allingeek.com>
Jessie Frazelle <jessie@docker.com>
jhaohai <jhaohai@foxmail.com>
Jianqing Wang <tsing@jianqing.org>
John Starks <jostarks@microsoft.com>
Jonathan Boulle <jonathanboulle@gmail.com>
Jon Johnson <jonjohnson@google.com>
Jon Poler <jonathan.poler@apcera.com>
Jordan Liggitt <jliggitt@redhat.com>
Josh Hawn <josh.hawn@docker.com>
Julien Fernandez <julien.fernandez@gmail.com>
Keerthan Mala <kmala@engineyard.com>
Kelsey Hightower <kelsey.hightower@gmail.com>
Kenneth Lim <kennethlimcp@gmail.com>
Kenny Leung <kleung@google.com>
Ke Xu <leonhartx.k@gmail.com>
liuchang0812 <liuchang0812@gmail.com>
Liu Hua <sdu.liu@huawei.com>
Li Yi <denverdino@gmail.com>
Louis Kottmann <louis.kottmann@gmail.com>
Luke Carpenter <x@rubynerd.net>
Mary Anthony <mary@docker.com>
Matt Bentley <mbentley@mbentley.net>
Matt Duch <matt@learnmetrics.com>
Matt Moore <mattmoor@google.com>
Matt Robenolt <matt@ydekproductions.com>
Michael Prokop <mika@grml.org>
Michal Minar <miminar@redhat.com>
Miquel Sabaté <msabate@suse.com>
Misty Stanley-Jones <misty@docker.com>
Morgan Bauer <mbauer@us.ibm.com>
moxiegirl <mary@docker.com>
Nathan Sullivan <nathan@nightsys.net>
nevermosby <robolwq@qq.com>
Nghia Tran <tcnghia@gmail.com>
Nikita Tarasov <nikita@mygento.ru>
Nuutti Kotivuori <nuutti.kotivuori@poplatek.fi>
Oilbeater <liumengxinfly@gmail.com>
Olivier Gambier <olivier@docker.com>
Olivier Jacques <olivier.jacques@hp.com>
Omer Cohen <git@omer.io>
Patrick Devine <patrick.devine@docker.com>
Phil Estes <estesp@linux.vnet.ibm.com>
Philip Misiowiec <philip@atlashealth.com>
Richard Scothern <richard.scothern@docker.com>
Rodolfo Carvalho <rhcarvalho@gmail.com>
Rusty Conover <rusty@luckydinosaur.com>
Sean Boran <Boran@users.noreply.github.com>
Sebastiaan van Stijn <github@gone.nl>
Sebastien Coavoux <s.coavoux@free.fr>
Serge Dubrouski <sergeyfd@gmail.com>
Sharif Nassar <sharif@mrwacky.com>
Shawn Falkner-Horine <dreadpirateshawn@gmail.com>
Shreyas Karnik <karnik.shreyas@gmail.com>
Simon Thulbourn <simon+github@thulbourn.com>
Spencer Rinehart <anubis@overthemonkey.com>
Stefan Majewsky <stefan.majewsky@sap.com>
Stefan Weil <sw@weilnetz.de>
Stephen J Day <stephen.day@docker.com>
Sungho Moon <sungho.moon@navercorp.com>
Sven Dowideit <SvenDowideit@home.org.au>
Sylvain Baubeau <sbaubeau@redhat.com>
Ted Reed <ted.reed@gmail.com>
tgic <farmer1992@gmail.com>
Thomas Sjögren <konstruktoid@users.noreply.github.com>
Tianon Gravi <admwiggin@gmail.com>
Tibor Vass <teabee89@gmail.com>
Tonis Tiigi <tonistiigi@gmail.com>
Tony Holdstock-Brown <tony@docker.com>
Trevor Pounds <trevor.pounds@gmail.com>
Troels Thomsen <troels@thomsen.io>
Victoria Bialas <victoria.bialas@docker.com>
Vincent Batts <vbatts@redhat.com>
Vincent Demeester <vincent@sbr.pm>
Vincent Giersch <vincent.giersch@ovh.net>
weiyuan.yl <weiyuan.yl@alibaba-inc.com>
W. Trevor King <wking@tremily.us>
xg.song <xg.song@venusource.com>
xiekeyang <xiekeyang@huawei.com>
Yann ROBERT <yann.robert@anantaplex.fr>
yuzou <zouyu7@huawei.com>
zhouhaibing089 <zhouhaibing089@gmail.com>
姜继忠 <jizhong.jiangjz@alibaba-inc.com>

View file

@ -11,7 +11,7 @@ Most people should use the [official Registry docker image](https://hub.docker.c
People looking for advanced operational use cases might consider rolling their own image with a custom Dockerfile inheriting `FROM registry:2`.
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).
OS X users who want to run natively can do so following [the instructions here](osx-setup-guide.md).
### Gotchas
@ -19,29 +19,19 @@ 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.
## Configure the development environment
## Build the development environment
The first prerequisite of properly building distribution targets is to have a Go
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
cd distribution
go get github.com/docker/distribution/cmd/registry
If you are planning to create a pull request with changes, you may want to clone directly from your [fork](https://help.github.com/en/articles/about-forks).
## Build and run from source
First, build the binaries:
$ make
+ bin/registry
+ bin/digest
+ bin/registry-api-descriptor-template
+ binaries
The above will install the source repository into the `GOPATH`.
Now create the directory for the registry data (this might require you to set permissions properly)
@ -52,56 +42,78 @@ Now create the directory for the registry data (this might require you to set pe
The `registry`
binary can then be run with the following:
$ ./bin/registry --version
./bin/registry github.com/distribution/distribution/v3 v2.7.0-1993-g8857a194
$ $GOPATH/bin/registry --version
$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:
$ ./bin/registry serve cmd/registry/config-dev.yml
INFO[0000] debug server listening :5001
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-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] 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] 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
$ $GOPATH/bin/registry serve $GOPATH/src/github.com/docker/distribution/cmd/registry/config-example.yml
INFO[0000] endpoint local-5003 disabled, skipping app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
INFO[0000] endpoint local-8083 disabled, skipping app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
INFO[0000] listening on :5000 app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
INFO[0000] debug server listening localhost:5001
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.
Please install the following into `GOPATH` for it to work:
Run `make` to build the binaries:
go get github.com/tools/godep github.com/golang/lint/golint
**TODO(stevvooe):** Add a `make setup` command to Makefile to run this. Have to think about how to interact with Godeps properly.
Once these commands are available in the `GOPATH`, run `make` to get a full
build:
$ make
+ bin/registry
+ bin/digest
+ bin/registry-api-descriptor-template
+ clean
+ fmt
+ 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
The above provides a repeatable build using the contents of the vendor
directory. We can verify this worked by running
The above provides a repeatable build using the contents of the vendored
Godeps 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:
$ ./bin/registry --version
./bin/registry github.com/distribution/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.
$ ./bin/registry -version
./bin/registry github.com/docker/distribution v2.0.0-alpha.2-80-g16d8b2c.m
### Optional build tags
Optional [build tags](http://golang.org/pkg/go/build/) can be provided using
the environment variable `BUILDTAGS`.
<dl>
<dt>noresumabledigest</dt>
<dd>Compiles without resumable digest support</dd>
<dt>include_gcs</dt>
<dd>Adds support for <a href="https://cloud.google.com/storage">Google Cloud Storage</a></dd>
</dl>
the environment variable `DOCKER_BUILDTAGS`.

View file

@ -2,8 +2,8 @@
## 2.5.0 (2016-06-14)
#### Storage
- Ensure uploads directory is cleaned after upload is committed
### Storage
- Ensure uploads directory is cleaned after upload is commited
- Add ability to cap concurrent operations in filesystem driver
- S3: Add 'us-gov-west-1' to the valid region list
- Swift: Handle ceph not returning Last-Modified header for HEAD requests
@ -23,11 +23,13 @@
- Update the auth spec scope grammar to reflect the fact that hostnames are optionally supported
- Clarify API documentation around catalog fetch behavior
#### API
### API
- Support returning HTTP 429 (Too Many Requests)
#### Documentation
### Documentation
- Update auth documentation examples to show "expires in" as int
#### Docker Image
### Docker Image
- Use Alpine Linux as base image

View file

@ -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.

View file

@ -1,15 +1,14 @@
# Contributing to the registry
## Before reporting an issue...
## Before reporting an issue...
### If your problem is with...
- automated builds or your [Docker Hub](https://hub.docker.com/) account
- Report it to [Hub Support](https://hub.docker.com/support/)
- Distributions of Docker for desktop or Linux
- Report [Mac Desktop issues](https://github.com/docker/for-mac)
- Report [Windows Desktop issues](https://github.com/docker/for-win)
- Report [Linux issues](https://github.com/docker/for-linux)
- automated builds
- your account on the [Docker Hub](https://hub.docker.com/)
- any other [Docker Hub](https://hub.docker.com/) issue
Then please do not report your issue here - you should instead report it to [https://support.docker.com](https://support.docker.com)
### If you...
@ -17,16 +16,10 @@
- can't figure out something
- 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.
[Click here for an invite to the CNCF community slack](https://slack.cncf.io/)
Then please do not open an issue here yet - you should first try one of the following support forums:
### Reporting security issues
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](mailto:cncf-distribution-security@lists.cncf.io).
- irc: #docker-distribution on freenode
- mailing-list: <distribution@dockerproject.org> or https://groups.google.com/a/dockerproject.org/forum/#!forum/distribution
## Reporting an issue properly
@ -34,7 +27,7 @@ By following these simple rules you will get better and faster feedback on your
- search the bugtracker for an already reported issue
### If you found an issue that describes your problem:
### If you found an issue that describes your problem:
- please read other user comments first, and confirm this is the same issue: a given error condition might be indicative of different problems - you may also find a workaround in the comments
- please refrain from adding "same thing here" or "+1" comments
@ -47,10 +40,10 @@ 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:
- bad title: "It doesn't work with my docker"
- 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 info`
- `docker exec <registry-container> registry --version`
- `docker exec <registry-container> registry -version`
3. copy the command line you used to launch your Registry
4. restart your docker daemon in debug mode (add `-D` to the daemon launch arguments)
5. reproduce your problem and get your docker daemon logs showing the error
@ -58,72 +51,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)
8. indicate if you are using an enterprise proxy, Nginx, or anything else between you and your Registry
## Contributing Code
Contributions should be made via pull requests. Pull requests will be reviewed
by one or more maintainers or reviewers and merged when acceptable.
## Contributing a patch for a known bug, or a small correction
You should follow the basic GitHub workflow:
1. Use your own [fork](https://help.github.com/en/articles/about-forks)
2. Create your [change](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes)
3. Test your code
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)
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)
1. fork
2. commit a change
3. make sure the tests pass
4. PR
Refer to [containerd's contribution guide](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes)
for tips on creating a successful contribution.
Additionally, you must [sign your commits](https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work). It's very simple:
## 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
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/)):
Some simple rules to ensure quick merge:
```
Developer Certificate of Origin
Version 1.1
- clearly point to the issue(s) you want to fix in your PR comment (e.g., `closes #12345`)
- prefer multiple (smaller) PRs addressing individual issues over a big one trying to address multiple issues at once
- 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.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
## Contributing new features
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
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.
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
have the right to submit it under the open source license
indicated in the file; or
Your PR will be reviewed by the community, then ultimately by the project maintainers, before being merged.
(b) The contribution is based upon previous work that, to the best
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
It's mandatory to:
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
- 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)
- address maintainers' comments and modify your submission accordingly
- write tests for any new code
(d) I understand and agree that this project and the contribution
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.
```
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.
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
commit automatically with `git commit -s`.
It is possible that the code base does not currently comply with these
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.

View file

@ -1,59 +1,17 @@
# syntax=docker/dockerfile:1
FROM golang:1.6-alpine
ARG GO_VERSION=1.19.10
ARG ALPINE_VERSION=3.18
ARG XX_VERSION=1.2.1
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
ENV DOCKER_BUILDTAGS include_oss include_gcs
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
COPY --from=xx / /
RUN apk add --no-cache bash coreutils file git
ENV GO111MODULE=auto
ENV CGO_ENABLED=0
WORKDIR /src
FROM base AS version
ARG PKG=github.com/distribution/distribution/v3
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); \
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;
FROM base AS build
ARG TARGETPLATFORM
ARG LDFLAGS="-s -w"
ARG BUILDTAGS="include_gcs"
RUN --mount=type=bind,target=/src,rw \
--mount=type=cache,target=/root/.cache/go-build \
--mount=target=/go/pkg/mod,type=cache \
--mount=type=bind,source=/tmp/.ldflags,target=/tmp/.ldflags,from=version \
set -x ; xx-go build -tags "${BUILDTAGS}" -trimpath -ldflags "$(cat /tmp/.ldflags) ${LDFLAGS}" -o /usr/bin/registry ./cmd/registry \
&& xx-verify --static /usr/bin/registry
FROM scratch AS binary
COPY --from=build /usr/bin/registry /
FROM base AS releaser
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
WORKDIR /work
RUN --mount=from=binary,target=/build \
--mount=type=bind,target=/src \
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version \
VERSION=$(cat /tmp/.version) \
&& mkdir -p /out \
&& cp /build/registry /src/README.md /src/LICENSE . \
&& tar -czvf "/out/registry_${VERSION#v}_${TARGETOS}_${TARGETARCH}${TARGETVARIANT}.tar.gz" * \
&& sha256sum -z "/out/registry_${VERSION#v}_${TARGETOS}_${TARGETARCH}${TARGETVARIANT}.tar.gz" | awk '{ print $1 }' > "/out/registry_${VERSION#v}_${TARGETOS}_${TARGETARCH}${TARGETVARIANT}.tar.gz.sha256"
FROM scratch AS artifact
COPY --from=releaser /out /
FROM alpine:${ALPINE_VERSION}
RUN apk add --no-cache ca-certificates
WORKDIR $DISTRIBUTION_DIR
COPY . $DISTRIBUTION_DIR
COPY cmd/registry/config-dev.yml /etc/docker/registry/config.yml
COPY --from=binary /registry /bin/registry
RUN set -ex \
&& apk add --no-cache make git
RUN make PREFIX=/go clean binaries
VOLUME ["/var/lib/registry"]
EXPOSE 5000
ENTRYPOINT ["registry"]

View file

@ -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?
Docker 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.

442
Godeps/Godeps.json generated Normal file
View file

@ -0,0 +1,442 @@
{
"ImportPath": "github.com/docker/distribution",
"GoVersion": "go1.6",
"GodepVersion": "v70",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/storage",
"Comment": "v1.2-334-g95361a2",
"Rev": "95361a2573b1fa92a00c5fc2707a80308483c6f9"
},
{
"ImportPath": "github.com/Sirupsen/logrus",
"Comment": "v0.7.3",
"Rev": "55eb11d21d2a31a3cc93838241d04800f52e823d"
},
{
"ImportPath": "github.com/Sirupsen/logrus/formatters/logstash",
"Comment": "v0.7.3",
"Rev": "55eb11d21d2a31a3cc93838241d04800f52e823d"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/awserr",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/awsutil",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/client",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/client/metadata",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/corehandlers",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/defaults",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/ec2metadata",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/request",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/session",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/endpoints",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/restxml",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/signer/v4",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/waiter",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/cloudfront/sign",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/s3",
"Comment": "v1.1.0-14-g49c3892",
"Rev": "49c3892b61af1d4996292a3025f36e4dfa25eaee"
},
{
"ImportPath": "github.com/bugsnag/bugsnag-go",
"Comment": "v1.0.2-5-gb1d1530",
"Rev": "b1d153021fcd90ca3f080db36bec96dc690fb274"
},
{
"ImportPath": "github.com/bugsnag/bugsnag-go/errors",
"Comment": "v1.0.2-5-gb1d1530",
"Rev": "b1d153021fcd90ca3f080db36bec96dc690fb274"
},
{
"ImportPath": "github.com/bugsnag/osext",
"Rev": "0dd3f918b21bec95ace9dc86c7e70266cfc5c702"
},
{
"ImportPath": "github.com/bugsnag/panicwrap",
"Comment": "1.0.0-2-ge2c2850",
"Rev": "e2c28503fcd0675329da73bf48b33404db873782"
},
{
"ImportPath": "github.com/denverdino/aliyungo/common",
"Rev": "6ffb587da9da6d029d0ce517b85fecc82172d502"
},
{
"ImportPath": "github.com/denverdino/aliyungo/oss",
"Rev": "6ffb587da9da6d029d0ce517b85fecc82172d502"
},
{
"ImportPath": "github.com/denverdino/aliyungo/util",
"Rev": "6ffb587da9da6d029d0ce517b85fecc82172d502"
},
{
"ImportPath": "github.com/docker/goamz/aws",
"Rev": "f0a21f5b2e12f83a505ecf79b633bb2035cf6f85"
},
{
"ImportPath": "github.com/docker/goamz/s3",
"Rev": "f0a21f5b2e12f83a505ecf79b633bb2035cf6f85"
},
{
"ImportPath": "github.com/docker/libtrust",
"Rev": "fa567046d9b14f6aa788882a950d69651d230b21"
},
{
"ImportPath": "github.com/garyburd/redigo/internal",
"Rev": "535138d7bcd717d6531c701ef5933d98b1866257"
},
{
"ImportPath": "github.com/garyburd/redigo/redis",
"Rev": "535138d7bcd717d6531c701ef5933d98b1866257"
},
{
"ImportPath": "github.com/go-ini/ini",
"Comment": "v1.8.6",
"Rev": "afbd495e5aaea13597b5e14fe514ddeaa4d76fc3"
},
{
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "8d92cf5fc15a4382f8964b08e1f42a75c0591aa3"
},
{
"ImportPath": "github.com/gorilla/context",
"Rev": "14f550f51af52180c2eefed15e5fd18d63c0a64a"
},
{
"ImportPath": "github.com/gorilla/handlers",
"Rev": "60c7bfde3e33c201519a200a4507a158cc03a17b"
},
{
"ImportPath": "github.com/gorilla/mux",
"Rev": "e444e69cbd2e2e3e0749a2f3c717cec491552bbf"
},
{
"ImportPath": "github.com/inconshreveable/mousetrap",
"Rev": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
},
{
"ImportPath": "github.com/jmespath/go-jmespath",
"Comment": "0.2.2-12-g0b12d6b",
"Rev": "0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74"
},
{
"ImportPath": "github.com/mitchellh/mapstructure",
"Rev": "482a9fd5fa83e8c4e7817413b80f3eb8feec03ef"
},
{
"ImportPath": "github.com/ncw/swift",
"Rev": "ce444d6d47c51d4dda9202cd38f5094dd8e27e86"
},
{
"ImportPath": "github.com/ncw/swift/swifttest",
"Rev": "ce444d6d47c51d4dda9202cd38f5094dd8e27e86"
},
{
"ImportPath": "github.com/spf13/cobra",
"Rev": "312092086bed4968099259622145a0c9ae280064"
},
{
"ImportPath": "github.com/spf13/pflag",
"Rev": "5644820622454e71517561946e3d94b9f9db6842"
},
{
"ImportPath": "github.com/stevvooe/resumable",
"Rev": "51ad44105773cafcbe91927f70ac68e1bf78f8b4"
},
{
"ImportPath": "github.com/stevvooe/resumable/sha256",
"Rev": "51ad44105773cafcbe91927f70ac68e1bf78f8b4"
},
{
"ImportPath": "github.com/stevvooe/resumable/sha512",
"Rev": "51ad44105773cafcbe91927f70ac68e1bf78f8b4"
},
{
"ImportPath": "github.com/yvasiyarov/go-metrics",
"Rev": "57bccd1ccd43f94bb17fdd8bf3007059b802f85e"
},
{
"ImportPath": "github.com/yvasiyarov/gorelic",
"Comment": "v0.0.6-8-ga9bba5b",
"Rev": "a9bba5b9ab508a086f9a12b8c51fab68478e2128"
},
{
"ImportPath": "github.com/yvasiyarov/newrelic_platform_go",
"Rev": "b21fdbd4370f3717f3bbd2bf41c223bc273068e6"
},
{
"ImportPath": "golang.org/x/crypto/bcrypt",
"Rev": "c10c31b5e94b6f7a0283272dc2bb27163dcea24b"
},
{
"ImportPath": "golang.org/x/crypto/blowfish",
"Rev": "c10c31b5e94b6f7a0283272dc2bb27163dcea24b"
},
{
"ImportPath": "golang.org/x/crypto/ocsp",
"Rev": "c10c31b5e94b6f7a0283272dc2bb27163dcea24b"
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "4876518f9e71663000c348837735820161a42df7"
},
{
"ImportPath": "golang.org/x/net/context/ctxhttp",
"Rev": "4876518f9e71663000c348837735820161a42df7"
},
{
"ImportPath": "golang.org/x/net/http2",
"Rev": "4876518f9e71663000c348837735820161a42df7"
},
{
"ImportPath": "golang.org/x/net/http2/hpack",
"Rev": "4876518f9e71663000c348837735820161a42df7"
},
{
"ImportPath": "golang.org/x/net/internal/timeseries",
"Rev": "4876518f9e71663000c348837735820161a42df7"
},
{
"ImportPath": "golang.org/x/net/trace",
"Rev": "4876518f9e71663000c348837735820161a42df7"
},
{
"ImportPath": "golang.org/x/oauth2",
"Rev": "045497edb6234273d67dbc25da3f2ddbc4c4cacf"
},
{
"ImportPath": "golang.org/x/oauth2/google",
"Rev": "045497edb6234273d67dbc25da3f2ddbc4c4cacf"
},
{
"ImportPath": "golang.org/x/oauth2/internal",
"Rev": "045497edb6234273d67dbc25da3f2ddbc4c4cacf"
},
{
"ImportPath": "golang.org/x/oauth2/jws",
"Rev": "045497edb6234273d67dbc25da3f2ddbc4c4cacf"
},
{
"ImportPath": "golang.org/x/oauth2/jwt",
"Rev": "045497edb6234273d67dbc25da3f2ddbc4c4cacf"
},
{
"ImportPath": "golang.org/x/time/rate",
"Rev": "a4bde12657593d5e90d0533a3e4fd95e635124cb"
},
{
"ImportPath": "google.golang.org/api/gensupport",
"Rev": "9bf6e6e569ff057f75d9604a46c52928f17d2b54"
},
{
"ImportPath": "google.golang.org/api/googleapi",
"Rev": "9bf6e6e569ff057f75d9604a46c52928f17d2b54"
},
{
"ImportPath": "google.golang.org/api/googleapi/internal/uritemplates",
"Rev": "9bf6e6e569ff057f75d9604a46c52928f17d2b54"
},
{
"ImportPath": "google.golang.org/api/storage/v1",
"Rev": "9bf6e6e569ff057f75d9604a46c52928f17d2b54"
},
{
"ImportPath": "google.golang.org/appengine",
"Rev": "12d5545dc1cfa6047a286d5e853841b6471f4c19"
},
{
"ImportPath": "google.golang.org/appengine/internal",
"Rev": "12d5545dc1cfa6047a286d5e853841b6471f4c19"
},
{
"ImportPath": "google.golang.org/appengine/internal/app_identity",
"Rev": "12d5545dc1cfa6047a286d5e853841b6471f4c19"
},
{
"ImportPath": "google.golang.org/appengine/internal/base",
"Rev": "12d5545dc1cfa6047a286d5e853841b6471f4c19"
},
{
"ImportPath": "google.golang.org/appengine/internal/datastore",
"Rev": "12d5545dc1cfa6047a286d5e853841b6471f4c19"
},
{
"ImportPath": "google.golang.org/appengine/internal/log",
"Rev": "12d5545dc1cfa6047a286d5e853841b6471f4c19"
},
{
"ImportPath": "google.golang.org/appengine/internal/modules",
"Rev": "12d5545dc1cfa6047a286d5e853841b6471f4c19"
},
{
"ImportPath": "google.golang.org/appengine/internal/remote_api",
"Rev": "12d5545dc1cfa6047a286d5e853841b6471f4c19"
},
{
"ImportPath": "google.golang.org/cloud",
"Rev": "975617b05ea8a58727e6c1a06b6161ff4185a9f2"
},
{
"ImportPath": "google.golang.org/cloud/compute/metadata",
"Rev": "975617b05ea8a58727e6c1a06b6161ff4185a9f2"
},
{
"ImportPath": "google.golang.org/cloud/internal",
"Rev": "975617b05ea8a58727e6c1a06b6161ff4185a9f2"
},
{
"ImportPath": "google.golang.org/cloud/internal/opts",
"Rev": "975617b05ea8a58727e6c1a06b6161ff4185a9f2"
},
{
"ImportPath": "google.golang.org/cloud/storage",
"Rev": "975617b05ea8a58727e6c1a06b6161ff4185a9f2"
},
{
"ImportPath": "google.golang.org/grpc",
"Rev": "d3ddb4469d5a1b949fc7a7da7c1d6a0d1b6de994"
},
{
"ImportPath": "google.golang.org/grpc/codes",
"Rev": "d3ddb4469d5a1b949fc7a7da7c1d6a0d1b6de994"
},
{
"ImportPath": "google.golang.org/grpc/credentials",
"Rev": "d3ddb4469d5a1b949fc7a7da7c1d6a0d1b6de994"
},
{
"ImportPath": "google.golang.org/grpc/grpclog",
"Rev": "d3ddb4469d5a1b949fc7a7da7c1d6a0d1b6de994"
},
{
"ImportPath": "google.golang.org/grpc/internal",
"Rev": "d3ddb4469d5a1b949fc7a7da7c1d6a0d1b6de994"
},
{
"ImportPath": "google.golang.org/grpc/metadata",
"Rev": "d3ddb4469d5a1b949fc7a7da7c1d6a0d1b6de994"
},
{
"ImportPath": "google.golang.org/grpc/naming",
"Rev": "d3ddb4469d5a1b949fc7a7da7c1d6a0d1b6de994"
},
{
"ImportPath": "google.golang.org/grpc/peer",
"Rev": "d3ddb4469d5a1b949fc7a7da7c1d6a0d1b6de994"
},
{
"ImportPath": "google.golang.org/grpc/transport",
"Rev": "d3ddb4469d5a1b949fc7a7da7c1d6a0d1b6de994"
},
{
"ImportPath": "gopkg.in/check.v1",
"Rev": "64131543e7896d5bcc6bd5a76287eb75ea96c673"
},
{
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "bef53efd0c76e49e6de55ead051f886bea7e9420"
},
{
"ImportPath": "rsc.io/letsencrypt",
"Rev": "a019c9e6fce0c7132679dea13bd8df7c86ffe26c"
}
]
}

5
Godeps/Readme generated Normal file
View file

@ -0,0 +1,5 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

View file

@ -1,26 +1,58 @@
# 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)
# GitHub ID, Name, Email address
"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","milos.gajdos@docker.com"
"sargun","Sargun Dhillon","sargun@sargun.me"
"wy65701436","Wang Yan","wangyan@vmware.com"
"stevelasker","Steve Lasker","steve.lasker@microsoft.com"
# It is structured to be consumable by both humans and programs.
# To extract its contents programmatically, use any TOML-compliant parser.
#
# REVIEWERS
# GitHub ID, Name, Email address
"dmcgowan","Derek McGowan","derek@mcgstyle.net"
"stevvooe","Stephen Day","stevvooe@gmail.com"
"thajeztah","Sebastiaan van Stijn","github@gone.nl"
"DavidSpek", "David van der Spek", "vanderspek.david@gmail.com"
"Jamstah", "James Hewitt", "james.hewitt@gmail.com"
# This file is compiled into the MAINTAINERS file in docker/opensource.
#
[Org]
[Org."Core maintainers"]
people = [
"aaronlehmann",
"dmcgowan",
"dmp42",
"richardscothern",
"shykes",
"stevvooe",
]
[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.aaronlehmann]
Name = "Aaron Lehmann"
Email = "aaron.lehmann@docker.com"
GitHub = "aaronlehmann"
[people.dmcgowan]
Name = "Derek McGowan"
Email = "derek@mcgstyle.net"
GitHub = "dmcgowan"
[people.dmp42]
Name = "Olivier Gambier"
Email = "olivier@docker.com"
GitHub = "dmp42"
[people.richardscothern]
Name = "Richard Scothern"
Email = "richard.scothern@gmail.com"
GitHub = "richardscothern"
[people.shykes]
Name = "Solomon Hykes"
Email = "solomon@docker.com"
GitHub = "shykes"
[people.stevvooe]
Name = "Stephen Day"
Email = "stephen.day@docker.com"
GitHub = "stevvooe"

160
Makefile
View file

@ -1,21 +1,9 @@
# Root directory of the project (absolute path).
ROOTDIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))
# Set an output prefix, which is the local directory if not specified
PREFIX?=$(shell pwd)
# Used to populate version variable in main package.
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)
PKG=github.com/distribution/distribution/v3
# Project packages.
PACKAGES=$(shell go list -tags "${BUILDTAGS}" ./... | grep -v /vendor/)
INTEGRATION_PACKAGE=${PKG}
COVERAGE_PACKAGES=$(filter-out ${PKG}/registry/storage/driver/%,${PACKAGES})
# Project binaries.
COMMANDS=registry digest registry-api-descriptor-template
VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always)
# Allow turning off function inlining and variable registerization
ifeq (${DISABLE_OPTIMIZATION},true)
@ -23,98 +11,96 @@ ifeq (${DISABLE_OPTIMIZATION},true)
VERSION:="$(VERSION)-noopt"
endif
WHALE = "+"
GO_LDFLAGS=-ldflags "-X `go list ./version`.Version=$(VERSION)"
# Go files
#
TESTFLAGS_RACE=
GOFILES=$(shell find . -type f -name '*.go')
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.Package=$(PKG) $(EXTRA_LDFLAGS)'
BINARIES=$(addprefix bin/,$(COMMANDS))
# Flags passed to `go test`
TESTFLAGS ?= -v $(TESTFLAGS_RACE)
TESTFLAGS_PARALLEL ?= 8
.PHONY: all build binaries clean test test-race test-full integration coverage validate lint validate-git validate-vendor vendor mod-outdated
.PHONY: clean all fmt vet lint build test binaries
.DEFAULT: all
all: fmt vet lint build test binaries
all: binaries
AUTHORS: .mailmap .git/HEAD
git log --format='%aN <%aE>' | sort -fu > $@
# This only needs to be generated by hand when cutting full releases.
version/version.go:
@echo "$(WHALE) $@"
./version/version.sh > $@
test: ## run tests, except integration test with test.short
@echo "$(WHALE) $@"
@go test ${GO_TAGS} -test.short ${TESTFLAGS} $(filter-out ${INTEGRATION_PACKAGE},${PACKAGES})
# Required for go 1.5 to build
GO15VENDOREXPERIMENT := 1
test-race: ## run tests, except integration test with test.short and race
@echo "$(WHALE) $@"
@go test ${GO_TAGS} -race -test.short ${TESTFLAGS} $(filter-out ${INTEGRATION_PACKAGE},${PACKAGES})
# Package list
PKGS := $(shell go list -tags "${DOCKER_BUILDTAGS}" ./... | grep -v ^github.com/docker/distribution/vendor/)
test-full: ## run tests, except integration tests
@echo "$(WHALE) $@"
@go test ${GO_TAGS} ${TESTFLAGS} $(filter-out ${INTEGRATION_PACKAGE},${PACKAGES})
# Resolving binary dependencies for specific targets
GOLINT := $(shell which golint || echo '')
GODEP := $(shell which godep || echo '')
integration: ## run integration tests
@echo "$(WHALE) $@"
@go test ${TESTFLAGS} -parallel ${TESTFLAGS_PARALLEL} ${INTEGRATION_PACKAGE}
${PREFIX}/bin/registry: $(wildcard **/*.go)
@echo "+ $@"
@go build -tags "${DOCKER_BUILDTAGS}" -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/registry
coverage: ## generate coverprofiles from the unit tests
@echo "$(WHALE) $@"
@rm -f coverage.txt
@go test ${GO_TAGS} -i ${TESTFLAGS} $(filter-out ${INTEGRATION_PACKAGE},${COVERAGE_PACKAGES}) 2> /dev/null
@( for pkg in $(filter-out ${INTEGRATION_PACKAGE},${COVERAGE_PACKAGES}); do \
go test ${GO_TAGS} ${TESTFLAGS} \
-cover \
-coverprofile=profile.out \
-covermode=atomic $$pkg || exit; \
if [ -f profile.out ]; then \
cat profile.out >> coverage.txt; \
rm profile.out; \
fi; \
done )
${PREFIX}/bin/digest: $(wildcard **/*.go)
@echo "+ $@"
@go build -tags "${DOCKER_BUILDTAGS}" -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/digest
FORCE:
${PREFIX}/bin/registry-api-descriptor-template: $(wildcard **/*.go)
@echo "+ $@"
@go build -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/registry-api-descriptor-template
# Build a binary from a cmd.
bin/%: cmd/% FORCE
@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} ./$<
docs/spec/api.md: docs/spec/api.md.tmpl ${PREFIX}/bin/registry-api-descriptor-template
./bin/registry-api-descriptor-template $< > $@
binaries: $(BINARIES) ## build binaries
@echo "$(WHALE) $@"
vet:
@echo "+ $@"
@go vet -tags "${DOCKER_BUILDTAGS}" $(PKGS)
fmt:
@echo "+ $@"
@test -z "$$(gofmt -s -l . 2>&1 | grep -v ^vendor/ | tee /dev/stderr)" || \
(echo >&2 "+ please format Go code with 'gofmt -s'" && false)
lint:
@echo "+ $@"
$(if $(GOLINT), , \
$(error Please install golint: `go get -u github.com/golang/lint/golint`))
@test -z "$$($(GOLINT) ./... 2>&1 | grep -v ^vendor/ | tee /dev/stderr)"
build:
@echo "$(WHALE) $@"
@go build -buildmode=pie ${GO_GCFLAGS} ${GO_BUILD_FLAGS} ${GO_LDFLAGS} --ldflags '-extldflags "-Wl,-z,now" -s' ${GO_TAGS} $(PACKAGES)
@echo "+ $@"
@go build -tags "${DOCKER_BUILDTAGS}" -v ${GO_LDFLAGS} $(PKGS)
clean: ## clean up binaries
@echo "$(WHALE) $@"
@rm -f $(BINARIES)
test:
@echo "+ $@"
@go test -test.short -tags "${DOCKER_BUILDTAGS}" $(PKGS)
validate: ## run all validators
docker buildx bake $@
test-full:
@echo "+ $@"
@go test -tags "${DOCKER_BUILDTAGS}" $(PKGS)
lint: ## run all linters
docker buildx bake $@
binaries: ${PREFIX}/bin/registry ${PREFIX}/bin/digest ${PREFIX}/bin/registry-api-descriptor-template
@echo "+ $@"
validate-git: ## validate git
docker buildx bake $@
clean:
@echo "+ $@"
@rm -rf "${PREFIX}/bin/registry" "${PREFIX}/bin/digest" "${PREFIX}/bin/registry-api-descriptor-template"
validate-vendor: ## validate vendor
docker buildx bake $@
dep-save:
@echo "+ $@"
$(if $(GODEP), , \
$(error Please install godep: go get github.com/tools/godep))
@$(GODEP) save $(PKGS)
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)/*
dep-restore:
@echo "+ $@"
$(if $(GODEP), , \
$(error Please install godep: go get github.com/tools/godep))
@$(GODEP) restore -v
mod-outdated: ## check outdated dependencies
docker buildx bake $@
dep-validate: dep-restore
@echo "+ $@"
@rm -Rf .vendor.bak
@mv vendor .vendor.bak
@rm -Rf Godeps
@$(GODEP) save ./...
@test -z "$$(diff -r vendor .vendor.bak 2>&1 | tee /dev/stderr)" || \
(echo >&2 "+ borked dependencies! what you have in Godeps/Godeps.json does not match with what you have in vendor" && false)
@rm -Rf .vendor.bak

137
README.md
View file

@ -1,41 +1,32 @@
<p align="center">
<img style="align: center; padding-left: 10px; padding-right: 10px; padding-bottom: 10px;" width="238px" height="238px" src="./distribution-logo.svg" />
</p>
# Distribution
[![Build Status](https://github.com/distribution/distribution/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/distribution/distribution/actions?query=workflow%3ACI)
[![GoDoc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/distribution/distribution)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
[![codecov](https://codecov.io/gh/distribution/distribution/branch/main/graph/badge.svg)](https://codecov.io/gh/distribution/distribution)
[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Fdistribution.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Fdistribution?ref=badge_shield)
[![OCI Conformance](https://github.com/distribution/distribution/workflows/conformance/badge.svg)](https://github.com/distribution/distribution/actions?query=workflow%3Aconformance)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/distribution/distribution/badge)](https://api.securityscorecards.dev/projects/github.com/distribution/distribution)
The Docker toolset to pack, ship, store, and deliver content.
The toolset to pack, ship, store, and deliver content.
This repository's main product is 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
for storing and distributing container images using the
[OCI Distribution Specification](https://github.com/opencontainers/distribution-spec).
The goal of this project is to provide a simple, secure, and scalable base
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.
<img src="https://www.docker.com/sites/default/files/oyster-registry-3.png" width=200px/>
[![Circle CI](https://circleci.com/gh/docker/distribution/tree/master.svg?style=svg)](https://circleci.com/gh/docker/distribution/tree/master)
[![GoDoc](https://godoc.org/github.com/docker/distribution?status.svg)](https://godoc.org/github.com/docker/distribution)
This repository contains the following components:
|**Component** |Description |
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **registry** | An implementation of the [OCI Distribution Specification](https://github.com/opencontainers/distribution-spec). |
| **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**. |
| **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. |
| **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://godoc.org/github.com/docker/distribution) for details. **Note**: These libraries are **unstable**. |
| **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/index.md) 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
registry using HTTP. This project contains a client implementation which
is currently in use by Docker, however, it is deprecated for the
[implementation in containerd](https://github.com/containerd/containerd/tree/master/remotes/docker)
and will not support new features.
This project should provide an implementation to a V2 API for use in the [Docker
core project](https://github.com/docker/docker). The API should be embeddable
and simplify the process of securely pulling and pushing content from `docker`
daemons.
### What are the long term goals of the Distribution project?
@ -52,23 +43,89 @@ system that allow users to:
* Implement their own home made solution through good specs, and solid
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](docs/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
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](docs/recipes/building.md).
## Communication
## Support
For async communication and long running discussions please use issues and pull requests on the github repo.
This will be the best place to discuss design and implementation.
If any issues are encountered while using the _Distribution_ project, several
avenues are available for support:
For sync communication we have a #distribution channel in the [CNCF Slack](https://slack.cncf.io/)
that everyone is welcome to join and chat about development.
<table>
<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).
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
copy of the license, titled CC-BY-4.0, at http://creativecommons.org/licenses/by/4.0/.
## License
This project is distributed under [Apache License, Version 2.0](LICENSE).

View file

@ -1,16 +1,267 @@
# 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,
that can be customised for different backends and use cases. This is used by many
of the largest registry operators, including Docker Hub, GitHub, GitLab, Harbor
and Digital Ocean.
2. A reference implementation of the OCI registry standards, and an easy way to
experiment with new propsals in the registry space as these standards change.
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](#distribution-goals)
* [Distribution Components](#distribution-components)
* [Project Planning](#project-planning): release-relationship to the Docker Platform.
This road map is a living document, providing an overview of the goals and
considerations made in respect of the future of the project.
## 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.

View file

@ -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.

View file

@ -1,16 +1,15 @@
package distribution
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"time"
"github.com/distribution/distribution/v3/reference"
"github.com/opencontainers/go-digest"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/docker/distribution/context"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/reference"
)
var (
@ -63,23 +62,16 @@ type Descriptor struct {
// encoded as utf-8.
MediaType string `json:"mediaType,omitempty"`
// Digest uniquely identifies the content. A byte stream can be verified
// against this digest.
Digest digest.Digest `json:"digest,omitempty"`
// Size in bytes of content.
Size int64 `json:"size,omitempty"`
// Digest uniquely identifies the content. A byte stream can be verified
// against against this digest.
Digest digest.Digest `json:"digest,omitempty"`
// URLs contains the source URLs of this content.
URLs []string `json:"urls,omitempty"`
// Annotations contains arbitrary metadata relating to the targeted content.
Annotations map[string]string `json:"annotations,omitempty"`
// Platform describes the platform which the image in the manifest runs on.
// This should only be used when referring to a manifest.
Platform *v1.Platform `json:"platform,omitempty"`
// NOTE: Before adding a field here, please ensure that all
// other options have been exhausted. Much of the type relationships
// depend on the simplicity of this type.
@ -142,23 +134,25 @@ type BlobDescriptorServiceFactory interface {
// ReadSeekCloser is the primary reader type for blob data, combining
// io.ReadSeeker with io.Closer.
//
// Deprecated: use [io.ReadSeekCloser].
type ReadSeekCloser = io.ReadSeekCloser
type ReadSeekCloser interface {
io.ReadSeeker
io.Closer
}
// BlobProvider describes operations for getting blob data.
type BlobProvider interface {
// Get returns the entire blob identified by digest along with the descriptor.
Get(ctx context.Context, dgst digest.Digest) ([]byte, error)
// Open provides an [io.ReadSeekCloser] to the blob identified by the provided
// descriptor. If the blob is not known to the service, an error is returned.
Open(ctx context.Context, dgst digest.Digest) (io.ReadSeekCloser, error)
// Open provides a ReadSeekCloser to the blob identified by the provided
// descriptor. If the blob is not known to the service, an error will be
// returned.
Open(ctx context.Context, dgst digest.Digest) (ReadSeekCloser, error)
}
// BlobServer can serve blobs via http.
type BlobServer interface {
// ServeBlob attempts to serve the blob, identified by dgst, via http. The
// ServeBlob attempts to serve the blob, identifed by dgst, via http. The
// service may decide to redirect the client elsewhere or serve the data
// directly.
//
@ -198,18 +192,6 @@ type BlobCreateOption interface {
Apply(interface{}) error
}
// CreateOptions is a collection of blob creation modifiers relevant to general
// blob storage intended to be configured by the BlobCreateOption.Apply method.
type CreateOptions struct {
Mount struct {
ShouldMount bool
From reference.Canonical
// Stat allows to pass precalculated descriptor to link and return.
// Blob access check will be skipped if set.
Stat *Descriptor
}
}
// BlobWriter provides a handle for inserting data into a blob store.
// Instances should be obtained from BlobWriteService.Writer and
// BlobWriteService.Resume. If supported by the store, a writer can be

89
circle.yml Normal file
View file

@ -0,0 +1,89 @@
# Pony-up!
machine:
pre:
# Install gvm
- bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/1.0.22/binscripts/gvm-installer)
# Install codecov for coverage
- pip install --user codecov
post:
# go
- gvm install go1.6 --prefer-binary --name=stable
environment:
# Convenient shortcuts to "common" locations
CHECKOUT: /home/ubuntu/$CIRCLE_PROJECT_REPONAME
BASE_DIR: src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
# Trick circle brainflat "no absolute path" behavior
BASE_STABLE: ../../../$HOME/.gvm/pkgsets/stable/global/$BASE_DIR
DOCKER_BUILDTAGS: "include_oss include_gcs"
# Workaround Circle parsing dumb bugs and/or YAML wonkyness
CIRCLE_PAIN: "mode: set"
hosts:
# Not used yet
fancy: 127.0.0.1
dependencies:
pre:
# Copy the code to the gopath of all go versions
- >
gvm use stable &&
mkdir -p "$(dirname $BASE_STABLE)" &&
cp -R "$CHECKOUT" "$BASE_STABLE"
override:
# Install dependencies for every copied clone/go version
- gvm use stable && go get github.com/tools/godep:
pwd: $BASE_STABLE
post:
# For the stable go version, additionally install linting tools
- >
gvm use stable &&
go get github.com/axw/gocov/gocov github.com/golang/lint/golint
test:
pre:
# Output the go versions we are going to test
# - gvm use old && go version
- gvm use stable && go version
# Ensure validation of dependencies
- gvm use stable && if test -n "`git diff --stat=1000 master | grep -Ei \"vendor|godeps\"`"; then make dep-validate; fi:
pwd: $BASE_STABLE
# First thing: build everything. This will catch compile errors, and it's
# also necessary for go vet to work properly (see #807).
- gvm use stable && godep go install $(go list ./... | grep -v "/vendor/"):
pwd: $BASE_STABLE
# FMT
- gvm use stable && make fmt:
pwd: $BASE_STABLE
# VET
- gvm use stable && make vet:
pwd: $BASE_STABLE
# LINT
- gvm use stable && make lint:
pwd: $BASE_STABLE
override:
# Test stable, and report
- gvm use stable; export ROOT_PACKAGE=$(go list .); go list -tags "$DOCKER_BUILDTAGS" ./... | grep -v "/vendor/" | xargs -L 1 -I{} bash -c 'export PACKAGE={}; godep go test -tags "$DOCKER_BUILDTAGS" -test.short -coverprofile=$GOPATH/src/$PACKAGE/coverage.out -coverpkg=$(./coverpkg.sh $PACKAGE $ROOT_PACKAGE) $PACKAGE':
timeout: 600
pwd: $BASE_STABLE
post:
# Report to codecov
- bash <(curl -s https://codecov.io/bash):
pwd: $BASE_STABLE
## Notes
# Disabled the -race detector due to massive memory usage.
# Do we want these as well?
# - go get code.google.com/p/go.tools/cmd/goimports
# - test -z "$(goimports -l -w ./... | tee /dev/stderr)"
# http://labix.org/gocheck

View file

@ -7,11 +7,8 @@ import (
"log"
"os"
"github.com/distribution/distribution/v3/version"
"github.com/opencontainers/go-digest"
_ "crypto/sha256"
_ "crypto/sha512"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/version"
)
var (
@ -35,7 +32,7 @@ func init() {
func usage() {
fmt.Fprintf(os.Stderr, "usage: %s [files...]\n", os.Args[0])
fmt.Fprint(os.Stderr, `
fmt.Fprintf(os.Stderr, `
Calculate the digest of one or more input files, emitting the result
to standard out. If no files are provided, the digest of stdin will
be calculated.
@ -62,6 +59,7 @@ func main() {
if flag.NArg() > 0 {
for _, path := range flag.Args() {
fp, err := os.Open(path)
if err != nil {
log.Printf("%s: %v", path, err)
fail = true

View file

@ -4,7 +4,7 @@
// For example, to generate a new API specification, one would execute the
// following command from the repo root:
//
// $ registry-api-descriptor-template docs/spec/api.md.tmpl > docs/spec/api.md
// $ registry-api-descriptor-template docs/spec/api.md.tmpl > docs/spec/api.md
//
// The templates are passed in the api/v2.APIDescriptor object. Please see the
// package documentation for fields available on that object. The template
@ -20,13 +20,14 @@ import (
"regexp"
"text/template"
"github.com/distribution/distribution/v3/registry/api/errcode"
v2 "github.com/distribution/distribution/v3/registry/api/v2"
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2"
)
var spaceRegex = regexp.MustCompile(`\n\s*`)
func main() {
if len(os.Args) != 2 {
log.Fatalln("please specify a template to execute.")
}
@ -126,4 +127,5 @@ end:
}
return output
}

View file

@ -29,8 +29,6 @@ redis:
readtimeout: 10ms
writetimeout: 10ms
notifications:
events:
includereferences: true
endpoints:
- name: local-8082
url: http://localhost:5003/callback

View file

@ -22,7 +22,7 @@ storage:
delete:
enabled: true
cache:
blobdescriptor: inmemory
blobdescriptor: redis
filesystem:
rootdirectory: /var/lib/registry
maintenance:
@ -31,10 +31,7 @@ storage:
http:
addr: :5000
debug:
addr: :5001
prometheus:
enabled: true
path: /metrics
addr: localhost:5001
headers:
X-Content-Type-Options: [nosniff]
redis:
@ -47,8 +44,6 @@ redis:
readtimeout: 10ms
writetimeout: 10ms
notifications:
events:
includereferences: true
endpoints:
- name: local-5003
url: http://localhost:5003/callback

View file

@ -11,10 +11,6 @@ http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
auth:
htpasswd:
realm: basic-realm
path: /etc/registry
health:
storagedriver:
enabled: true

View file

@ -3,18 +3,21 @@ package main
import (
_ "net/http/pprof"
"github.com/distribution/distribution/v3/registry"
_ "github.com/distribution/distribution/v3/registry/auth/htpasswd"
_ "github.com/distribution/distribution/v3/registry/auth/silly"
_ "github.com/distribution/distribution/v3/registry/auth/token"
_ "github.com/distribution/distribution/v3/registry/proxy"
_ "github.com/distribution/distribution/v3/registry/storage/driver/azure"
_ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
_ "github.com/distribution/distribution/v3/registry/storage/driver/gcs"
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
_ "github.com/distribution/distribution/v3/registry/storage/driver/middleware/cloudfront"
_ "github.com/distribution/distribution/v3/registry/storage/driver/middleware/redirect"
_ "github.com/distribution/distribution/v3/registry/storage/driver/s3-aws"
"github.com/docker/distribution/registry"
_ "github.com/docker/distribution/registry/auth/htpasswd"
_ "github.com/docker/distribution/registry/auth/silly"
_ "github.com/docker/distribution/registry/auth/token"
_ "github.com/docker/distribution/registry/proxy"
_ "github.com/docker/distribution/registry/storage/driver/azure"
_ "github.com/docker/distribution/registry/storage/driver/filesystem"
_ "github.com/docker/distribution/registry/storage/driver/gcs"
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
_ "github.com/docker/distribution/registry/storage/driver/middleware/cloudfront"
_ "github.com/docker/distribution/registry/storage/driver/middleware/redirect"
_ "github.com/docker/distribution/registry/storage/driver/oss"
_ "github.com/docker/distribution/registry/storage/driver/s3-aws"
_ "github.com/docker/distribution/registry/storage/driver/s3-goamz"
_ "github.com/docker/distribution/registry/storage/driver/swift"
)
func main() {

View file

@ -1,9 +1,9 @@
package configuration
import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"strings"
@ -22,14 +22,8 @@ type Configuration struct {
// Log supports setting various parameters related to the logging
// subsystem.
Log struct {
// AccessLog configures access logging.
AccessLog struct {
// Disabled disables access logging.
Disabled bool `yaml:"disabled,omitempty"`
} `yaml:"accesslog,omitempty"`
// Level is the granularity at which registry operations are logged.
Level Loglevel `yaml:"level,omitempty"`
Level Loglevel `yaml:"level"`
// Formatter overrides the default formatter with another. Options
// include "text", "json" and "logstash".
@ -39,17 +33,13 @@ type Configuration struct {
// the logger context.
Fields map[string]interface{} `yaml:"fields,omitempty"`
// Hooks allows users to configure the log hooks, to enabling the
// Hooks allows users to configurate the log hooks, to enabling the
// sequent handling behavior, when defined levels of log message emit.
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.
//
// Deprecated: Use Log.Level instead.
// Loglevel is the level at which registry operations are logged. This is
// deprecated. Please use Log.Level in the future.
Loglevel Loglevel `yaml:"loglevel,omitempty"`
// Storage is the configuration for the registry's storage driver
@ -87,10 +77,6 @@ type Configuration struct {
// Location headers
RelativeURLs bool `yaml:"relativeurls,omitempty"`
// Amount of time to wait for connection to drain before shutting down when registry
// receives a stop signal
DrainTimeout time.Duration `yaml:"draintimeout,omitempty"`
// TLS instructs the http server to listen with a TLS configuration.
// This only support simple tls configuration with a cert and key.
// Mostly, this is useful for testing situations or simple deployments
@ -110,12 +96,6 @@ type Configuration struct {
// A file may contain multiple CA certificates encoded as PEM
ClientCAs []string `yaml:"clientcas,omitempty"`
// Specifies the lowest TLS version allowed
MinimumTLS string `yaml:"minimumtls,omitempty"`
// Specifies a list of cipher suites allowed
CipherSuites []string `yaml:"ciphersuites,omitempty"`
// LetsEncrypt is used to configuration setting up TLS through
// Let's Encrypt instead of manually specifying certificate and
// key. If a TLS certificate is specified, the Let's Encrypt
@ -127,14 +107,6 @@ type Configuration struct {
// Email is the email to use during Let's Encrypt registration
Email string `yaml:"email,omitempty"`
// Hosts specifies the hosts which are allowed to obtain Let's
// Encrypt certificates.
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:"tls,omitempty"`
@ -150,19 +122,7 @@ type Configuration struct {
Debug struct {
// Addr specifies the bind address for the debug server.
Addr string `yaml:"addr,omitempty"`
// Prometheus configures the Prometheus telemetry endpoint.
Prometheus struct {
Enabled bool `yaml:"enabled,omitempty"`
Path string `yaml:"path,omitempty"`
} `yaml:"prometheus,omitempty"`
} `yaml:"debug,omitempty"`
// HTTP2 configuration options
HTTP2 struct {
// Specifies whether the registry should disallow clients attempting
// to connect via http2. If set to true, only http/1.1 is supported.
Disabled bool `yaml:"disabled,omitempty"`
} `yaml:"http2,omitempty"`
} `yaml:"http,omitempty"`
// Notifications specifies configuration about various endpoint to which
@ -174,20 +134,12 @@ type Configuration struct {
// Addr specifies the the redis instance available to the application.
Addr string `yaml:"addr,omitempty"`
// Usernames can be used as a finer-grained permission control since the introduction of the redis 6.0.
Username string `yaml:"username,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"`
// TLS configures settings for redis in-transit encryption
TLS struct {
Enabled bool `yaml:"enabled,omitempty"`
} `yaml:"tls,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
@ -207,78 +159,19 @@ type Configuration struct {
} `yaml:"pool,omitempty"`
} `yaml:"redis,omitempty"`
Health Health `yaml:"health,omitempty"`
Catalog Catalog `yaml:"catalog,omitempty"`
Health Health `yaml:"health,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.
//
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since
// 2015. These options should only be used if you need to provide
// backward compatibility.
// Schema1 configures how schema1 manifests will be handled
Schema1 struct {
// TrustKey is the signing key to use for adding the signature to
// schema1 manifests.
//
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since
// 2015. These options should only be used if you need to provide
// backward compatibility.
TrustKey string `yaml:"signingkeyfile,omitempty"`
// Enabled determines if schema1 manifests should be pullable.
//
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since
// 2015. These options should only be used if you need to provide
// backward compatibility.
Enabled bool `yaml:"enabled,omitempty"`
} `yaml:"schema1,omitempty"`
} `yaml:"compatibility,omitempty"`
// Validation configures validation options for the registry.
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 struct {
// Repository configures policies for repositories
Repository struct {
// Classes is a list of repository classes which the
// registry allows content for. This class is matched
// against the configuration media type inside uploaded
// manifests. When non-empty, the registry will enforce
// the class in authorized resources.
Classes []string `yaml:"classes"`
} `yaml:"repository,omitempty"`
} `yaml:"policy,omitempty"`
}
// Catalog is composed of MaxEntries.
// Catalog endpoint (/v2/_catalog) configuration, it provides the configuration
// options to control the maximum number of entries returned by the catalog endpoint.
type Catalog struct {
// Max number of entries returned by the catalog endpoint. Requesting n entries
// to the catalog endpoint will return at most MaxEntries entries.
// An empty or a negative value will set a default of 1000 maximum entries by default.
MaxEntries int `yaml:"maxentries,omitempty"`
}
// LogHook is composed of hook Level and Type.
@ -295,7 +188,7 @@ type LogHook struct {
// Levels set which levels of log message will let hook executed.
Levels []string `yaml:"levels,omitempty"`
// MailOptions allows user to configure email parameters.
// MailOptions allows user to configurate email parameters.
MailOptions MailOptions `yaml:"options,omitempty"`
}
@ -311,7 +204,7 @@ type MailOptions struct {
// Password defines password of login user
Password string `yaml:"password,omitempty"`
// Insecure defines if smtp login skips the secure certification.
// Insecure defines if smtp login skips the secure cerification.
Insecure bool `yaml:"insecure,omitempty"`
} `yaml:"smtp,omitempty"`
@ -389,7 +282,7 @@ type Health struct {
type v0_1Configuration Configuration
// UnmarshalYAML implements the yaml.Unmarshaler interface
// Unmarshals a string of the form X.Y into a Version, validating that X and Y can represent unsigned integers
// Unmarshals a string of the form X.Y into a Version, validating that X and Y can represent uints
func (version *Version) UnmarshalYAML(unmarshal func(interface{}) error) error {
var versionString string
err := unmarshal(&versionString)
@ -431,7 +324,7 @@ func (loglevel *Loglevel) UnmarshalYAML(unmarshal func(interface{}) error) error
switch loglevelString {
case "error", "warn", "info", "debug":
default:
return fmt.Errorf("invalid loglevel %s Must be one of [error, warn, info, debug]", loglevelString)
return fmt.Errorf("Invalid loglevel %s Must be one of [error, warn, info, debug]", loglevelString)
}
*loglevel = Loglevel(loglevelString)
@ -506,7 +399,7 @@ func (storage *Storage) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
if len(types) > 1 {
return fmt.Errorf("must provide exactly one storage type. Provided: %v", types)
return fmt.Errorf("Must provide exactly one storage type. Provided: %v", types)
}
}
*storage = storageMap
@ -594,8 +487,6 @@ func (auth Auth) MarshalYAML() (interface{}, error) {
// Notifications configures multiple http endpoints.
type Notifications struct {
// EventConfig is the configuration for the event format that is sent to each Endpoint.
EventConfig Events `yaml:"events,omitempty"`
// Endpoints is a list of http configurations for endpoints that
// respond to webhook notifications. In the future, we may allow other
// kinds of endpoints, such as external queues.
@ -605,32 +496,21 @@ type Notifications struct {
// Endpoint describes the configuration of an http webhook notification
// endpoint.
type Endpoint struct {
Name string `yaml:"name"` // identifies the endpoint in the registry instance.
Disabled bool `yaml:"disabled"` // disables the endpoint
URL string `yaml:"url"` // post url for the endpoint.
Headers http.Header `yaml:"headers"` // static headers that should be added to all requests
Timeout time.Duration `yaml:"timeout"` // HTTP timeout
Threshold int `yaml:"threshold"` // circuit breaker threshold before backing off on failure
Backoff time.Duration `yaml:"backoff"` // backoff duration
IgnoredMediaTypes []string `yaml:"ignoredmediatypes"` // target media types to ignore
Ignore Ignore `yaml:"ignore"` // ignore event types
}
// Events configures notification events.
type Events struct {
IncludeReferences bool `yaml:"includereferences"` // include reference data in manifest events
}
// Ignore configures mediaTypes and actions of the event, that it won't be propagated
type Ignore struct {
MediaTypes []string `yaml:"mediatypes"` // target media types to ignore
Actions []string `yaml:"actions"` // ignore action types
Name string `yaml:"name"` // identifies the endpoint in the registry instance.
Disabled bool `yaml:"disabled"` // disables the endpoint
URL string `yaml:"url"` // post url for the endpoint.
Headers http.Header `yaml:"headers"` // static headers that should be added to all requests
Timeout time.Duration `yaml:"timeout"` // HTTP timeout
Threshold int `yaml:"threshold"` // circuit breaker threshold before backing off on failure
Backoff time.Duration `yaml:"backoff"` // backoff duration
}
// 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).
@ -644,6 +524,16 @@ type BugsnagReporting struct {
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.
type Middleware struct {
// Name the middleware registers itself as
@ -664,11 +554,6 @@ type Proxy struct {
// Password of the hub user
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"`
}
// Parse parses an input configuration yaml document into a Configuration struct
@ -679,7 +564,7 @@ type Proxy struct {
// 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
func Parse(rd io.Reader) (*Configuration, error) {
in, err := io.ReadAll(rd)
in, err := ioutil.ReadAll(rd)
if err != nil {
return nil, err
}
@ -690,27 +575,15 @@ func Parse(rd io.Reader) (*Configuration, error) {
ParseAs: reflect.TypeOf(v0_1Configuration{}),
ConversionFunc: func(c interface{}) (interface{}, error) {
if v0_1, ok := c.(*v0_1Configuration); ok {
if v0_1.Log.Level == Loglevel("") {
if v0_1.Loglevel != Loglevel("") {
v0_1.Log.Level = v0_1.Loglevel
} else {
v0_1.Log.Level = Loglevel("info")
}
if v0_1.Loglevel == Loglevel("") {
v0_1.Loglevel = Loglevel("info")
}
if v0_1.Loglevel != Loglevel("") {
v0_1.Loglevel = Loglevel("")
}
if v0_1.Catalog.MaxEntries <= 0 {
v0_1.Catalog.MaxEntries = 1000
}
if v0_1.Storage.Type() == "" {
return nil, errors.New("no storage configuration provided")
return nil, fmt.Errorf("No storage configuration provided")
}
return (*Configuration)(v0_1), nil
}
return nil, fmt.Errorf("expected *v0_1Configuration, received %#v", c)
return nil, fmt.Errorf("Expected *v0_1Configuration, received %#v", c)
},
},
})

View file

@ -7,41 +7,37 @@ import (
"reflect"
"strings"
"testing"
"time"
"gopkg.in/check.v1"
. "gopkg.in/check.v1"
"gopkg.in/yaml.v2"
)
// Hook up gocheck into the "go test" runner
func Test(t *testing.T) { check.TestingT(t) }
func Test(t *testing.T) { TestingT(t) }
// configStruct is a canonical example configuration, which should map to configYamlV0_1
var configStruct = Configuration{
Version: "0.1",
Log: struct {
AccessLog struct {
Disabled bool `yaml:"disabled,omitempty"`
} `yaml:"accesslog,omitempty"`
Level Loglevel `yaml:"level,omitempty"`
Formatter string `yaml:"formatter,omitempty"`
Fields map[string]interface{} `yaml:"fields,omitempty"`
Hooks []LogHook `yaml:"hooks,omitempty"`
ReportCaller bool `yaml:"reportcaller,omitempty"`
Level Loglevel `yaml:"level"`
Formatter string `yaml:"formatter,omitempty"`
Fields map[string]interface{} `yaml:"fields,omitempty"`
Hooks []LogHook `yaml:"hooks,omitempty"`
}{
Level: "info",
Fields: map[string]interface{}{"environment": "test"},
},
Loglevel: "info",
Storage: Storage{
"somedriver": Parameters{
"string1": "string-value1",
"string2": "string-value2",
"bool1": true,
"bool2": false,
"nil1": nil,
"int1": 42,
"url1": "https://foo.example.com",
"path1": "/some-path",
"s3": Parameters{
"region": "us-east-1",
"bucket": "my-bucket",
"rootdirectory": "/registry",
"encrypt": true,
"secure": false,
"accesskey": "SAMPLEACCESSKEY",
"secretkey": "SUPERSECRET",
"host": nil,
"port": 42,
},
},
Auth: Auth{
@ -63,61 +59,37 @@ var configStruct = Configuration{
Headers: http.Header{
"Authorization": []string{"Bearer <example>"},
},
IgnoredMediaTypes: []string{"application/octet-stream"},
Ignore: Ignore{
MediaTypes: []string{"application/octet-stream"},
Actions: []string{"pull"},
},
},
},
},
Catalog: Catalog{
MaxEntries: 1000,
},
HTTP: struct {
Addr string `yaml:"addr,omitempty"`
Net string `yaml:"net,omitempty"`
Host string `yaml:"host,omitempty"`
Prefix string `yaml:"prefix,omitempty"`
Secret string `yaml:"secret,omitempty"`
RelativeURLs bool `yaml:"relativeurls,omitempty"`
DrainTimeout time.Duration `yaml:"draintimeout,omitempty"`
Addr string `yaml:"addr,omitempty"`
Net string `yaml:"net,omitempty"`
Host string `yaml:"host,omitempty"`
Prefix string `yaml:"prefix,omitempty"`
Secret string `yaml:"secret,omitempty"`
RelativeURLs bool `yaml:"relativeurls,omitempty"`
TLS struct {
Certificate string `yaml:"certificate,omitempty"`
Key string `yaml:"key,omitempty"`
ClientCAs []string `yaml:"clientcas,omitempty"`
MinimumTLS string `yaml:"minimumtls,omitempty"`
CipherSuites []string `yaml:"ciphersuites,omitempty"`
LetsEncrypt struct {
CacheFile string `yaml:"cachefile,omitempty"`
Email string `yaml:"email,omitempty"`
Hosts []string `yaml:"hosts,omitempty"`
DirectoryURL string `yaml:"directoryurl,omitempty"`
Certificate string `yaml:"certificate,omitempty"`
Key string `yaml:"key,omitempty"`
ClientCAs []string `yaml:"clientcas,omitempty"`
LetsEncrypt struct {
CacheFile string `yaml:"cachefile,omitempty"`
Email string `yaml:"email,omitempty"`
} `yaml:"letsencrypt,omitempty"`
} `yaml:"tls,omitempty"`
Headers http.Header `yaml:"headers,omitempty"`
Debug struct {
Addr string `yaml:"addr,omitempty"`
Prometheus struct {
Enabled bool `yaml:"enabled,omitempty"`
Path string `yaml:"path,omitempty"`
} `yaml:"prometheus,omitempty"`
Addr string `yaml:"addr,omitempty"`
} `yaml:"debug,omitempty"`
HTTP2 struct {
Disabled bool `yaml:"disabled,omitempty"`
} `yaml:"http2,omitempty"`
}{
TLS: struct {
Certificate string `yaml:"certificate,omitempty"`
Key string `yaml:"key,omitempty"`
ClientCAs []string `yaml:"clientcas,omitempty"`
MinimumTLS string `yaml:"minimumtls,omitempty"`
CipherSuites []string `yaml:"ciphersuites,omitempty"`
LetsEncrypt struct {
CacheFile string `yaml:"cachefile,omitempty"`
Email string `yaml:"email,omitempty"`
Hosts []string `yaml:"hosts,omitempty"`
DirectoryURL string `yaml:"directoryurl,omitempty"`
Certificate string `yaml:"certificate,omitempty"`
Key string `yaml:"key,omitempty"`
ClientCAs []string `yaml:"clientcas,omitempty"`
LetsEncrypt struct {
CacheFile string `yaml:"cachefile,omitempty"`
Email string `yaml:"email,omitempty"`
} `yaml:"letsencrypt,omitempty"`
}{
ClientCAs: []string{"/path/to/ca.pem"},
@ -125,45 +97,6 @@ var configStruct = Configuration{
Headers: http.Header{
"X-Content-Type-Options": []string{"nosniff"},
},
HTTP2: struct {
Disabled bool `yaml:"disabled,omitempty"`
}{
Disabled: false,
},
},
Redis: struct {
Addr string `yaml:"addr,omitempty"`
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"`
DB int `yaml:"db,omitempty"`
TLS struct {
Enabled bool `yaml:"enabled,omitempty"`
} `yaml:"tls,omitempty"`
DialTimeout time.Duration `yaml:"dialtimeout,omitempty"`
ReadTimeout time.Duration `yaml:"readtimeout,omitempty"`
WriteTimeout time.Duration `yaml:"writetimeout,omitempty"`
Pool struct {
MaxIdle int `yaml:"maxidle,omitempty"`
MaxActive int `yaml:"maxactive,omitempty"`
IdleTimeout time.Duration `yaml:"idletimeout,omitempty"`
} `yaml:"pool,omitempty"`
}{
Addr: "localhost:6379",
Username: "alice",
Password: "123456",
DB: 1,
Pool: struct {
MaxIdle int `yaml:"maxidle,omitempty"`
MaxActive int `yaml:"maxactive,omitempty"`
IdleTimeout time.Duration `yaml:"idletimeout,omitempty"`
}{
MaxIdle: 16,
MaxActive: 64,
IdleTimeout: time.Second * 300,
},
DialTimeout: time.Millisecond * 10,
ReadTimeout: time.Millisecond * 10,
WriteTimeout: time.Millisecond * 10,
},
}
@ -171,19 +104,20 @@ var configStruct = Configuration{
var configYamlV0_1 = `
version: 0.1
log:
level: info
fields:
environment: test
loglevel: info
storage:
somedriver:
string1: string-value1
string2: string-value2
bool1: true
bool2: false
nil1: ~
int1: 42
url1: "https://foo.example.com"
path1: "/some-path"
s3:
region: us-east-1
bucket: my-bucket
rootdirectory: /registry
encrypt: true
secure: false
accesskey: SAMPLEACCESSKEY
secretkey: SUPERSECRET
host: ~
port: 42
auth:
silly:
realm: silly
@ -194,13 +128,6 @@ notifications:
url: http://example.com
headers:
Authorization: [Bearer <example>]
ignoredmediatypes:
- application/octet-stream
ignore:
mediatypes:
- application/octet-stream
actions:
- pull
reporting:
bugsnag:
apikey: BugsnagApiKey
@ -209,26 +136,13 @@ http:
- /path/to/ca.pem
headers:
X-Content-Type-Options: [nosniff]
redis:
addr: localhost:6379
username: alice
password: 123456
db: 1
pool:
maxidle: 16
maxactive: 64
idletimeout: 300s
dialtimeout: 10ms
readtimeout: 10ms
writetimeout: 10ms
`
// inmemoryConfigYamlV0_1 is a Version 0.1 yaml document specifying an inmemory
// storage driver with no parameters
var inmemoryConfigYamlV0_1 = `
version: 0.1
log:
level: info
loglevel: info
storage: inmemory
auth:
silly:
@ -240,13 +154,6 @@ notifications:
url: http://example.com
headers:
Authorization: [Bearer <example>]
ignoredmediatypes:
- application/octet-stream
ignore:
mediatypes:
- application/octet-stream
actions:
- pull
http:
headers:
X-Content-Type-Options: [nosniff]
@ -256,68 +163,50 @@ type ConfigSuite struct {
expectedConfig *Configuration
}
var _ = check.Suite(new(ConfigSuite))
var _ = Suite(new(ConfigSuite))
func (suite *ConfigSuite) SetUpTest(c *check.C) {
func (suite *ConfigSuite) SetUpTest(c *C) {
os.Clearenv()
suite.expectedConfig = copyConfig(configStruct)
}
// TestMarshalRoundtrip validates that configStruct can be marshaled and
// unmarshaled without changing any parameters
func (suite *ConfigSuite) TestMarshalRoundtrip(c *check.C) {
func (suite *ConfigSuite) TestMarshalRoundtrip(c *C) {
configBytes, err := yaml.Marshal(suite.expectedConfig)
c.Assert(err, check.IsNil)
c.Assert(err, IsNil)
config, err := Parse(bytes.NewReader(configBytes))
c.Log(string(configBytes))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseSimple validates that configYamlV0_1 can be parsed into a struct
// matching configStruct
func (suite *ConfigSuite) TestParseSimple(c *check.C) {
func (suite *ConfigSuite) TestParseSimple(c *C) {
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseInmemory validates that configuration yaml with storage provided as
// a string can be parsed into a Configuration struct with no storage parameters
func (suite *ConfigSuite) TestParseInmemory(c *check.C) {
func (suite *ConfigSuite) TestParseInmemory(c *C) {
suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
suite.expectedConfig.Reporting = Reporting{}
suite.expectedConfig.Log.Fields = nil
suite.expectedConfig.Redis = struct {
Addr string `yaml:"addr,omitempty"`
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"`
DB int `yaml:"db,omitempty"`
TLS struct {
Enabled bool `yaml:"enabled,omitempty"`
} `yaml:"tls,omitempty"`
DialTimeout time.Duration `yaml:"dialtimeout,omitempty"`
ReadTimeout time.Duration `yaml:"readtimeout,omitempty"`
WriteTimeout time.Duration `yaml:"writetimeout,omitempty"`
Pool struct {
MaxIdle int `yaml:"maxidle,omitempty"`
MaxActive int `yaml:"maxactive,omitempty"`
IdleTimeout time.Duration `yaml:"idletimeout,omitempty"`
} `yaml:"pool,omitempty"`
}{}
config, err := Parse(bytes.NewReader([]byte(inmemoryConfigYamlV0_1)))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseIncomplete validates that an incomplete yaml configuration cannot
// be parsed without providing environment variables to fill in the missing
// components.
func (suite *ConfigSuite) TestParseIncomplete(c *check.C) {
func (suite *ConfigSuite) TestParseIncomplete(c *C) {
incompleteConfigYaml := "version: 0.1"
_, err := Parse(bytes.NewReader([]byte(incompleteConfigYaml)))
c.Assert(err, check.NotNil)
c.Assert(err, NotNil)
suite.expectedConfig.Log.Fields = nil
suite.expectedConfig.Storage = Storage{"filesystem": Parameters{"rootdirectory": "/tmp/testroot"}}
@ -325,23 +214,6 @@ func (suite *ConfigSuite) TestParseIncomplete(c *check.C) {
suite.expectedConfig.Reporting = Reporting{}
suite.expectedConfig.Notifications = Notifications{}
suite.expectedConfig.HTTP.Headers = nil
suite.expectedConfig.Redis = struct {
Addr string `yaml:"addr,omitempty"`
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"`
DB int `yaml:"db,omitempty"`
TLS struct {
Enabled bool `yaml:"enabled,omitempty"`
} `yaml:"tls,omitempty"`
DialTimeout time.Duration `yaml:"dialtimeout,omitempty"`
ReadTimeout time.Duration `yaml:"readtimeout,omitempty"`
WriteTimeout time.Duration `yaml:"writetimeout,omitempty"`
Pool struct {
MaxIdle int `yaml:"maxidle,omitempty"`
MaxActive int `yaml:"maxactive,omitempty"`
IdleTimeout time.Duration `yaml:"idletimeout,omitempty"`
} `yaml:"pool,omitempty"`
}{}
// Note: this also tests that REGISTRY_STORAGE and
// REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY can be used together
@ -351,57 +223,57 @@ func (suite *ConfigSuite) TestParseIncomplete(c *check.C) {
os.Setenv("REGISTRY_AUTH_SILLY_REALM", "silly")
config, err := Parse(bytes.NewReader([]byte(incompleteConfigYaml)))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseWithSameEnvStorage validates that providing environment variables
// that match the given storage type will only include environment-defined
// parameters and remove yaml-defined parameters
func (suite *ConfigSuite) TestParseWithSameEnvStorage(c *check.C) {
suite.expectedConfig.Storage = Storage{"somedriver": Parameters{"region": "us-east-1"}}
func (suite *ConfigSuite) TestParseWithSameEnvStorage(c *C) {
suite.expectedConfig.Storage = Storage{"s3": Parameters{"region": "us-east-1"}}
os.Setenv("REGISTRY_STORAGE", "somedriver")
os.Setenv("REGISTRY_STORAGE_SOMEDRIVER_REGION", "us-east-1")
os.Setenv("REGISTRY_STORAGE", "s3")
os.Setenv("REGISTRY_STORAGE_S3_REGION", "us-east-1")
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseWithDifferentEnvStorageParams validates that providing environment variables that change
// and add to the given storage parameters will change and add parameters to the parsed
// Configuration struct
func (suite *ConfigSuite) TestParseWithDifferentEnvStorageParams(c *check.C) {
suite.expectedConfig.Storage.setParameter("string1", "us-west-1")
suite.expectedConfig.Storage.setParameter("bool1", true)
func (suite *ConfigSuite) TestParseWithDifferentEnvStorageParams(c *C) {
suite.expectedConfig.Storage.setParameter("region", "us-west-1")
suite.expectedConfig.Storage.setParameter("secure", true)
suite.expectedConfig.Storage.setParameter("newparam", "some Value")
os.Setenv("REGISTRY_STORAGE_SOMEDRIVER_STRING1", "us-west-1")
os.Setenv("REGISTRY_STORAGE_SOMEDRIVER_BOOL1", "true")
os.Setenv("REGISTRY_STORAGE_SOMEDRIVER_NEWPARAM", "some Value")
os.Setenv("REGISTRY_STORAGE_S3_REGION", "us-west-1")
os.Setenv("REGISTRY_STORAGE_S3_SECURE", "true")
os.Setenv("REGISTRY_STORAGE_S3_NEWPARAM", "some Value")
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseWithDifferentEnvStorageType validates that providing an environment variable that
// changes the storage type will be reflected in the parsed Configuration struct
func (suite *ConfigSuite) TestParseWithDifferentEnvStorageType(c *check.C) {
func (suite *ConfigSuite) TestParseWithDifferentEnvStorageType(c *C) {
suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
os.Setenv("REGISTRY_STORAGE", "inmemory")
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseWithDifferentEnvStorageTypeAndParams validates that providing an environment variable
// that changes the storage type will be reflected in the parsed Configuration struct and that
// environment storage parameters will also be included
func (suite *ConfigSuite) TestParseWithDifferentEnvStorageTypeAndParams(c *check.C) {
func (suite *ConfigSuite) TestParseWithDifferentEnvStorageTypeAndParams(c *C) {
suite.expectedConfig.Storage = Storage{"filesystem": Parameters{}}
suite.expectedConfig.Storage.setParameter("rootdirectory", "/tmp/testroot")
@ -409,89 +281,96 @@ func (suite *ConfigSuite) TestParseWithDifferentEnvStorageTypeAndParams(c *check
os.Setenv("REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY", "/tmp/testroot")
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// 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
func (suite *ConfigSuite) TestParseWithSameEnvLoglevel(c *check.C) {
func (suite *ConfigSuite) TestParseWithSameEnvLoglevel(c *C) {
os.Setenv("REGISTRY_LOGLEVEL", "info")
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseWithDifferentEnvLoglevel validates that providing an environment variable defining the
// log level will override the value provided in the yaml document
func (suite *ConfigSuite) TestParseWithDifferentEnvLoglevel(c *check.C) {
suite.expectedConfig.Log.Level = "error"
func (suite *ConfigSuite) TestParseWithDifferentEnvLoglevel(c *C) {
suite.expectedConfig.Loglevel = "error"
os.Setenv("REGISTRY_LOG_LEVEL", "error")
os.Setenv("REGISTRY_LOGLEVEL", "error")
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseInvalidLoglevel validates that the parser will fail to parse a
// configuration if the loglevel is malformed
func (suite *ConfigSuite) TestParseInvalidLoglevel(c *check.C) {
func (suite *ConfigSuite) TestParseInvalidLoglevel(c *C) {
invalidConfigYaml := "version: 0.1\nloglevel: derp\nstorage: inmemory"
_, err := Parse(bytes.NewReader([]byte(invalidConfigYaml)))
c.Assert(err, check.NotNil)
c.Assert(err, NotNil)
os.Setenv("REGISTRY_LOGLEVEL", "derp")
_, err = Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.NotNil)
c.Assert(err, NotNil)
}
// TestParseWithDifferentEnvReporting validates that environment variables
// properly override reporting parameters
func (suite *ConfigSuite) TestParseWithDifferentEnvReporting(c *check.C) {
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, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseInvalidVersion validates that the parser will fail to parse a newer configuration
// version than the CurrentVersion
func (suite *ConfigSuite) TestParseInvalidVersion(c *check.C) {
func (suite *ConfigSuite) TestParseInvalidVersion(c *C) {
suite.expectedConfig.Version = MajorMinorVersion(CurrentVersion.Major(), CurrentVersion.Minor()+1)
configBytes, err := yaml.Marshal(suite.expectedConfig)
c.Assert(err, check.IsNil)
c.Assert(err, IsNil)
_, err = Parse(bytes.NewReader(configBytes))
c.Assert(err, check.NotNil)
c.Assert(err, NotNil)
}
// TestParseExtraneousVars validates that environment variables referring to
// nonexistent variables don't cause side effects.
func (suite *ConfigSuite) TestParseExtraneousVars(c *check.C) {
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
os.Setenv("registry_REPORTING_NEWRELIC_LICENSEKEY", "NewRelicLicenseKey")
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)))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseEnvVarImplicitMaps validates that environment variables can set
// values in maps that don't already exist.
func (suite *ConfigSuite) TestParseEnvVarImplicitMaps(c *check.C) {
func (suite *ConfigSuite) TestParseEnvVarImplicitMaps(c *C) {
readonly := make(map[string]interface{})
readonly["enabled"] = true
@ -503,41 +382,41 @@ func (suite *ConfigSuite) TestParseEnvVarImplicitMaps(c *check.C) {
os.Setenv("REGISTRY_STORAGE_MAINTENANCE_READONLY_ENABLED", "true")
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseEnvWrongTypeMap validates that incorrectly attempting to unmarshal a
// string over existing map fails.
func (suite *ConfigSuite) TestParseEnvWrongTypeMap(c *check.C) {
os.Setenv("REGISTRY_STORAGE_SOMEDRIVER", "somestring")
func (suite *ConfigSuite) TestParseEnvWrongTypeMap(c *C) {
os.Setenv("REGISTRY_STORAGE_S3", "somestring")
_, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.NotNil)
c.Assert(err, NotNil)
}
// TestParseEnvWrongTypeStruct validates that incorrectly attempting to
// unmarshal a string into a struct fails.
func (suite *ConfigSuite) TestParseEnvWrongTypeStruct(c *check.C) {
func (suite *ConfigSuite) TestParseEnvWrongTypeStruct(c *C) {
os.Setenv("REGISTRY_STORAGE_LOG", "somestring")
_, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.NotNil)
c.Assert(err, NotNil)
}
// TestParseEnvWrongTypeSlice validates that incorrectly attempting to
// unmarshal a string into a slice fails.
func (suite *ConfigSuite) TestParseEnvWrongTypeSlice(c *check.C) {
func (suite *ConfigSuite) TestParseEnvWrongTypeSlice(c *C) {
os.Setenv("REGISTRY_LOG_HOOKS", "somestring")
_, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.NotNil)
c.Assert(err, NotNil)
}
// TestParseEnvMany tests several environment variable overrides.
// The result is not checked - the goal of this test is to detect panics
// from misuse of reflection.
func (suite *ConfigSuite) TestParseEnvMany(c *check.C) {
func (suite *ConfigSuite) TestParseEnvMany(c *C) {
os.Setenv("REGISTRY_VERSION", "0.1")
os.Setenv("REGISTRY_LOG_LEVEL", "debug")
os.Setenv("REGISTRY_LOG_FORMATTER", "json")
@ -545,16 +424,16 @@ func (suite *ConfigSuite) TestParseEnvMany(c *check.C) {
os.Setenv("REGISTRY_LOG_FIELDS", "abc: xyz")
os.Setenv("REGISTRY_LOG_HOOKS", "- type: asdf")
os.Setenv("REGISTRY_LOGLEVEL", "debug")
os.Setenv("REGISTRY_STORAGE", "somedriver")
os.Setenv("REGISTRY_STORAGE", "s3")
os.Setenv("REGISTRY_AUTH_PARAMS", "param1: value1")
os.Setenv("REGISTRY_AUTH_PARAMS_VALUE2", "value2")
os.Setenv("REGISTRY_AUTH_PARAMS_VALUE2", "value2")
_, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.IsNil)
c.Assert(err, IsNil)
}
func checkStructs(c *check.C, t reflect.Type, structsChecked map[string]struct{}) {
func checkStructs(c *C, t reflect.Type, structsChecked map[string]struct{}) {
for t.Kind() == reflect.Ptr || t.Kind() == reflect.Map || t.Kind() == reflect.Slice {
t = t.Elem()
}
@ -590,7 +469,7 @@ func checkStructs(c *check.C, t reflect.Type, structsChecked map[string]struct{}
// TestValidateConfigStruct makes sure that the config struct has no members
// with yaml tags that would be ambiguous to the environment variable parser.
func (suite *ConfigSuite) TestValidateConfigStruct(c *check.C) {
func (suite *ConfigSuite) TestValidateConfigStruct(c *C) {
structsChecked := make(map[string]struct{})
checkStructs(c, reflect.TypeOf(Configuration{}), structsChecked)
}
@ -601,7 +480,6 @@ func copyConfig(config Configuration) *Configuration {
configCopy.Version = MajorMinorVersion(config.Version.Major(), config.Version.Minor())
configCopy.Loglevel = config.Loglevel
configCopy.Log = config.Log
configCopy.Catalog = config.Catalog
configCopy.Log.Fields = make(map[string]interface{}, len(config.Log.Fields))
for k, v := range config.Log.Fields {
configCopy.Log.Fields[k] = v
@ -612,7 +490,8 @@ func copyConfig(config Configuration) *Configuration {
configCopy.Storage.setParameter(k, v)
}
configCopy.Reporting = Reporting{
Bugsnag: BugsnagReporting{config.Reporting.Bugsnag.APIKey, config.Reporting.Bugsnag.ReleaseStage, config.Reporting.Bugsnag.Endpoint},
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{}}
@ -621,14 +500,14 @@ func copyConfig(config Configuration) *Configuration {
}
configCopy.Notifications = Notifications{Endpoints: []Endpoint{}}
configCopy.Notifications.Endpoints = append(configCopy.Notifications.Endpoints, config.Notifications.Endpoints...)
for _, v := range config.Notifications.Endpoints {
configCopy.Notifications.Endpoints = append(configCopy.Notifications.Endpoints, v)
}
configCopy.HTTP.Headers = make(http.Header)
for k, v := range config.HTTP.Headers {
configCopy.HTTP.Headers[k] = v
}
configCopy.Redis = config.Redis
return configCopy
}

View file

@ -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)
})
}

View file

@ -8,7 +8,7 @@ import (
"strconv"
"strings"
"github.com/sirupsen/logrus"
"github.com/Sirupsen/logrus"
"gopkg.in/yaml.v2"
)
@ -23,7 +23,7 @@ func MajorMinorVersion(major, minor uint) Version {
}
func (version Version) major() (uint, error) {
majorPart, _, _ := strings.Cut(string(version), ".")
majorPart := strings.Split(string(version), ".")[0]
major, err := strconv.ParseUint(majorPart, 10, 0)
return uint(major), err
}
@ -35,7 +35,7 @@ func (version Version) Major() uint {
}
func (version Version) minor() (uint, error) {
_, minorPart, _ := strings.Cut(string(version), ".")
minorPart := strings.Split(string(version), ".")[1]
minor, err := strconv.ParseUint(minorPart, 10, 0)
return uint(minor), err
}
@ -89,8 +89,8 @@ func NewParser(prefix string, parseInfos []VersionedParseInfo) *Parser {
}
for _, env := range os.Environ() {
k, v, _ := strings.Cut(env, "=")
p.env = append(p.env, envVar{k, v})
envParts := strings.SplitN(env, "=", 2)
p.env = append(p.env, envVar{envParts[0], envParts[1]})
}
// We must sort the environment variables lexically by name so that
@ -122,7 +122,7 @@ func (p *Parser) Parse(in []byte, v interface{}) error {
parseInfo, ok := p.mapping[versionedStruct.Version]
if !ok {
return fmt.Errorf("unsupported version: %q", versionedStruct.Version)
return fmt.Errorf("Unsupported version: %q", versionedStruct.Version)
}
parseAs := reflect.New(parseInfo.ParseAs)
@ -138,7 +138,7 @@ func (p *Parser) Parse(in []byte, v interface{}) error {
err = p.overwriteFields(parseAs, pathStr, path[1:], envVar.value)
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)
case reflect.Map:
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:
if v.NumMethod() == 0 {
if !v.IsNil() {
@ -239,7 +220,7 @@ func (p *Parser) overwriteStruct(v reflect.Value, fullpath string, path []string
}
case reflect.Ptr:
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
field.Set(reflect.New(sf.Type))
}
}

View file

@ -1,103 +0,0 @@
package configuration
import (
"os"
"reflect"
"gopkg.in/check.v1"
)
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"`
type ParserSuite struct{}
var _ = check.Suite(new(ParserSuite))
func (suite *ParserSuite) TestParserOverwriteIninitializedPoiner(c *check.C) {
config := localConfiguration{}
os.Setenv("REGISTRY_LOG_FORMATTER", "json")
defer os.Unsetenv("REGISTRY_LOG_FORMATTER")
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)
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, expectedConfig)
}
const testConfig2 = `version: "0.1"
log:
formatter: "text"
notifications:
- name: "val1"
- name: "val2"
- name: "car"`
func (suite *ParserSuite) TestParseOverwriteUnininitializedPoiner(c *check.C) {
config := localConfiguration{}
os.Setenv("REGISTRY_LOG_FORMATTER", "json")
defer os.Unsetenv("REGISTRY_LOG_FORMATTER")
// override only first two notificationsvalues
// in the tetConfig: leave the last value unchanged.
os.Setenv("REGISTRY_NOTIFICATIONS_0_NAME", "foo")
defer os.Unsetenv("REGISTRY_NOTIFICATIONS_0_NAME")
os.Setenv("REGISTRY_NOTIFICATIONS_1_NAME", "bar")
defer os.Unsetenv("REGISTRY_NOTIFICATIONS_1_NAME")
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)
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, expectedConfig)
}

View file

@ -1,16 +1,21 @@
package context
import (
"context"
"sync"
"github.com/distribution/distribution/v3/uuid"
"github.com/docker/distribution/uuid"
"golang.org/x/net/context"
)
// Context is a copy of Context from the golang.org/x/net/context package.
type Context interface {
context.Context
}
// instanceContext is a context that provides only an instance id. It is
// provided as the main background context.
type instanceContext struct {
context.Context
Context
id string // id of context, logged as "instance.id"
once sync.Once // once protect generation of the id
}
@ -21,7 +26,7 @@ func (ic *instanceContext) Value(key interface{}) interface{} {
// We want to lazy initialize the UUID such that we don't
// call a random generator from the package initialization
// 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.Generate().String()
})
return ic.id
@ -37,10 +42,17 @@ var background = &instanceContext{
// Background returns a non-nil, empty Context. The background context
// provides a single key, "instance.id" that is globally unique to the
// process.
func Background() context.Context {
func Background() Context {
return background
}
// WithValue returns a copy of parent in which the value associated with key is
// val. Use context Values only for request-scoped data that transits processes
// and APIs, not for passing optional parameters to functions.
func WithValue(parent Context, key, val interface{}) Context {
return context.WithValue(parent, key, val)
}
// stringMapContext is a simple context implementation that checks a map for a
// key, falling back to a parent if not present.
type stringMapContext struct {

View file

@ -1,71 +1,72 @@
// Package context provides several utilities for working with
// Go's context in http requests. Primarily, the focus is on logging relevant
// request information but this package is not limited to that purpose.
// golang.org/x/net/context in http requests. Primarily, the focus is on
// logging relevant request information but this package is not limited to
// that purpose.
//
// The easiest way to get started is to get the background context:
//
// ctx := context.Background()
// ctx := context.Background()
//
// 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
// line should be called before anything else:
//
// ctx := context.WithVersion(context.Background(), version)
// ctx := context.WithVersion(context.Background(), version)
//
// The above will store the version in the context and will be available to
// the logger.
//
// # Logging
// Logging
//
// The most useful aspect of this package is GetLogger. This function takes
// any context.Context interface and returns the current logger from the
// context. Canonical usage looks like this:
//
// GetLogger(ctx).Infof("something interesting happened")
// GetLogger(ctx).Infof("something interesting happened")
//
// GetLogger also takes optional key arguments. The keys will be looked up in
// the context and reported with the logger. The following example would
// return a logger that prints the version with each log message:
//
// ctx := context.Context(context.Background(), "version", version)
// GetLogger(ctx, "version").Infof("this log message has a version field")
// ctx := context.Context(context.Background(), "version", version)
// GetLogger(ctx, "version").Infof("this log message has a version field")
//
// The above would print out a log message like this:
//
// INFO[0000] this log message has a version field version=v2.0.0-alpha.2.m
// INFO[0000] this log message has a version field version=v2.0.0-alpha.2.m
//
// When used with WithLogger, we gain the ability to decorate the context with
// loggers that have information from disparate parts of the call stack.
// Following from the version example, we can build a new context with the
// configured logger such that we always print the version field:
//
// ctx = WithLogger(ctx, GetLogger(ctx, "version"))
// ctx = WithLogger(ctx, GetLogger(ctx, "version"))
//
// Since the logger has been pushed to the context, we can now get the version
// field for free with our log messages. Future calls to GetLogger on the new
// context will have the version field:
//
// GetLogger(ctx).Infof("this log message has a version field")
// GetLogger(ctx).Infof("this log message has a version field")
//
// This becomes more powerful when we start stacking loggers. Let's say we
// have the version logger from above but also want a request id. Using the
// context above, in our request scoped function, we place another logger in
// the context:
//
// ctx = context.WithValue(ctx, "http.request.id", "unique id") // called when building request context
// ctx = WithLogger(ctx, GetLogger(ctx, "http.request.id"))
// ctx = context.WithValue(ctx, "http.request.id", "unique id") // called when building request context
// ctx = WithLogger(ctx, GetLogger(ctx, "http.request.id"))
//
// When GetLogger is called on the new context, "http.request.id" will be
// included as a logger field, along with the original "version" field:
//
// INFO[0000] this log message has a version field http.request.id=unique id version=v2.0.0-alpha.2.m
// INFO[0000] this log message has a version field http.request.id=unique id version=v2.0.0-alpha.2.m
//
// Note that this only affects the new context, the previous context, with the
// version field, can be used independently. Put another way, the new logger,
// added to the request context, is unique to that context and can have
// request scoped variables.
// request scoped varaibles.
//
// # HTTP Requests
// HTTP Requests
//
// This package also contains several methods for working with http requests.
// The concepts are very similar to those described above. We simply place the
@ -73,13 +74,13 @@
// available. GetRequestLogger can then be called to get request specific
// variables in a log line:
//
// ctx = WithRequest(ctx, req)
// GetRequestLogger(ctx).Infof("request variables")
// ctx = WithRequest(ctx, req)
// GetRequestLogger(ctx).Infof("request variables")
//
// Like above, if we want to include the request data in all log messages in
// the context, we push the logger to a new context and use that one:
//
// ctx = WithLogger(ctx, GetRequestLogger(ctx))
// ctx = WithLogger(ctx, GetRequestLogger(ctx))
//
// The concept is fairly powerful and ensures that calls throughout the stack
// can be traced in log messages. Using the fields like "http.request.id", one

View file

@ -1,7 +1,6 @@
package context
import (
"context"
"errors"
"net"
"net/http"
@ -9,9 +8,9 @@ import (
"sync"
"time"
"github.com/distribution/distribution/v3/uuid"
log "github.com/Sirupsen/logrus"
"github.com/docker/distribution/uuid"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
)
// Common errors used with this package.
@ -32,10 +31,12 @@ func parseIP(ipStr string) net.IP {
// account proxy headers.
func RemoteAddr(r *http.Request) string {
if prior := r.Header.Get("X-Forwarded-For"); prior != "" {
remoteAddr, _, _ := strings.Cut(prior, ",")
remoteAddr = strings.Trim(remoteAddr, " ")
if parseIP(remoteAddr) != nil {
return remoteAddr
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
@ -67,7 +68,7 @@ func RemoteIP(r *http.Request) string {
// is available at "http.request". Other common attributes are available under
// the prefix "http.request.". If a request is already present on the context,
// this method will panic.
func WithRequest(ctx context.Context, r *http.Request) context.Context {
func WithRequest(ctx Context, r *http.Request) Context {
if ctx.Value("http.request") != nil {
// NOTE(stevvooe): This needs to be considered a programming error. It
// is unlikely that we'd want to have more than one request in
@ -86,7 +87,7 @@ func WithRequest(ctx context.Context, r *http.Request) context.Context {
// 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) {
func GetRequest(ctx Context) (*http.Request, error) {
if r, ok := ctx.Value("http.request").(*http.Request); r != nil && ok {
return r, nil
}
@ -95,24 +96,34 @@ func GetRequest(ctx context.Context) (*http.Request, error) {
// GetRequestID attempts to resolve the current request id, if possible. An
// error is return if it is not available on the context.
func GetRequestID(ctx context.Context) string {
func GetRequestID(ctx Context) string {
return GetStringValue(ctx, "http.request.id")
}
// WithResponseWriter returns a new context and response writer that makes
// interesting response statistics available within the context.
func WithResponseWriter(ctx context.Context, w http.ResponseWriter) (context.Context, http.ResponseWriter) {
func WithResponseWriter(ctx Context, w http.ResponseWriter) (Context, http.ResponseWriter) {
irw := instrumentedResponseWriter{
ResponseWriter: w,
Context: ctx,
}
if closeNotifier, ok := w.(http.CloseNotifier); ok {
irwCN := &instrumentedResponseWriterCN{
instrumentedResponseWriter: irw,
CloseNotifier: closeNotifier,
}
return irwCN, irwCN
}
return &irw, &irw
}
// GetResponseWriter returns the http.ResponseWriter from the provided
// context. If not present, ErrNoResponseWriterContext is returned. The
// returned instance provides instrumentation in the context.
func GetResponseWriter(ctx context.Context) (http.ResponseWriter, error) {
func GetResponseWriter(ctx Context) (http.ResponseWriter, error) {
v := ctx.Value("http.response")
rw, ok := v.(http.ResponseWriter)
@ -132,7 +143,7 @@ var getVarsFromRequest = mux.Vars
// example, if looking for the variable "name", it can be accessed as
// "vars.name". Implementations that are accessing values need not know that
// the underlying context is implemented with gorilla/mux vars.
func WithVars(ctx context.Context, r *http.Request) context.Context {
func WithVars(ctx Context, r *http.Request) Context {
return &muxVarsContext{
Context: ctx,
vars: getVarsFromRequest(r),
@ -142,7 +153,7 @@ func WithVars(ctx context.Context, r *http.Request) context.Context {
// GetRequestLogger returns a logger that contains fields from the request in
// the current context. If the request is not available in the context, no
// fields will display. Request loggers can safely be pushed onto the context.
func GetRequestLogger(ctx context.Context) Logger {
func GetRequestLogger(ctx Context) Logger {
return GetLogger(ctx,
"http.request.id",
"http.request.method",
@ -158,7 +169,7 @@ func GetRequestLogger(ctx context.Context) Logger {
// Because the values are read at call time, pushing a logger returned from
// this function on the context will lead to missing or invalid data. Only
// call this at the end of a request, after the response has been written.
func GetResponseLogger(ctx context.Context) Logger {
func GetResponseLogger(ctx Context) Logger {
l := getLogrusLogger(ctx,
"http.response.written",
"http.response.status",
@ -175,7 +186,7 @@ func GetResponseLogger(ctx context.Context) Logger {
// httpRequestContext makes information about a request available to context.
type httpRequestContext struct {
context.Context
Context
startedAt time.Time
id string
@ -187,42 +198,54 @@ type httpRequestContext struct {
// "request.<component>". For example, r.RequestURI
func (ctx *httpRequestContext) Value(key interface{}) interface{} {
if keyStr, ok := key.(string); ok {
switch keyStr {
case "http.request":
if keyStr == "http.request" {
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
case "http.request.remoteaddr":
case "remoteaddr":
return RemoteAddr(ctx.r)
case "http.request.method":
case "method":
return ctx.r.Method
case "http.request.host":
case "host":
return ctx.r.Host
case "http.request.referer":
case "referer":
referer := ctx.r.Referer()
if referer != "" {
return referer
}
case "http.request.useragent":
case "useragent":
return ctx.r.UserAgent()
case "http.request.id":
case "id":
return ctx.id
case "http.request.startedat":
case "startedat":
return ctx.startedAt
case "http.request.contenttype":
if ct := ctx.r.Header.Get("Content-Type"); ct != "" {
case "contenttype":
ct := ctx.r.Header.Get("Content-Type")
if ct != "" {
return ct
}
default:
// no match; fall back to standard behavior below
}
}
fallback:
return ctx.Context.Value(key)
}
type muxVarsContext struct {
context.Context
Context
vars map[string]string
}
@ -231,9 +254,12 @@ func (ctx *muxVarsContext) Value(key interface{}) interface{} {
if keyStr == "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 strings.HasPrefix(keyStr, "vars.") {
keyStr = strings.TrimPrefix(keyStr, "vars.")
}
if v, ok := ctx.vars[keyStr]; ok {
return v
}
}
@ -241,12 +267,20 @@ func (ctx *muxVarsContext) Value(key interface{}) interface{} {
return ctx.Context.Value(key)
}
// instrumentedResponseWriterCN provides response writer information in a
// context. It implements http.CloseNotifier so that users can detect
// early disconnects.
type instrumentedResponseWriterCN struct {
instrumentedResponseWriter
http.CloseNotifier
}
// instrumentedResponseWriter provides response writer information in a
// context. This variant is only used in the case where CloseNotifier is not
// implemented by the parent ResponseWriter.
type instrumentedResponseWriter struct {
http.ResponseWriter
context.Context
Context
mu sync.Mutex
status int
@ -285,25 +319,46 @@ func (irw *instrumentedResponseWriter) Flush() {
func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} {
if keyStr, ok := key.(string); ok {
switch keyStr {
case "http.response":
if keyStr == "http.response" {
return irw
case "http.response.written":
irw.mu.Lock()
defer irw.mu.Unlock()
}
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 "http.response.status":
irw.mu.Lock()
defer irw.mu.Unlock()
case "status":
return irw.status
case "http.response.contenttype":
if ct := irw.Header().Get("Content-Type"); ct != "" {
return ct
case "contenttype":
contentType := irw.Header().Get("Content-Type")
if contentType != "" {
return contentType
}
default:
// no match; fall back to standard behavior below
}
}
fallback:
return irw.Context.Value(key)
}
func (irw *instrumentedResponseWriterCN) Value(key interface{}) interface{} {
if keyStr, ok := key.(string); ok {
if keyStr == "http.response" {
return irw
}
}
return irw.instrumentedResponseWriter.Value(key)
}

View file

@ -14,7 +14,7 @@ func TestWithRequest(t *testing.T) {
var req http.Request
start := time.Now()
req.Method = http.MethodGet
req.Method = "GET"
req.Host = "example.com"
req.RequestURI = "/test-test"
req.Header = make(http.Header)
@ -22,7 +22,7 @@ func TestWithRequest(t *testing.T) {
req.Header.Set("User-Agent", "test/0.1")
ctx := WithRequest(Background(), &req)
for _, tc := range []struct {
for _, testcase := range []struct {
key string
expected interface{}
}{
@ -61,18 +61,18 @@ func TestWithRequest(t *testing.T) {
key: "http.request.startedat",
},
} {
v := ctx.Value(tc.key)
v := ctx.Value(testcase.key)
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 {
t.Fatalf("%s: %v != %v", tc.key, v, tc.expected)
if testcase.expected != nil && v != testcase.expected {
t.Fatalf("%s: %v != %v", testcase.key, v, testcase.expected)
}
// Key specific checks!
switch tc.key {
switch testcase.key {
case "http.request.id":
if _, ok := v.(string); !ok {
t.Fatalf("request id not a string: %v", v)
@ -195,7 +195,7 @@ func TestWithVars(t *testing.T) {
}
ctx := WithVars(Background(), &req)
for _, tc := range []struct {
for _, testcase := range []struct {
key string
expected interface{}
}{
@ -212,10 +212,10 @@ func TestWithVars(t *testing.T) {
expected: "qwer",
},
} {
v := ctx.Value(tc.key)
v := ctx.Value(testcase.key)
if !reflect.DeepEqual(v, tc.expected) {
t.Fatalf("%q: %v != %v", tc.key, v, tc.expected)
if !reflect.DeepEqual(v, testcase.expected) {
t.Fatalf("%q: %v != %v", testcase.key, v, testcase.expected)
}
}
}
@ -253,36 +253,33 @@ func TestRemoteAddr(t *testing.T) {
// X-Forwarded-For set by proxy
expectedRemote = "127.0.0.1"
proxyReq, err := http.NewRequest(http.MethodGet, frontend.URL, nil)
proxyReq, err := http.NewRequest("GET", frontend.URL, nil)
if err != nil {
t.Fatal(err)
}
resp, err := http.DefaultClient.Do(proxyReq)
_, err = http.DefaultClient.Do(proxyReq)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
// RemoteAddr in X-Real-Ip
getReq, err := http.NewRequest(http.MethodGet, backend.URL, nil)
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}
resp, err = http.DefaultClient.Do(getReq)
_, err = http.DefaultClient.Do(getReq)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
// Valid X-Real-Ip and invalid X-Forwarded-For
getReq.Header["X-forwarded-for"] = []string{"1.2.3"}
resp, err = http.DefaultClient.Do(getReq)
_, err = http.DefaultClient.Do(getReq)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
}

View file

@ -1,17 +1,10 @@
package context
import (
"context"
"fmt"
"github.com/Sirupsen/logrus"
"runtime"
"sync"
"github.com/sirupsen/logrus"
)
var (
defaultLogger *logrus.Entry = logrus.StandardLogger().WithField("go.version", runtime.Version())
defaultLoggerMu sync.RWMutex
)
// Logger provides a leveled-logging interface.
@ -45,28 +38,24 @@ type Logger interface {
Warn(args ...interface{})
Warnf(format string, args ...interface{})
Warnln(args ...interface{})
WithError(err error) *logrus.Entry
}
type loggerKey struct{}
// WithLogger creates a new context with provided logger.
func WithLogger(ctx context.Context, logger Logger) context.Context {
return context.WithValue(ctx, loggerKey{}, logger)
func WithLogger(ctx Context, logger Logger) Context {
return WithValue(ctx, "logger", logger)
}
// GetLoggerWithField returns a logger instance with the specified field key
// and value without affecting the context. Extra specified keys will be
// resolved from the context.
func GetLoggerWithField(ctx context.Context, key, value interface{}, keys ...interface{}) Logger {
func GetLoggerWithField(ctx Context, key, value interface{}, keys ...interface{}) Logger {
return getLogrusLogger(ctx, keys...).WithField(fmt.Sprint(key), value)
}
// GetLoggerWithFields returns a logger instance with the specified fields
// without affecting the context. Extra specified keys will be resolved from
// the context.
func GetLoggerWithFields(ctx context.Context, fields map[interface{}]interface{}, keys ...interface{}) Logger {
func GetLoggerWithFields(ctx Context, fields map[interface{}]interface{}, keys ...interface{}) Logger {
// must convert from interface{} -> interface{} to string -> interface{} for logrus.
lfields := make(logrus.Fields, len(fields))
for key, value := range fields {
@ -82,31 +71,19 @@ func GetLoggerWithFields(ctx context.Context, fields map[interface{}]interface{}
// argument passed to GetLogger will be passed to fmt.Sprint when expanded as
// a logging key field. If context keys are integer constants, for example,
// its recommended that a String method is implemented.
func GetLogger(ctx context.Context, keys ...interface{}) Logger {
func GetLogger(ctx Context, keys ...interface{}) Logger {
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
// are provided, they will be resolved on the context and included in the
// logger. Only use this function if specific logrus functionality is
// required.
func getLogrusLogger(ctx context.Context, keys ...interface{}) *logrus.Entry {
func getLogrusLogger(ctx Context, keys ...interface{}) *logrus.Entry {
var logger *logrus.Entry
// Get a logger, if it is present.
loggerInterface := ctx.Value(loggerKey{})
loggerInterface := ctx.Value("logger")
if loggerInterface != nil {
if lgr, ok := loggerInterface.(*logrus.Entry); ok {
logger = lgr
@ -122,9 +99,9 @@ func getLogrusLogger(ctx context.Context, keys ...interface{}) *logrus.Entry {
fields["instance.id"] = instanceID
}
defaultLoggerMu.RLock()
logger = defaultLogger.WithFields(fields)
defaultLoggerMu.RUnlock()
fields["go.version"] = runtime.Version()
// If no logger is found, just return the standard logger.
logger = logrus.StandardLogger().WithFields(fields)
}
fields := logrus.Fields{}

View file

@ -1,11 +1,10 @@
package context
import (
"context"
"runtime"
"time"
"github.com/distribution/distribution/v3/uuid"
"github.com/docker/distribution/uuid"
)
// WithTrace allocates a traced timing span in a new context. This allows a
@ -24,20 +23,20 @@ import (
//
// Here is an example of the usage:
//
// func timedOperation(ctx Context) {
// ctx, done := WithTrace(ctx)
// defer done("this will be the log message")
// // ... function body ...
// }
// func timedOperation(ctx Context) {
// ctx, done := WithTrace(ctx)
// defer done("this will be the log message")
// // ... function body ...
// }
//
// If the function ran for roughly 1s, such a usage would emit a log message
// 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
// package and a trace id is emitted that can be linked with parent ids.
func WithTrace(ctx context.Context) (context.Context, func(format string, a ...interface{})) {
func WithTrace(ctx Context) (Context, func(format string, a ...interface{})) {
if ctx == nil {
ctx = Background()
}
@ -70,7 +69,7 @@ func WithTrace(ctx context.Context) (context.Context, func(format string, a ...i
// also provides fast lookup for the various attributes that are available on
// the trace.
type traced struct {
context.Context
Context
id string
parent string
start time.Time

View file

@ -8,7 +8,6 @@ import (
// TestWithTrace ensures that tracing has the expected values in the context.
func TestWithTrace(t *testing.T) {
t.Parallel()
pc, file, _, _ := runtime.Caller(0) // get current caller.
f := runtime.FuncForPC(pc)
@ -36,29 +35,12 @@ func TestWithTrace(t *testing.T) {
ctx, done := WithTrace(Background())
defer done("this will be emitted at end of test")
tests := append(base, valueTestCase{
checkContextForValues(t, ctx, append(base, valueTestCase{
key: "trace.func",
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 {
t.Fatalf("unexpected value: %v != %v", v, tc.expected)
}
})
}
tracedFn := func() {
traced := func() {
parentID := ctx.Value("trace.id") // ensure the parent trace id is correct.
pc, _, _, _ := runtime.Caller(0) // get current caller.
@ -66,32 +48,15 @@ func TestWithTrace(t *testing.T) {
ctx, done := WithTrace(ctx)
defer done("this should be subordinate to the other trace")
time.Sleep(time.Second)
tests := append(base, valueTestCase{
checkContextForValues(t, ctx, append(base, valueTestCase{
key: "trace.func",
expected: f.Name(),
}, valueTestCase{
key: "trace.parent.id",
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
}
if v != tc.expected {
t.Fatalf("unexpected value: %v != %v", v, tc.expected)
}
})
}
}))
}
tracedFn()
traced()
time.Sleep(time.Second)
}
@ -101,3 +66,20 @@ type valueTestCase struct {
expected interface{}
notnilorempty bool // just check not empty/not nil
}
func checkContextForValues(t *testing.T, ctx Context, 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)
}
}
}

View file

@ -1,14 +1,13 @@
package context
import (
"context"
"time"
)
// Since looks up key, which should be a time.Time, and returns the duration
// since that time. If the key is not found, the value returned will be zero.
// This is helpful when inferring metrics related to context execution times.
func Since(ctx context.Context, key interface{}) time.Duration {
func Since(ctx Context, key interface{}) time.Duration {
if startedAt, ok := ctx.Value(key).(time.Time); ok {
return time.Since(startedAt)
}
@ -17,7 +16,7 @@ func Since(ctx context.Context, key interface{}) time.Duration {
// GetStringValue returns a string value from the context. The empty string
// will be returned if not found.
func GetStringValue(ctx context.Context, key interface{}) (value string) {
func GetStringValue(ctx Context, key interface{}) (value string) {
if valuev, ok := ctx.Value(key).(string); ok {
value = valuev
}

View file

@ -1,22 +1,16 @@
package context
import "context"
type versionKey struct{}
func (versionKey) String() string { return "version" }
// WithVersion stores the application version in the context. The new context
// gets a logger to ensure log messages are marked with the application
// version.
func WithVersion(ctx context.Context, version string) context.Context {
ctx = context.WithValue(ctx, versionKey{}, version)
func WithVersion(ctx Context, version string) Context {
ctx = WithValue(ctx, "version", version)
// push a new logger onto the stack
return WithLogger(ctx, GetLogger(ctx, versionKey{}))
return WithLogger(ctx, GetLogger(ctx, "version"))
}
// GetVersion returns the application version from the context. An empty
// string may returned if the version was not set on the context.
func GetVersion(ctx context.Context) string {
return GetStringValue(ctx, versionKey{})
func GetVersion(ctx Context) string {
return GetStringValue(ctx, "version")
}

36
contrib/apache/README.MD Normal file
View 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
View 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
View 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 commmand 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:32777/v2/registry1/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:32777
> 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":"registry1","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.

View 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"

View 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

View 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;

View 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;

View 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;
}

View 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;
}
}

View 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

View 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 additonal
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 .`

View file

@ -0,0 +1,82 @@
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"
- "6666:6666"
links:
- registryv2:registryv2
- malevolent:malevolent
- registryv2token:registryv2token
- tokenserver:tokenserver
- registryv2tokenoauth:registryv2tokenoauth
- 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
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"
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

View 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" ]
[[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"

View file

@ -0,0 +1,101 @@
# 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
}
# 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
run docker_t login -u $user -p $password -e $email $1
if [ "$status" -ne 0 ]; then
echo $output
fi
[ "$status" -eq 0 ]
# First line is WARNING about credential save or email deprecation (maybe both)
[ "${lines[2]}" = "Login Succeeded" -o "${lines[1]}" = "Login Succeeded" ]
}
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 reates 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
}

View file

@ -0,0 +1,43 @@
#!/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_file() {
mkdir -p $2
cp $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

View file

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC9TCCAd+gAwIBAgIQKQTGjKpSVBW78ef0fOcxRTALBgkqhkiG9w0BAQswJjER
MA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE1MDgyMDIz
MjE0OVoXDTE4MDgwNDIzMjE0OVowJjERMA8GA1UEChMIUXVpY2tUTFMxETAPBgNV
BAMTCFF1aWNrVExTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwoPM
xiDZK6Fwy5r3waRkfJHhyZZH828Jyj+nz5UVkMyOM/xN6MgJ2w911hTj1wSXG2n3
AohF3gTFNrDYh4j2qRZnixDrOM5GBm2/KJbyfBIYkrR45yLfjidO7MRnhaPZ5Fov
l+RKwNBXP4Q2mUe7q9FM457Rm8hAcqXP04AJT20m1QSYQivDgxsDxuAQte3VEy1E
0j0CwUKoFHT6MHOnDPEZbc4r1+ba34WBM1Sc5KXyV2JlbtU07J4hACYWVsD7vQCl
VFlZNE4E35ahMDZ+ODLal9PAT8ARLdAtjvRWrT+h8qZ4Yfwt/sGF1K4CAkTP3H5p
uMkJG56zmqIEYeHMuwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAKQwDwYDVR0TAQH/
BAUwAwEB/zALBgkqhkiG9w0BAQsDggEBALpieTckiPEeb3rTAWl7waDPLPOIhS5C
XHVfOm7cPmRn3pT2VuR8y74U7a1uOkYMgJnCWb8lSXhbqC89FatLnAhKqo4I9oD8
2BXgYeIpP5/OWBcjzmsMnowrvokc0chAmAR0Ux6AP0eX9amC0lGMuTHdw3+is0AR
lhoImOUPXvgMH7W2RimpSgnX0R5wKqfuGwMfbGa0xhWBZ+wekAKcU8b+pIHDyX0c
EQcir2y8/lVjECXSAIlV6iasPQ3hm1sd0xq1hx4yrwYFvQb7yEhOXbK24HLr/20D
RRmEOuS8gg2XtUFv66z/VOw/nUleIg9GAuWDJaiu9frmIma4/tIY4qY=
-----END CERTIFICATE-----

View file

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDETCCAfugAwIBAgIQZRKt7OeG+TlC2riszYwQQTALBgkqhkiG9w0BAQswJjER
MA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE1MDgyMDIz
MjE0OVoXDTE4MDgwNDIzMjE0OVowKzERMA8GA1UEChMIUXVpY2tUTFMxFjAUBgNV
BAMTDWxvY2FscmVnaXN0cnkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDPdsUBStNMz4coXfQVIJIafG85VkngM4fV7hrg7AbiGLCWvq8cWOrYM50G9Wmo
twK1WeQ6bigYOjINgSfTxcy3adciVZIIJyXqboz6n2V0yRPWpakof939bvuAurAP
tSqQ2V5fGN0ZZn4J4IbXMSovKwo7sG3X6i4q/8DYHZ/mKjvCRMPC3MGWqunknpkm
dzyKbIFHaDKlAqIOwTsDhHvGzm/9n3D+h4sl5ZPBobuBEV2u5GR0H5ujak4+Kczt
thCWtRkzCfnjW0TEanheSYJGu8OgCGoFjQnHotgqvOO6iHZCsrB3gf8WQeou+y9e
+OyLZv3FmqdC9SXr3b0LGQTFAgMBAAGjOjA4MA4GA1UdDwEB/wQEAwIAoDAMBgNV
HRMBAf8EAjAAMBgGA1UdEQQRMA+CDWxvY2FscmVnaXN0cnkwCwYJKoZIhvcNAQEL
A4IBAQC/PP2Y9QVhO8t4BXML1QpNRWqXG8Gg0P1XIh6M6FoxcGIodLdbzui828YB
wm9ZlyKars+nDdgLdQWawdV7hSd6s2NeQlHYQSGLsdTAVkgIxiD7D2Tw3kAZ6Zrj
dPikoVAc+rBMm/BXQLzy95IAbBVOHOpBkOOgF+TYxeLnOc3GzbUqBi1Pq97DMaxr
DaDuywH55P/6v7qt610UIsZ6+RZ78iiRx4Q+oRxEqGT0rXI76gVxOFabbJuFr1n1
kEWa3u/BssJzX3KVAm7oUtaBnj2SH5fokFmvZ5lBXA4QO/5doOa8yZiFFvvQs7EY
SWDxLrvS33UCtsCcpPggjehnxKaC
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAz3bFAUrTTM+HKF30FSCSGnxvOVZJ4DOH1e4a4OwG4hiwlr6v
HFjq2DOdBvVpqLcCtVnkOm4oGDoyDYEn08XMt2nXIlWSCCcl6m6M+p9ldMkT1qWp
KH/d/W77gLqwD7UqkNleXxjdGWZ+CeCG1zEqLysKO7Bt1+ouKv/A2B2f5io7wkTD
wtzBlqrp5J6ZJnc8imyBR2gypQKiDsE7A4R7xs5v/Z9w/oeLJeWTwaG7gRFdruRk
dB+bo2pOPinM7bYQlrUZMwn541tExGp4XkmCRrvDoAhqBY0Jx6LYKrzjuoh2QrKw
d4H/FkHqLvsvXvjsi2b9xZqnQvUl6929CxkExQIDAQABAoIBAQCZjCUI7NFwwxQc
m1UAogeglMJZJHUu+9SoUD8Sg34grvdbyqueBm1iMOkiclaOKU1W3b4eRNNmAwRy
nEnW4km+4hX48m5PnHHijYnIIFsd0YjeT+Pf9qtdXFvGjeWq6oIjjM3dAnD50LKu
KsCB2oCHQoqjXNQfftJGvt2C1oI2/WvdOR4prnGXElVfASswX4PkP5LCfLhIx+Fr
7ErfaRIKigLSaAWLKaw3IlL12Q/KkuGcnzYIzIRwY4VJ64ENN6M3+KknfGovQItL
sCxceSe61THDP9AAI3Mequm8z3H0CImOWhJCge5l7ttLLMXZXqGxDCVx+3zvqlCa
X0cgGSVBAoGBAOvTN3oJJx1vnh1mRj8+hqzFq1bjm4T/Wp314QWLeo++43II4uMM
5hxUlO5ViY1sKxQrGwK+9c9ddxAvm5OAFFkzgW9EhDCu0tXUb2/vAJQ93SgqbcRu
coXWJpk0eNW/ouk2s1X8dzs+sCs3a4H64fEEj8yhwoyovjfucspsn7t1AoGBAOE2
ayLKx7CcWCiD/VGNvP7714MDst2isyq8reg8LEMmAaXR2IWWj5eGwKrImTQCsrjW
P37aBp1lcWuuYRKl/WEGBy6JLNdATyUoYc1Yo+8YdenekkOtOHHJerlK3OKi3ZVp
q4HJY9wzKg/wYLcbTmjjzKj+OBIZWwig73XUHwoRAoGBAJnuIrYbp1aFdvXFvnCl
xY6c8DwlEWx8qY+V4S2XX4bYmOnkdwSxdLplU1lGqCSRyIS/pj/imdyjK4Z7LNfY
sG+RORmB5a9JTgGZSqwLm5snzmXbXA7t8P7/S+6Q25baIeKMe/7SbplTT/bFk/0h
371MtvhhVfYuZwtnL7KFuLXJAoGBAMQ3UHKYsBC8tsZd8Pf8AL07mFHKiC04Etfa
Wb5rpri+RVM+mGITgnmnavehHHHHJAWMjPetZ3P8rSv/Ww4PVsoQoXM3Cr1jh1E9
dLCfWPz4l8syIscaBYKF4wnLItXGxj3mOgoy93EjlrMaYHlILjGOv4JBM4L5WmoT
JW7IaF6xAoGAZ4K8MwU/cAah8VinMmLGxvWWuBSgTTebuY5zN603MvFLKv5necuc
BZfTTxD+gOnxRT6QAh++tOsbBmsgR9HmTSlQSSgw1L7cwGyXzLCDYw+5K/03KXSU
DaFdgtfcDDJO8WtjOgjyTRzEAOsqFta1ige4pIu5fTilNVMQlhts5Iw=
-----END RSA PRIVATE KEY-----

View 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 -f $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 -f $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 -f $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 ]
}

View file

@ -0,0 +1,9 @@
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

View 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;

View 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;
}
}

View 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;
}

View file

@ -0,0 +1,5 @@
client_max_body_size 0;
chunked_transfer_encoding on;
location /v2/ {
include docker-registry-v2.conf;
}

View file

@ -0,0 +1,221 @@
# 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;
}

View file

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE9TCCAt+gAwIBAgIQMsdPWoLAso/tIOvLk8R/sDALBgkqhkiG9w0BAQswJjER
MA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE1MDUyNjIw
NTQwMVoXDTE4MDUxMDIwNTQwMVowJjERMA8GA1UEChMIUXVpY2tUTFMxETAPBgNV
BAMTCFF1aWNrVExTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1YeX
GTvXPKlWA2lMbCvIGB9JYld/otf8aqs6euVJK1f09ngj5b6VoVlI8o1ScVcHKlKx
BGfPMThnM7fiEmsfDSPuCIlGmTqR0t4t9dHRnLBGbZmR8JdAs7LKpP+PFYu0JTIT
wFcjXIs+45cIF2HpsYY6zkj0bmNsyYmT1U1BTW+qqmhvc0Jkr+ikElOQ93Pn7zIO
cXtxdERdzdzXY5cfL3CCaoJDgXOsKPQfYrCi5Zl6sLZVBkIc6Q2fErSIjTp45+NY
AjiOxfUT0MOFtA0/HzYvVp3gTNPGEWM3dF1hwzCqJ32odbw/3TiFCEeC1B82p1sR
sgoFZ6Vbfy9fMhB5S7BBtbqF09Yq/PMM3drOvWIxMF4aOY55ilrtKVwmnckiB0mE
CPOColUUyiWIwwvp82InYsX5ekfS4x1mX1iz8zQEuTF5QHdKiUfd4A33ZMf0Ve6p
y9SaMmos99uVQMzWlwj7nVACXjb9Ee6MY/ePRl7Z2gBxEYV41SGFRg8LNkQ//fYk
o2vJ4Bp4aOh/O3ZQNv1eqEDmf/Su5lYCzURyQ2srcRRdwpteDPX+NHYn2d07knHN
NQvOJn6EkcsDbgp0vSr6mFDv2GZWkTOAd8jZyrcErrLHAxRNm0Va+CEIKLhswf1G
Y2kFkPL1otI8OSDvdJSjZ2GjRSwXhM2Mf3PzfAkCAwEAAaMjMCEwDgYDVR0PAQH/
BAQDAgCkMA8GA1UdEwEB/wQFMAMBAf8wCwYJKoZIhvcNAQELA4ICAQDBxOHKnF9z
PZWPNKDRmBPtmnU2IHh6JJ9HzqGALJJbBU0MUSD/aLBBkYeS0YSHgYZ1hXLsfuRU
lm/czV41hU1FTDqS2fFpcAAGH+6/rwyfrz+GYr2K4b/ijCwOMbMrDWO54zqZT3KU
GFBpkrh4fNyKdgUNJsy0Q0it3gOGSUmLvEQUzqxPFVz7h/pF/Cecr0/kpjbpsxna
XQkhtDyKDIQfPCq8Ci1vox5WvBbBkdzDtyCm+KSb6VC3pCX6LV5NkS7YM7mtscTi
QdYfLbKX05kUVG2R9SShJn5BSXzGk9M5FR5koGY0lMHwmJqaOqazXjqa1jR7UNDK
UyExHIXSqJ+nCf4bChEsaC1uwu3Gr7PfP41Zb2U3Raf8UmFnbz6Hx0sS4zBvyJ5w
Ntemve4M1mB7++oLZ4PkuwK82SkQ8YK0z+lGJQRjg/HP3fVETV8TlIPJAvg7bRnH
sMrLb/V+K6iY+08kQ2rpU02itRjKnU/DLoha4KVjafY8eIcIR2lpwrYjx+KYpkcF
AMEC7MnuzhyUfDL++GO6XGwRnx2E54MnKtkrECObMSzwuLysPmjhrEUH6YR7zGib
KmN6vQkA4s5053R+Tu0k1JGaw90SfvcW4bxGcFjU4Kg0KqlY1y8tnt+ZiHmK0naA
KauB3KY1NiL+Ng5DCzNdkwDkWH78ZguI2w==
-----END CERTIFICATE-----

View file

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE9TCCAt+gAwIBAgIRAKbgxG1zgQI81ISaHxqLfpcwCwYJKoZIhvcNAQELMCYx
ETAPBgNVBAoTCFF1aWNrVExTMREwDwYDVQQDEwhRdWlja1RMUzAeFw0xNTA1MjYy
MDU0MjJaFw0xODA1MTAyMDU0MjJaMBMxETAPBgNVBAoTCFF1aWNrVExTMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq0Pc8DQ9AyvokFzm9v4a+29TCA3/
oARHbx59G+GOeGkrwG6ZWSZa/oNEJf3NJcU00V04k+fQuVoYBCgBXec9TEBvXa8M
WpLxp5U9LyYkv0AiSPfT2fJEE8mC+isMl+DbmgBcShwRXpeZQyIbEJhedS8mIjW/
MgJbdTylEq1UcZSLMuky+RWv10dw02fLuN1302OgfJRZooPug9rPYHHGbTB0o7II
hGlhziLVTKV9W1RP8Aop8TamSD85OV6shDaCvmMFr1YNDjcJJ5MGMaSmq0Krq9v4
nFwmuhOo8gvw/HhzYcxyMHnqMt6EgvbVWwXOoW7xiI3BEDFV33xgTp61bFpcdCai
gwUNzfe4/dHeCk/r3pteWOxH1bvcxUlmUB65wjRAwKuIX8Z0hC4ZlM30o+z11Aru
5QqKMrbSlOcd6yHT6NM1ZRyD+nbFORqB8W51g344eYl0zqQjxTQ0TNjJWDR2RWB/
Vlp5N+WRjDpsBscR8kt2Q1My17gWzvHfijGETZpbvmo2f+Keqc9fcfzkIe/VZFoO
nhRqhl2PSphcWdimk8Bwf5jC2uDAXWCdvVWvRSP4Xg8zpDwLhlsfLaWVH9n+WG3j
NLQ8EmHWaZlJSeW4BiDYsXmpTAkeLmwoS+pk2WL0TSQ7+S3DyrmTeVANHipNQZeB
twZJXIXR6Jc8hgsCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgCgMBMGA1UdJQQMMAoG
CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwCwYJKoZIhvcNAQELA4ICAQCl0cTLbLIn
XFuxreei+y6TlG2Z5XcxJ84mr8VLAaQMlJOLZV0O/suFBu9KqBuvPaHhGRnKE2uw
Vxdj9qaDdvmvuzi4jYyUA/sQuqq1+wHwGTadOi9r0IsL8OxzsG16OlhuXzhoQVdw
C9z1jad4HC7uihQ5yhl2ltAA+h5G0Sr1b9El2mx4p6BV+okmTvrqrmjshQb1GZwx
jG6SJ/uvjGf7rn09ZyYafF9ZDTMNodNXjW8orqGlFdXZLPFJ9agUFfwWfqD2lrtm
Fu+Ei0ZvKOtyzmh06eO2aGAHJCBTfcDM4tBKBKp0MOMoZkcQQDNpSyI12j6s1wtx
/1dC8QDyfFpZFXTbKn3q+6MpR+u5zqVquYjwP5DqGTvX0e1sLSthv7LRiOi0qHv1
bZ8JoWhRMNumui9mzwar5t20ExcWxGxizZY+t+OIj4kaAeRoKK6r6FrYBnTjM+iR
+xtML5UHPOSmYfNcai0Wn4T7hwpgnCJ+K7qGYjFUCarsINppQEwkxHAvuX+asc38
nA0wd7ByulkMJph0gP6j6LuJf28JODi6EQ7FcQItMeTuPrc+mpqJ4jP7vTTSJG7Q
wvqXLMgFQFR+2PG0s10hbY/Y/nwZAROfAs7ADED+EcDPTl/+XjVyo/aYIeOb/07W
SpS/cacZYUsSLgB4cWbxElcc/p7CW1PbOA==
-----END CERTIFICATE-----

View file

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAq0Pc8DQ9AyvokFzm9v4a+29TCA3/oARHbx59G+GOeGkrwG6Z
WSZa/oNEJf3NJcU00V04k+fQuVoYBCgBXec9TEBvXa8MWpLxp5U9LyYkv0AiSPfT
2fJEE8mC+isMl+DbmgBcShwRXpeZQyIbEJhedS8mIjW/MgJbdTylEq1UcZSLMuky
+RWv10dw02fLuN1302OgfJRZooPug9rPYHHGbTB0o7IIhGlhziLVTKV9W1RP8Aop
8TamSD85OV6shDaCvmMFr1YNDjcJJ5MGMaSmq0Krq9v4nFwmuhOo8gvw/HhzYcxy
MHnqMt6EgvbVWwXOoW7xiI3BEDFV33xgTp61bFpcdCaigwUNzfe4/dHeCk/r3pte
WOxH1bvcxUlmUB65wjRAwKuIX8Z0hC4ZlM30o+z11Aru5QqKMrbSlOcd6yHT6NM1
ZRyD+nbFORqB8W51g344eYl0zqQjxTQ0TNjJWDR2RWB/Vlp5N+WRjDpsBscR8kt2
Q1My17gWzvHfijGETZpbvmo2f+Keqc9fcfzkIe/VZFoOnhRqhl2PSphcWdimk8Bw
f5jC2uDAXWCdvVWvRSP4Xg8zpDwLhlsfLaWVH9n+WG3jNLQ8EmHWaZlJSeW4BiDY
sXmpTAkeLmwoS+pk2WL0TSQ7+S3DyrmTeVANHipNQZeBtwZJXIXR6Jc8hgsCAwEA
AQKCAgBJcL1iR5ROMtr0ZNIp4gciALfjQVV3gb48GR/e/9b/LWI0j3i0sOzeLN3h
SLda1fjzOn1Td1ma0dZwmdMUOF+hvhPDYZfzkwWLLkThXgLt/At3rMYstGWa8pN2
wVUSH7sri7IHmYedP3baQdrHP/9pUsGQc+m8ASTE3i+PFcKbPe5+818HTtRrhVgN
X3oNmPKUNCmSom7ZcKer5P1+Ruum0NuDgomCdkoZgfhjeKeLrVjl/wXDSQL/AhWA
02c4/sML7xx19nl8uf7z+Gj0ir1pvRouhRJTwnRc4KdWu+Yn7WLU8j2ZKf5St/as
zjnpYVEdCp0KSHccgXtobUZDEG2NCHmM6gR2j3qgoUAYjHyqPYlph2r5C47q+p4c
dDWkpwZwGiuYq9qpZj24X6BfppxExcX6AwOgFLZLp80IynwrMVxFsDd2J+KpKRQ1
+ZtYPcULwInF9MNi/dv84pxGOmmOaIUyjN8Sw4eqANU4T5uvTjUj7Ou6KYyfmxgG
y++vjpRN7tN1t1Hwde8SVWobvmhU+5SJVHV8INoJD7uciaevPo9pt833SQTtDXeY
PVBhOKO7thAxdUiqlU/1nGTXnf1VO6wAjaVYoTnP4tJ97WuTptwd2F5znVWHFGVh
lzJAzmFOuyCnRnInsf4n5EmWJnT7XF2CofQqAJ8NIddrU8GnQQKCAQEAyqWAiPMK
I/dMzlS7oJGlhbKZ5R4buc+EoZqtW7/8/S+0L6IaQvpEUilD+aDQyaxXjoKiQQL+
0UeeSmF/zU5BsOTpB8AuJUfYoUe0N+x7hO5eIcoCB/QWYX+iC3tCN4j1Iwt6VliV
PBYEiLUYPngSIHob/nK8UtgxrWQ3Fik9XJtWhePHrvMvDBalgCKdnyhuucGxKUjc
TtPcyMFdi0z4Kt/FAm+5u/v4ZkO909Ish0FrAqQ9t5ETfvTTTYKBmzny6/LSPTK9
0XIsHltuC1xG4vGQsES/Ph++Yj3Vn011FqvFZeBUHbfcQuB4h5wcb+90d4GU1kux
eabsHPIZKrlN4QKCAQEA2Fs8NAN5K9i7qbxZCJPi6DJV6XMznk6JVGb+qkkChCyq
IOXb95+c9CIpe6w2d3res3zvML3zbdz2Lyp9G0ve6tSlOaSnHeyIxZ5SRB+yQrcF
GXtsx370bOGjCi1/NH85kwKlMuROFJKleJQv8rKpIEo5aPSPV9Cc/VsUqBpvR+O0
U1HMv57P4yJA/ddw6imHJBl3jTmWBpK4B+LBsCbdypxdVoO8t32Lb2BqDTaPJfYU
RJUpjn/efLLoP6CWxYtqpUlY5tc7NJGAokl8Fo1mPn02klydvs09uiXE80Li2Hoc
/meMH07Lbt2VTw6iGNRX6VpIHEUZGZeS6rbAvO4ZawKCAQEAjOtGVPXdyWEB0kHu
MBzYY/7tMf0b/rymWNL9Vt5NiauQu8cYSBdNR21WzdLdHkFwqbOCLX9twA7zrnna
q+SNnfuxaShlbptls9HvKyySQMCaSRj3DJzaq3ZcM2vFgmUFQxeKPV1geeY9xOta
LqbExDzmFq2m9F1PPmqAPDL1bt6+7mCVzb1irB9be52WysUNKrPdBP6b5V1DHYAK
EwK1WOs/TxBusqDn/gWBjjmLqYr+ZVndaTfDvPd3sWDdzBoiKZ40QUZ15Z5lu76M
6e2DhfHCUjGcZBEjDaI+WYc9s0REAzJajEf9Lax3ZKZUyCpWbXx5CgSdKCHB8+cP
RTyTQQKCAQEAsxx8r5a8hocLfQ43Kvm7HH0nUHeVoRXlbOFDLNf6ZE/RnCCOxOX3
esiZTRAZmzo2CaOBJPnr/+SwTgW/woxCBGh8TEc6LnS2GdviwRD4c3CuoRTjzhgU
49q8Ld3SdDRrBoBnIMWOuktY/4S2WRZ9GwU3l+L2lD1Y6gmwBSa1P2+Lxnpupagk
9CVUZpEnokM05LbMmTa2M8Tc43Je5KSYcnaWctvmrIUbnN3VjhC/2y5oQwq1d4n2
N4eo65vXlbzAUgtxtNEz62YVdsSdHNJ8dXkVZ3+S+/VPh75i2PxjbdFSFW7Futlx
YtvAEs3LdgC8squSDQ1LJTutXfBjiUUX9wKCAQBiCMre86tLyJu6Qb6X1cRAwO7m
4kyGzIUtijXko6mWxb4X/usVvzhSaNVYbHbMZXjX+J5vhBOul+RmQ3EY6nw0H2z8
9D4z/rnQVqeb0uvIeUhBPni+s4fS4bA92M6Ie5bhiOSF2JjjJr38BFnTZARE7C+7
ZII7z2c0eQz/wAAt9fWWroAB2mIm6wxq0LNij2NoE0iq6k2xJE1/k8qhXpsN0zAv
bjG72Q7WryBeK/eIDK9e5wGlfLVDOx2Evlcaj70oJxuoRh57e8fCYy8huJQT+Wlx
Qw4zhxiyzAMq8SEqFsm8dVO4Bu2FwzmmehA80ieSb+si7JZU92xGDT394Im2
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIFCTCCAvOgAwIBAgIQdcXDOHrLsd2ENSfj5h8ZmjALBgkqhkiG9w0BAQswJjER
MA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE1MDUyNjIw
NTQwM1oXDTE4MDUxMDIwNTQwM1owJzERMA8GA1UEChMIUXVpY2tUTFMxEjAQBgNV
BAMTCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2K
saEVcHq0eldu5kABbWtZsf9keK7lz8beVIowzOqp5IHpGlggtH7xDVeigA/sLdds
WTgKEOq3zsJzdgfEti5TNAjjmPqjMKkolqv3LXDJG0dZ2GZ8W/eBB6X1wB0LKr3i
ye3/5jb/wCZYVGGMQXj0VQxY8Qq+OHEp0effeheJqA0OYOj+RaZwi20OR/KmJRgY
wXU33bZyapuyT4krhFlFbtzXeKsKQPrT2ePWxPAceqUGUTIqyJySYIw6vb72YxjX
FNRw6Jg7B7RqVJaVCfBrVxtAv+rCLOhUOVYmWhgWEIODPXiqOGwB0VUApAVAYqfi
TYnJIZ7QYLlQx5VPNlzZuSJTUzKmHQLtLcTqdO5HmLxfxc0WuS/ftK916wy/jpSc
m2DiHjIy6aAEaHKGQrNgT+no68kp30xkYAVsIs0BFpl6Q2iNr5e0uKta82A0xU1Q
we7swSHOHCevuDZfFA/CqnBptOjvNUuVytcroCeCrV/ftp75w/Fd9zOcb6LGLxM2
2UzhkSXl3II250xj74Q3q8T9TDxCLty7oiawhaYKI+8SDYc510EQ7MH46WMO+3Uq
JkpmmELd9POgnnZ1JrCFmf0flUKTi2CqU3wrBPpPMwFBxoFipp5iL87npACHc3DY
6uaoF4Pf9Et1Fd7HRon8RMsKkrSF92NFiBx5UvhZAgMBAAGjNjA0MA4GA1UdDwEB
/wQEAwIAoDAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDALBgkq
hkiG9w0BAQsDggIBAC0F4ci1nqZ9KUhEEAmWmy8g89DovNNIGSC51r2WJ/COmYUX
X70TONscsBL/kx5MK4xoAmb+EN6Yy8i+z9NkNJd0B+2MjXPMFBpgGb0UiPv2wEmZ
5PAKyjwTxNIm6L/nFhkmVqfsQHfjHukXES4C0ff6fj6fuDpBfl5nTlVmc9LpP+hT
5RAwW10qumucGxAWGNBWW+K66cf8O7n/0nQykxJxYjBx16ZB80H2uvqFDKDVFqze
co5M4euXQq9KiXPRlcC9rab2a7FGLHd0TyPkq6TvfsqpxcryyKS4rIAz3sQh/tl/
/qm1tBcZW2bce3UlF2Wb2dW9HqvIu1O84f6ptLqwgKcIdTbwgQZ0kbFoWE2kWJSV
w+eAFb7tz1LDTpF3NRlz+1K27pBQWRQgcqoIRoQXpC0LfQY9Mp70QIfUQdUh6tnO
8hmq5y623tfxiDwCxb/EOpwCmwK1Cp9cloZTDefVE1r6NkEJWeeHG79VljUGF1KT
NKzXWrrsFtge/hU9Pj+frcZO9qExxPCcsrdZcoK7Ll8s+pjulRvbnCnJkNpeOI3P
iz6+sdGmzKSKg2daRM67Zmy5tmlBEX/eV7kFqt+b3HsdUiLo3Ng2lyPLNNDfwUtB
EukgYGjVJoyqLjLXgsCxLJlk7X/ogVwf8SlAnQ7p6KuxGWm02vlUpEmJp+Hq
-----END CERTIFICATE-----

View file

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFETCCAvugAwIBAgIQJ+iLgsp9gA0DmROqW+tHFzALBgkqhkiG9w0BAQswJjER
MA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE1MDUyNjIw
NTQxNloXDTE4MDUxMDIwNTQxNlowKzERMA8GA1UEChMIUXVpY2tUTFMxFjAUBgNV
BAMTDWxvY2FscmVnaXN0cnkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
AQDHR/A6uiQ9X/Xh5ivmdjRr5XVr1D7+fU9Qu6ohArqtBuJsLr6t2RBTS9w6PIAf
xjQSMSFlrm/CY+hbfBMSgm9NeH23o3kYCgoEPhP/634A45W5xwUFno388U8/NHK7
qwzSP1ezKXfXNvzuo1mZhT08aVdGMOrZUcZZZl8R3RPcIRw9XDSfXKVkMluH6egk
8iLdOxdIdRS58DeSI09FskWe3cIZ5kJmMqnKoIbYSJCVVeYPO0RFlIBi+zpdVyI/
r9LG0r0plRdz/HJevbOitU2y93S1s9NWMNEkOFU1PFJmsF3ZzNqJFCySj00y/Hcs
jPULYwIxYdqcv16cTNmd3P6FegvuzLJLjNuGaLJGc1antv+p62P7ZdE3DyprFuxs
MJgDL9+NjDaIzoamFf0Uv7K3F7hxrrAHfvm1CMUOyQLg9J6Wl4mLsOy2ZhCbdNFs
T6dobAUGvz4Muj9V8V5pR+nFehjmsPENSsTcs5j0e8zTWtvMFISdS+NZAkpiz0s4
PV8DLgk5Rp1ZG2V5OnRPLMOTgK0nngc5GVaxf7OYCrFHbBJ8tL93MXNQptNFeBpV
FhjUGqVFcz+6nbFX2NsFLZnghQRs9lej4TTG33NSAYusKqhVwpYFf8CsXCcvYuU6
RlkCYjr3PB+nX1UDa0eUGm0zOabf9O3D1VzHQBpDuzSHQwIDAQABozowODAOBgNV
HQ8BAf8EBAMCAKAwDAYDVR0TAQH/BAIwADAYBgNVHREEETAPgg1sb2NhbHJlZ2lz
dHJ5MAsGCSqGSIb3DQEBCwOCAgEAaPfAs6saij4FZIPbzAb5M6ZVvfXBg+AfH52t
p3tFsnWUJCiOh9ywsc2NcmJdleKDc4/spElFMUarHqcE1ua6EH15O5GEnHWKj8EY
PVQFrPvf30UkRGNPl8eC7afZtCNk9MLllIATAzBr5Z1i+psV7MmgBKpbZ4B0TnhR
GXNT60QaCJ9RfUuc2z7RHJNo9XTn3Q44X7TFj+P3jHOWzTf8y6Mz6saTy2bugIUy
AfRgRgq/bB8hRjrazg55FIlrMv7dr3J0cIuqmaHfsw7Q2ECMCXW8oQXMBzfuIT0n
sG4u0oVxdNx4OdHsAubGjjwNDhxJvN5j8+YFqZMu03i8LbyamTwsrZg2C3QrRUq8
SujQEEB+AmO0lpuJ24FsOOYVSYCpLy2ugrKOr2NUqbiBKZs8uBh6RGACfunMZlEw
4BntohiO7oZ5gjvhGZNUEqzMChw7knvVjZ+DkhFk9yE4qIL7VsJSUNI2ZJym/Xeq
jr/oT8CpP8/mFZspa6DFciPfhGLQqKcaZZohL7461pOYWY5C2vsJNR2ucBZzTFvD
BiN/rMnIGFrxUscCCje6RLmrsZ3Lb7bfhB3W6kwzLRfr/XEygAzx6S2mlOM34kqF
HFpKrg9TtLIpYLAKAIfuNbrLaNP1UKh7iLarhDz/qDcvRka/qJTzLD3eLeGXefAP
KjJ1S7s=
-----END CERTIFICATE-----

View file

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE9DCCAt6gAwIBAgIQb58oJ+9SvWUCcYWA+L1oiTALBgkqhkiG9w0BAQswJjER
MA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE1MDUyNjIw
NTUwMFoXDTE4MDUxMDIwNTUwMFowEzERMA8GA1UEChMIUXVpY2tUTFMwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDDmOL3EhBm4So3agPMmF0z1+/nPlrE
xoG7x0HYPk5CP3PF3TNVk3ArBPkMzge0/895a4ZEb9j+LUQEjOZa/ZwuLmSjfJSt
9xTXI1ldp8KasyzQZjC33/bUj7FGxGzgbHyJrGGBoH2W5HdswH4WzhCnGTslyiDo
VN4hklJ7gr+Geq3TPf8Eji+1L71MOrUyoNp7BaQBQT/gKxK0nV+ZuSk6eaiu+om7
slp3x4bc21o7eIMmNXggJP6p9fMDctnioKhAPcm+5ADiFYSjivLeUQ85VkMTpmdU
yvq6ziK3Ls6erD+S3xLvcHYAaeu84qLd7qdPwkHMTQsDpO4vPMIwL8piMzZV+kwL
Bq+5xk5//FwnQH0pSo2Nr4vRn+DITZc3GKyGUJQoOUgAdfGNskTt8GXa4IsHn5iw
zr12vGaxb//GDm0RLHnh7NVbD8xxDHIJq+fJNFb7MdXa8v31PYebkWuaPhYt6HQC
I/D81zwcJIOGfzNITS2ifM5tvMaUXireo4pLC2v2aSY6RrPq1owlB6jGFwGwZSAF
O6rxSqWO1gLfhJLzqcw/NjWnO7nCZEs/iKgAa22K2CtTt3dDMTvSBYKdkRe/FYQC
MCa7MFJSaH85pYRzoDN4IuVpvROrtuQmlI47oZzb64uCPoA4A8AN+k8iysqITsgK
1m8ePPXhbu4YlwIDAQABozUwMzAOBgNVHQ8BAf8EBAMCAKAwEwYDVR0lBAwwCgYI
KwYBBQUHAwIwDAYDVR0TAQH/BAIwADALBgkqhkiG9w0BAQsDggIBALSgrCdEQd3I
vb/FNkNZkAwdjfBD6j7ZtPBwvjEiiyNTx9hOLBGvbey7kr0HtW0KkLWsdRmCc+3z
ev9I5VjDOtpiqrvuAA1wRBaL3UzGyj/eFjPJpvkfJi8zjkIZ2y18QG3yJ6Eqy6dD
0aIQAHl9hkXMOVrf364gf0p7EoOGtSlfQ56yIGDPTFKKiy+Al0S42p17lhI4coz9
zGXE1/SiNeZgdsk4zHDqhzzBp8foZuSL1sGcIXHkG8RtqZ1WvCyIPYRyIjIKZcXd
JCEM//EbgDzQ7VE/jm+hIlYfPjM7fmUzsfii+bIrp/0HGEU3HN++LsA6eQOwWPa/
PrxKPP36EVXb72QK8C3lmz6y+CHhuuAm0C1b1qmYVEs4eRE21S8eB2l0KUlfOecf
xZ1LWp1agKt6fGqRgcsR3/qO27l8W7hlbFNPeOTgr6NQQkEMRW5OxbnZ58ULXqr3
gWh8Na3D4+3j53035UBBQUMmeeFfWCvtr5n0+6BTAi62Cwwu9QQQBM/2f9/9K+B7
cW0xPYtczm+VwJL6/rDtNN9xPWitxab1dkZp2XcHG3VWtYvE2R2EtEoKvvCLPggx
zcafsZfcD1wlvtQF7YjykGJnMa0SB0GBl9SQtvGc8PkP39yXHqXZhIoo3fp4qm9v
RfbdpOr8p/Ks34ZqQPukFwpM1s/6aicF
-----END CERTIFICATE-----

View file

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIFCTCCAvOgAwIBAgIQPjclBRGzhznCybQzYRQTyjALBgkqhkiG9w0BAQswJjER
MA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE1MDUyNjIw
NTQ1NloXDTE4MDUxMDIwNTQ1NlowJzERMA8GA1UEChMIUXVpY2tUTFMxEjAQBgNV
BAMTCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALBe
C9O6es+mStDowUd1kiM59VkinzzdHgE24LvKmGxQ6fDnnT8S9L7iyzoxcJWlvSHu
pfyZWvij0ZIyRZ288XemTEFYq25RK0IBGGdvYz9OqT2R3lblBQrXDjSi9WG16sGx
60MGhM2egGMqFQ5DBfT16IKw00+RjFgCVzJ8T64Lzw82E0e7d6hl39SPybY+uvrt
SID60hYGmXoOdaiC9qquivks67BZprGNfORrvyJNrCFI6oKUFWHrQ1PpGd2tOwJN
1P3gkkS8pVlAif6ZQkAf+zuKu+l4j5tKxGlJAkJsafVJDLOxBKutUj5msha0g6uJ
gFXUe0+G8hkNcEjd8XqUUCwIOY3pdv4WsydKBk3uH9zMnYolw53k1q0ObvoY1NXf
beMxHQAtDi7nfQGlae9cuuOSymy95WuvzfhZFKdPWUe8lKN9QXFIWVoCFnOm8T3P
+FNCUE+p8DIWkal6Ul9THi/Kz4p7twyrUp1LwT5EtSaJ3iGAmB9I+8/1vmZT3lPi
nX8P+iVGM5yOUnptrsFm0bUcJWRD6iaTK1KxpH+Is4h2kiUiSz1tC/9bKaJYN2o9
oy7q7+ZVfHSmIxLo8ZFYsaZBcXi96cKuuPMR3X4ISPwKDqP5irxU/QbI+YQBMshg
G4b0BNoMZ50g30r3Hcsifw4pzPQF0RDMOBeCiOi3AgMBAAGjNjA0MA4GA1UdDwEB
/wQEAwIAoDAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDALBgkq
hkiG9w0BAQsDggIBAFuS/VrMNUwEMyUIktDyna5ExYh/FDOE+YEYf8tsX7dSMhRK
wE560/AcVZcbKKAZOnZ/262a++8tparsQt+bXBJ2so6YUqsFDNdOLCI2aShjWDRe
TNhqmLIO3FNsLRKp96WHVz+jFoiECsoYfKn0jgqTqxx+7nWFqgBaNSlF5cbCgLCH
jQV1uQhzsw/Mh/32hXAidkv/nLeLf7FbKq08hgthtoP+XstlzZ5BxkPodjb8XWXG
DSS49SWX971GHa1apwMKfxVGSppxn18ZwEmW1BUfQBNxtMytqA9DK3+xuoUdXkB0
iJbm3Jc10JSRju8iyL121Xt6f8O33paVz/ndDJIWztUOjnItc89rxHsINPt5+cUt
jix8ohwmHGDrK7ZooXBvotvmGT/xhPr2eHUAG8JuSJ/Cr09UUOwUEigz4CfgJOHm
XukdzjOkb4r7lhNmVeGqrjRol1W0Wsc1NGH++J6xdkIeQ+i23kHwFHfQWV/J69tm
rOn2N+qijtmbIy9YfVcrFDtUtEAzXylZ2StCVQNofd0M7tXNdrUL8yAFwlrhWGJV
wsSP++1xH2Ie6Diupy8z6rbP383HmnmVPU/UecgLrlX2lEpt/UZkkX1Xm+6PhrrT
HDeeULvqtUP3PD8wS0C873Pl9GXOKISqf0HKEIDUAVZhQOsGFqiZH0388M4L
-----END CERTIFICATE-----

View file

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFETCCAvugAwIBAgIQCnqSQalw9ytL5bHLgHZe+jALBgkqhkiG9w0BAQswJjER
MA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE1MDUyNjIw
NTQ1OFoXDTE4MDUxMDIwNTQ1OFowKzERMA8GA1UEChMIUXVpY2tUTFMxFjAUBgNV
BAMTDWxvY2FscmVnaXN0cnkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
AQC9gvT3cwz0Ih9+7Ilv5lc15HsEiSmEMh4nOMZrSaamKgf/ydCiGo3DQapr/XDK
FHMLKq68AxwfOlzmEFQ4d9umpPMQ2+4GBr0VG23ppGtQApIPHgD06S0/CeHmDIXN
FXcKybPX/9KbgNkXBWbbJkJy0EcsdP8VJD50Q2WH89nvgEYJNFuKEELD3iGY6bBF
jeDTle5jYA7CgBKvD2avn31g24Qhxn8n8/BdYO/U0kw0qmoy1veLOjCAW0os0jkM
NlKrFpyHEWNj5B3X6UgSn8EGQaVbDq17PrQwlHJYU4nih0TnD1OwvBnFnd27pXjr
68eGA6Zc2BbUnhNGhppWHZ46LpPxpIbafSOH3ES3N/MZAfcUKIUntLlWE2xCQgFV
TW95WeVtP/r1aWgIHu0E2Jb2eHCE+qXYqJxSU7S4DcknmmcTS69hzyHs+92Ec+7Q
m0aQFZ0dyPoYPwXMgZpTAIuXEGg/FKC1fiS/deTW37DyvB2jppehKW3RJY3uso7R
o9vs6DJx1OdU5XEq9R3n7op61N7PK8Wxmn7TVYHEZHkITVvtucZZd1FNTOrOJaNJ
UnE+FuPK1Mrff+jz666Ru4zQL0CondOamX3QR5tuNK6MTqFs87wKY25qsqz7cS27
kHW+r7UNWbJY3/UQhaPZM78zCZa2IL1nBFUjsFvEA4rtYwIDAQABozowODAOBgNV
HQ8BAf8EBAMCAKAwDAYDVR0TAQH/BAIwADAYBgNVHREEETAPgg1sb2NhbHJlZ2lz
dHJ5MAsGCSqGSIb3DQEBCwOCAgEAHVGMyoyX4lRzWCDkUjrXkrDZzuv03M2ojW2Q
UL61ejMkTWQW8R4gKrcPHAOJAPKVfGEVOrQH3ZMyxV2HnWrJ7egrn65zOzmLbWSh
O7gdpL6YYjBr218fqJn/8HadXZa4k70JyympYOLojeWSLy3KP03U+y7AMcdE1uG6
6HJI54ZjBoW/nEyWmMh/mfMz8EN+Mgek48Z9AVaOswbtHtDIXN7XO0jbB3DbY5Yh
prVqVLYAz4sCchGTadj+aEChF5sJkKREDvAew/njC0WGS2TmMJ+V1uVhXV6354mr
edk79YvdwzwDgeYArkprahMtn9eu1aSTfUXsmM5OP5tR4gyFV1kUmTPY1yUd/yO+
638wV0mWtGbbf6j8dUKeUBCyt2qGg8J80OUeFdvdHMswtaUq951NApX44BinPkbK
moBVQByZ5OEcmMidFC9SqYSUwTQ7uNyWeguhCXav+l3x900YlKnUQgRUZntPwXjs
yc7MXv0j0E86Gme6G1O02zamwkRgr3qOTHu2oQOow/a24fM4HASayLR0Kegt0sh3
rzk0HRF1mGonf1Ecyyj/3LpHVsgYSckwtJoZLOqtDMn+CKtOCEByssQfD+E9Qe07
qMyvcwpXUpfqe3ZERbJ10m98Z88VeK/XGt9ptq7HY47n1KL6lx3oyXwZIw8pq928
89dcqL0=
-----END CERTIFICATE-----

View file

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1 @@
testuser:$apr1$YmLhHjm6$AjP4z8J1WgcUNxU8J4ue5.

View file

@ -0,0 +1,64 @@
#!/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 registry:0.9.1
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
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" \
$DIR

View file

@ -0,0 +1,109 @@
#!/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"}
email="distribution@docker.com"
function setup() {
tempImage $image
}
@test "Test valid certificates" {
docker_t tag -f $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 -f $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 -f $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 -f $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 -f $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 -f $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 -f $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 -f $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 -f $image $hostname:5448/$image
run docker_t push $hostname:5448/$image
[ "$status" -ne 0 ]
}

View file

@ -0,0 +1,119 @@
#!/usr/bin/env bats
# This tests contacting a registry using a token server
load helpers
user="testuser"
password="testpassword"
email="a@nowhere.com"
base="hello-world"
@test "Test token server login" {
run docker_t login -u $user -p $password -e $email localregistry:5554
echo $output
[ "$status" -eq 0 ]
# First line is WARNING about credential save or email deprecation
[ "${lines[2]}" = "Login Succeeded" -o "${lines[1]}" = "Login Succeeded" ]
}
@test "Test token server bad login" {
run docker_t login -u "testuser" -p "badpassword" -e $email localregistry:5554
[ "$status" -ne 0 ]
run docker_t login -u "baduser" -p "testpassword" -e $email localregistry:5554
[ "$status" -ne 0 ]
}
@test "Test push and pull with token auth" {
login localregistry:5555
image="localregistry:5555/testuser/token"
build $image "$base:latest"
run docker_t push $image
echo $output
[ "$status" -eq 0 ]
docker_t rmi $image
docker_t pull $image
}
@test "Test push and pull with token auth wrong namespace" {
login localregistry:5555
image="localregistry:5555/notuser/token"
build $image "$base:latest"
run docker_t push $image
[ "$status" -ne 0 ]
}
@test "Test oauth token server login" {
version_check docker "$GOLEM_DIND_VERSION" "1.11.0"
login_oauth localregistry:5557
}
@test "Test oauth token server bad login" {
version_check docker "$GOLEM_DIND_VERSION" "1.11.0"
run docker_t login -u "testuser" -p "badpassword" -e $email localregistry:5557
[ "$status" -ne 0 ]
run docker_t login -u "baduser" -p "testpassword" -e $email localregistry:5557
[ "$status" -ne 0 ]
}
@test "Test oauth push and pull with token auth" {
version_check docker "$GOLEM_DIND_VERSION" "1.11.0"
login_oauth localregistry:5558
image="localregistry:5558/testuser/token"
build $image "$base:latest"
run docker_t push $image
echo $output
[ "$status" -eq 0 ]
docker_t rmi $image
docker_t pull $image
}
@test "Test oauth push and build with token auth" {
version_check docker "$GOLEM_DIND_VERSION" "1.11.0"
login_oauth localregistry:5558
image="localregistry:5558/testuser/token-build"
tempImage $image
run docker_t push $image
echo $output
[ "$status" -eq 0 ]
has_digest "$output"
docker_t rmi $image
image2="localregistry:5558/testuser/token-build-2"
run build $image2 $image
echo $output
[ "$status" -eq 0 ]
run docker_t push $image2
echo $output
[ "$status" -eq 0 ]
has_digest "$output"
}
@test "Test oauth push and pull with token auth wrong namespace" {
version_check docker "$GOLEM_DIND_VERSION" "1.11.0"
login_oauth localregistry:5558
image="localregistry:5558/notuser/token"
build $image "$base:latest"
run docker_t push $image
[ "$status" -ne 0 ]
}

View file

@ -0,0 +1 @@
testuser:$2y$05$T2MlBvkN1R/yICNnLuf1leOlOfAY0DvybctbbWUFKlojfkShVgn4m

View file

@ -0,0 +1,8 @@
FROM dmcgowan/token-server:oauth
WORKDIR /
COPY ./.htpasswd /.htpasswd
COPY ./certs/auth.localregistry.cert /tls.cert
COPY ./certs/auth.localregistry.key /tls.key
COPY ./certs/signing.key /sign.key

View file

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDHDCCAgagAwIBAgIRAKhhQMnqZx+hkOmoUYgPb+kwCwYJKoZIhvcNAQELMCYx
ETAPBgNVBAoTCFF1aWNrVExTMREwDwYDVQQDEwhRdWlja1RMUzAeFw0xNjAxMjgw
MDQyMzFaFw0xOTAxMTIwMDQyMzFaMDAxETAPBgNVBAoTCFF1aWNrVExTMRswGQYD
VQQDExJhdXRoLmxvY2FscmVnaXN0cnkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQD1tUf1EghBlIRrE83yF4zDgRu7vH2Jo0kygKJUWtQQe+DfXyjjE/fg
FdKnnoEjsIeF9hxNbTt0ldDz7/n97pbMhoiXULi9iq4jlgSzVL2XEAgrON0YSY/c
Lmmd1KSa/pOUZr2WMAYPZ+FdQfE1W7SMNbErPefBqYdFzpZ+esAtvbajYwIjl8Vy
9c4bidx4vgnNrR9GcFYibjC5sj8syh/OtbzzqiVGT8YcPpmMG6KNRkausa4gqpon
NKYG8C3WDaiPCLYKcvFrFfdEWF/m2oj14eXACXT9iwp8r4bsLgXrZwqcpKOWfVRu
qHC8aV476EYgxWCAOANExUdUaRt5wL/jAgMBAAGjPzA9MA4GA1UdDwEB/wQEAwIA
oDAMBgNVHRMBAf8EAjAAMB0GA1UdEQQWMBSCEmF1dGgubG9jYWxyZWdpc3RyeTAL
BgkqhkiG9w0BAQsDggEBABxPGK9FdGDxcLowNsExKnnZvmQT3H0u+Dux1gkp0AhH
KOrmx3LUENUKLSgotzx133tgOgR5lzAWVFy7bhLwlPhOslxf2oEfztsAMd/tY8rW
PrG2ZqYqlzEQQ9INbAc3woo5A3slN07uhP3F16jNqoMM4zRmw6Ba70CluGKT7x5+
xVjKoWITLjWDXT5m35PnsN8CpBaFzXYcod/5p9XwCFp0s+aNxfpZECCV/3yqIr+J
ALzroPh43FAlG96o4NyYZ2Msp63newN19R2+TgpV4nXuw2mLVDpvetP7RRqnpvj/
qwRgt5j4hFjJWb61M0ELL7A9fA71h1ImdGCvnArdBQs=
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA9bVH9RIIQZSEaxPN8heMw4Ebu7x9iaNJMoCiVFrUEHvg318o
4xP34BXSp56BI7CHhfYcTW07dJXQ8+/5/e6WzIaIl1C4vYquI5YEs1S9lxAIKzjd
GEmP3C5pndSkmv6TlGa9ljAGD2fhXUHxNVu0jDWxKz3nwamHRc6WfnrALb22o2MC
I5fFcvXOG4nceL4Jza0fRnBWIm4wubI/LMofzrW886olRk/GHD6ZjBuijUZGrrGu
IKqaJzSmBvAt1g2ojwi2CnLxaxX3RFhf5tqI9eHlwAl0/YsKfK+G7C4F62cKnKSj
ln1UbqhwvGleO+hGIMVggDgDRMVHVGkbecC/4wIDAQABAoIBAQCrsjXKRwOF8CZo
PLqZBWPT6hBbK+f9miC4LbNBhwbRTf9hl7mWlImOCTHe95/+NIk/Ty+P21jEqzwM
ehETJPoziX9BXaL6sEHnlBlMx1aEjStoKKA3LJBeqAAdzk4IEQVHmlO4824IreqJ
pF7Njnunzo0zTlr4tWJVoXsAfv5z9tNtdkxYBbIa0fjfGtlqXU3gLq58FCON3mB/
NGc0AyA1UFGp0FzpdEcwTGD4InsXbcmsl2l/VPBJuZbryITRqWs6BbK++80DRhNt
afMhP+IzKrWSCp0rBYrqqz6AevtlKdEfQK1yXPEjN/63QLMevt8mF/1JCp//TQnf
Z6bIQbAhAoGBAP7vFA0PcvoXt9MXvvAwrKY1s6pNw4nWPG27qY1/m+DkBwP8IQms
4AWGv1wscZzXJYTvaLO5/qjmGUj50ohcVEvyZJioh1pKXA8Chxvd6rBA/O/Lj5E0
3MOSA5Q0gxJ0Mhv0zGbbyN5fY8D8zhxoqQP4LoW+UdZG2Oi6JxsQ9c9dAoGBAPa8
U3bGuM5OGA9EWP7mkB/VnjDTL1aEIN3cOHbHIKwH/loxdYcNMBE7vwxV1CzgIzXT
wsL0iE15fQdK938u0+um8aH5QtbWNI8tdk1XVjEC/i3C7N6WVUutneCKUDb4QxiB
9OvWCbNNiN+xTKBBM93YlwO3GYfrW9Pmm9q1+hg/AoGBALJlUS22gun50PxaIJZq
KVcCO2DQnCYHki/j48mN4+HjD/m85M2lePrFCYIR48syTyIQer9SR5+frVAA6k/b
9G1VCQo+3MDVSkiCp1Nb3tBKGfYgB65ARMBinDiI6rPuNeaUTrkn0g+yxtaU0hLV
Nnj9omia/x+oYj+xjI4HN0xNAoGARy92dSJIV104m88ATip/EnAzP6ruUWu1f8z1
jW9OAdQckjEK03f+kjpGmGx61qekAPejjVO3r4KJi/0ZAtyjz61OsYiUvB748wYO
x6mW+HUAmHtQk7eTzE2+6vV8xx9BXGTCIPiTu+N2xfMFRIcLS8odZ7j/6LMCv1Qd
SzCNg0kCgYBaNlEs4pK1VxZZpEWwVmFpgIxfEfxLIaGrek6wBTcCn/VA2M0oHuez
mlMio8VY0yWPBJz30JflDiTmYIvteLPMHT0N0J6isiXLhzJSFI4+cAMLE2Q5v8rz
W+W5/L8YZeierW0qJat1BrgStaf5ZLpiOc9pKBSwycydPH5BfVdK/A==
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC9TCCAd+gAwIBAgIQNS9SaFSFBN7Zvwjalrf2DDALBgkqhkiG9w0BAQswJjER
MA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE2MDEyODAw
NDIzMFoXDTE5MDExMjAwNDIzMFowJjERMA8GA1UEChMIUXVpY2tUTFMxETAPBgNV
BAMTCFF1aWNrVExTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu/Pf
fQ7VUTSXs12PRyrLDVDz7kPDbGNTt0vF7FYDmTTGOU3i62xZNOGuxBezAiVSV5A3
lopwsv4OH7DRtSaPn+XCt1JDALna2WrjT0MshypMd5o2c3jmGUfAKf5gjizgIoEl
d4e5aqEBuOQP+QCEde+8p8N1buQW+zMy9srM2O/7BFMIaQ07CWLlj3hIiF+L5rKD
L6dWtKT7INRmRwpuZZnThEWnBSNgayrWek6G0i3y8QYTfVA1SwA+H3grJxy5NrLp
GYXSmu2509mu0QAHhx05t1rJhwhFz/4sG7j8AggYeDXEqfQ/VIb/bvnW9bD+vrQ2
ZnICvxnzNMYBx23BkQIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAKQwDwYDVR0TAQH/
BAUwAwEB/zALBgkqhkiG9w0BAQsDggEBALvTi6E44Fltu83dFLVEj0kLtusI/TTH
Tw6upoB5pRG+7A75w0Ii8bvvd2tNpBOg+L+80xyIFqaNkXhLKTN4lgtd7WiCuyb/
w1BEuF/+RjCXhu6wQ/63ab46d6ctaQ1zjxlU2rQLQXQFALI8ntyn/TELc01HYkr2
x3NHlbnBNlgI2CKXPeUBzvBylTCcdYGwoa+2ZPdIsFjle2aCIBoZ+WNZlIbFwgLh
XCHwcbviC+thjqOneJpJZmRW9AxQ638ki6iGItdrJewCN/1dcL2KKjxnC5VHbpne
SOjEPNXihY08Brl8myhFNtRRKZ55MJIYzDtVQSkCaT91Q3XX9tSZadY=
-----END CERTIFICATE-----

View file

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDETCCAfugAwIBAgIQN7rT95eAy75c4n6/AsDJODALBgkqhkiG9w0BAQswJjER
MA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE2MDEyODAw
NDIzMloXDTE5MDExMjAwNDIzMlowKzERMA8GA1UEChMIUXVpY2tUTFMxFjAUBgNV
BAMTDWxvY2FscmVnaXN0cnkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDLi75QEkl/qekcoOJlNv9y1IXvrbU2ssl4ViJiZRjWx+/CkyCCOyf9YUpAgRLr
Pskqde2mwhuNP8yBlOBb17Sapz7N3+hJi5j9vLBAFcamPeF3PqxjFv7j5TKkRmSI
dFYQclREwMUd3qEH322KkqOnsEEfdmCgFqWORe+QR5AxzxQP3Pnd4OYH1yZCh0MQ
P2pJgrxxf2I5I/m1AUgoHV1cdBbCv9LGohJPpMtwPC0dJpgMFcnf6hT37At236AY
V437HiRruY7iPWkYFrSPWpwdslJ32MZvRN5RS163jZXjiZ7qWnQOiiDJfXe4evB/
yQLN4m0qVQxsMz7rkY7OsqaXAgMBAAGjOjA4MA4GA1UdDwEB/wQEAwIAoDAMBgNV
HRMBAf8EAjAAMBgGA1UdEQQRMA+CDWxvY2FscmVnaXN0cnkwCwYJKoZIhvcNAQEL
A4IBAQAyUb3EuMaOylBeV8+4KeBiE4lxykDOwLLSk3jXRsVVtfJpX3v8l5vwo/Jf
iG8tzzz+7uiskI96u3TsekUtVkUxujfKevMP+369K/59s7NRmwwlFMyB2fvL14B2
oweVjWvM/8fZl6irtFdbJFXXRm7paKso5cmfImxhojAwohgcd4XTVLE/7juYa582
AaBdRuIiyL71MU9qa1mC5+57AaSLPYaPKpahemgYYkV1Z403Kd6rXchxdQ8JIAL8
+0oYTSC+svnz1tUU/V5E5id9LQaTmDN5iIVFhNpqAaZmR45UI86woWvnkMb8Ants
4aknwTwY3300PuTqBdQufvOFDRN5
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAy4u+UBJJf6npHKDiZTb/ctSF7621NrLJeFYiYmUY1sfvwpMg
gjsn/WFKQIES6z7JKnXtpsIbjT/MgZTgW9e0mqc+zd/oSYuY/bywQBXGpj3hdz6s
Yxb+4+UypEZkiHRWEHJURMDFHd6hB99tipKjp7BBH3ZgoBaljkXvkEeQMc8UD9z5
3eDmB9cmQodDED9qSYK8cX9iOSP5tQFIKB1dXHQWwr/SxqIST6TLcDwtHSaYDBXJ
3+oU9+wLdt+gGFeN+x4ka7mO4j1pGBa0j1qcHbJSd9jGb0TeUUtet42V44me6lp0
DoogyX13uHrwf8kCzeJtKlUMbDM+65GOzrKmlwIDAQABAoIBAF6vFMp+lz4RteSh
Wm8m1FGAVwWVUpStOlcGClynFpTi0L88XYT3K7UMStQSttBDlqRv0ysdZF+ia+lj
bbKLdvHyFp8CJzX/AB4YZgyJlKzEYFtuBhbaHZu5hIMyU5W+OELSTCznV0p7w4C8
CGLLr+FTdhfCo1QU9NJn6fa9s2/XRdSClBBalAHYs0ZS7ZckaF/sPiC/VapfBMet
qjJXNYiO6pXYriGWKF9zdAMfk2CM0BVWbnwQZkMSEQirrTcJwm3ezyloXCv2nywK
/VzbUT1HJVyzo5oAwTd0MwDc2oEMiFzlfO028zY4LDltpia+SyWvFi5NaIqzFESc
yLgJacECgYEA3jvH+ZQHQf42Md8TCciokaYvwWIKJdk4WRjbvE5cBZekyXAm7/3b
/1VFDKsy2RPlfmfHP3wy9rlnjzsRveB5qaclgS8aI67AYsWd/yRgfRatl7Ve9bHl
LY6VM5L/DZTxykcqivwjc77XoDuBfUKs6tyuSLQku+FOTbLtNYlUCHECgYEA6nkR
lkXufyLmDhNb3093RsYvPcs1kGaIIGTnz3cxWNh485DgsyLBuYQ5ugupQkzM8YSt
ohDTmVpggqjlXQxCg0Zw8gkEV0v8KsLGjn1CuTJg/mBArXlelq1FEeRAYC9/YfOz
ocXegHV7wDKKtcraNZFsEc7Z0LwbC9wtzSFG44cCgYASkMX1CLPOhJE8e1lY0OWc
PVjx++HDJbF6aAQ7aARyBygiF/d4xylw3EvHcinuTqY2eC8CE7siN3z6T0H9Ldqc
HLWaZDf30SqLVd0MKprQ+GsKKIHFXtY5hxbZ1ybtmIrWjjl0oPnJOqFC5pW7xC0z
9bmtozcKZxkmjpMYjN9zUQKBgQCqV6KLRerqunPgLfhE1/qTlE+l2QflDFhBEI3I
j5NuNHZKnSphehK7sHAv1WD2Jc2OeRGb+BWCB8Ktqf5YBxwbOwW7EQnyUeW1OyP9
SMs8uHj21P6oCNDLLr5LLUQHnPoyM1aBZLstICzziMR1JhY5bJjSpzBfEQmlKCSu
LkrN6QKBgQCRXrBJRUxeJj7wCnCSq0Clf9NhCpQnwo4bEx8sKlj8K8ku8MvwQwoM
3KfWc7bOl6A2/mM/k4yoHtBMM9X9xqYtsgeFhxuiWBcfTmTxWh73LQ48Kgbrgodt
6yTccnjr7OtBidD85c6lgjAUgcL43QY8mlw0OhzXAZ2R5HWFp4ht+w==
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC9TCCAd+gAwIBAgIRAJ6IIisIZxL86oe3oeoAgWUwCwYJKoZIhvcNAQELMCYx
ETAPBgNVBAoTCFF1aWNrVExTMREwDwYDVQQDEwhRdWlja1RMUzAeFw0xNjAxMjgw
MDQyMzNaFw0xOTAxMTIwMDQyMzNaMBMxETAPBgNVBAoTCFF1aWNrVExTMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3IXUwqSdO2QTj2ET6fJPGe+KWVnt
QCQQWjkWVpOz8L2A29BRvv9z6lYNf9sOM0Xb5IUAgoZ/s3U6LNYT/RWYFBfeo40r
Xd/MNKAn0kFsSb6BIKmUwPqFeqc8wiPX6yY4SbF1sUTkCTkw3yFHg/AIlwmhpFH3
9mAmV+x0kTzFR/78ZDD5CUNS59bbu+7UqB06YrJuVEwPY98YixSPXTcaKimsUe+K
IY8FQ6yN6l27MK56wlj4hw2gYz+cyBUBCExCgYMQlOSg2ilH4qYyFvccSDUH7jTA
NwpsIBfdoUVbI+j2ivn+ZGD614LtIStGgUu0mDDVxVOWnRvq/z7LMaa2jwIDAQAB
ozUwMzAOBgNVHQ8BAf8EBAMCAKAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0T
AQH/BAIwADALBgkqhkiG9w0BAQsDggEBAJq3JzTLrIWCF8rHLTTm1icE9PjOO0sV
a1wrmdJ6NwRbJ66dLZ/4G/NZjVOnce9WFHYLFSEG+wx5YVUPuJXpJaSdy0h8F0Uw
hiJwgeVsGg7vcf4G6mWHrsauDOhylnD31UtYPX1Ao/jcntyyf+gCQpY1J/B8l1yU
LNOwvWLVLpZwZ4ehbKA/UnDXgA+3uHvpzl//cPe0cnt+Mhrgzk5mIMwVR6zCZw1G
oVutAHpv2PXxRwTMu51J+QtSL2b2w3mGHxDLpmz8UdXOtkxdpmDT8kIOtX0T5yGL
29F3fa81iZPs02GWjSGOfOzmCCvaA4C5KJvY/WulF7OOgwvrBpQwqTI=
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA3IXUwqSdO2QTj2ET6fJPGe+KWVntQCQQWjkWVpOz8L2A29BR
vv9z6lYNf9sOM0Xb5IUAgoZ/s3U6LNYT/RWYFBfeo40rXd/MNKAn0kFsSb6BIKmU
wPqFeqc8wiPX6yY4SbF1sUTkCTkw3yFHg/AIlwmhpFH39mAmV+x0kTzFR/78ZDD5
CUNS59bbu+7UqB06YrJuVEwPY98YixSPXTcaKimsUe+KIY8FQ6yN6l27MK56wlj4
hw2gYz+cyBUBCExCgYMQlOSg2ilH4qYyFvccSDUH7jTANwpsIBfdoUVbI+j2ivn+
ZGD614LtIStGgUu0mDDVxVOWnRvq/z7LMaa2jwIDAQABAoIBAD2tiNZv6DImSXo+
sq0qQomEf/OBvWPFMnWppd/NK/TXa+UPHO4I0MjoDJqIEC6zCU+fC4d2St1MmlrT
/X85vPFRw8mGwGxfHeRSLxEVj04I5GDYTWy0JQUrJUk/cTKp2/Bwm/RaylTyFAM0
caYrSpvD69vjuTDFr7PDxM6iaqM53zK/vD8kCe81z+wN0UbAKsLlUOKztjH6SzL9
uVOkekIT/j3L2xxyQhjmhfA3TuCP4uNK/+6/4ovl9Nj4pQsFomsCk4phgqy9SOm1
4yufmVd8k7J3cppMlMPNc+7tqe2Xn593Y8QT95y3yhtkFECF70yBw64HMDDpA22p
5b/JV9ECgYEA9H4RBXOwbdjcpCa9H3mFjHqUQCqNme1vOSGiflZh9KBCDKgdqugm
KHpvAECADie0p6XRHpxRvufKnGFkJwedfeiKz51T+0dqgPxWncYT1TC+cAjOSzfM
wBpUOcAyvTTviwGbg4bLanHo4remzCbcnRvHQX4YfPFCjT9GhsU+XEUCgYEA5ubz
IlSu1wwFJpoO24ZykGUyqGUQXzR0NrXiLrpF0764qjmHyF8SPJPv1XegSxP/nUTz
SjVfJ7wye/X9qlOpBY8mzy9qQMMKc1cQBV1yVW8IRZ7pMYQZO7qmrZD/DWTa5qWt
pqSbIH2FKedELsKJA/SBtczKjspOdDKyh0UelsMCgYA7DyTfc0XAEy2hPXZb3wgC
mi2rnlvcPf2rCFPvPsCkzf2GfynDehaVmpWrsuj8Al1iTezI/yvD+Mv5oJEH2JAT
tROq+S8rOOIiTFJEBHAQBJlMCOSESPNdyD5mQOZAzEO9CWNejzYd/WwrL//Luut5
zBcC3AngTIsuAYXw0j6xHQKBgQDamkAJep7k3W5q82OplgoUhpqFLtlnKSP1QBFZ
J+U/6Mqv7jONEeUUEQL42H6bVd2kqUikMw9ZcSVikquLfBUDPFoDwOIZWg4k0IJM
cgHyvGHad+5SgLva/oUawbGWnqtXvfc/U4vCINPXrimxE1/grLW4xp/mu8W24OCA
jIG/PQKBgD/Apl+sfqiB/6ONBjjIswA4yFkEXHSZNpAgcPwhA+cO5D0afEWz2HIx
VeOh5NjN1EL0hX8clFW4bfkK1Vr0kjvbMUXnBWaibUgpiVQl9O9WjaKQLZrp4sRu
x2kJ07Qn6ri7f/lsqOELZwBy95iHWRdePptaAKkRGxJstHI7dgUt
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,18 @@
version: 0.1
loglevel: debug
storage:
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /tmp/registry-dev
http:
addr: 0.0.0.0:5000
tls:
certificate: "/etc/docker/registry/localregistry.cert"
key: "/etc/docker/registry/localregistry.key"
auth:
token:
realm: "https://auth.localregistry:5559/token/"
issuer: "registry-test"
service: "registry-test"
rootcertbundle: "/etc/docker/registry/tokenbundle.pem"

Some files were not shown because too many files have changed in this diff Show more