mirror of https://github.com/k3d-io/k3d
clusterCreate: add docker's --gpus flag (#395)
parent
e5660f4b1f
commit
d0158c97c6
File diff suppressed because it is too large
Load Diff
@ -1,63 +0,0 @@ |
|||||||
version: "{build}" |
|
||||||
|
|
||||||
image: Visual Studio 2017 |
|
||||||
|
|
||||||
clone_folder: c:\gopath\src\github.com\containerd\containerd |
|
||||||
|
|
||||||
branches: |
|
||||||
only: |
|
||||||
- master |
|
||||||
- /release\/.*/ |
|
||||||
|
|
||||||
environment: |
|
||||||
GOPATH: C:\gopath |
|
||||||
CGO_ENABLED: 1 |
|
||||||
matrix: |
|
||||||
- GO_VERSION: 1.13.10 |
|
||||||
|
|
||||||
before_build: |
|
||||||
- choco install -y mingw --version 5.3.0 |
|
||||||
# Install Go |
|
||||||
- rd C:\Go /s /q |
|
||||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go%GO_VERSION%.windows-amd64.zip |
|
||||||
- 7z x go%GO_VERSION%.windows-amd64.zip -oC:\ >nul |
|
||||||
- go version |
|
||||||
- choco install codecov |
|
||||||
# Clone hcsshim at the vendored version |
|
||||||
- bash.exe -elc "export PATH=/c/tools/mingw64/bin:$PATH; |
|
||||||
rm -rf /c/gopath/src/github.com/Microsoft/hcsshim; |
|
||||||
git clone -q https://github.com/Microsoft/hcsshim.git /c/gopath/src/github.com/Microsoft/hcsshim; |
|
||||||
export HCSSHIM_VERSION=`grep Microsoft/hcsshim vendor.conf | awk '{print $2}'`; |
|
||||||
echo Using Microsoft/hcsshim $HCSSHIM_VERSION; |
|
||||||
pushd /c/gopath/src/github.com/Microsoft/hcsshim; |
|
||||||
git checkout $HCSSHIM_VERSION; |
|
||||||
popd" |
|
||||||
# Print host version. TODO: Remove this when containerd has a way to get host version |
|
||||||
- ps: $psversiontable |
|
||||||
|
|
||||||
build_script: |
|
||||||
# Build containerd-shim-runhcs-v1.exe and runhcs.exe from Microsoft/hcsshim |
|
||||||
- bash.exe -elc "export PATH=/c/tools/mingw64/bin:$PATH; |
|
||||||
export GOBIN=/c/gopath/src/github.com/Microsoft/hcsshim/bin; |
|
||||||
mkdir $GOBIN; |
|
||||||
pushd /c/gopath/src/github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1; |
|
||||||
go install; |
|
||||||
cd ../runhcs; |
|
||||||
go install; |
|
||||||
ls -al $GOBIN; |
|
||||||
popd" |
|
||||||
- bash.exe -elc "export PATH=/c/tools/mingw64/bin:/c/gopath/bin:$PATH; |
|
||||||
script/setup/install-dev-tools; |
|
||||||
mingw32-make.exe check" |
|
||||||
- bash.exe -elc "export PATH=/c/tools/mingw64/bin:$PATH ; mingw32-make.exe build binaries" |
|
||||||
|
|
||||||
test_script: |
|
||||||
# TODO: need an equivalent of TRAVIS_COMMIT_RANGE |
|
||||||
# - GIT_CHECK_EXCLUDE="./vendor" TRAVIS_COMMIT_RANGE="${TRAVIS_COMMIT_RANGE/.../..}" C:\MinGW\bin\mingw32-make.exe dco |
|
||||||
- bash.exe -lc "export PATH=/c/tools/mingw64/bin:/c/gopath/src/github.com/containerd/containerd/bin:$PATH ; mingw32-make.exe coverage root-coverage" |
|
||||||
# - bash.exe -elc "export PATH=/c/tools/mingw64/bin:/c/gopath/src/github.com/containerd/containerd/bin:$PATH ; mingw32-make.exe integration" |
|
||||||
# Run the integration suite a second time. See discussion in github.com/containerd/containerd/pull/1759 |
|
||||||
# - bash.exe -elc "export PATH=/c/tools/mingw64/bin:/c/gopath/src/github.com/containerd/containerd/bin:$PATH; TESTFLAGS_PARALLEL=1 mingw32-make.exe integration" |
|
||||||
|
|
||||||
on_success: |
|
||||||
codecov --flag windows -f coverage.txt |
|
@ -0,0 +1,246 @@ |
|||||||
|
# -*- mode: ruby -*- |
||||||
|
# vi: set ft=ruby : |
||||||
|
|
||||||
|
# Copyright The containerd Authors. |
||||||
|
# |
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
# you may not use this file except in compliance with the License. |
||||||
|
# You may obtain a copy of the License at |
||||||
|
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
|
||||||
|
# Unless required by applicable law or agreed to in writing, software |
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
# See the License for the specific language governing permissions and |
||||||
|
# limitations under the License. |
||||||
|
|
||||||
|
# Vagrantfile for cgroup2 and SELinux |
||||||
|
Vagrant.configure("2") do |config| |
||||||
|
config.vm.box = "fedora/32-cloud-base" |
||||||
|
memory = 4096 |
||||||
|
cpus = 2 |
||||||
|
config.vm.provider :virtualbox do |v| |
||||||
|
v.memory = memory |
||||||
|
v.cpus = cpus |
||||||
|
end |
||||||
|
config.vm.provider :libvirt do |v| |
||||||
|
v.memory = memory |
||||||
|
v.cpus = cpus |
||||||
|
end |
||||||
|
|
||||||
|
# Disabled by default. To run: |
||||||
|
# vagrant up --provision-with=upgrade-packages |
||||||
|
# To upgrade only specific packages: |
||||||
|
# UPGRADE_PACKAGES=selinux vagrant up --provision-with=upgrade-packages |
||||||
|
# |
||||||
|
config.vm.provision "upgrade-packages", type: "shell", run: "never" do |sh| |
||||||
|
sh.upload_path = "/tmp/vagrant-upgrade-packages" |
||||||
|
sh.env = { |
||||||
|
'UPGRADE_PACKAGES': ENV['UPGRADE_PACKAGES'], |
||||||
|
} |
||||||
|
sh.inline = <<~SHELL |
||||||
|
#!/usr/bin/env bash |
||||||
|
set -eux -o pipefail |
||||||
|
dnf -y upgrade ${UPGRADE_PACKAGES} |
||||||
|
SHELL |
||||||
|
end |
||||||
|
|
||||||
|
# To re-run, installing CNI from RPM: |
||||||
|
# INSTALL_PACKAGES="containernetworking-plugins" vagrant up --provision-with=install-packages |
||||||
|
# |
||||||
|
config.vm.provision "install-packages", type: "shell", run: "once" do |sh| |
||||||
|
sh.upload_path = "/tmp/vagrant-install-packages" |
||||||
|
sh.env = { |
||||||
|
'INSTALL_PACKAGES': ENV['INSTALL_PACKAGES'], |
||||||
|
} |
||||||
|
sh.inline = <<~SHELL |
||||||
|
#!/usr/bin/env bash |
||||||
|
set -eux -o pipefail |
||||||
|
dnf -y install \ |
||||||
|
container-selinux \ |
||||||
|
curl \ |
||||||
|
gcc \ |
||||||
|
git \ |
||||||
|
iptables \ |
||||||
|
libseccomp-devel \ |
||||||
|
libselinux-devel \ |
||||||
|
lsof \ |
||||||
|
make \ |
||||||
|
${INSTALL_PACKAGES} |
||||||
|
SHELL |
||||||
|
end |
||||||
|
|
||||||
|
# To re-run this provisioner, installing a different version of go: |
||||||
|
# GO_VERSION="1.14.6" vagrant up --provision-with=install-golang |
||||||
|
# |
||||||
|
config.vm.provision "install-golang", type: "shell", run: "once" do |sh| |
||||||
|
sh.upload_path = "/tmp/vagrant-install-golang" |
||||||
|
sh.env = { |
||||||
|
'GO_VERSION': ENV['GO_VERSION'] || "1.13.15", |
||||||
|
} |
||||||
|
sh.inline = <<~SHELL |
||||||
|
#!/usr/bin/env bash |
||||||
|
set -eux -o pipefail |
||||||
|
curl -fsSL "https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz" | tar Cxz /usr/local |
||||||
|
cat >> /etc/environment <<EOF |
||||||
|
PATH=/usr/local/go/bin:$PATH |
||||||
|
GO111MODULE=off |
||||||
|
EOF |
||||||
|
source /etc/environment |
||||||
|
cat >> /etc/profile.d/sh.local <<EOF |
||||||
|
GOPATH=\\$HOME/go |
||||||
|
PATH=\\$GOPATH/bin:\\$PATH |
||||||
|
export GOPATH PATH |
||||||
|
EOF |
||||||
|
source /etc/profile.d/sh.local |
||||||
|
SHELL |
||||||
|
end |
||||||
|
|
||||||
|
config.vm.provision "setup-gopath", type: "shell", run: "once" do |sh| |
||||||
|
sh.upload_path = "/tmp/vagrant-setup-gopath" |
||||||
|
sh.inline = <<~SHELL |
||||||
|
#!/usr/bin/env bash |
||||||
|
source /etc/environment |
||||||
|
source /etc/profile.d/sh.local |
||||||
|
set -eux -o pipefail |
||||||
|
mkdir -p ${GOPATH}/src/github.com/containerd |
||||||
|
ln -fnsv /vagrant ${GOPATH}/src/github.com/containerd/containerd |
||||||
|
SHELL |
||||||
|
end |
||||||
|
|
||||||
|
config.vm.provision "install-runc", type: "shell", run: "once" do |sh| |
||||||
|
sh.upload_path = "/tmp/vagrant-install-runc" |
||||||
|
sh.env = { |
||||||
|
'RUNC_FLAVOR': ENV['RUNC_FLAVOR'] || "runc", |
||||||
|
} |
||||||
|
sh.inline = <<~SHELL |
||||||
|
#!/usr/bin/env bash |
||||||
|
source /etc/environment |
||||||
|
source /etc/profile.d/sh.local |
||||||
|
set -eux -o pipefail |
||||||
|
${GOPATH}/src/github.com/containerd/containerd/script/setup/install-runc |
||||||
|
type runc |
||||||
|
runc --version |
||||||
|
chcon -v -t container_runtime_exec_t $(type -ap runc) |
||||||
|
SHELL |
||||||
|
end |
||||||
|
|
||||||
|
config.vm.provision "install-cni", type: "shell", run: "once" do |sh| |
||||||
|
sh.upload_path = "/tmp/vagrant-install-cni" |
||||||
|
sh.env = { |
||||||
|
'CNI_BINARIES': 'bridge dhcp flannel host-device host-local ipvlan loopback macvlan portmap ptp tuning vlan', |
||||||
|
} |
||||||
|
sh.inline = <<~SHELL |
||||||
|
#!/usr/bin/env bash |
||||||
|
source /etc/environment |
||||||
|
source /etc/profile.d/sh.local |
||||||
|
set -eux -o pipefail |
||||||
|
${GOPATH}/src/github.com/containerd/containerd/script/setup/install-cni |
||||||
|
PATH=/opt/cni/bin:$PATH type ${CNI_BINARIES} || true |
||||||
|
SHELL |
||||||
|
end |
||||||
|
|
||||||
|
config.vm.provision "install-cri-tools", type: "shell", run: "once" do |sh| |
||||||
|
sh.upload_path = "/tmp/vagrant-install-cri-tools" |
||||||
|
sh.env = { |
||||||
|
'CRI_TOOLS_VERSION': ENV['CRI_TOOLS_VERSION'] || '16911795a3c33833fa0ec83dac1ade3172f6989e', |
||||||
|
'GOBIN': '/usr/local/bin', |
||||||
|
} |
||||||
|
sh.inline = <<~SHELL |
||||||
|
#!/usr/bin/env bash |
||||||
|
source /etc/environment |
||||||
|
source /etc/profile.d/sh.local |
||||||
|
set -eux -o pipefail |
||||||
|
${GOPATH}/src/github.com/containerd/containerd/script/setup/install-critools |
||||||
|
type crictl critest |
||||||
|
critest --version |
||||||
|
SHELL |
||||||
|
end |
||||||
|
|
||||||
|
config.vm.provision "install-containerd", type: "shell", run: "once" do |sh| |
||||||
|
sh.upload_path = "/tmp/vagrant-install-containerd" |
||||||
|
sh.inline = <<~SHELL |
||||||
|
#!/usr/bin/env bash |
||||||
|
source /etc/environment |
||||||
|
source /etc/profile.d/sh.local |
||||||
|
set -eux -o pipefail |
||||||
|
cd ${GOPATH}/src/github.com/containerd/containerd |
||||||
|
make BUILDTAGS="seccomp selinux no_aufs no_btrfs no_devmapper no_zfs" binaries install |
||||||
|
type containerd |
||||||
|
containerd --version |
||||||
|
chcon -v -t container_runtime_exec_t /usr/local/bin/{containerd,containerd-shim*} |
||||||
|
./script/setup/config-containerd |
||||||
|
SHELL |
||||||
|
end |
||||||
|
|
||||||
|
# SELinux is Enforcing by default. |
||||||
|
# To set SELinux as Disabled on a VM that has already been provisioned: |
||||||
|
# SELINUX=Disabled vagrant up --provision-with=selinux |
||||||
|
# To set SELinux as Permissive on a VM that has already been provsioned |
||||||
|
# SELINUX=Permissive vagrant up --provision-with=selinux |
||||||
|
config.vm.provision "selinux", type: "shell", run: "never" do |sh| |
||||||
|
sh.upload_path = "/tmp/vagrant-selinux" |
||||||
|
sh.env = { |
||||||
|
'SELINUX': ENV['SELINUX'] || "Enforcing" |
||||||
|
} |
||||||
|
sh.inline = <<~SHELL |
||||||
|
/vagrant/script/setup/config-selinux |
||||||
|
/vagrant/script/setup/config-containerd |
||||||
|
SHELL |
||||||
|
end |
||||||
|
|
||||||
|
# SELinux is permissive by default (via provisioning) in this VM. To re-run with SELinux enforcing: |
||||||
|
# vagrant up --provision-with=selinux-enforcing,test-integration |
||||||
|
# |
||||||
|
config.vm.provision "test-integration", type: "shell", run: "never" do |sh| |
||||||
|
sh.upload_path = "/tmp/test-integration" |
||||||
|
sh.env = { |
||||||
|
'RUNC_FLAVOR': ENV['RUNC_FLAVOR'] || "runc", |
||||||
|
} |
||||||
|
sh.inline = <<~SHELL |
||||||
|
#!/usr/bin/env bash |
||||||
|
source /etc/environment |
||||||
|
source /etc/profile.d/sh.local |
||||||
|
set -eux -o pipefail |
||||||
|
rm -rf /var/lib/containerd-test /run/containerd-test |
||||||
|
cd ${GOPATH}/src/github.com/containerd/containerd |
||||||
|
make integration EXTRA_TESTFLAGS="-no-criu -test.v" TEST_RUNTIME=io.containerd.runc.v2 RUNC_FLAVOR=$RUNC_FLAVOR |
||||||
|
SHELL |
||||||
|
end |
||||||
|
|
||||||
|
# SELinux is permissive by default (via provisioning) in this VM. To re-run with SELinux enforcing: |
||||||
|
# vagrant up --provision-with=selinux-enforcing,test-cri |
||||||
|
# |
||||||
|
config.vm.provision "test-cri", type: "shell", run: "never" do |sh| |
||||||
|
sh.upload_path = "/tmp/test-cri" |
||||||
|
sh.env = { |
||||||
|
'CRITEST_ARGS': ENV['CRITEST_ARGS'], |
||||||
|
} |
||||||
|
sh.inline = <<~SHELL |
||||||
|
#!/usr/bin/env bash |
||||||
|
source /etc/environment |
||||||
|
source /etc/profile.d/sh.local |
||||||
|
set -eux -o pipefail |
||||||
|
systemctl disable --now containerd || true |
||||||
|
rm -rf /var/lib/containerd /run/containerd |
||||||
|
function cleanup() |
||||||
|
{ |
||||||
|
journalctl -u containerd > /tmp/containerd.log |
||||||
|
systemctl stop containerd |
||||||
|
} |
||||||
|
selinux=$(getenforce) |
||||||
|
if [[ $selinux == Enforcing ]]; then |
||||||
|
setenforce 0 |
||||||
|
fi |
||||||
|
systemctl enable --now ${GOPATH}/src/github.com/containerd/containerd/containerd.service |
||||||
|
if [[ $selinux == Enforcing ]]; then |
||||||
|
setenforce 1 |
||||||
|
fi |
||||||
|
trap cleanup EXIT |
||||||
|
ctr version |
||||||
|
critest --parallel=$(nproc) ${CRITEST_ARGS} |
||||||
|
SHELL |
||||||
|
end |
||||||
|
|
||||||
|
end |
649
vendor/github.com/containerd/containerd/api/services/containers/v1/containers.pb.go
generated
vendored
649
vendor/github.com/containerd/containerd/api/services/containers/v1/containers.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
937
vendor/github.com/containerd/containerd/api/services/content/v1/content.pb.go
generated
vendored
937
vendor/github.com/containerd/containerd/api/services/content/v1/content.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
334
vendor/github.com/containerd/containerd/api/services/introspection/v1/introspection.pb.go
generated
vendored
334
vendor/github.com/containerd/containerd/api/services/introspection/v1/introspection.pb.go
generated
vendored
427
vendor/github.com/containerd/containerd/api/services/namespaces/v1/namespace.pb.go
generated
vendored
427
vendor/github.com/containerd/containerd/api/services/namespaces/v1/namespace.pb.go
generated
vendored
1318
vendor/github.com/containerd/containerd/api/services/snapshots/v1/snapshots.pb.go
generated
vendored
1318
vendor/github.com/containerd/containerd/api/services/snapshots/v1/snapshots.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
17
vendor/github.com/containerd/containerd/api/services/snapshots/v1/snapshots.proto
generated
vendored
17
vendor/github.com/containerd/containerd/api/services/snapshots/v1/snapshots.proto
generated
vendored
File diff suppressed because it is too large
Load Diff
114
vendor/github.com/containerd/containerd/api/services/version/v1/version.pb.go
generated
vendored
114
vendor/github.com/containerd/containerd/api/services/version/v1/version.pb.go
generated
vendored
@ -0,0 +1 @@ |
|||||||
|
comment: false |
@ -1,83 +0,0 @@ |
|||||||
/* |
|
||||||
Copyright The containerd Authors. |
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
you may not use this file except in compliance with the License. |
|
||||||
You may obtain a copy of the License at |
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software |
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
See the License for the specific language governing permissions and |
|
||||||
limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
// Package namespaces provides tools for working with namespaces across
|
|
||||||
// containerd.
|
|
||||||
//
|
|
||||||
// Namespaces collect resources such as containers and images, into a unique
|
|
||||||
// identifier space. This means that two applications can use the same
|
|
||||||
// identifiers and not conflict while using containerd.
|
|
||||||
//
|
|
||||||
// This package can be used to ensure that client and server functions
|
|
||||||
// correctly store the namespace on the context.
|
|
||||||
package namespaces |
|
||||||
|
|
||||||
import ( |
|
||||||
"regexp" |
|
||||||
|
|
||||||
"github.com/containerd/containerd/errdefs" |
|
||||||
"github.com/pkg/errors" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
maxLength = 76 |
|
||||||
alpha = `[A-Za-z]` |
|
||||||
alphanum = `[A-Za-z0-9]+` |
|
||||||
label = alpha + alphanum + `(:?[-]+` + alpha + alphanum + `)*` |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
// namespaceRe validates that a namespace matches valid identifiers.
|
|
||||||
//
|
|
||||||
// Rules for domains, defined in RFC 1035, section 2.3.1, are used for
|
|
||||||
// namespaces.
|
|
||||||
namespaceRe = regexp.MustCompile(reAnchor(label + reGroup("[.]"+reGroup(label)) + "*")) |
|
||||||
) |
|
||||||
|
|
||||||
// Validate returns nil if the string s is a valid namespace.
|
|
||||||
//
|
|
||||||
// To allow such namespace identifiers to be used across various contexts
|
|
||||||
// safely, the character set has been restricted to that defined for domains in
|
|
||||||
// RFC 1035, section 2.3.1. This will make namespace identifiers safe for use
|
|
||||||
// across networks, filesystems and other media.
|
|
||||||
//
|
|
||||||
// The identifier specification departs from RFC 1035 in that it allows
|
|
||||||
// "labels" to start with number and only enforces a total length restriction
|
|
||||||
// of 76 characters.
|
|
||||||
//
|
|
||||||
// While the character set may be expanded in the future, namespace identifiers
|
|
||||||
// are guaranteed to be safely used as filesystem path components.
|
|
||||||
//
|
|
||||||
// For the most part, this doesn't need to be called directly when using the
|
|
||||||
// context-oriented functions.
|
|
||||||
func Validate(s string) error { |
|
||||||
if len(s) > maxLength { |
|
||||||
return errors.Wrapf(errdefs.ErrInvalidArgument, "namespace %q greater than maximum length (%d characters)", s, maxLength) |
|
||||||
} |
|
||||||
|
|
||||||
if !namespaceRe.MatchString(s) { |
|
||||||
return errors.Wrapf(errdefs.ErrInvalidArgument, "namespace %q must match %v", s, namespaceRe) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func reGroup(s string) string { |
|
||||||
return `(?:` + s + `)` |
|
||||||
} |
|
||||||
|
|
||||||
func reAnchor(s string) string { |
|
||||||
return `^` + s + `$` |
|
||||||
} |
|
@ -0,0 +1,797 @@ |
|||||||
|
/* |
||||||
|
Copyright The containerd Authors. |
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
you may not use this file except in compliance with the License. |
||||||
|
You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software |
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
See the License for the specific language governing permissions and |
||||||
|
limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
// Package docker provides a general type to represent any way of referencing images within the registry.
|
||||||
|
// Its main purpose is to abstract tags and digests (content-addressable hash).
|
||||||
|
//
|
||||||
|
// Grammar
|
||||||
|
//
|
||||||
|
// reference := name [ ":" tag ] [ "@" digest ]
|
||||||
|
// name := [domain '/'] path-component ['/' path-component]*
|
||||||
|
// domain := domain-component ['.' domain-component]* [':' port-number]
|
||||||
|
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
|
||||||
|
// port-number := /[0-9]+/
|
||||||
|
// path-component := alpha-numeric [separator alpha-numeric]*
|
||||||
|
// alpha-numeric := /[a-z0-9]+/
|
||||||
|
// separator := /[_.]|__|[-]*/
|
||||||
|
//
|
||||||
|
// tag := /[\w][\w.-]{0,127}/
|
||||||
|
//
|
||||||
|
// digest := digest-algorithm ":" digest-hex
|
||||||
|
// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]*
|
||||||
|
// digest-algorithm-separator := /[+.-_]/
|
||||||
|
// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/
|
||||||
|
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
|
||||||
|
//
|
||||||
|
// identifier := /[a-f0-9]{64}/
|
||||||
|
// short-identifier := /[a-f0-9]{6,64}/
|
||||||
|
package docker |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"path" |
||||||
|
"regexp" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/opencontainers/go-digest" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
// NameTotalLengthMax is the maximum total number of characters in a repository name.
|
||||||
|
NameTotalLengthMax = 255 |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
|
||||||
|
ErrReferenceInvalidFormat = errors.New("invalid reference format") |
||||||
|
|
||||||
|
// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
|
||||||
|
ErrTagInvalidFormat = errors.New("invalid tag format") |
||||||
|
|
||||||
|
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
|
||||||
|
ErrDigestInvalidFormat = errors.New("invalid digest format") |
||||||
|
|
||||||
|
// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
|
||||||
|
ErrNameContainsUppercase = errors.New("repository name must be lowercase") |
||||||
|
|
||||||
|
// ErrNameEmpty is returned for empty, invalid repository names.
|
||||||
|
ErrNameEmpty = errors.New("repository name must have at least one component") |
||||||
|
|
||||||
|
// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
|
||||||
|
ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax) |
||||||
|
|
||||||
|
// ErrNameNotCanonical is returned when a name is not canonical.
|
||||||
|
ErrNameNotCanonical = errors.New("repository name must be canonical") |
||||||
|
) |
||||||
|
|
||||||
|
// Reference is an opaque object reference identifier that may include
|
||||||
|
// modifiers such as a hostname, name, tag, and digest.
|
||||||
|
type Reference interface { |
||||||
|
// String returns the full reference
|
||||||
|
String() string |
||||||
|
} |
||||||
|
|
||||||
|
// Field provides a wrapper type for resolving correct reference types when
|
||||||
|
// working with encoding.
|
||||||
|
type Field struct { |
||||||
|
reference Reference |
||||||
|
} |
||||||
|
|
||||||
|
// AsField wraps a reference in a Field for encoding.
|
||||||
|
func AsField(reference Reference) Field { |
||||||
|
return Field{reference} |
||||||
|
} |
||||||
|
|
||||||
|
// Reference unwraps the reference type from the field to
|
||||||
|
// return the Reference object. This object should be
|
||||||
|
// of the appropriate type to further check for different
|
||||||
|
// reference types.
|
||||||
|
func (f Field) Reference() Reference { |
||||||
|
return f.reference |
||||||
|
} |
||||||
|
|
||||||
|
// MarshalText serializes the field to byte text which
|
||||||
|
// is the string of the reference.
|
||||||
|
func (f Field) MarshalText() (p []byte, err error) { |
||||||
|
return []byte(f.reference.String()), nil |
||||||
|
} |
||||||
|
|
||||||
|
// UnmarshalText parses text bytes by invoking the
|
||||||
|
// reference parser to ensure the appropriately
|
||||||
|
// typed reference object is wrapped by field.
|
||||||
|
func (f *Field) UnmarshalText(p []byte) error { |
||||||
|
r, err := Parse(string(p)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
f.reference = r |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Named is an object with a full name
|
||||||
|
type Named interface { |
||||||
|
Reference |
||||||
|
Name() string |
||||||
|
} |
||||||
|
|
||||||
|
// Tagged is an object which has a tag
|
||||||
|
type Tagged interface { |
||||||
|
Reference |
||||||
|
Tag() string |
||||||
|
} |
||||||
|
|
||||||
|
// NamedTagged is an object including a name and tag.
|
||||||
|
type NamedTagged interface { |
||||||
|
Named |
||||||
|
Tag() string |
||||||
|
} |
||||||
|
|
||||||
|
// Digested is an object which has a digest
|
||||||
|
// in which it can be referenced by
|
||||||
|
type Digested interface { |
||||||
|
Reference |
||||||
|
Digest() digest.Digest |
||||||
|
} |
||||||
|
|
||||||
|
// Canonical reference is an object with a fully unique
|
||||||
|
// name including a name with domain and digest
|
||||||
|
type Canonical interface { |
||||||
|
Named |
||||||
|
Digest() digest.Digest |
||||||
|
} |
||||||
|
|
||||||
|
// namedRepository is a reference to a repository with a name.
|
||||||
|
// A namedRepository has both domain and path components.
|
||||||
|
type namedRepository interface { |
||||||
|
Named |
||||||
|
Domain() string |
||||||
|
Path() string |
||||||
|
} |
||||||
|
|
||||||
|
// Domain returns the domain part of the Named reference
|
||||||
|
func Domain(named Named) string { |
||||||
|
if r, ok := named.(namedRepository); ok { |
||||||
|
return r.Domain() |
||||||
|
} |
||||||
|
domain, _ := splitDomain(named.Name()) |
||||||
|
return domain |
||||||
|
} |
||||||
|
|
||||||
|
// Path returns the name without the domain part of the Named reference
|
||||||
|
func Path(named Named) (name string) { |
||||||
|
if r, ok := named.(namedRepository); ok { |
||||||
|
return r.Path() |
||||||
|
} |
||||||
|
_, path := splitDomain(named.Name()) |
||||||
|
return path |
||||||
|
} |
||||||
|
|
||||||
|
func splitDomain(name string) (string, string) { |
||||||
|
match := anchoredNameRegexp.FindStringSubmatch(name) |
||||||
|
if len(match) != 3 { |
||||||
|
return "", name |
||||||
|
} |
||||||
|
return match[1], match[2] |
||||||
|
} |
||||||
|
|
||||||
|
// SplitHostname splits a named reference into a
|
||||||
|
// hostname and name string. If no valid hostname is
|
||||||
|
// found, the hostname is empty and the full value
|
||||||
|
// is returned as name
|
||||||
|
// DEPRECATED: Use Domain or Path
|
||||||
|
func SplitHostname(named Named) (string, string) { |
||||||
|
if r, ok := named.(namedRepository); ok { |
||||||
|
return r.Domain(), r.Path() |
||||||
|
} |
||||||
|
return splitDomain(named.Name()) |
||||||
|
} |
||||||
|
|
||||||
|
// Parse parses s and returns a syntactically valid Reference.
|
||||||
|
// If an error was encountered it is returned, along with a nil Reference.
|
||||||
|
// NOTE: Parse will not handle short digests.
|
||||||
|
func Parse(s string) (Reference, error) { |
||||||
|
matches := ReferenceRegexp.FindStringSubmatch(s) |
||||||
|
if matches == nil { |
||||||
|
if s == "" { |
||||||
|
return nil, ErrNameEmpty |
||||||
|
} |
||||||
|
if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil { |
||||||
|
return nil, ErrNameContainsUppercase |
||||||
|
} |
||||||
|
return nil, ErrReferenceInvalidFormat |
||||||
|
} |
||||||
|
|
||||||
|
if len(matches[1]) > NameTotalLengthMax { |
||||||
|
return nil, ErrNameTooLong |
||||||
|
} |
||||||
|
|
||||||
|
var repo repository |
||||||
|
|
||||||
|
nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1]) |
||||||
|
if len(nameMatch) == 3 { |
||||||
|
repo.domain = nameMatch[1] |
||||||
|
repo.path = nameMatch[2] |
||||||
|
} else { |
||||||
|
repo.domain = "" |
||||||
|
repo.path = matches[1] |
||||||
|
} |
||||||
|
|
||||||
|
ref := reference{ |
||||||
|
namedRepository: repo, |
||||||
|
tag: matches[2], |
||||||
|
} |
||||||
|
if matches[3] != "" { |
||||||
|
var err error |
||||||
|
ref.digest, err = digest.Parse(matches[3]) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
r := getBestReferenceType(ref) |
||||||
|
if r == nil { |
||||||
|
return nil, ErrNameEmpty |
||||||
|
} |
||||||
|
|
||||||
|
return r, nil |
||||||
|
} |
||||||
|
|
||||||
|
// ParseNamed parses s and returns a syntactically valid reference implementing
|
||||||
|
// the Named interface. The reference must have a name and be in the canonical
|
||||||
|
// form, otherwise an error is returned.
|
||||||
|
// If an error was encountered it is returned, along with a nil Reference.
|
||||||
|
// NOTE: ParseNamed will not handle short digests.
|
||||||
|
func ParseNamed(s string) (Named, error) { |
||||||
|
named, err := ParseNormalizedNamed(s) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if named.String() != s { |
||||||
|
return nil, ErrNameNotCanonical |
||||||
|
} |
||||||
|
return named, nil |
||||||
|
} |
||||||
|
|
||||||
|
// WithName returns a named object representing the given string. If the input
|
||||||
|
// is invalid ErrReferenceInvalidFormat will be returned.
|
||||||
|
func WithName(name string) (Named, error) { |
||||||
|
if len(name) > NameTotalLengthMax { |
||||||
|
return nil, ErrNameTooLong |
||||||
|
} |
||||||
|
|
||||||
|
match := anchoredNameRegexp.FindStringSubmatch(name) |
||||||
|
if match == nil || len(match) != 3 { |
||||||
|
return nil, ErrReferenceInvalidFormat |
||||||
|
} |
||||||
|
return repository{ |
||||||
|
domain: match[1], |
||||||
|
path: match[2], |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// WithTag combines the name from "name" and the tag from "tag" to form a
|
||||||
|
// reference incorporating both the name and the tag.
|
||||||
|
func WithTag(name Named, tag string) (NamedTagged, error) { |
||||||
|
if !anchoredTagRegexp.MatchString(tag) { |
||||||
|
return nil, ErrTagInvalidFormat |
||||||
|
} |
||||||
|
var repo repository |
||||||
|
if r, ok := name.(namedRepository); ok { |
||||||
|
repo.domain = r.Domain() |
||||||
|
repo.path = r.Path() |
||||||
|
} else { |
||||||
|
repo.path = name.Name() |
||||||
|
} |
||||||
|
if canonical, ok := name.(Canonical); ok { |
||||||
|
return reference{ |
||||||
|
namedRepository: repo, |
||||||
|
tag: tag, |
||||||
|
digest: canonical.Digest(), |
||||||
|
}, nil |
||||||
|
} |
||||||
|
return taggedReference{ |
||||||
|
namedRepository: repo, |
||||||
|
tag: tag, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// WithDigest combines the name from "name" and the digest from "digest" to form
|
||||||
|
// a reference incorporating both the name and the digest.
|
||||||
|
func WithDigest(name Named, digest digest.Digest) (Canonical, error) { |
||||||
|
if !anchoredDigestRegexp.MatchString(digest.String()) { |
||||||
|
return nil, ErrDigestInvalidFormat |
||||||
|
} |
||||||
|
var repo repository |
||||||
|
if r, ok := name.(namedRepository); ok { |
||||||
|
repo.domain = r.Domain() |
||||||
|
repo.path = r.Path() |
||||||
|
} else { |
||||||
|
repo.path = name.Name() |
||||||
|
} |
||||||
|
if tagged, ok := name.(Tagged); ok { |
||||||
|
return reference{ |
||||||
|
namedRepository: repo, |
||||||
|
tag: tagged.Tag(), |
||||||
|
digest: digest, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
return canonicalReference{ |
||||||
|
namedRepository: repo, |
||||||
|
digest: digest, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// TrimNamed removes any tag or digest from the named reference.
|
||||||
|
func TrimNamed(ref Named) Named { |
||||||
|
domain, path := SplitHostname(ref) |
||||||
|
return repository{ |
||||||
|
domain: domain, |
||||||
|
path: path, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func getBestReferenceType(ref reference) Reference { |
||||||
|
if ref.Name() == "" { |
||||||
|
// Allow digest only references
|
||||||
|
if ref.digest != "" { |
||||||
|
return digestReference(ref.digest) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
if ref.tag == "" { |
||||||
|
if ref.digest != "" { |
||||||
|
return canonicalReference{ |
||||||
|
namedRepository: ref.namedRepository, |
||||||
|
digest: ref.digest, |
||||||
|
} |
||||||
|
} |
||||||
|
return ref.namedRepository |
||||||
|
} |
||||||
|
if ref.digest == "" { |
||||||
|
return taggedReference{ |
||||||
|
namedRepository: ref.namedRepository, |
||||||
|
tag: ref.tag, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ref |
||||||
|
} |
||||||
|
|
||||||
|
type reference struct { |
||||||
|
namedRepository |
||||||
|
tag string |
||||||
|
digest digest.Digest |
||||||
|
} |
||||||
|
|
||||||
|
func (r reference) String() string { |
||||||
|
return r.Name() + ":" + r.tag + "@" + r.digest.String() |
||||||
|
} |
||||||
|
|
||||||
|
func (r reference) Tag() string { |
||||||
|
return r.tag |
||||||
|
} |
||||||
|
|
||||||
|
func (r reference) Digest() digest.Digest { |
||||||
|
return r.digest |
||||||
|
} |
||||||
|
|
||||||
|
type repository struct { |
||||||
|
domain string |
||||||
|
path string |
||||||
|
} |
||||||
|
|
||||||
|
func (r repository) String() string { |
||||||
|
return r.Name() |
||||||
|
} |
||||||
|
|
||||||
|
func (r repository) Name() string { |
||||||
|
if r.domain == "" { |
||||||
|
return r.path |
||||||
|
} |
||||||
|
return r.domain + "/" + r.path |
||||||
|
} |
||||||
|
|
||||||
|
func (r repository) Domain() string { |
||||||
|
return r.domain |
||||||
|
} |
||||||
|
|
||||||
|
func (r repository) Path() string { |
||||||
|
return r.path |
||||||
|
} |
||||||
|
|
||||||
|
type digestReference digest.Digest |
||||||
|
|
||||||
|
func (d digestReference) String() string { |
||||||
|
return digest.Digest(d).String() |
||||||
|
} |
||||||
|
|
||||||
|
func (d digestReference) Digest() digest.Digest { |
||||||
|
return digest.Digest(d) |
||||||
|
} |
||||||
|
|
||||||
|
type taggedReference struct { |
||||||
|
namedRepository |
||||||
|
tag string |
||||||
|
} |
||||||
|
|
||||||
|
func (t taggedReference) String() string { |
||||||
|
return t.Name() + ":" + t.tag |
||||||
|
} |
||||||
|
|
||||||
|
func (t taggedReference) Tag() string { |
||||||
|
return t.tag |
||||||
|
} |
||||||
|
|
||||||
|
type canonicalReference struct { |
||||||
|
namedRepository |
||||||
|
digest digest.Digest |
||||||
|
} |
||||||
|
|
||||||
|
func (c canonicalReference) String() string { |
||||||
|
return c.Name() + "@" + c.digest.String() |
||||||
|
} |
||||||
|
|
||||||
|
func (c canonicalReference) Digest() digest.Digest { |
||||||
|
return c.digest |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
// alphaNumericRegexp defines the alpha numeric atom, typically a
|
||||||
|
// component of names. This only allows lower case characters and digits.
|
||||||
|
alphaNumericRegexp = match(`[a-z0-9]+`) |
||||||
|
|
||||||
|
// separatorRegexp defines the separators allowed to be embedded in name
|
||||||
|
// components. This allow one period, one or two underscore and multiple
|
||||||
|
// dashes.
|
||||||
|
separatorRegexp = match(`(?:[._]|__|[-]*)`) |
||||||
|
|
||||||
|
// nameComponentRegexp restricts registry path component names to start
|
||||||
|
// with at least one letter or number, with following parts able to be
|
||||||
|
// separated by one period, one or two underscore and multiple dashes.
|
||||||
|
nameComponentRegexp = expression( |
||||||
|
alphaNumericRegexp, |
||||||
|
optional(repeated(separatorRegexp, alphaNumericRegexp))) |
||||||
|
|
||||||
|
// domainComponentRegexp restricts the registry domain component of a
|
||||||
|
// repository name to start with a component as defined by DomainRegexp
|
||||||
|
// and followed by an optional port.
|
||||||
|
domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`) |
||||||
|
|
||||||
|
// DomainRegexp defines the structure of potential domain components
|
||||||
|
// that may be part of image names. This is purposely a subset of what is
|
||||||
|
// allowed by DNS to ensure backwards compatibility with Docker image
|
||||||
|
// names.
|
||||||
|
DomainRegexp = expression( |
||||||
|
domainComponentRegexp, |
||||||
|
optional(repeated(literal(`.`), domainComponentRegexp)), |
||||||
|
optional(literal(`:`), match(`[0-9]+`))) |
||||||
|
|
||||||
|
// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
|
||||||
|
TagRegexp = match(`[\w][\w.-]{0,127}`) |
||||||
|
|
||||||
|
// anchoredTagRegexp matches valid tag names, anchored at the start and
|
||||||
|
// end of the matched string.
|
||||||
|
anchoredTagRegexp = anchored(TagRegexp) |
||||||
|
|
||||||
|
// DigestRegexp matches valid digests.
|
||||||
|
DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`) |
||||||
|
|
||||||
|
// anchoredDigestRegexp matches valid digests, anchored at the start and
|
||||||
|
// end of the matched string.
|
||||||
|
anchoredDigestRegexp = anchored(DigestRegexp) |
||||||
|
|
||||||
|
// NameRegexp is the format for the name component of references. The
|
||||||
|
// regexp has capturing groups for the domain and name part omitting
|
||||||
|
// the separating forward slash from either.
|
||||||
|
NameRegexp = expression( |
||||||
|
optional(DomainRegexp, literal(`/`)), |
||||||
|
nameComponentRegexp, |
||||||
|
optional(repeated(literal(`/`), nameComponentRegexp))) |
||||||
|
|
||||||
|
// anchoredNameRegexp is used to parse a name value, capturing the
|
||||||
|
// domain and trailing components.
|
||||||
|
anchoredNameRegexp = anchored( |
||||||
|
optional(capture(DomainRegexp), literal(`/`)), |
||||||
|
capture(nameComponentRegexp, |
||||||
|
optional(repeated(literal(`/`), nameComponentRegexp)))) |
||||||
|
|
||||||
|
// ReferenceRegexp is the full supported format of a reference. The regexp
|
||||||
|
// is anchored and has capturing groups for name, tag, and digest
|
||||||
|
// components.
|
||||||
|
ReferenceRegexp = anchored(capture(NameRegexp), |
||||||
|
optional(literal(":"), capture(TagRegexp)), |
||||||
|
optional(literal("@"), capture(DigestRegexp))) |
||||||
|
|
||||||
|
// IdentifierRegexp is the format for string identifier used as a
|
||||||
|
// content addressable identifier using sha256. These identifiers
|
||||||
|
// are like digests without the algorithm, since sha256 is used.
|
||||||
|
IdentifierRegexp = match(`([a-f0-9]{64})`) |
||||||
|
|
||||||
|
// ShortIdentifierRegexp is the format used to represent a prefix
|
||||||
|
// of an identifier. A prefix may be used to match a sha256 identifier
|
||||||
|
// within a list of trusted identifiers.
|
||||||
|
ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`) |
||||||
|
|
||||||
|
// anchoredIdentifierRegexp is used to check or match an
|
||||||
|
// identifier value, anchored at start and end of string.
|
||||||
|
anchoredIdentifierRegexp = anchored(IdentifierRegexp) |
||||||
|
) |
||||||
|
|
||||||
|
// match compiles the string to a regular expression.
|
||||||
|
var match = regexp.MustCompile |
||||||
|
|
||||||
|
// literal compiles s into a literal regular expression, escaping any regexp
|
||||||
|
// reserved characters.
|
||||||
|
func literal(s string) *regexp.Regexp { |
||||||
|
re := match(regexp.QuoteMeta(s)) |
||||||
|
|
||||||
|
if _, complete := re.LiteralPrefix(); !complete { |
||||||
|
panic("must be a literal") |
||||||
|
} |
||||||
|
|
||||||
|
return re |
||||||
|
} |
||||||
|
|
||||||
|
// expression defines a full expression, where each regular expression must
|
||||||
|
// follow the previous.
|
||||||
|
func expression(res ...*regexp.Regexp) *regexp.Regexp { |
||||||
|
var s string |
||||||
|
for _, re := range res { |
||||||
|
s += re.String() |
||||||
|
} |
||||||
|
|
||||||
|
return match(s) |
||||||
|
} |
||||||
|
|
||||||
|
// optional wraps the expression in a non-capturing group and makes the
|
||||||
|
// production optional.
|
||||||
|
func optional(res ...*regexp.Regexp) *regexp.Regexp { |
||||||
|
return match(group(expression(res...)).String() + `?`) |
||||||
|
} |
||||||
|
|
||||||
|
// repeated wraps the regexp in a non-capturing group to get one or more
|
||||||
|
// matches.
|
||||||
|
func repeated(res ...*regexp.Regexp) *regexp.Regexp { |
||||||
|
return match(group(expression(res...)).String() + `+`) |
||||||
|
} |
||||||
|
|
||||||
|
// group wraps the regexp in a non-capturing group.
|
||||||
|
func group(res ...*regexp.Regexp) *regexp.Regexp { |
||||||
|
return match(`(?:` + expression(res...).String() + `)`) |
||||||
|
} |
||||||
|
|
||||||
|
// capture wraps the expression in a capturing group.
|
||||||
|
func capture(res ...*regexp.Regexp) *regexp.Regexp { |
||||||
|
return match(`(` + expression(res...).String() + `)`) |
||||||
|
} |
||||||
|
|
||||||
|
// anchored anchors the regular expression by adding start and end delimiters.
|
||||||
|
func anchored(res ...*regexp.Regexp) *regexp.Regexp { |
||||||
|
return match(`^` + expression(res...).String() + `$`) |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
legacyDefaultDomain = "index.docker.io" |
||||||
|
defaultDomain = "docker.io" |
||||||
|
officialRepoName = "library" |
||||||
|
defaultTag = "latest" |
||||||
|
) |
||||||
|
|
||||||
|
// normalizedNamed represents a name which has been
|
||||||
|
// normalized and has a familiar form. A familiar name
|
||||||
|
// is what is used in Docker UI. An example normalized
|
||||||
|
// name is "docker.io/library/ubuntu" and corresponding
|
||||||
|
// familiar name of "ubuntu".
|
||||||
|
type normalizedNamed interface { |
||||||
|
Named |
||||||
|
Familiar() Named |
||||||
|
} |
||||||
|
|
||||||
|
// ParseNormalizedNamed parses a string into a named reference
|
||||||
|
// transforming a familiar name from Docker UI to a fully
|
||||||
|
// qualified reference. If the value may be an identifier
|
||||||
|
// use ParseAnyReference.
|
||||||
|
func ParseNormalizedNamed(s string) (Named, error) { |
||||||
|
if ok := anchoredIdentifierRegexp.MatchString(s); ok { |
||||||
|
return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s) |
||||||
|
} |
||||||
|
domain, remainder := splitDockerDomain(s) |
||||||
|
var remoteName string |
||||||
|
if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 { |
||||||
|
remoteName = remainder[:tagSep] |
||||||
|
} else { |
||||||
|
remoteName = remainder |
||||||
|
} |
||||||
|
if strings.ToLower(remoteName) != remoteName { |
||||||
|
return nil, errors.New("invalid reference format: repository name must be lowercase") |
||||||
|
} |
||||||
|
|
||||||
|
ref, err := Parse(domain + "/" + remainder) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
named, isNamed := ref.(Named) |
||||||
|
if !isNamed { |
||||||
|
return nil, fmt.Errorf("reference %s has no name", ref.String()) |
||||||
|
} |
||||||
|
return named, nil |
||||||
|
} |
||||||
|
|
||||||
|
// ParseDockerRef normalizes the image reference following the docker convention. This is added
|
||||||
|
// mainly for backward compatibility.
|
||||||
|
// The reference returned can only be either tagged or digested. For reference contains both tag
|
||||||
|
// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
|
||||||
|
// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
|
||||||
|
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
|
||||||
|
func ParseDockerRef(ref string) (Named, error) { |
||||||
|
named, err := ParseNormalizedNamed(ref) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if _, ok := named.(NamedTagged); ok { |
||||||
|
if canonical, ok := named.(Canonical); ok { |
||||||
|
// The reference is both tagged and digested, only
|
||||||
|
// return digested.
|
||||||
|
newNamed, err := WithName(canonical.Name()) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
newCanonical, err := WithDigest(newNamed, canonical.Digest()) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return newCanonical, nil |
||||||
|
} |
||||||
|
} |
||||||
|
return TagNameOnly(named), nil |
||||||
|
} |
||||||
|
|
||||||
|
// splitDockerDomain splits a repository name to domain and remotename string.
|
||||||
|
// If no valid domain is found, the default domain is used. Repository name
|
||||||
|
// needs to be already validated before.
|
||||||
|
func splitDockerDomain(name string) (domain, remainder string) { |
||||||
|
i := strings.IndexRune(name, '/') |
||||||
|
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") { |
||||||
|
domain, remainder = defaultDomain, name |
||||||
|
} else { |
||||||
|
domain, remainder = name[:i], name[i+1:] |
||||||
|
} |
||||||
|
if domain == legacyDefaultDomain { |
||||||
|
domain = defaultDomain |
||||||
|
} |
||||||
|
if domain == defaultDomain && !strings.ContainsRune(remainder, '/') { |
||||||
|
remainder = officialRepoName + "/" + remainder |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// familiarizeName returns a shortened version of the name familiar
|
||||||
|
// to to the Docker UI. Familiar names have the default domain
|
||||||
|
// "docker.io" and "library/" repository prefix removed.
|
||||||
|
// For example, "docker.io/library/redis" will have the familiar
|
||||||
|
// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
|
||||||
|
// Returns a familiarized named only reference.
|
||||||
|
func familiarizeName(named namedRepository) repository { |
||||||
|
repo := repository{ |
||||||
|
domain: named.Domain(), |
||||||
|
path: named.Path(), |
||||||
|
} |
||||||
|
|
||||||
|
if repo.domain == defaultDomain { |
||||||
|
repo.domain = "" |
||||||
|
// Handle official repositories which have the pattern "library/<official repo name>"
|
||||||
|
if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName { |
||||||
|
repo.path = split[1] |
||||||
|
} |
||||||
|
} |
||||||
|
return repo |
||||||
|
} |
||||||
|
|
||||||
|
func (r reference) Familiar() Named { |
||||||
|
return reference{ |
||||||
|
namedRepository: familiarizeName(r.namedRepository), |
||||||
|
tag: r.tag, |
||||||
|
digest: r.digest, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (r repository) Familiar() Named { |
||||||
|
return familiarizeName(r) |
||||||
|
} |
||||||
|
|
||||||
|
func (t taggedReference) Familiar() Named { |
||||||
|
return taggedReference{ |
||||||
|
namedRepository: familiarizeName(t.namedRepository), |
||||||
|
tag: t.tag, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c canonicalReference) Familiar() Named { |
||||||
|
return canonicalReference{ |
||||||
|
namedRepository: familiarizeName(c.namedRepository), |
||||||
|
digest: c.digest, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TagNameOnly adds the default tag "latest" to a reference if it only has
|
||||||
|
// a repo name.
|
||||||
|
func TagNameOnly(ref Named) Named { |
||||||
|
if IsNameOnly(ref) { |
||||||
|
namedTagged, err := WithTag(ref, defaultTag) |
||||||
|
if err != nil { |
||||||
|
// Default tag must be valid, to create a NamedTagged
|
||||||
|
// type with non-validated input the WithTag function
|
||||||
|
// should be used instead
|
||||||
|
panic(err) |
||||||
|
} |
||||||
|
return namedTagged |
||||||
|
} |
||||||
|
return ref |
||||||
|
} |
||||||
|
|
||||||
|
// ParseAnyReference parses a reference string as a possible identifier,
|
||||||
|
// full digest, or familiar name.
|
||||||
|
func ParseAnyReference(ref string) (Reference, error) { |
||||||
|
if ok := anchoredIdentifierRegexp.MatchString(ref); ok { |
||||||
|
return digestReference("sha256:" + ref), nil |
||||||
|
} |
||||||
|
if dgst, err := digest.Parse(ref); err == nil { |
||||||
|
return digestReference(dgst), nil |
||||||
|
} |
||||||
|
|
||||||
|
return ParseNormalizedNamed(ref) |
||||||
|
} |
||||||
|
|
||||||
|
// IsNameOnly returns true if reference only contains a repo name.
|
||||||
|
func IsNameOnly(ref Named) bool { |
||||||
|
if _, ok := ref.(NamedTagged); ok { |
||||||
|
return false |
||||||
|
} |
||||||
|
if _, ok := ref.(Canonical); ok { |
||||||
|
return false |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// FamiliarName returns the familiar name string
|
||||||
|
// for the given named, familiarizing if needed.
|
||||||
|
func FamiliarName(ref Named) string { |
||||||
|
if nn, ok := ref.(normalizedNamed); ok { |
||||||
|
return nn.Familiar().Name() |
||||||
|
} |
||||||
|
return ref.Name() |
||||||
|
} |
||||||
|
|
||||||
|
// FamiliarString returns the familiar string representation
|
||||||
|
// for the given reference, familiarizing if needed.
|
||||||
|
func FamiliarString(ref Reference) string { |
||||||
|
if nn, ok := ref.(normalizedNamed); ok { |
||||||
|
return nn.Familiar().String() |
||||||
|
} |
||||||
|
return ref.String() |
||||||
|
} |
||||||
|
|
||||||
|
// FamiliarMatch reports whether ref matches the specified pattern.
|
||||||
|
// See https://godoc.org/path#Match for supported patterns.
|
||||||
|
func FamiliarMatch(pattern string, ref Reference) (bool, error) { |
||||||
|
matched, err := path.Match(pattern, FamiliarString(ref)) |
||||||
|
if namedRef, isNamed := ref.(Named); isNamed && !matched { |
||||||
|
matched, _ = path.Match(pattern, FamiliarName(namedRef)) |
||||||
|
} |
||||||
|
return matched, err |
||||||
|
} |
@ -0,0 +1,283 @@ |
|||||||
|
/* |
||||||
|
Copyright The containerd Authors. |
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
you may not use this file except in compliance with the License. |
||||||
|
You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software |
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
See the License for the specific language governing permissions and |
||||||
|
limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package docker |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
// ErrorCoder is the base interface for ErrorCode and Error allowing
|
||||||
|
// users of each to just call ErrorCode to get the real ID of each
|
||||||
|
type ErrorCoder interface { |
||||||
|
ErrorCode() ErrorCode |
||||||
|
} |
||||||
|
|
||||||
|
// ErrorCode represents the error type. The errors are serialized via strings
|
||||||
|
// and the integer format may change and should *never* be exported.
|
||||||
|
type ErrorCode int |
||||||
|
|
||||||
|
var _ error = ErrorCode(0) |
||||||
|
|
||||||
|
// ErrorCode just returns itself
|
||||||
|
func (ec ErrorCode) ErrorCode() ErrorCode { |
||||||
|
return ec |
||||||
|
} |
||||||
|
|
||||||
|
// Error returns the ID/Value
|
||||||
|
func (ec ErrorCode) Error() string { |
||||||
|
// NOTE(stevvooe): Cannot use message here since it may have unpopulated args.
|
||||||
|
return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1)) |
||||||
|
} |
||||||
|
|
||||||
|
// Descriptor returns the descriptor for the error code.
|
||||||
|
func (ec ErrorCode) Descriptor() ErrorDescriptor { |
||||||
|
d, ok := errorCodeToDescriptors[ec] |
||||||
|
|
||||||
|
if !ok { |
||||||
|
return ErrorCodeUnknown.Descriptor() |
||||||
|
} |
||||||
|
|
||||||
|
return d |
||||||
|
} |
||||||
|
|
||||||
|
// String returns the canonical identifier for this error code.
|
||||||
|
func (ec ErrorCode) String() string { |
||||||
|
return ec.Descriptor().Value |
||||||
|
} |
||||||
|
|
||||||
|
// Message returned the human-readable error message for this error code.
|
||||||
|
func (ec ErrorCode) Message() string { |
||||||
|
return ec.Descriptor().Message |
||||||
|
} |
||||||
|
|
||||||
|
// MarshalText encodes the receiver into UTF-8-encoded text and returns the
|
||||||
|
// result.
|
||||||
|
func (ec ErrorCode) MarshalText() (text []byte, err error) { |
||||||
|
return []byte(ec.String()), nil |
||||||
|
} |
||||||
|
|
||||||
|
// UnmarshalText decodes the form generated by MarshalText.
|
||||||
|
func (ec *ErrorCode) UnmarshalText(text []byte) error { |
||||||
|
desc, ok := idToDescriptors[string(text)] |
||||||
|
|
||||||
|
if !ok { |
||||||
|
desc = ErrorCodeUnknown.Descriptor() |
||||||
|
} |
||||||
|
|
||||||
|
*ec = desc.Code |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// WithMessage creates a new Error struct based on the passed-in info and
|
||||||
|
// overrides the Message property.
|
||||||
|
func (ec ErrorCode) WithMessage(message string) Error { |
||||||
|
return Error{ |
||||||
|
Code: ec, |
||||||
|
Message: message, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithDetail creates a new Error struct based on the passed-in info and
|
||||||
|
// set the Detail property appropriately
|
||||||
|
func (ec ErrorCode) WithDetail(detail interface{}) Error { |
||||||
|
return Error{ |
||||||
|
Code: ec, |
||||||
|
Message: ec.Message(), |
||||||
|
}.WithDetail(detail) |
||||||
|
} |
||||||
|
|
||||||
|
// WithArgs creates a new Error struct and sets the Args slice
|
||||||
|
func (ec ErrorCode) WithArgs(args ...interface{}) Error { |
||||||
|
return Error{ |
||||||
|
Code: ec, |
||||||
|
Message: ec.Message(), |
||||||
|
}.WithArgs(args...) |
||||||
|
} |
||||||
|
|
||||||
|
// Error provides a wrapper around ErrorCode with extra Details provided.
|
||||||
|
type Error struct { |
||||||
|
Code ErrorCode `json:"code"` |
||||||
|
Message string `json:"message"` |
||||||
|
Detail interface{} `json:"detail,omitempty"` |
||||||
|
|
||||||
|
// TODO(duglin): See if we need an "args" property so we can do the
|
||||||
|
// variable substitution right before showing the message to the user
|
||||||
|
} |
||||||
|
|
||||||
|
var _ error = Error{} |
||||||
|
|
||||||
|
// ErrorCode returns the ID/Value of this Error
|
||||||
|
func (e Error) ErrorCode() ErrorCode { |
||||||
|
return e.Code |
||||||
|
} |
||||||
|
|
||||||
|
// Error returns a human readable representation of the error.
|
||||||
|
func (e Error) Error() string { |
||||||
|
return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message) |
||||||
|
} |
||||||
|
|
||||||
|
// WithDetail will return a new Error, based on the current one, but with
|
||||||
|
// some Detail info added
|
||||||
|
func (e Error) WithDetail(detail interface{}) Error { |
||||||
|
return Error{ |
||||||
|
Code: e.Code, |
||||||
|
Message: e.Message, |
||||||
|
Detail: detail, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithArgs uses the passed-in list of interface{} as the substitution
|
||||||
|
// variables in the Error's Message string, but returns a new Error
|
||||||
|
func (e Error) WithArgs(args ...interface{}) Error { |
||||||
|
return Error{ |
||||||
|
Code: e.Code, |
||||||
|
Message: fmt.Sprintf(e.Code.Message(), args...), |
||||||
|
Detail: e.Detail, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ErrorDescriptor provides relevant information about a given error code.
|
||||||
|
type ErrorDescriptor struct { |
||||||
|
// Code is the error code that this descriptor describes.
|
||||||
|
Code ErrorCode |
||||||
|
|
||||||
|
// Value provides a unique, string key, often captilized with
|
||||||
|
// underscores, to identify the error code. This value is used as the
|
||||||
|
// keyed value when serializing api errors.
|
||||||
|
Value string |
||||||
|
|
||||||
|
// Message is a short, human readable decription of the error condition
|
||||||
|
// included in API responses.
|
||||||
|
Message string |
||||||
|
|
||||||
|
// Description provides a complete account of the errors purpose, suitable
|
||||||
|
// for use in documentation.
|
||||||
|
Description string |
||||||
|
|
||||||
|
// HTTPStatusCode provides the http status code that is associated with
|
||||||
|
// this error condition.
|
||||||
|
HTTPStatusCode int |
||||||
|
} |
||||||
|
|
||||||
|
// ParseErrorCode returns the value by the string error code.
|
||||||
|
// `ErrorCodeUnknown` will be returned if the error is not known.
|
||||||
|
func ParseErrorCode(value string) ErrorCode { |
||||||
|
ed, ok := idToDescriptors[value] |
||||||
|
if ok { |
||||||
|
return ed.Code |
||||||
|
} |
||||||
|
|
||||||
|
return ErrorCodeUnknown |
||||||
|
} |
||||||
|
|
||||||
|
// Errors provides the envelope for multiple errors and a few sugar methods
|
||||||
|
// for use within the application.
|
||||||
|
type Errors []error |
||||||
|
|
||||||
|
var _ error = Errors{} |
||||||
|
|
||||||
|
func (errs Errors) Error() string { |
||||||
|
switch len(errs) { |
||||||
|
case 0: |
||||||
|
return "<nil>" |
||||||
|
case 1: |
||||||
|
return errs[0].Error() |
||||||
|
default: |
||||||
|
msg := "errors:\n" |
||||||
|
for _, err := range errs { |
||||||
|
msg += err.Error() + "\n" |
||||||
|
} |
||||||
|
return msg |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Len returns the current number of errors.
|
||||||
|
func (errs Errors) Len() int { |
||||||
|
return len(errs) |
||||||
|
} |
||||||
|
|
||||||
|
// MarshalJSON converts slice of error, ErrorCode or Error into a
|
||||||
|
// slice of Error - then serializes
|
||||||
|
func (errs Errors) MarshalJSON() ([]byte, error) { |
||||||
|
var tmpErrs struct { |
||||||
|
Errors []Error `json:"errors,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
for _, daErr := range errs { |
||||||
|
var err Error |
||||||
|
|
||||||
|
switch daErr := daErr.(type) { |
||||||
|
case ErrorCode: |
||||||
|
err = daErr.WithDetail(nil) |
||||||
|
case Error: |
||||||
|
err = daErr |
||||||
|
default: |
||||||
|
err = ErrorCodeUnknown.WithDetail(daErr) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// If the Error struct was setup and they forgot to set the
|
||||||
|
// Message field (meaning its "") then grab it from the ErrCode
|
||||||
|
msg := err.Message |
||||||
|
if msg == "" { |
||||||
|
msg = err.Code.Message() |
||||||
|
} |
||||||
|
|
||||||
|
tmpErrs.Errors = append(tmpErrs.Errors, Error{ |
||||||
|
Code: err.Code, |
||||||
|
Message: msg, |
||||||
|
Detail: err.Detail, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return json.Marshal(tmpErrs) |
||||||
|
} |
||||||
|
|
||||||
|
// UnmarshalJSON deserializes []Error and then converts it into slice of
|
||||||
|
// Error or ErrorCode
|
||||||
|
func (errs *Errors) UnmarshalJSON(data []byte) error { |
||||||
|
var tmpErrs struct { |
||||||
|
Errors []Error |
||||||
|
} |
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &tmpErrs); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
var newErrs Errors |
||||||
|
for _, daErr := range tmpErrs.Errors { |
||||||
|
// If Message is empty or exactly matches the Code's message string
|
||||||
|
// then just use the Code, no need for a full Error struct
|
||||||
|
if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) { |
||||||
|
// Error's w/o details get converted to ErrorCode
|
||||||
|
newErrs = append(newErrs, daErr.Code) |
||||||
|
} else { |
||||||
|
// Error's w/ details are untouched
|
||||||
|
newErrs = append(newErrs, Error{ |
||||||
|
Code: daErr.Code, |
||||||
|
Message: daErr.Message, |
||||||
|
Detail: daErr.Detail, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
*errs = newErrs |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,154 @@ |
|||||||
|
/* |
||||||
|
Copyright The containerd Authors. |
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
you may not use this file except in compliance with the License. |
||||||
|
You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software |
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
See the License for the specific language governing permissions and |
||||||
|
limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package docker |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"net/http" |
||||||
|
"sort" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
errorCodeToDescriptors = map[ErrorCode]ErrorDescriptor{} |
||||||
|
idToDescriptors = map[string]ErrorDescriptor{} |
||||||
|
groupToDescriptors = map[string][]ErrorDescriptor{} |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// ErrorCodeUnknown is a generic error that can be used as a last
|
||||||
|
// resort if there is no situation-specific error message that can be used
|
||||||
|
ErrorCodeUnknown = Register("errcode", ErrorDescriptor{ |
||||||
|
Value: "UNKNOWN", |
||||||
|
Message: "unknown error", |
||||||
|
Description: `Generic error returned when the error does not have an |
||||||
|
API classification.`, |
||||||
|
HTTPStatusCode: http.StatusInternalServerError, |
||||||
|
}) |
||||||
|
|
||||||
|
// ErrorCodeUnsupported is returned when an operation is not supported.
|
||||||
|
ErrorCodeUnsupported = Register("errcode", ErrorDescriptor{ |
||||||
|
Value: "UNSUPPORTED", |
||||||
|
Message: "The operation is unsupported.", |
||||||
|
Description: `The operation was unsupported due to a missing |
||||||
|
implementation or invalid set of parameters.`, |
||||||
|
HTTPStatusCode: http.StatusMethodNotAllowed, |
||||||
|
}) |
||||||
|
|
||||||
|
// ErrorCodeUnauthorized is returned if a request requires
|
||||||
|
// authentication.
|
||||||
|
ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{ |
||||||
|
Value: "UNAUTHORIZED", |
||||||
|
Message: "authentication required", |
||||||
|
Description: `The access controller was unable to authenticate |
||||||
|
the client. Often this will be accompanied by a |
||||||
|
Www-Authenticate HTTP response header indicating how to |
||||||
|
authenticate.`, |
||||||
|
HTTPStatusCode: http.StatusUnauthorized, |
||||||
|
}) |
||||||
|
|
||||||
|
// ErrorCodeDenied is returned if a client does not have sufficient
|
||||||
|
// permission to perform an action.
|
||||||
|
ErrorCodeDenied = Register("errcode", ErrorDescriptor{ |
||||||
|
Value: "DENIED", |
||||||
|
Message: "requested access to the resource is denied", |
||||||
|
Description: `The access controller denied access for the |
||||||
|
operation on a resource.`, |
||||||
|
HTTPStatusCode: http.StatusForbidden, |
||||||
|
}) |
||||||
|
|
||||||
|
// ErrorCodeUnavailable provides a common error to report unavailability
|
||||||
|
// of a service or endpoint.
|
||||||
|
ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{ |
||||||
|
Value: "UNAVAILABLE", |
||||||
|
Message: "service unavailable", |
||||||
|
Description: "Returned when a service is not available", |
||||||
|
HTTPStatusCode: http.StatusServiceUnavailable, |
||||||
|
}) |
||||||
|
|
||||||
|
// ErrorCodeTooManyRequests is returned if a client attempts too many
|
||||||
|
// times to contact a service endpoint.
|
||||||
|
ErrorCodeTooManyRequests = Register("errcode", ErrorDescriptor{ |
||||||
|
Value: "TOOMANYREQUESTS", |
||||||
|
Message: "too many requests", |
||||||
|
Description: `Returned when a client attempts to contact a |
||||||
|
service too many times`, |
||||||
|
HTTPStatusCode: http.StatusTooManyRequests, |
||||||
|
}) |
||||||
|
) |
||||||
|
|
||||||
|
var nextCode = 1000 |
||||||
|
var registerLock sync.Mutex |
||||||
|
|
||||||
|
// Register will make the passed-in error known to the environment and
|
||||||
|
// return a new ErrorCode
|
||||||
|
func Register(group string, descriptor ErrorDescriptor) ErrorCode { |
||||||
|
registerLock.Lock() |
||||||
|
defer registerLock.Unlock() |
||||||
|
|
||||||
|
descriptor.Code = ErrorCode(nextCode) |
||||||
|
|
||||||
|
if _, ok := idToDescriptors[descriptor.Value]; ok { |
||||||
|
panic(fmt.Sprintf("ErrorValue %q is already registered", descriptor.Value)) |
||||||
|
} |
||||||
|
if _, ok := errorCodeToDescriptors[descriptor.Code]; ok { |
||||||
|
panic(fmt.Sprintf("ErrorCode %v is already registered", descriptor.Code)) |
||||||
|
} |
||||||
|
|
||||||
|
groupToDescriptors[group] = append(groupToDescriptors[group], descriptor) |
||||||
|
errorCodeToDescriptors[descriptor.Code] = descriptor |
||||||
|
idToDescriptors[descriptor.Value] = descriptor |
||||||
|
|
||||||
|
nextCode++ |
||||||
|
return descriptor.Code |
||||||
|
} |
||||||
|
|
||||||
|
type byValue []ErrorDescriptor |
||||||
|
|
||||||
|
func (a byValue) Len() int { return len(a) } |
||||||
|
func (a byValue) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
||||||
|
func (a byValue) Less(i, j int) bool { return a[i].Value < a[j].Value } |
||||||
|
|
||||||
|
// GetGroupNames returns the list of Error group names that are registered
|
||||||
|
func GetGroupNames() []string { |
||||||
|
keys := []string{} |
||||||
|
|
||||||
|
for k := range groupToDescriptors { |
||||||
|
keys = append(keys, k) |
||||||
|
} |
||||||
|
sort.Strings(keys) |
||||||
|
return keys |
||||||
|
} |
||||||
|
|
||||||
|
// GetErrorCodeGroup returns the named group of error descriptors
|
||||||
|
func GetErrorCodeGroup(name string) []ErrorDescriptor { |
||||||
|
desc := groupToDescriptors[name] |
||||||
|
sort.Sort(byValue(desc)) |
||||||
|
return desc |
||||||
|
} |
||||||
|
|
||||||
|
// GetErrorAllDescriptors returns a slice of all ErrorDescriptors that are
|
||||||
|
// registered, irrespective of what group they're in
|
||||||
|
func GetErrorAllDescriptors() []ErrorDescriptor { |
||||||
|
result := []ErrorDescriptor{} |
||||||
|
|
||||||
|
for _, group := range GetGroupNames() { |
||||||
|
result = append(result, GetErrorCodeGroup(group)...) |
||||||
|
} |
||||||
|
sort.Sort(byValue(result)) |
||||||
|
return result |
||||||
|
} |
62
vendor/github.com/containerd/containerd/services/introspection/introspection.go
generated
vendored
62
vendor/github.com/containerd/containerd/services/introspection/introspection.go
generated
vendored
@ -0,0 +1,62 @@ |
|||||||
|
/* |
||||||
|
Copyright The containerd Authors. |
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
you may not use this file except in compliance with the License. |
||||||
|
You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software |
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
See the License for the specific language governing permissions and |
||||||
|
limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package introspection |
||||||
|
|
||||||
|
import ( |
||||||
|
context "context" |
||||||
|
|
||||||
|
api "github.com/containerd/containerd/api/services/introspection/v1" |
||||||
|
"github.com/containerd/containerd/errdefs" |
||||||
|
ptypes "github.com/gogo/protobuf/types" |
||||||
|
) |
||||||
|
|
||||||
|
type Service interface { |
||||||
|
Plugins(context.Context, []string) (*api.PluginsResponse, error) |
||||||
|
Server(context.Context, *ptypes.Empty) (*api.ServerResponse, error) |
||||||
|
} |
||||||
|
|
||||||
|
type introspectionRemote struct { |
||||||
|
client api.IntrospectionClient |
||||||
|
} |
||||||
|
|
||||||
|
var _ = (Service)(&introspectionRemote{}) |
||||||
|
|
||||||
|
func NewIntrospectionServiceFromClient(c api.IntrospectionClient) Service { |
||||||
|
return &introspectionRemote{client: c} |
||||||
|
} |
||||||
|
|
||||||
|
func (i *introspectionRemote) Plugins(ctx context.Context, filters []string) (*api.PluginsResponse, error) { |
||||||
|
resp, err := i.client.Plugins(ctx, &api.PluginsRequest{ |
||||||
|
Filters: filters, |
||||||
|
}) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return nil, errdefs.FromGRPC(err) |
||||||
|
} |
||||||
|
|
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (i *introspectionRemote) Server(ctx context.Context, in *ptypes.Empty) (*api.ServerResponse, error) { |
||||||
|
resp, err := i.client.Server(ctx, in) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return nil, errdefs.FromGRPC(err) |
||||||
|
} |
||||||
|
|
||||||
|
return resp, nil |
||||||
|
} |
@ -0,0 +1,227 @@ |
|||||||
|
/* |
||||||
|
Copyright The containerd Authors. |
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
you may not use this file except in compliance with the License. |
||||||
|
You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software |
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
See the License for the specific language governing permissions and |
||||||
|
limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package introspection |
||||||
|
|
||||||
|
import ( |
||||||
|
context "context" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"sync" |
||||||
|
|
||||||
|
api "github.com/containerd/containerd/api/services/introspection/v1" |
||||||
|
"github.com/containerd/containerd/api/types" |
||||||
|
"github.com/containerd/containerd/errdefs" |
||||||
|
"github.com/containerd/containerd/filters" |
||||||
|
"github.com/containerd/containerd/plugin" |
||||||
|
"github.com/containerd/containerd/services" |
||||||
|
"github.com/gogo/googleapis/google/rpc" |
||||||
|
ptypes "github.com/gogo/protobuf/types" |
||||||
|
"github.com/google/uuid" |
||||||
|
"google.golang.org/grpc" |
||||||
|
"google.golang.org/grpc/status" |
||||||
|
) |
||||||
|
|
||||||
|
func init() { |
||||||
|
plugin.Register(&plugin.Registration{ |
||||||
|
Type: plugin.ServicePlugin, |
||||||
|
ID: services.IntrospectionService, |
||||||
|
Requires: []plugin.Type{}, |
||||||
|
InitFn: func(ic *plugin.InitContext) (interface{}, error) { |
||||||
|
// this service works by using the plugin context up till the point
|
||||||
|
// this service is initialized. Since we require this service last,
|
||||||
|
// it should provide the full set of plugins.
|
||||||
|
pluginsPB := pluginsToPB(ic.GetAll()) |
||||||
|
return &Local{ |
||||||
|
plugins: pluginsPB, |
||||||
|
root: ic.Root, |
||||||
|
}, nil |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
type Local struct { |
||||||
|
mu sync.Mutex |
||||||
|
plugins []api.Plugin |
||||||
|
root string |
||||||
|
} |
||||||
|
|
||||||
|
var _ = (api.IntrospectionClient)(&Local{}) |
||||||
|
|
||||||
|
func (l *Local) UpdateLocal(root string, plugins []api.Plugin) { |
||||||
|
l.mu.Lock() |
||||||
|
defer l.mu.Unlock() |
||||||
|
l.root = root |
||||||
|
l.plugins = plugins |
||||||
|
} |
||||||
|
|
||||||
|
func (l *Local) Plugins(ctx context.Context, req *api.PluginsRequest, _ ...grpc.CallOption) (*api.PluginsResponse, error) { |
||||||
|
filter, err := filters.ParseAll(req.Filters...) |
||||||
|
if err != nil { |
||||||
|
return nil, errdefs.ToGRPCf(errdefs.ErrInvalidArgument, err.Error()) |
||||||
|
} |
||||||
|
|
||||||
|
var plugins []api.Plugin |
||||||
|
allPlugins := l.getPlugins() |
||||||
|
for _, p := range allPlugins { |
||||||
|
if !filter.Match(adaptPlugin(p)) { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
plugins = append(plugins, p) |
||||||
|
} |
||||||
|
|
||||||
|
return &api.PluginsResponse{ |
||||||
|
Plugins: plugins, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l *Local) getPlugins() []api.Plugin { |
||||||
|
l.mu.Lock() |
||||||
|
defer l.mu.Unlock() |
||||||
|
return l.plugins |
||||||
|
} |
||||||
|
|
||||||
|
func (l *Local) Server(ctx context.Context, _ *ptypes.Empty, _ ...grpc.CallOption) (*api.ServerResponse, error) { |
||||||
|
u, err := l.getUUID() |
||||||
|
if err != nil { |
||||||
|
return nil, errdefs.ToGRPC(err) |
||||||
|
} |
||||||
|
return &api.ServerResponse{ |
||||||
|
UUID: u, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l *Local) getUUID() (string, error) { |
||||||
|
l.mu.Lock() |
||||||
|
defer l.mu.Unlock() |
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(l.uuidPath()) |
||||||
|
if err != nil { |
||||||
|
if os.IsNotExist(err) { |
||||||
|
return l.generateUUID() |
||||||
|
} |
||||||
|
return "", err |
||||||
|
} |
||||||
|
u := string(data) |
||||||
|
if _, err := uuid.Parse(u); err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
return u, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l *Local) generateUUID() (string, error) { |
||||||
|
u, err := uuid.NewRandom() |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
path := l.uuidPath() |
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
uu := u.String() |
||||||
|
if err := ioutil.WriteFile(path, []byte(uu), 0666); err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
return uu, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l *Local) uuidPath() string { |
||||||
|
return filepath.Join(l.root, "uuid") |
||||||
|
} |
||||||
|
|
||||||
|
func adaptPlugin(o interface{}) filters.Adaptor { |
||||||
|
obj := o.(api.Plugin) |
||||||
|
return filters.AdapterFunc(func(fieldpath []string) (string, bool) { |
||||||
|
if len(fieldpath) == 0 { |
||||||
|
return "", false |
||||||
|
} |
||||||
|
|
||||||
|
switch fieldpath[0] { |
||||||
|
case "type": |
||||||
|
return obj.Type, len(obj.Type) > 0 |
||||||
|
case "id": |
||||||
|
return obj.ID, len(obj.ID) > 0 |
||||||
|
case "platforms": |
||||||
|
// TODO(stevvooe): Another case here where have multiple values.
|
||||||
|
// May need to refactor the filter system to allow filtering by
|
||||||
|
// platform, if this is required.
|
||||||
|
case "capabilities": |
||||||
|
// TODO(stevvooe): Need a better way to match against
|
||||||
|
// collections. We can only return "the value" but really it
|
||||||
|
// would be best if we could return a set of values for the
|
||||||
|
// path, any of which could match.
|
||||||
|
} |
||||||
|
|
||||||
|
return "", false |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func pluginsToPB(plugins []*plugin.Plugin) []api.Plugin { |
||||||
|
var pluginsPB []api.Plugin |
||||||
|
for _, p := range plugins { |
||||||
|
var platforms []types.Platform |
||||||
|
for _, p := range p.Meta.Platforms { |
||||||
|
platforms = append(platforms, types.Platform{ |
||||||
|
OS: p.OS, |
||||||
|
Architecture: p.Architecture, |
||||||
|
Variant: p.Variant, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
var requires []string |
||||||
|
for _, r := range p.Registration.Requires { |
||||||
|
requires = append(requires, r.String()) |
||||||
|
} |
||||||
|
|
||||||
|
var initErr *rpc.Status |
||||||
|
if err := p.Err(); err != nil { |
||||||
|
st, ok := status.FromError(errdefs.ToGRPC(err)) |
||||||
|
if ok { |
||||||
|
var details []*ptypes.Any |
||||||
|
for _, d := range st.Proto().Details { |
||||||
|
details = append(details, &ptypes.Any{ |
||||||
|
TypeUrl: d.TypeUrl, |
||||||
|
Value: d.Value, |
||||||
|
}) |
||||||
|
} |
||||||
|
initErr = &rpc.Status{ |
||||||
|
Code: int32(st.Code()), |
||||||
|
Message: st.Message(), |
||||||
|
Details: details, |
||||||
|
} |
||||||
|
} else { |
||||||
|
initErr = &rpc.Status{ |
||||||
|
Code: int32(rpc.UNKNOWN), |
||||||
|
Message: err.Error(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pluginsPB = append(pluginsPB, api.Plugin{ |
||||||
|
Type: p.Registration.Type.String(), |
||||||
|
ID: p.Registration.ID, |
||||||
|
Requires: requires, |
||||||
|
Platforms: platforms, |
||||||
|
Capabilities: p.Meta.Capabilities, |
||||||
|
Exports: p.Meta.Exports, |
||||||
|
InitErr: initErr, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return pluginsPB |
||||||
|
} |
@ -0,0 +1,85 @@ |
|||||||
|
/* |
||||||
|
Copyright The containerd Authors. |
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
you may not use this file except in compliance with the License. |
||||||
|
You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software |
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
See the License for the specific language governing permissions and |
||||||
|
limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package introspection |
||||||
|
|
||||||
|
import ( |
||||||
|
context "context" |
||||||
|
|
||||||
|
api "github.com/containerd/containerd/api/services/introspection/v1" |
||||||
|
"github.com/containerd/containerd/plugin" |
||||||
|
"github.com/containerd/containerd/services" |
||||||
|
ptypes "github.com/gogo/protobuf/types" |
||||||
|
"github.com/pkg/errors" |
||||||
|
"google.golang.org/grpc" |
||||||
|
) |
||||||
|
|
||||||
|
func init() { |
||||||
|
plugin.Register(&plugin.Registration{ |
||||||
|
Type: plugin.GRPCPlugin, |
||||||
|
ID: "introspection", |
||||||
|
Requires: []plugin.Type{"*"}, |
||||||
|
InitFn: func(ic *plugin.InitContext) (interface{}, error) { |
||||||
|
// this service works by using the plugin context up till the point
|
||||||
|
// this service is initialized. Since we require this service last,
|
||||||
|
// it should provide the full set of plugins.
|
||||||
|
plugins, err := ic.GetByType(plugin.ServicePlugin) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
p, ok := plugins[services.IntrospectionService] |
||||||
|
if !ok { |
||||||
|
return nil, errors.New("introspection service not found") |
||||||
|
} |
||||||
|
|
||||||
|
i, err := p.Instance() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
allPluginsPB := pluginsToPB(ic.GetAll()) |
||||||
|
|
||||||
|
localClient, ok := i.(*Local) |
||||||
|
if !ok { |
||||||
|
return nil, errors.Errorf("Could not create a local client for introspection service") |
||||||
|
} |
||||||
|
localClient.UpdateLocal(ic.Root, allPluginsPB) |
||||||
|
|
||||||
|
return &server{ |
||||||
|
local: localClient, |
||||||
|
}, nil |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
type server struct { |
||||||
|
local api.IntrospectionClient |
||||||
|
} |
||||||
|
|
||||||
|
var _ = (api.IntrospectionServer)(&server{}) |
||||||
|
|
||||||
|
func (s *server) Register(server *grpc.Server) error { |
||||||
|
api.RegisterIntrospectionServer(server, s) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *server) Plugins(ctx context.Context, req *api.PluginsRequest) (*api.PluginsResponse, error) { |
||||||
|
return s.local.Plugins(ctx, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *server) Server(ctx context.Context, empty *ptypes.Empty) (*api.ServerResponse, error) { |
||||||
|
return s.local.Server(ctx, empty) |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
/* |
||||||
|
Copyright The containerd Authors. |
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
you may not use this file except in compliance with the License. |
||||||
|
You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software |
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
See the License for the specific language governing permissions and |
||||||
|
limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package services |
||||||
|
|
||||||
|
const ( |
||||||
|
// ContentService is id of content service.
|
||||||
|
ContentService = "content-service" |
||||||
|
// SnapshotsService is id of snapshots service.
|
||||||
|
SnapshotsService = "snapshots-service" |
||||||
|
// ImagesService is id of images service.
|
||||||
|
ImagesService = "images-service" |
||||||
|
// ContainersService is id of containers service.
|
||||||
|
ContainersService = "containers-service" |
||||||
|
// TasksService is id of tasks service.
|
||||||
|
TasksService = "tasks-service" |
||||||
|
// NamespacesService is id of namespaces service.
|
||||||
|
NamespacesService = "namespaces-service" |
||||||
|
// LeasesService is id of leases service.
|
||||||
|
LeasesService = "leases-service" |
||||||
|
// DiffService is id of diff service.
|
||||||
|
DiffService = "diff-service" |
||||||
|
// IntrospectionService is the id of introspection service
|
||||||
|
IntrospectionService = "introspection-service" |
||||||
|
) |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue