Merge pull request #2822 from thaJeztah/bump_docker

vendor: github.com/docker/docker c2cc352355d4c26be60be8ea3a1acfddc20fdfd3
This commit is contained in:
Silvin Lubecki 2020-11-03 14:03:32 +01:00 committed by GitHub
commit 1d02eaf47d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 45 additions and 1408 deletions

View File

@ -13,7 +13,7 @@ github.com/creack/pty 2a38352e8b4d7ab6c336eef107e4
github.com/davecgh/go-spew 8991bc29aa16c548c550c7ff78260e27b9ab7c73 # v1.1.1 github.com/davecgh/go-spew 8991bc29aa16c548c550c7ff78260e27b9ab7c73 # v1.1.1
github.com/docker/compose-on-kubernetes 78e6a00beda64ac8ccb9fec787e601fe2ce0d5bb # v0.5.0-alpha1 github.com/docker/compose-on-kubernetes 78e6a00beda64ac8ccb9fec787e601fe2ce0d5bb # v0.5.0-alpha1
github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580 github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580
github.com/docker/docker 73dc6a680cdd48f2f993f1a1b91aac1dd23d47c8 github.com/docker/docker c2cc352355d4c26be60be8ea3a1acfddc20fdfd3
github.com/docker/docker-credential-helpers 54f0238b6bf101fc3ad3b34114cb5520beb562f5 # v0.6.3 github.com/docker/docker-credential-helpers 54f0238b6bf101fc3ad3b34114cb5520beb562f5 # v0.6.3
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 # Contains a customized version of canonical/json and is used by Notary. The package is periodically rebased on current Go versions. github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 # Contains a customized version of canonical/json and is used by Notary. The package is periodically rebased on current Go versions.
github.com/docker/go-connections 7395e3f8aa162843a74ed6d48e79627d9792ac55 # v0.4.0 github.com/docker/go-connections 7395e3f8aa162843a74ed6d48e79627d9792ac55 # v0.4.0

View File

@ -1,21 +0,0 @@
package tarsum // import "github.com/docker/docker/pkg/tarsum"
// BuilderContext is an interface extending TarSum by adding the Remove method.
// In general there was concern about adding this method to TarSum itself
// so instead it is being added just to "BuilderContext" which will then
// only be used during the .dockerignore file processing
// - see builder/evaluator.go
type BuilderContext interface {
TarSum
Remove(string)
}
func (bc *tarSum) Remove(filename string) {
for i, fis := range bc.sums {
if fis.Name() == filename {
bc.sums = append(bc.sums[:i], bc.sums[i+1:]...)
// Note, we don't just return because there could be
// more than one with this name
}
}
}

View File

@ -1,133 +0,0 @@
package tarsum // import "github.com/docker/docker/pkg/tarsum"
import (
"runtime"
"sort"
"strings"
)
// FileInfoSumInterface provides an interface for accessing file checksum
// information within a tar file. This info is accessed through interface
// so the actual name and sum cannot be melded with.
type FileInfoSumInterface interface {
// File name
Name() string
// Checksum of this particular file and its headers
Sum() string
// Position of file in the tar
Pos() int64
}
type fileInfoSum struct {
name string
sum string
pos int64
}
func (fis fileInfoSum) Name() string {
return fis.name
}
func (fis fileInfoSum) Sum() string {
return fis.sum
}
func (fis fileInfoSum) Pos() int64 {
return fis.pos
}
// FileInfoSums provides a list of FileInfoSumInterfaces.
type FileInfoSums []FileInfoSumInterface
// GetFile returns the first FileInfoSumInterface with a matching name.
func (fis FileInfoSums) GetFile(name string) FileInfoSumInterface {
// We do case insensitive matching on Windows as c:\APP and c:\app are
// the same. See issue #33107.
for i := range fis {
if (runtime.GOOS == "windows" && strings.EqualFold(fis[i].Name(), name)) ||
(runtime.GOOS != "windows" && fis[i].Name() == name) {
return fis[i]
}
}
return nil
}
// GetAllFile returns a FileInfoSums with all matching names.
func (fis FileInfoSums) GetAllFile(name string) FileInfoSums {
f := FileInfoSums{}
for i := range fis {
if fis[i].Name() == name {
f = append(f, fis[i])
}
}
return f
}
// GetDuplicatePaths returns a FileInfoSums with all duplicated paths.
func (fis FileInfoSums) GetDuplicatePaths() (dups FileInfoSums) {
seen := make(map[string]int, len(fis)) // allocate earl. no need to grow this map.
for i := range fis {
f := fis[i]
if _, ok := seen[f.Name()]; ok {
dups = append(dups, f)
} else {
seen[f.Name()] = 0
}
}
return dups
}
// Len returns the size of the FileInfoSums.
func (fis FileInfoSums) Len() int { return len(fis) }
// Swap swaps two FileInfoSum values if a FileInfoSums list.
func (fis FileInfoSums) Swap(i, j int) { fis[i], fis[j] = fis[j], fis[i] }
// SortByPos sorts FileInfoSums content by position.
func (fis FileInfoSums) SortByPos() {
sort.Sort(byPos{fis})
}
// SortByNames sorts FileInfoSums content by name.
func (fis FileInfoSums) SortByNames() {
sort.Sort(byName{fis})
}
// SortBySums sorts FileInfoSums content by sums.
func (fis FileInfoSums) SortBySums() {
dups := fis.GetDuplicatePaths()
if len(dups) > 0 {
sort.Sort(bySum{fis, dups})
} else {
sort.Sort(bySum{fis, nil})
}
}
// byName is a sort.Sort helper for sorting by file names.
// If names are the same, order them by their appearance in the tar archive
type byName struct{ FileInfoSums }
func (bn byName) Less(i, j int) bool {
if bn.FileInfoSums[i].Name() == bn.FileInfoSums[j].Name() {
return bn.FileInfoSums[i].Pos() < bn.FileInfoSums[j].Pos()
}
return bn.FileInfoSums[i].Name() < bn.FileInfoSums[j].Name()
}
// bySum is a sort.Sort helper for sorting by the sums of all the fileinfos in the tar archive
type bySum struct {
FileInfoSums
dups FileInfoSums
}
func (bs bySum) Less(i, j int) bool {
if bs.dups != nil && bs.FileInfoSums[i].Name() == bs.FileInfoSums[j].Name() {
return bs.FileInfoSums[i].Pos() < bs.FileInfoSums[j].Pos()
}
return bs.FileInfoSums[i].Sum() < bs.FileInfoSums[j].Sum()
}
// byPos is a sort.Sort helper for sorting by the sums of all the fileinfos by their original order
type byPos struct{ FileInfoSums }
func (bp byPos) Less(i, j int) bool {
return bp.FileInfoSums[i].Pos() < bp.FileInfoSums[j].Pos()
}

View File

@ -1,301 +0,0 @@
// Package tarsum provides algorithms to perform checksum calculation on
// filesystem layers.
//
// The transportation of filesystems, regarding Docker, is done with tar(1)
// archives. There are a variety of tar serialization formats [2], and a key
// concern here is ensuring a repeatable checksum given a set of inputs from a
// generic tar archive. Types of transportation include distribution to and from a
// registry endpoint, saving and loading through commands or Docker daemon APIs,
// transferring the build context from client to Docker daemon, and committing the
// filesystem of a container to become an image.
//
// As tar archives are used for transit, but not preserved in many situations, the
// focus of the algorithm is to ensure the integrity of the preserved filesystem,
// while maintaining a deterministic accountability. This includes neither
// constraining the ordering or manipulation of the files during the creation or
// unpacking of the archive, nor include additional metadata state about the file
// system attributes.
package tarsum // import "github.com/docker/docker/pkg/tarsum"
import (
"archive/tar"
"bytes"
"compress/gzip"
"crypto"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"hash"
"io"
"path"
"strings"
)
const (
buf8K = 8 * 1024
buf16K = 16 * 1024
buf32K = 32 * 1024
)
// NewTarSum creates a new interface for calculating a fixed time checksum of a
// tar archive.
//
// This is used for calculating checksums of layers of an image, in some cases
// including the byte payload of the image's json metadata as well, and for
// calculating the checksums for buildcache.
func NewTarSum(r io.Reader, dc bool, v Version) (TarSum, error) {
return NewTarSumHash(r, dc, v, DefaultTHash)
}
// NewTarSumHash creates a new TarSum, providing a THash to use rather than
// the DefaultTHash.
func NewTarSumHash(r io.Reader, dc bool, v Version, tHash THash) (TarSum, error) {
headerSelector, err := getTarHeaderSelector(v)
if err != nil {
return nil, err
}
ts := &tarSum{Reader: r, DisableCompression: dc, tarSumVersion: v, headerSelector: headerSelector, tHash: tHash}
err = ts.initTarSum()
return ts, err
}
// NewTarSumForLabel creates a new TarSum using the provided TarSum version+hash label.
func NewTarSumForLabel(r io.Reader, disableCompression bool, label string) (TarSum, error) {
parts := strings.SplitN(label, "+", 2)
if len(parts) != 2 {
return nil, errors.New("tarsum label string should be of the form: {tarsum_version}+{hash_name}")
}
versionName, hashName := parts[0], parts[1]
version, ok := tarSumVersionsByName[versionName]
if !ok {
return nil, fmt.Errorf("unknown TarSum version name: %q", versionName)
}
hashConfig, ok := standardHashConfigs[hashName]
if !ok {
return nil, fmt.Errorf("unknown TarSum hash name: %q", hashName)
}
tHash := NewTHash(hashConfig.name, hashConfig.hash.New)
return NewTarSumHash(r, disableCompression, version, tHash)
}
// TarSum is the generic interface for calculating fixed time
// checksums of a tar archive.
type TarSum interface {
io.Reader
GetSums() FileInfoSums
Sum([]byte) string
Version() Version
Hash() THash
}
// tarSum struct is the structure for a Version0 checksum calculation.
type tarSum struct {
io.Reader
tarR *tar.Reader
tarW *tar.Writer
writer writeCloseFlusher
bufTar *bytes.Buffer
bufWriter *bytes.Buffer
bufData []byte
h hash.Hash
tHash THash
sums FileInfoSums
fileCounter int64
currentFile string
finished bool
first bool
DisableCompression bool // false by default. When false, the output gzip compressed.
tarSumVersion Version // this field is not exported so it can not be mutated during use
headerSelector tarHeaderSelector // handles selecting and ordering headers for files in the archive
}
func (ts tarSum) Hash() THash {
return ts.tHash
}
func (ts tarSum) Version() Version {
return ts.tarSumVersion
}
// THash provides a hash.Hash type generator and its name.
type THash interface {
Hash() hash.Hash
Name() string
}
// NewTHash is a convenience method for creating a THash.
func NewTHash(name string, h func() hash.Hash) THash {
return simpleTHash{n: name, h: h}
}
type tHashConfig struct {
name string
hash crypto.Hash
}
var (
// NOTE: DO NOT include MD5 or SHA1, which are considered insecure.
standardHashConfigs = map[string]tHashConfig{
"sha256": {name: "sha256", hash: crypto.SHA256},
"sha512": {name: "sha512", hash: crypto.SHA512},
}
)
// DefaultTHash is default TarSum hashing algorithm - "sha256".
var DefaultTHash = NewTHash("sha256", sha256.New)
type simpleTHash struct {
n string
h func() hash.Hash
}
func (sth simpleTHash) Name() string { return sth.n }
func (sth simpleTHash) Hash() hash.Hash { return sth.h() }
func (ts *tarSum) encodeHeader(h *tar.Header) error {
for _, elem := range ts.headerSelector.selectHeaders(h) {
// Ignore these headers to be compatible with versions
// before go 1.10
if elem[0] == "gname" || elem[0] == "uname" {
elem[1] = ""
}
if _, err := ts.h.Write([]byte(elem[0] + elem[1])); err != nil {
return err
}
}
return nil
}
func (ts *tarSum) initTarSum() error {
ts.bufTar = bytes.NewBuffer([]byte{})
ts.bufWriter = bytes.NewBuffer([]byte{})
ts.tarR = tar.NewReader(ts.Reader)
ts.tarW = tar.NewWriter(ts.bufTar)
if !ts.DisableCompression {
ts.writer = gzip.NewWriter(ts.bufWriter)
} else {
ts.writer = &nopCloseFlusher{Writer: ts.bufWriter}
}
if ts.tHash == nil {
ts.tHash = DefaultTHash
}
ts.h = ts.tHash.Hash()
ts.h.Reset()
ts.first = true
ts.sums = FileInfoSums{}
return nil
}
func (ts *tarSum) Read(buf []byte) (int, error) {
if ts.finished {
return ts.bufWriter.Read(buf)
}
if len(ts.bufData) < len(buf) {
switch {
case len(buf) <= buf8K:
ts.bufData = make([]byte, buf8K)
case len(buf) <= buf16K:
ts.bufData = make([]byte, buf16K)
case len(buf) <= buf32K:
ts.bufData = make([]byte, buf32K)
default:
ts.bufData = make([]byte, len(buf))
}
}
buf2 := ts.bufData[:len(buf)]
n, err := ts.tarR.Read(buf2)
if err != nil {
if err == io.EOF {
if _, err := ts.h.Write(buf2[:n]); err != nil {
return 0, err
}
if !ts.first {
ts.sums = append(ts.sums, fileInfoSum{name: ts.currentFile, sum: hex.EncodeToString(ts.h.Sum(nil)), pos: ts.fileCounter})
ts.fileCounter++
ts.h.Reset()
} else {
ts.first = false
}
if _, err := ts.tarW.Write(buf2[:n]); err != nil {
return 0, err
}
currentHeader, err := ts.tarR.Next()
if err != nil {
if err == io.EOF {
if err := ts.tarW.Close(); err != nil {
return 0, err
}
if _, err := io.Copy(ts.writer, ts.bufTar); err != nil {
return 0, err
}
if err := ts.writer.Close(); err != nil {
return 0, err
}
ts.finished = true
return ts.bufWriter.Read(buf)
}
return 0, err
}
ts.currentFile = path.Join(".", path.Join("/", currentHeader.Name))
if err := ts.encodeHeader(currentHeader); err != nil {
return 0, err
}
if err := ts.tarW.WriteHeader(currentHeader); err != nil {
return 0, err
}
if _, err := io.Copy(ts.writer, ts.bufTar); err != nil {
return 0, err
}
ts.writer.Flush()
return ts.bufWriter.Read(buf)
}
return 0, err
}
// Filling the hash buffer
if _, err = ts.h.Write(buf2[:n]); err != nil {
return 0, err
}
// Filling the tar writer
if _, err = ts.tarW.Write(buf2[:n]); err != nil {
return 0, err
}
// Filling the output writer
if _, err = io.Copy(ts.writer, ts.bufTar); err != nil {
return 0, err
}
ts.writer.Flush()
return ts.bufWriter.Read(buf)
}
func (ts *tarSum) Sum(extra []byte) string {
ts.sums.SortBySums()
h := ts.tHash.Hash()
if extra != nil {
h.Write(extra)
}
for _, fis := range ts.sums {
h.Write([]byte(fis.Sum()))
}
checksum := ts.Version().String() + "+" + ts.tHash.Name() + ":" + hex.EncodeToString(h.Sum(nil))
return checksum
}
func (ts *tarSum) GetSums() FileInfoSums {
return ts.sums
}

View File

@ -1,158 +0,0 @@
package tarsum // import "github.com/docker/docker/pkg/tarsum"
import (
"archive/tar"
"errors"
"io"
"sort"
"strconv"
"strings"
)
// Version is used for versioning of the TarSum algorithm
// based on the prefix of the hash used
// i.e. "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"
type Version int
// Prefix of "tarsum"
const (
Version0 Version = iota
Version1
// VersionDev this constant will be either the latest or an unsettled next-version of the TarSum calculation
VersionDev
)
// WriteV1Header writes a tar header to a writer in V1 tarsum format.
func WriteV1Header(h *tar.Header, w io.Writer) {
for _, elem := range v1TarHeaderSelect(h) {
w.Write([]byte(elem[0] + elem[1]))
}
}
// VersionLabelForChecksum returns the label for the given tarsum
// checksum, i.e., everything before the first `+` character in
// the string or an empty string if no label separator is found.
func VersionLabelForChecksum(checksum string) string {
// Checksums are in the form: {versionLabel}+{hashID}:{hex}
sepIndex := strings.Index(checksum, "+")
if sepIndex < 0 {
return ""
}
return checksum[:sepIndex]
}
// GetVersions gets a list of all known tarsum versions.
func GetVersions() []Version {
v := []Version{}
for k := range tarSumVersions {
v = append(v, k)
}
return v
}
var (
tarSumVersions = map[Version]string{
Version0: "tarsum",
Version1: "tarsum.v1",
VersionDev: "tarsum.dev",
}
tarSumVersionsByName = map[string]Version{
"tarsum": Version0,
"tarsum.v1": Version1,
"tarsum.dev": VersionDev,
}
)
func (tsv Version) String() string {
return tarSumVersions[tsv]
}
// GetVersionFromTarsum returns the Version from the provided string.
func GetVersionFromTarsum(tarsum string) (Version, error) {
tsv := tarsum
if strings.Contains(tarsum, "+") {
tsv = strings.SplitN(tarsum, "+", 2)[0]
}
for v, s := range tarSumVersions {
if s == tsv {
return v, nil
}
}
return -1, ErrNotVersion
}
// Errors that may be returned by functions in this package
var (
ErrNotVersion = errors.New("string does not include a TarSum Version")
ErrVersionNotImplemented = errors.New("TarSum Version is not yet implemented")
)
// tarHeaderSelector is the interface which different versions
// of tarsum should use for selecting and ordering tar headers
// for each item in the archive.
type tarHeaderSelector interface {
selectHeaders(h *tar.Header) (orderedHeaders [][2]string)
}
type tarHeaderSelectFunc func(h *tar.Header) (orderedHeaders [][2]string)
func (f tarHeaderSelectFunc) selectHeaders(h *tar.Header) (orderedHeaders [][2]string) {
return f(h)
}
func v0TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
return [][2]string{
{"name", h.Name},
{"mode", strconv.FormatInt(h.Mode, 10)},
{"uid", strconv.Itoa(h.Uid)},
{"gid", strconv.Itoa(h.Gid)},
{"size", strconv.FormatInt(h.Size, 10)},
{"mtime", strconv.FormatInt(h.ModTime.UTC().Unix(), 10)},
{"typeflag", string([]byte{h.Typeflag})},
{"linkname", h.Linkname},
{"uname", h.Uname},
{"gname", h.Gname},
{"devmajor", strconv.FormatInt(h.Devmajor, 10)},
{"devminor", strconv.FormatInt(h.Devminor, 10)},
}
}
func v1TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
// Get extended attributes.
xAttrKeys := make([]string, len(h.Xattrs))
for k := range h.Xattrs {
xAttrKeys = append(xAttrKeys, k)
}
sort.Strings(xAttrKeys)
// Make the slice with enough capacity to hold the 11 basic headers
// we want from the v0 selector plus however many xattrs we have.
orderedHeaders = make([][2]string, 0, 11+len(xAttrKeys))
// Copy all headers from v0 excluding the 'mtime' header (the 5th element).
v0headers := v0TarHeaderSelect(h)
orderedHeaders = append(orderedHeaders, v0headers[0:5]...)
orderedHeaders = append(orderedHeaders, v0headers[6:]...)
// Finally, append the sorted xattrs.
for _, k := range xAttrKeys {
orderedHeaders = append(orderedHeaders, [2]string{k, h.Xattrs[k]})
}
return
}
var registeredHeaderSelectors = map[Version]tarHeaderSelectFunc{
Version0: v0TarHeaderSelect,
Version1: v1TarHeaderSelect,
VersionDev: v1TarHeaderSelect,
}
func getTarHeaderSelector(v Version) (tarHeaderSelector, error) {
headerSelector, ok := registeredHeaderSelectors[v]
if !ok {
return nil, ErrVersionNotImplemented
}
return headerSelector, nil
}

View File

@ -1,22 +0,0 @@
package tarsum // import "github.com/docker/docker/pkg/tarsum"
import (
"io"
)
type writeCloseFlusher interface {
io.WriteCloser
Flush() error
}
type nopCloseFlusher struct {
io.Writer
}
func (n *nopCloseFlusher) Close() error {
return nil
}
func (n *nopCloseFlusher) Flush() error {
return nil
}

View File

@ -1,7 +1,6 @@
package registry // import "github.com/docker/docker/registry" package registry // import "github.com/docker/docker/registry"
import ( import (
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@ -12,7 +11,6 @@ import (
"github.com/docker/distribution/registry/client/transport" "github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry" registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/errdefs"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -22,51 +20,6 @@ const (
AuthClientID = "docker" AuthClientID = "docker"
) )
// loginV1 tries to register/login to the v1 registry server.
func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent string) (string, string, error) {
registryEndpoint := apiEndpoint.ToV1Endpoint(userAgent, nil)
serverAddress := registryEndpoint.String()
logrus.Debugf("attempting v1 login to registry endpoint %s", serverAddress)
if serverAddress == "" {
return "", "", errdefs.System(errors.New("server Error: Server Address not set"))
}
req, err := http.NewRequest(http.MethodGet, serverAddress+"users/", nil)
if err != nil {
return "", "", err
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
resp, err := registryEndpoint.client.Do(req)
if err != nil {
// fallback when request could not be completed
return "", "", fallbackError{
err: err,
}
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", "", errdefs.System(err)
}
switch resp.StatusCode {
case http.StatusOK:
return "Login Succeeded", "", nil
case http.StatusUnauthorized:
return "", "", errdefs.Unauthorized(errors.New("Wrong login/password, please try again"))
case http.StatusForbidden:
// *TODO: Use registry configuration to determine what this says, if anything?
return "", "", errdefs.Forbidden(errors.Errorf("Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress))
case http.StatusInternalServerError:
logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body)
return "", "", errdefs.System(errors.New("Internal Server Error"))
}
return "", "", errdefs.System(errors.Errorf("Login: %s (Code: %d; Headers: %s)", body,
resp.StatusCode, resp.Header))
}
type loginCredentialStore struct { type loginCredentialStore struct {
authConfig *types.AuthConfig authConfig *types.AuthConfig
} }

View File

@ -26,7 +26,7 @@ type serviceConfig struct {
registrytypes.ServiceConfig registrytypes.ServiceConfig
} }
var ( const (
// DefaultNamespace is the default namespace // DefaultNamespace is the default namespace
DefaultNamespace = "docker.io" DefaultNamespace = "docker.io"
// DefaultRegistryVersionHeader is the name of the default HTTP header // DefaultRegistryVersionHeader is the name of the default HTTP header
@ -39,29 +39,26 @@ var (
IndexServer = "https://" + IndexHostname + "/v1/" IndexServer = "https://" + IndexHostname + "/v1/"
// IndexName is the name of the index // IndexName is the name of the index
IndexName = "docker.io" IndexName = "docker.io"
)
var (
// DefaultV2Registry is the URI of the default v2 registry // DefaultV2Registry is the URI of the default v2 registry
DefaultV2Registry = &url.URL{ DefaultV2Registry = &url.URL{
Scheme: "https", Scheme: "https",
Host: "registry-1.docker.io", Host: "registry-1.docker.io",
} }
)
var (
// ErrInvalidRepositoryName is an error returned if the repository name did // ErrInvalidRepositoryName is an error returned if the repository name did
// not have the correct form // not have the correct form
ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
emptyServiceConfig, _ = newServiceConfig(ServiceOptions{}) emptyServiceConfig, _ = newServiceConfig(ServiceOptions{})
) validHostPortRegex = regexp.MustCompile(`^` + reference.DomainRegexp.String() + `$`)
var ( // for mocking in unit tests
validHostPortRegex = regexp.MustCompile(`^` + reference.DomainRegexp.String() + `$`) lookupIP = net.LookupIP
) )
// for mocking in unit tests
var lookupIP = net.LookupIP
// newServiceConfig returns a new instance of ServiceConfig // newServiceConfig returns a new instance of ServiceConfig
func newServiceConfig(options ServiceOptions) (*serviceConfig, error) { func newServiceConfig(options ServiceOptions) (*serviceConfig, error) {
config := &serviceConfig{ config := &serviceConfig{

View File

@ -7,14 +7,6 @@ import (
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
) )
type notFoundError string
func (e notFoundError) Error() string {
return string(e)
}
func (notFoundError) NotFound() {}
func translateV2AuthError(err error) error { func translateV2AuthError(err error) error {
switch e := err.(type) { switch e := err.(type) {
case *url.Error: case *url.Error:

View File

@ -1,96 +0,0 @@
package resumable // import "github.com/docker/docker/registry/resumable"
import (
"fmt"
"io"
"net/http"
"time"
"github.com/sirupsen/logrus"
)
type requestReader struct {
client *http.Client
request *http.Request
lastRange int64
totalSize int64
currentResponse *http.Response
failures uint32
maxFailures uint32
waitDuration time.Duration
}
// NewRequestReader makes it possible to resume reading a request's body transparently
// maxfail is the number of times we retry to make requests again (not resumes)
// totalsize is the total length of the body; auto detect if not provided
func NewRequestReader(c *http.Client, r *http.Request, maxfail uint32, totalsize int64) io.ReadCloser {
return &requestReader{client: c, request: r, maxFailures: maxfail, totalSize: totalsize, waitDuration: 5 * time.Second}
}
// NewRequestReaderWithInitialResponse makes it possible to resume
// reading the body of an already initiated request.
func NewRequestReaderWithInitialResponse(c *http.Client, r *http.Request, maxfail uint32, totalsize int64, initialResponse *http.Response) io.ReadCloser {
return &requestReader{client: c, request: r, maxFailures: maxfail, totalSize: totalsize, currentResponse: initialResponse, waitDuration: 5 * time.Second}
}
func (r *requestReader) Read(p []byte) (n int, err error) {
if r.client == nil || r.request == nil {
return 0, fmt.Errorf("client and request can't be nil")
}
isFreshRequest := false
if r.lastRange != 0 && r.currentResponse == nil {
readRange := fmt.Sprintf("bytes=%d-%d", r.lastRange, r.totalSize)
r.request.Header.Set("Range", readRange)
time.Sleep(r.waitDuration)
}
if r.currentResponse == nil {
r.currentResponse, err = r.client.Do(r.request)
isFreshRequest = true
}
if err != nil && r.failures+1 != r.maxFailures {
r.cleanUpResponse()
r.failures++
time.Sleep(time.Duration(r.failures) * r.waitDuration)
return 0, nil
} else if err != nil {
r.cleanUpResponse()
return 0, err
}
if r.currentResponse.StatusCode == http.StatusRequestedRangeNotSatisfiable && r.lastRange == r.totalSize && r.currentResponse.ContentLength == 0 {
r.cleanUpResponse()
return 0, io.EOF
} else if r.currentResponse.StatusCode != http.StatusPartialContent && r.lastRange != 0 && isFreshRequest {
r.cleanUpResponse()
return 0, fmt.Errorf("the server doesn't support byte ranges")
}
if r.totalSize == 0 {
r.totalSize = r.currentResponse.ContentLength
} else if r.totalSize <= 0 {
r.cleanUpResponse()
return 0, fmt.Errorf("failed to auto detect content length")
}
n, err = r.currentResponse.Body.Read(p)
r.lastRange += int64(n)
if err != nil {
r.cleanUpResponse()
}
if err != nil && err != io.EOF {
logrus.Infof("encountered error during pull and clearing it before resume: %s", err)
err = nil
}
return n, err
}
func (r *requestReader) Close() error {
r.cleanUpResponse()
r.client = nil
r.request = nil
return nil
}
func (r *requestReader) cleanUpResponse() {
if r.currentResponse != nil {
r.currentResponse.Body.Close()
r.currentResponse = nil
}
}

View File

@ -108,36 +108,35 @@ func (s *DefaultService) LoadInsecureRegistries(registries []string) error {
// It can be used to verify the validity of a client's credentials. // It can be used to verify the validity of a client's credentials.
func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) { func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
// TODO Use ctx when searching for repositories // TODO Use ctx when searching for repositories
serverAddress := authConfig.ServerAddress var registryHostName = IndexHostname
if serverAddress == "" {
serverAddress = IndexServer if authConfig.ServerAddress != "" {
} serverAddress := authConfig.ServerAddress
if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") { if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") {
serverAddress = "https://" + serverAddress serverAddress = "https://" + serverAddress
} }
u, err := url.Parse(serverAddress) u, err := url.Parse(serverAddress)
if err != nil { if err != nil {
return "", "", errdefs.InvalidParameter(errors.Errorf("unable to parse server address: %v", err)) return "", "", errdefs.InvalidParameter(errors.Errorf("unable to parse server address: %v", err))
}
registryHostName = u.Host
} }
endpoints, err := s.LookupPushEndpoints(u.Host) // Lookup endpoints for authentication using "LookupPushEndpoints", which
// excludes mirrors to prevent sending credentials of the upstream registry
// to a mirror.
endpoints, err := s.LookupPushEndpoints(registryHostName)
if err != nil { if err != nil {
return "", "", errdefs.InvalidParameter(err) return "", "", errdefs.InvalidParameter(err)
} }
for _, endpoint := range endpoints { for _, endpoint := range endpoints {
login := loginV2 status, token, err = loginV2(authConfig, endpoint, userAgent)
if endpoint.Version == APIVersion1 {
login = loginV1
}
status, token, err = login(authConfig, endpoint, userAgent)
if err == nil { if err == nil {
return return
} }
if fErr, ok := err.(fallbackError); ok { if fErr, ok := err.(fallbackError); ok {
err = fErr.err logrus.WithError(fErr.err).Infof("Error logging in to endpoint, trying next endpoint")
logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err)
continue continue
} }
@ -150,18 +149,13 @@ func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig,
// splitReposSearchTerm breaks a search term into an index name and remote name // splitReposSearchTerm breaks a search term into an index name and remote name
func splitReposSearchTerm(reposName string) (string, string) { func splitReposSearchTerm(reposName string) (string, string) {
nameParts := strings.SplitN(reposName, "/", 2) nameParts := strings.SplitN(reposName, "/", 2)
var indexName, remoteName string
if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") { !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
// This is a Docker Index repos (ex: samalba/hipache or ubuntu) // This is a Docker Hub repository (ex: samalba/hipache or ubuntu),
// 'docker.io' // use the default Docker Hub registry (docker.io)
indexName = IndexName return IndexName, reposName
remoteName = reposName
} else {
indexName = nameParts[0]
remoteName = nameParts[1]
} }
return indexName, remoteName return nameParts[0], nameParts[1]
} }
// Search queries the public registry for images matching the specified // Search queries the public registry for images matching the specified
@ -184,7 +178,7 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
} }
// *TODO: Search multiple indexes. // *TODO: Search multiple indexes.
endpoint, err := NewV1Endpoint(index, userAgent, http.Header(headers)) endpoint, err := NewV1Endpoint(index, userAgent, headers)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -228,13 +222,8 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
r := newSession(client, authConfig, endpoint) r := newSession(client, authConfig, endpoint)
if index.Official { if index.Official {
localName := remoteName // If pull "library/foo", it's stored locally under "foo"
if strings.HasPrefix(localName, "library/") { remoteName = strings.TrimPrefix(remoteName, "library/")
// If pull "library/foo", it's stored locally under "foo"
localName = strings.SplitN(localName, "/", 2)[1]
}
return r.SearchRepositories(localName, limit)
} }
return r.SearchRepositories(remoteName, limit) return r.SearchRepositories(remoteName, limit)
} }
@ -259,6 +248,7 @@ type APIEndpoint struct {
} }
// ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
// Deprecated: this function is deprecated and will be removed in a future update
func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) *V1Endpoint { func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) *V1Endpoint {
return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders) return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders)
} }
@ -280,24 +270,22 @@ func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, er
return s.tlsConfig(mirrorURL.Host) return s.tlsConfig(mirrorURL.Host)
} }
// LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference. // LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference.
// It gives preference to v2 endpoints over v1, mirrors over the actual // It gives preference to mirrors over the actual registry, and HTTPS over plain HTTP.
// registry, and HTTPS over plain HTTP.
func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) { func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
return s.lookupEndpoints(hostname) return s.lookupV2Endpoints(hostname)
} }
// LookupPushEndpoints creates a list of endpoints to try to push to, in order of preference. // LookupPushEndpoints creates a list of v2 endpoints to try to push to, in order of preference.
// It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP. // It gives preference to HTTPS over plain HTTP. Mirrors are not included.
// Mirrors are not included.
func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) { func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
allEndpoints, err := s.lookupEndpoints(hostname) allEndpoints, err := s.lookupV2Endpoints(hostname)
if err == nil { if err == nil {
for _, endpoint := range allEndpoints { for _, endpoint := range allEndpoints {
if !endpoint.Mirror { if !endpoint.Mirror {
@ -307,7 +295,3 @@ func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEn
} }
return endpoints, err return endpoints, err
} }
func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
return s.lookupV2Endpoints(hostname)
}

View File

@ -10,7 +10,6 @@ import (
func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) { func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
tlsConfig := tlsconfig.ServerDefault() tlsConfig := tlsconfig.ServerDefault()
if hostname == DefaultNamespace || hostname == IndexHostname { if hostname == DefaultNamespace || hostname == IndexHostname {
// v2 mirrors
for _, mirror := range s.config.Mirrors { for _, mirror := range s.config.Mirrors {
if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") { if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") {
mirror = "https://" + mirror mirror = "https://" + mirror
@ -24,15 +23,13 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
return nil, err return nil, err
} }
endpoints = append(endpoints, APIEndpoint{ endpoints = append(endpoints, APIEndpoint{
URL: mirrorURL, URL: mirrorURL,
// guess mirrors are v2
Version: APIVersion2, Version: APIVersion2,
Mirror: true, Mirror: true,
TrimHostname: true, TrimHostname: true,
TLSConfig: mirrorTLSConfig, TLSConfig: mirrorTLSConfig,
}) })
} }
// v2 registry
endpoints = append(endpoints, APIEndpoint{ endpoints = append(endpoints, APIEndpoint{
URL: DefaultV2Registry, URL: DefaultV2Registry,
Version: APIVersion2, Version: APIVersion2,

View File

@ -1,43 +1,26 @@
package registry // import "github.com/docker/docker/registry" package registry // import "github.com/docker/docker/registry"
import ( import (
"bytes"
"crypto/sha256"
// this is required for some certificates // this is required for some certificates
_ "crypto/sha512" _ "crypto/sha512"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"net/url" "net/url"
"strconv"
"strings" "strings"
"sync" "sync"
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry" registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/tarsum"
"github.com/docker/docker/registry/resumable"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
var (
// ErrRepoNotFound is returned if the repository didn't exist on the
// remote side
ErrRepoNotFound notFoundError = "Repository not found"
)
// A Session is used to communicate with a V1 registry // A Session is used to communicate with a V1 registry
type Session struct { type Session struct {
indexEndpoint *V1Endpoint indexEndpoint *V1Endpoint
@ -214,527 +197,6 @@ func NewSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1E
return newSession(client, authConfig, endpoint), nil return newSession(client, authConfig, endpoint), nil
} }
// ID returns this registry session's ID.
func (r *Session) ID() string {
return r.id
}
// GetRemoteHistory retrieves the history of a given image from the registry.
// It returns a list of the parent's JSON files (including the requested image).
func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) {
res, err := r.client.Get(registry + "images/" + imgID + "/ancestry")
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
if res.StatusCode == http.StatusUnauthorized {
return nil, errcode.ErrorCodeUnauthorized.WithArgs()
}
return nil, newJSONError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res)
}
var history []string
if err := json.NewDecoder(res.Body).Decode(&history); err != nil {
return nil, fmt.Errorf("Error while reading the http response: %v", err)
}
logrus.Debugf("Ancestry: %v", history)
return history, nil
}
// LookupRemoteImage checks if an image exists in the registry
func (r *Session) LookupRemoteImage(imgID, registry string) error {
res, err := r.client.Get(registry + "images/" + imgID + "/json")
if err != nil {
return err
}
res.Body.Close()
if res.StatusCode != http.StatusOK {
return newJSONError(fmt.Sprintf("HTTP code %d", res.StatusCode), res)
}
return nil
}
// GetRemoteImageJSON retrieves an image's JSON metadata from the registry.
func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int64, error) {
res, err := r.client.Get(registry + "images/" + imgID + "/json")
if err != nil {
return nil, -1, fmt.Errorf("Failed to download json: %s", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, -1, newJSONError(fmt.Sprintf("HTTP code %d", res.StatusCode), res)
}
// if the size header is not present, then set it to '-1'
imageSize := int64(-1)
if hdr := res.Header.Get("X-Docker-Size"); hdr != "" {
imageSize, err = strconv.ParseInt(hdr, 10, 64)
if err != nil {
return nil, -1, err
}
}
jsonString, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, -1, fmt.Errorf("Failed to parse downloaded json: %v (%s)", err, jsonString)
}
return jsonString, imageSize, nil
}
// GetRemoteImageLayer retrieves an image layer from the registry
func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io.ReadCloser, error) {
var (
statusCode = 0
res *http.Response
err error
imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID)
)
req, err := http.NewRequest(http.MethodGet, imageURL, nil)
if err != nil {
return nil, fmt.Errorf("Error while getting from the server: %v", err)
}
res, err = r.client.Do(req)
if err != nil {
logrus.Debugf("Error contacting registry %s: %v", registry, err)
// the only case err != nil && res != nil is https://golang.org/src/net/http/client.go#L515
if res != nil {
if res.Body != nil {
res.Body.Close()
}
statusCode = res.StatusCode
}
return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
statusCode, imgID)
}
if res.StatusCode != http.StatusOK {
res.Body.Close()
return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
res.StatusCode, imgID)
}
if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 {
logrus.Debug("server supports resume")
return resumable.NewRequestReaderWithInitialResponse(r.client, req, 5, imgSize, res), nil
}
logrus.Debug("server doesn't support resume")
return res.Body, nil
}
// GetRemoteTag retrieves the tag named in the askedTag argument from the given
// repository. It queries each of the registries supplied in the registries
// argument, and returns data from the first one that answers the query
// successfully.
func (r *Session) GetRemoteTag(registries []string, repositoryRef reference.Named, askedTag string) (string, error) {
repository := reference.Path(repositoryRef)
if strings.Count(repository, "/") == 0 {
// This will be removed once the registry supports auto-resolution on
// the "library" namespace
repository = "library/" + repository
}
for _, host := range registries {
endpoint := fmt.Sprintf("%srepositories/%s/tags/%s", host, repository, askedTag)
res, err := r.client.Get(endpoint)
if err != nil {
return "", err
}
logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
defer res.Body.Close()
if res.StatusCode == 404 {
return "", ErrRepoNotFound
}
if res.StatusCode != http.StatusOK {
continue
}
var tagID string
if err := json.NewDecoder(res.Body).Decode(&tagID); err != nil {
return "", err
}
return tagID, nil
}
return "", fmt.Errorf("Could not reach any registry endpoint")
}
// GetRemoteTags retrieves all tags from the given repository. It queries each
// of the registries supplied in the registries argument, and returns data from
// the first one that answers the query successfully. It returns a map with
// tag names as the keys and image IDs as the values.
func (r *Session) GetRemoteTags(registries []string, repositoryRef reference.Named) (map[string]string, error) {
repository := reference.Path(repositoryRef)
if strings.Count(repository, "/") == 0 {
// This will be removed once the registry supports auto-resolution on
// the "library" namespace
repository = "library/" + repository
}
for _, host := range registries {
endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
res, err := r.client.Get(endpoint)
if err != nil {
return nil, err
}
logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
defer res.Body.Close()
if res.StatusCode == 404 {
return nil, ErrRepoNotFound
}
if res.StatusCode != http.StatusOK {
continue
}
result := make(map[string]string)
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
return nil, err
}
return result, nil
}
return nil, fmt.Errorf("Could not reach any registry endpoint")
}
func buildEndpointsList(headers []string, indexEp string) ([]string, error) {
var endpoints []string
parsedURL, err := url.Parse(indexEp)
if err != nil {
return nil, err
}
var urlScheme = parsedURL.Scheme
// The registry's URL scheme has to match the Index'
for _, ep := range headers {
epList := strings.Split(ep, ",")
for _, epListElement := range epList {
endpoints = append(
endpoints,
fmt.Sprintf("%s://%s/v1/", urlScheme, strings.TrimSpace(epListElement)))
}
}
return endpoints, nil
}
// GetRepositoryData returns lists of images and endpoints for the repository
func (r *Session) GetRepositoryData(name reference.Named) (*RepositoryData, error) {
repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.String(), reference.Path(name))
logrus.Debugf("[registry] Calling GET %s", repositoryTarget)
req, err := http.NewRequest(http.MethodGet, repositoryTarget, nil)
if err != nil {
return nil, err
}
// this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests
req.Header.Set("X-Docker-Token", "true")
res, err := r.client.Do(req)
if err != nil {
// check if the error is because of i/o timeout
// and return a non-obtuse error message for users
// "Get https://index.docker.io/v1/repositories/library/busybox/images: i/o timeout"
// was a top search on the docker user forum
if isTimeout(err) {
return nil, fmt.Errorf("network timed out while trying to connect to %s. You may want to check your internet connection or if you are behind a proxy", repositoryTarget)
}
return nil, fmt.Errorf("Error while pulling image: %v", err)
}
defer res.Body.Close()
if res.StatusCode == http.StatusUnauthorized {
return nil, errcode.ErrorCodeUnauthorized.WithArgs()
}
// TODO: Right now we're ignoring checksums in the response body.
// In the future, we need to use them to check image validity.
if res.StatusCode == 404 {
return nil, newJSONError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res)
} else if res.StatusCode != http.StatusOK {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
logrus.Debugf("Error reading response body: %s", err)
}
return nil, newJSONError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, reference.Path(name), errBody), res)
}
var endpoints []string
if res.Header.Get("X-Docker-Endpoints") != "" {
endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.String())
if err != nil {
return nil, err
}
} else {
// Assume the endpoint is on the same host
endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", r.indexEndpoint.URL.Scheme, req.URL.Host))
}
remoteChecksums := []*ImgData{}
if err := json.NewDecoder(res.Body).Decode(&remoteChecksums); err != nil {
return nil, err
}
// Forge a better object from the retrieved data
imgsData := make(map[string]*ImgData, len(remoteChecksums))
for _, elem := range remoteChecksums {
imgsData[elem.ID] = elem
}
return &RepositoryData{
ImgList: imgsData,
Endpoints: endpoints,
}, nil
}
// PushImageChecksumRegistry uploads checksums for an image
func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string) error {
u := registry + "images/" + imgData.ID + "/checksum"
logrus.Debugf("[registry] Calling PUT %s", u)
req, err := http.NewRequest(http.MethodPut, u, nil)
if err != nil {
return err
}
req.Header.Set("X-Docker-Checksum", imgData.Checksum)
req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload)
res, err := r.client.Do(req)
if err != nil {
return fmt.Errorf("Failed to upload metadata: %v", err)
}
defer res.Body.Close()
if len(res.Cookies()) > 0 {
r.client.Jar.SetCookies(req.URL, res.Cookies())
}
if res.StatusCode != http.StatusOK {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err)
}
var jsonBody map[string]string
if err := json.Unmarshal(errBody, &jsonBody); err != nil {
errBody = []byte(err.Error())
} else if jsonBody["error"] == "Image already exists" {
return ErrAlreadyExists
}
return fmt.Errorf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody)
}
return nil
}
// PushImageJSONRegistry pushes JSON metadata for a local image to the registry
func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string) error {
u := registry + "images/" + imgData.ID + "/json"
logrus.Debugf("[registry] Calling PUT %s", u)
req, err := http.NewRequest(http.MethodPut, u, bytes.NewReader(jsonRaw))
if err != nil {
return err
}
req.Header.Add("Content-type", "application/json")
res, err := r.client.Do(req)
if err != nil {
return fmt.Errorf("Failed to upload metadata: %s", err)
}
defer res.Body.Close()
if res.StatusCode == http.StatusUnauthorized && strings.HasPrefix(registry, "http://") {
return newJSONError("HTTP code 401, Docker will not send auth headers over HTTP.", res)
}
if res.StatusCode != http.StatusOK {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return newJSONError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
}
var jsonBody map[string]string
if err := json.Unmarshal(errBody, &jsonBody); err != nil {
errBody = []byte(err.Error())
} else if jsonBody["error"] == "Image already exists" {
return ErrAlreadyExists
}
return newJSONError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res)
}
return nil
}
// PushImageLayerRegistry sends the checksum of an image layer to the registry
func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
u := registry + "images/" + imgID + "/layer"
logrus.Debugf("[registry] Calling PUT %s", u)
tarsumLayer, err := tarsum.NewTarSum(layer, false, tarsum.Version0)
if err != nil {
return "", "", err
}
h := sha256.New()
h.Write(jsonRaw)
h.Write([]byte{'\n'})
checksumLayer := io.TeeReader(tarsumLayer, h)
req, err := http.NewRequest(http.MethodPut, u, checksumLayer)
if err != nil {
return "", "", err
}
req.Header.Add("Content-Type", "application/octet-stream")
req.ContentLength = -1
req.TransferEncoding = []string{"chunked"}
res, err := r.client.Do(req)
if err != nil {
return "", "", fmt.Errorf("Failed to upload layer: %v", err)
}
if rc, ok := layer.(io.Closer); ok {
if err := rc.Close(); err != nil {
return "", "", err
}
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", newJSONError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
}
return "", "", newJSONError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res)
}
checksumPayload = "sha256:" + hex.EncodeToString(h.Sum(nil))
return tarsumLayer.Sum(jsonRaw), checksumPayload, nil
}
// PushRegistryTag pushes a tag on the registry.
// Remote has the format '<user>/<repo>
func (r *Session) PushRegistryTag(remote reference.Named, revision, tag, registry string) error {
// "jsonify" the string
revision = "\"" + revision + "\""
path := fmt.Sprintf("repositories/%s/tags/%s", reference.Path(remote), tag)
req, err := http.NewRequest(http.MethodPut, registry+path, strings.NewReader(revision))
if err != nil {
return err
}
req.Header.Add("Content-type", "application/json")
req.ContentLength = int64(len(revision))
res, err := r.client.Do(req)
if err != nil {
return err
}
res.Body.Close()
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated {
return newJSONError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, reference.Path(remote)), res)
}
return nil
}
// PushImageJSONIndex uploads an image list to the repository
func (r *Session) PushImageJSONIndex(remote reference.Named, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
cleanImgList := []*ImgData{}
if validate {
for _, elem := range imgList {
if elem.Checksum != "" {
cleanImgList = append(cleanImgList, elem)
}
}
} else {
cleanImgList = imgList
}
imgListJSON, err := json.Marshal(cleanImgList)
if err != nil {
return nil, err
}
var suffix string
if validate {
suffix = "images"
}
u := fmt.Sprintf("%srepositories/%s/%s", r.indexEndpoint.String(), reference.Path(remote), suffix)
logrus.Debugf("[registry] PUT %s", u)
logrus.Debugf("Image list pushed to index:\n%s", imgListJSON)
headers := map[string][]string{
"Content-type": {"application/json"},
// this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests
"X-Docker-Token": {"true"},
}
if validate {
headers["X-Docker-Endpoints"] = regs
}
// Redirect if necessary
var res *http.Response
for {
if res, err = r.putImageRequest(u, headers, imgListJSON); err != nil {
return nil, err
}
if !shouldRedirect(res) {
break
}
res.Body.Close()
u = res.Header.Get("Location")
logrus.Debugf("Redirected to %s", u)
}
defer res.Body.Close()
if res.StatusCode == http.StatusUnauthorized {
return nil, errcode.ErrorCodeUnauthorized.WithArgs()
}
var tokens, endpoints []string
if !validate {
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
logrus.Debugf("Error reading response body: %s", err)
}
return nil, newJSONError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, reference.Path(remote), errBody), res)
}
tokens = res.Header["X-Docker-Token"]
logrus.Debugf("Auth token: %v", tokens)
if res.Header.Get("X-Docker-Endpoints") == "" {
return nil, fmt.Errorf("Index response didn't contain any endpoints")
}
endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.String())
if err != nil {
return nil, err
}
} else {
if res.StatusCode != http.StatusNoContent {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
logrus.Debugf("Error reading response body: %s", err)
}
return nil, newJSONError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, reference.Path(remote), errBody), res)
}
}
return &RepositoryData{
Endpoints: endpoints,
}, nil
}
func (r *Session) putImageRequest(u string, headers map[string][]string, body []byte) (*http.Response, error) {
req, err := http.NewRequest(http.MethodPut, u, bytes.NewReader(body))
if err != nil {
return nil, err
}
req.ContentLength = int64(len(body))
for k, v := range headers {
req.Header[k] = v
}
response, err := r.client.Do(req)
if err != nil {
return nil, err
}
return response, nil
}
func shouldRedirect(response *http.Response) bool {
return response.StatusCode >= 300 && response.StatusCode < 400
}
// SearchRepositories performs a search against the remote repository // SearchRepositories performs a search against the remote repository
func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.SearchResults, error) { func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.SearchResults, error) {
if limit < 1 || limit > 100 { if limit < 1 || limit > 100 {
@ -755,28 +217,11 @@ func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.Sea
} }
defer res.Body.Close() defer res.Body.Close()
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
return nil, newJSONError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res) return nil, &jsonmessage.JSONError{
Message: fmt.Sprintf("Unexpected status code %d", res.StatusCode),
Code: res.StatusCode,
}
} }
result := new(registrytypes.SearchResults) result := new(registrytypes.SearchResults)
return result, errors.Wrap(json.NewDecoder(res.Body).Decode(result), "error decoding registry search results") return result, errors.Wrap(json.NewDecoder(res.Body).Decode(result), "error decoding registry search results")
} }
func isTimeout(err error) bool {
type timeout interface {
Timeout() bool
}
e := err
switch urlErr := err.(type) {
case *url.Error:
e = urlErr.Err
}
t, ok := e.(timeout)
return ok && t.Timeout()
}
func newJSONError(msg string, res *http.Response) error {
return &jsonmessage.JSONError{
Message: msg,
Code: res.StatusCode,
}
}

View File

@ -127,7 +127,7 @@ github.com/containerd/containerd c623d1b36f09f8ef6536a057bd65
github.com/containerd/fifo f15a3290365b9d2627d189e619ab4008e0069caf github.com/containerd/fifo f15a3290365b9d2627d189e619ab4008e0069caf
github.com/containerd/continuity efbc4488d8fe1bdc16bde3b2d2990d9b3a899165 github.com/containerd/continuity efbc4488d8fe1bdc16bde3b2d2990d9b3a899165
github.com/containerd/cgroups 318312a373405e5e91134d8063d04d59768a1bff github.com/containerd/cgroups 318312a373405e5e91134d8063d04d59768a1bff
github.com/containerd/console 8375c3424e4d7b114e8a90a4a40c8e1b40d1d4e6 # v1.0.0 github.com/containerd/console 5d7e1412f07b502a01029ea20e20e0d2be31fa7c # v1.0.1
github.com/containerd/go-runc 7016d3ce2328dd2cb1192b2076ebd565c4e8df0c github.com/containerd/go-runc 7016d3ce2328dd2cb1192b2076ebd565c4e8df0c
github.com/containerd/typeurl cd3ce7159eae562a4f60ceff37dada11a939d247 # v1.0.1 github.com/containerd/typeurl cd3ce7159eae562a4f60ceff37dada11a939d247 # v1.0.1
github.com/containerd/ttrpc 72bb1b21c5b0a4a107f59dd85f6ab58e564b68d6 # v1.0.1 github.com/containerd/ttrpc 72bb1b21c5b0a4a107f59dd85f6ab58e564b68d6 # v1.0.1