mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-20 09:39:08 +00:00
Merge pull request #18 from riyazdf/docker-pull-api
implement docker pull using docker API + notary library
This commit is contained in:
commit
4bef9648f9
@ -10,7 +10,6 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
@ -161,48 +160,22 @@ func dockerRm(container string) error {
|
||||
|
||||
func dockerPull(image string, trustedPull bool) error {
|
||||
log.Debugf("docker pull: %s", image)
|
||||
docker, err := exec.LookPath("docker")
|
||||
if err != nil {
|
||||
return errors.New("Docker does not seem to be installed")
|
||||
}
|
||||
var args = []string{"pull"}
|
||||
if trustedPull {
|
||||
log.Debugf("pulling %s with content trust", image)
|
||||
args = append(args, "--disable-content-trust=false")
|
||||
trustedImg, err := TrustedReference(image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Trusted pull for %s failed: %v", image, err)
|
||||
}
|
||||
image = trustedImg.String()
|
||||
}
|
||||
args = append(args, image)
|
||||
cmd := exec.Command(docker, args...)
|
||||
|
||||
stderrPipe, err := cmd.StderrPipe()
|
||||
cli, err := dockerClient()
|
||||
if err != nil {
|
||||
return errors.New("could not initialize Docker API client")
|
||||
}
|
||||
|
||||
if _, err := cli.ImagePull(context.Background(), image, types.ImagePullOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdoutPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = ioutil.ReadAll(stdoutPipe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stderr, err := ioutil.ReadAll(stderrPipe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %s", err, stderr)
|
||||
}
|
||||
|
||||
log.Debugf("docker pull: %s...Done", image)
|
||||
return nil
|
||||
}
|
||||
|
201
cmd/moby/trust.go
Normal file
201
cmd/moby/trust.go
Normal file
@ -0,0 +1,201 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/distribution/registry/client/auth"
|
||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/docker/cli/trust"
|
||||
notaryClient "github.com/docker/notary/client"
|
||||
"github.com/docker/notary/trustpinning"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// TrustedReference parses an image string, and does a notary lookup to verify and retrieve the signed digest reference
|
||||
func TrustedReference(image string) (reference.Reference, error) {
|
||||
ref, err := reference.ParseAnyReference(image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// to mimic docker pull: if we have a digest already, it's implicitly trusted
|
||||
if digestRef, ok := ref.(reference.Digested); ok {
|
||||
return digestRef, nil
|
||||
}
|
||||
// to mimic docker pull: if we have a digest already, it's implicitly trusted
|
||||
if canonicalRef, ok := ref.(reference.Canonical); ok {
|
||||
return canonicalRef, nil
|
||||
}
|
||||
|
||||
namedRef, ok := ref.(reference.Named)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to resolve image digest using content trust: reference is not named")
|
||||
}
|
||||
taggedRef, ok := namedRef.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to resolve image digest using content trust: reference is not tagged")
|
||||
}
|
||||
|
||||
gun := taggedRef.Name()
|
||||
targetName := taggedRef.Tag()
|
||||
server, err := getTrustServer(gun)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rt, err := GetReadOnlyAuthTransport(server, []string{gun}, "", "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nRepo, err := notaryClient.NewNotaryRepository(
|
||||
"",
|
||||
gun,
|
||||
server,
|
||||
rt,
|
||||
nil,
|
||||
trustpinning.TrustPinConfig{},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target, err := nRepo.GetTargetByName(targetName, trust.ReleasesRole, data.CanonicalTargetsRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Only get the tag if it's in the top level targets role or the releases delegation role
|
||||
// ignore it if it's in any other delegation roles
|
||||
if target.Role != trust.ReleasesRole && target.Role != data.CanonicalTargetsRole {
|
||||
return nil, errors.New("not signed in valid role")
|
||||
}
|
||||
|
||||
h, ok := target.Hashes["sha256"]
|
||||
if !ok {
|
||||
return nil, errors.New("no valid hash, expecting sha256")
|
||||
}
|
||||
|
||||
dgst := digest.NewDigestFromHex("sha256", hex.EncodeToString(h))
|
||||
|
||||
// Allow returning canonical reference with tag and digest
|
||||
return reference.WithDigest(taggedRef, dgst)
|
||||
}
|
||||
|
||||
func getTrustServer(gun string) (string, error) {
|
||||
if strings.HasPrefix(gun, "docker.io/") {
|
||||
return "https://notary.docker.io", nil
|
||||
}
|
||||
return "", errors.New("non-hub images not yet supported")
|
||||
}
|
||||
|
||||
type credentialStore struct {
|
||||
username string
|
||||
password string
|
||||
refreshTokens map[string]string
|
||||
}
|
||||
|
||||
func (tcs *credentialStore) Basic(url *url.URL) (string, string) {
|
||||
return tcs.username, tcs.password
|
||||
}
|
||||
|
||||
// refresh tokens are the long lived tokens that can be used instead of a password
|
||||
func (tcs *credentialStore) RefreshToken(u *url.URL, service string) string {
|
||||
return tcs.refreshTokens[service]
|
||||
}
|
||||
|
||||
func (tcs *credentialStore) SetRefreshToken(u *url.URL, service string, token string) {
|
||||
if tcs.refreshTokens != nil {
|
||||
tcs.refreshTokens[service] = token
|
||||
}
|
||||
}
|
||||
|
||||
// GetReadOnlyAuthTransport gets the Auth Transport used to communicate with notary
|
||||
func GetReadOnlyAuthTransport(server string, scopes []string, username, password, rootCAPath string) (http.RoundTripper, error) {
|
||||
httpsTransport, err := httpsTransport(rootCAPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/v2/", server), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pingClient := &http.Client{
|
||||
Transport: httpsTransport,
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
resp, err := pingClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
challengeManager := challenge.NewSimpleManager()
|
||||
if err := challengeManager.AddResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
creds := credentialStore{
|
||||
username: username,
|
||||
password: password,
|
||||
refreshTokens: make(map[string]string),
|
||||
}
|
||||
|
||||
var scopeObjs []auth.Scope
|
||||
for _, scopeName := range scopes {
|
||||
scopeObjs = append(scopeObjs, auth.RepositoryScope{
|
||||
Repository: scopeName,
|
||||
Actions: []string{"pull"},
|
||||
})
|
||||
}
|
||||
|
||||
// allow setting multiple scopes so we don't have to reauth
|
||||
tokenHandler := auth.NewTokenHandlerWithOptions(auth.TokenHandlerOptions{
|
||||
Transport: httpsTransport,
|
||||
Credentials: &creds,
|
||||
Scopes: scopeObjs,
|
||||
})
|
||||
|
||||
authedTransport := transport.NewTransport(httpsTransport, auth.NewAuthorizer(challengeManager, tokenHandler))
|
||||
return authedTransport, nil
|
||||
}
|
||||
|
||||
func httpsTransport(caFile string) (*http.Transport, error) {
|
||||
tlsConfig := &tls.Config{}
|
||||
transport := http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
// Override with the system cert pool if the caFile was empty
|
||||
if caFile == "" {
|
||||
systemCertPool, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport.TLSClientConfig.RootCAs = systemCertPool
|
||||
} else {
|
||||
certPool := x509.NewCertPool()
|
||||
pems, err := ioutil.ReadFile(caFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certPool.AppendCertsFromPEM(pems)
|
||||
transport.TLSClientConfig.RootCAs = certPool
|
||||
}
|
||||
return &transport, nil
|
||||
}
|
10
vendor.conf
10
vendor.conf
@ -1,13 +1,23 @@
|
||||
github.com/Sirupsen/logrus 10f801ebc38b33738c9d17d50860f484a0988ff5
|
||||
github.com/agl/ed25519 5312a61534124124185d41f09206b9fef1d88403
|
||||
github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621
|
||||
github.com/docker/docker 420b67f892d5424be59a788a51e2c4e64bb9cd66
|
||||
github.com/docker/docker-credential-helpers 71779cf7f595bf47f5a56d0629ce044e59b1cffa
|
||||
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
|
||||
github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5
|
||||
github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
|
||||
github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||
github.com/docker/notary v0.4.2
|
||||
github.com/gorilla/mux 4c1c3952b7d9d0a061a3fa7b36fd373ba0398ebc
|
||||
github.com/linuxkit/linuxkit ad8a4cad1f45a08f7dbc0ab02f28be31f767f1fd
|
||||
github.com/mattn/go-shellwords 02e3cf038dcea8290e44424da473dd12be796a8a
|
||||
github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512
|
||||
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
|
||||
github.com/opencontainers/runc 992a5be178a62e026f4069f443c6164912adbf09
|
||||
github.com/opencontainers/runtime-spec d094a5c9c1997ab086197b57e9378fabed394d92
|
||||
github.com/pkg/errors ff09b135c25aae272398c51a07235b90a75aa4f0
|
||||
github.com/spf13/cobra v1.5.1 https://github.com/dnephin/cobra.git
|
||||
github.com/spf13/pflag c990990ab4981d84da820b7b00c85139ca150b5f
|
||||
github.com/surma/gocpio fcb68777e7dc4ea43ffce871b552c0d073c17495
|
||||
github.com/xeipuuv/gojsonpointer 6fe8760cad3569743d51ddbb243b26f8456742dc
|
||||
github.com/xeipuuv/gojsonreference e02fc20de94c78484cd5ffb007f8af96be030a45
|
||||
|
26
vendor/github.com/Nvveen/Gotty/LICENSE
generated
vendored
Normal file
26
vendor/github.com/Nvveen/Gotty/LICENSE
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
Copyright (c) 2012, Neal van Veen (nealvanveen@gmail.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
514
vendor/github.com/Nvveen/Gotty/attributes.go
generated
vendored
Normal file
514
vendor/github.com/Nvveen/Gotty/attributes.go
generated
vendored
Normal file
@ -0,0 +1,514 @@
|
||||
// Copyright 2012 Neal van Veen. All rights reserved.
|
||||
// Usage of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package gotty
|
||||
|
||||
// Boolean capabilities
|
||||
var BoolAttr = [...]string{
|
||||
"auto_left_margin", "bw",
|
||||
"auto_right_margin", "am",
|
||||
"no_esc_ctlc", "xsb",
|
||||
"ceol_standout_glitch", "xhp",
|
||||
"eat_newline_glitch", "xenl",
|
||||
"erase_overstrike", "eo",
|
||||
"generic_type", "gn",
|
||||
"hard_copy", "hc",
|
||||
"has_meta_key", "km",
|
||||
"has_status_line", "hs",
|
||||
"insert_null_glitch", "in",
|
||||
"memory_above", "da",
|
||||
"memory_below", "db",
|
||||
"move_insert_mode", "mir",
|
||||
"move_standout_mode", "msgr",
|
||||
"over_strike", "os",
|
||||
"status_line_esc_ok", "eslok",
|
||||
"dest_tabs_magic_smso", "xt",
|
||||
"tilde_glitch", "hz",
|
||||
"transparent_underline", "ul",
|
||||
"xon_xoff", "nxon",
|
||||
"needs_xon_xoff", "nxon",
|
||||
"prtr_silent", "mc5i",
|
||||
"hard_cursor", "chts",
|
||||
"non_rev_rmcup", "nrrmc",
|
||||
"no_pad_char", "npc",
|
||||
"non_dest_scroll_region", "ndscr",
|
||||
"can_change", "ccc",
|
||||
"back_color_erase", "bce",
|
||||
"hue_lightness_saturation", "hls",
|
||||
"col_addr_glitch", "xhpa",
|
||||
"cr_cancels_micro_mode", "crxm",
|
||||
"has_print_wheel", "daisy",
|
||||
"row_addr_glitch", "xvpa",
|
||||
"semi_auto_right_margin", "sam",
|
||||
"cpi_changes_res", "cpix",
|
||||
"lpi_changes_res", "lpix",
|
||||
"backspaces_with_bs", "",
|
||||
"crt_no_scrolling", "",
|
||||
"no_correctly_working_cr", "",
|
||||
"gnu_has_meta_key", "",
|
||||
"linefeed_is_newline", "",
|
||||
"has_hardware_tabs", "",
|
||||
"return_does_clr_eol", "",
|
||||
}
|
||||
|
||||
// Numerical capabilities
|
||||
var NumAttr = [...]string{
|
||||
"columns", "cols",
|
||||
"init_tabs", "it",
|
||||
"lines", "lines",
|
||||
"lines_of_memory", "lm",
|
||||
"magic_cookie_glitch", "xmc",
|
||||
"padding_baud_rate", "pb",
|
||||
"virtual_terminal", "vt",
|
||||
"width_status_line", "wsl",
|
||||
"num_labels", "nlab",
|
||||
"label_height", "lh",
|
||||
"label_width", "lw",
|
||||
"max_attributes", "ma",
|
||||
"maximum_windows", "wnum",
|
||||
"max_colors", "colors",
|
||||
"max_pairs", "pairs",
|
||||
"no_color_video", "ncv",
|
||||
"buffer_capacity", "bufsz",
|
||||
"dot_vert_spacing", "spinv",
|
||||
"dot_horz_spacing", "spinh",
|
||||
"max_micro_address", "maddr",
|
||||
"max_micro_jump", "mjump",
|
||||
"micro_col_size", "mcs",
|
||||
"micro_line_size", "mls",
|
||||
"number_of_pins", "npins",
|
||||
"output_res_char", "orc",
|
||||
"output_res_line", "orl",
|
||||
"output_res_horz_inch", "orhi",
|
||||
"output_res_vert_inch", "orvi",
|
||||
"print_rate", "cps",
|
||||
"wide_char_size", "widcs",
|
||||
"buttons", "btns",
|
||||
"bit_image_entwining", "bitwin",
|
||||
"bit_image_type", "bitype",
|
||||
"magic_cookie_glitch_ul", "",
|
||||
"carriage_return_delay", "",
|
||||
"new_line_delay", "",
|
||||
"backspace_delay", "",
|
||||
"horizontal_tab_delay", "",
|
||||
"number_of_function_keys", "",
|
||||
}
|
||||
|
||||
// String capabilities
|
||||
var StrAttr = [...]string{
|
||||
"back_tab", "cbt",
|
||||
"bell", "bel",
|
||||
"carriage_return", "cr",
|
||||
"change_scroll_region", "csr",
|
||||
"clear_all_tabs", "tbc",
|
||||
"clear_screen", "clear",
|
||||
"clr_eol", "el",
|
||||
"clr_eos", "ed",
|
||||
"column_address", "hpa",
|
||||
"command_character", "cmdch",
|
||||
"cursor_address", "cup",
|
||||
"cursor_down", "cud1",
|
||||
"cursor_home", "home",
|
||||
"cursor_invisible", "civis",
|
||||
"cursor_left", "cub1",
|
||||
"cursor_mem_address", "mrcup",
|
||||
"cursor_normal", "cnorm",
|
||||
"cursor_right", "cuf1",
|
||||
"cursor_to_ll", "ll",
|
||||
"cursor_up", "cuu1",
|
||||
"cursor_visible", "cvvis",
|
||||
"delete_character", "dch1",
|
||||
"delete_line", "dl1",
|
||||
"dis_status_line", "dsl",
|
||||
"down_half_line", "hd",
|
||||
"enter_alt_charset_mode", "smacs",
|
||||
"enter_blink_mode", "blink",
|
||||
"enter_bold_mode", "bold",
|
||||
"enter_ca_mode", "smcup",
|
||||
"enter_delete_mode", "smdc",
|
||||
"enter_dim_mode", "dim",
|
||||
"enter_insert_mode", "smir",
|
||||
"enter_secure_mode", "invis",
|
||||
"enter_protected_mode", "prot",
|
||||
"enter_reverse_mode", "rev",
|
||||
"enter_standout_mode", "smso",
|
||||
"enter_underline_mode", "smul",
|
||||
"erase_chars", "ech",
|
||||
"exit_alt_charset_mode", "rmacs",
|
||||
"exit_attribute_mode", "sgr0",
|
||||
"exit_ca_mode", "rmcup",
|
||||
"exit_delete_mode", "rmdc",
|
||||
"exit_insert_mode", "rmir",
|
||||
"exit_standout_mode", "rmso",
|
||||
"exit_underline_mode", "rmul",
|
||||
"flash_screen", "flash",
|
||||
"form_feed", "ff",
|
||||
"from_status_line", "fsl",
|
||||
"init_1string", "is1",
|
||||
"init_2string", "is2",
|
||||
"init_3string", "is3",
|
||||
"init_file", "if",
|
||||
"insert_character", "ich1",
|
||||
"insert_line", "il1",
|
||||
"insert_padding", "ip",
|
||||
"key_backspace", "kbs",
|
||||
"key_catab", "ktbc",
|
||||
"key_clear", "kclr",
|
||||
"key_ctab", "kctab",
|
||||
"key_dc", "kdch1",
|
||||
"key_dl", "kdl1",
|
||||
"key_down", "kcud1",
|
||||
"key_eic", "krmir",
|
||||
"key_eol", "kel",
|
||||
"key_eos", "ked",
|
||||
"key_f0", "kf0",
|
||||
"key_f1", "kf1",
|
||||
"key_f10", "kf10",
|
||||
"key_f2", "kf2",
|
||||
"key_f3", "kf3",
|
||||
"key_f4", "kf4",
|
||||
"key_f5", "kf5",
|
||||
"key_f6", "kf6",
|
||||
"key_f7", "kf7",
|
||||
"key_f8", "kf8",
|
||||
"key_f9", "kf9",
|
||||
"key_home", "khome",
|
||||
"key_ic", "kich1",
|
||||
"key_il", "kil1",
|
||||
"key_left", "kcub1",
|
||||
"key_ll", "kll",
|
||||
"key_npage", "knp",
|
||||
"key_ppage", "kpp",
|
||||
"key_right", "kcuf1",
|
||||
"key_sf", "kind",
|
||||
"key_sr", "kri",
|
||||
"key_stab", "khts",
|
||||
"key_up", "kcuu1",
|
||||
"keypad_local", "rmkx",
|
||||
"keypad_xmit", "smkx",
|
||||
"lab_f0", "lf0",
|
||||
"lab_f1", "lf1",
|
||||
"lab_f10", "lf10",
|
||||
"lab_f2", "lf2",
|
||||
"lab_f3", "lf3",
|
||||
"lab_f4", "lf4",
|
||||
"lab_f5", "lf5",
|
||||
"lab_f6", "lf6",
|
||||
"lab_f7", "lf7",
|
||||
"lab_f8", "lf8",
|
||||
"lab_f9", "lf9",
|
||||
"meta_off", "rmm",
|
||||
"meta_on", "smm",
|
||||
"newline", "_glitch",
|
||||
"pad_char", "npc",
|
||||
"parm_dch", "dch",
|
||||
"parm_delete_line", "dl",
|
||||
"parm_down_cursor", "cud",
|
||||
"parm_ich", "ich",
|
||||
"parm_index", "indn",
|
||||
"parm_insert_line", "il",
|
||||
"parm_left_cursor", "cub",
|
||||
"parm_right_cursor", "cuf",
|
||||
"parm_rindex", "rin",
|
||||
"parm_up_cursor", "cuu",
|
||||
"pkey_key", "pfkey",
|
||||
"pkey_local", "pfloc",
|
||||
"pkey_xmit", "pfx",
|
||||
"print_screen", "mc0",
|
||||
"prtr_off", "mc4",
|
||||
"prtr_on", "mc5",
|
||||
"repeat_char", "rep",
|
||||
"reset_1string", "rs1",
|
||||
"reset_2string", "rs2",
|
||||
"reset_3string", "rs3",
|
||||
"reset_file", "rf",
|
||||
"restore_cursor", "rc",
|
||||
"row_address", "mvpa",
|
||||
"save_cursor", "row_address",
|
||||
"scroll_forward", "ind",
|
||||
"scroll_reverse", "ri",
|
||||
"set_attributes", "sgr",
|
||||
"set_tab", "hts",
|
||||
"set_window", "wind",
|
||||
"tab", "s_magic_smso",
|
||||
"to_status_line", "tsl",
|
||||
"underline_char", "uc",
|
||||
"up_half_line", "hu",
|
||||
"init_prog", "iprog",
|
||||
"key_a1", "ka1",
|
||||
"key_a3", "ka3",
|
||||
"key_b2", "kb2",
|
||||
"key_c1", "kc1",
|
||||
"key_c3", "kc3",
|
||||
"prtr_non", "mc5p",
|
||||
"char_padding", "rmp",
|
||||
"acs_chars", "acsc",
|
||||
"plab_norm", "pln",
|
||||
"key_btab", "kcbt",
|
||||
"enter_xon_mode", "smxon",
|
||||
"exit_xon_mode", "rmxon",
|
||||
"enter_am_mode", "smam",
|
||||
"exit_am_mode", "rmam",
|
||||
"xon_character", "xonc",
|
||||
"xoff_character", "xoffc",
|
||||
"ena_acs", "enacs",
|
||||
"label_on", "smln",
|
||||
"label_off", "rmln",
|
||||
"key_beg", "kbeg",
|
||||
"key_cancel", "kcan",
|
||||
"key_close", "kclo",
|
||||
"key_command", "kcmd",
|
||||
"key_copy", "kcpy",
|
||||
"key_create", "kcrt",
|
||||
"key_end", "kend",
|
||||
"key_enter", "kent",
|
||||
"key_exit", "kext",
|
||||
"key_find", "kfnd",
|
||||
"key_help", "khlp",
|
||||
"key_mark", "kmrk",
|
||||
"key_message", "kmsg",
|
||||
"key_move", "kmov",
|
||||
"key_next", "knxt",
|
||||
"key_open", "kopn",
|
||||
"key_options", "kopt",
|
||||
"key_previous", "kprv",
|
||||
"key_print", "kprt",
|
||||
"key_redo", "krdo",
|
||||
"key_reference", "kref",
|
||||
"key_refresh", "krfr",
|
||||
"key_replace", "krpl",
|
||||
"key_restart", "krst",
|
||||
"key_resume", "kres",
|
||||
"key_save", "ksav",
|
||||
"key_suspend", "kspd",
|
||||
"key_undo", "kund",
|
||||
"key_sbeg", "kBEG",
|
||||
"key_scancel", "kCAN",
|
||||
"key_scommand", "kCMD",
|
||||
"key_scopy", "kCPY",
|
||||
"key_screate", "kCRT",
|
||||
"key_sdc", "kDC",
|
||||
"key_sdl", "kDL",
|
||||
"key_select", "kslt",
|
||||
"key_send", "kEND",
|
||||
"key_seol", "kEOL",
|
||||
"key_sexit", "kEXT",
|
||||
"key_sfind", "kFND",
|
||||
"key_shelp", "kHLP",
|
||||
"key_shome", "kHOM",
|
||||
"key_sic", "kIC",
|
||||
"key_sleft", "kLFT",
|
||||
"key_smessage", "kMSG",
|
||||
"key_smove", "kMOV",
|
||||
"key_snext", "kNXT",
|
||||
"key_soptions", "kOPT",
|
||||
"key_sprevious", "kPRV",
|
||||
"key_sprint", "kPRT",
|
||||
"key_sredo", "kRDO",
|
||||
"key_sreplace", "kRPL",
|
||||
"key_sright", "kRIT",
|
||||
"key_srsume", "kRES",
|
||||
"key_ssave", "kSAV",
|
||||
"key_ssuspend", "kSPD",
|
||||
"key_sundo", "kUND",
|
||||
"req_for_input", "rfi",
|
||||
"key_f11", "kf11",
|
||||
"key_f12", "kf12",
|
||||
"key_f13", "kf13",
|
||||
"key_f14", "kf14",
|
||||
"key_f15", "kf15",
|
||||
"key_f16", "kf16",
|
||||
"key_f17", "kf17",
|
||||
"key_f18", "kf18",
|
||||
"key_f19", "kf19",
|
||||
"key_f20", "kf20",
|
||||
"key_f21", "kf21",
|
||||
"key_f22", "kf22",
|
||||
"key_f23", "kf23",
|
||||
"key_f24", "kf24",
|
||||
"key_f25", "kf25",
|
||||
"key_f26", "kf26",
|
||||
"key_f27", "kf27",
|
||||
"key_f28", "kf28",
|
||||
"key_f29", "kf29",
|
||||
"key_f30", "kf30",
|
||||
"key_f31", "kf31",
|
||||
"key_f32", "kf32",
|
||||
"key_f33", "kf33",
|
||||
"key_f34", "kf34",
|
||||
"key_f35", "kf35",
|
||||
"key_f36", "kf36",
|
||||
"key_f37", "kf37",
|
||||
"key_f38", "kf38",
|
||||
"key_f39", "kf39",
|
||||
"key_f40", "kf40",
|
||||
"key_f41", "kf41",
|
||||
"key_f42", "kf42",
|
||||
"key_f43", "kf43",
|
||||
"key_f44", "kf44",
|
||||
"key_f45", "kf45",
|
||||
"key_f46", "kf46",
|
||||
"key_f47", "kf47",
|
||||
"key_f48", "kf48",
|
||||
"key_f49", "kf49",
|
||||
"key_f50", "kf50",
|
||||
"key_f51", "kf51",
|
||||
"key_f52", "kf52",
|
||||
"key_f53", "kf53",
|
||||
"key_f54", "kf54",
|
||||
"key_f55", "kf55",
|
||||
"key_f56", "kf56",
|
||||
"key_f57", "kf57",
|
||||
"key_f58", "kf58",
|
||||
"key_f59", "kf59",
|
||||
"key_f60", "kf60",
|
||||
"key_f61", "kf61",
|
||||
"key_f62", "kf62",
|
||||
"key_f63", "kf63",
|
||||
"clr_bol", "el1",
|
||||
"clear_margins", "mgc",
|
||||
"set_left_margin", "smgl",
|
||||
"set_right_margin", "smgr",
|
||||
"label_format", "fln",
|
||||
"set_clock", "sclk",
|
||||
"display_clock", "dclk",
|
||||
"remove_clock", "rmclk",
|
||||
"create_window", "cwin",
|
||||
"goto_window", "wingo",
|
||||
"hangup", "hup",
|
||||
"dial_phone", "dial",
|
||||
"quick_dial", "qdial",
|
||||
"tone", "tone",
|
||||
"pulse", "pulse",
|
||||
"flash_hook", "hook",
|
||||
"fixed_pause", "pause",
|
||||
"wait_tone", "wait",
|
||||
"user0", "u0",
|
||||
"user1", "u1",
|
||||
"user2", "u2",
|
||||
"user3", "u3",
|
||||
"user4", "u4",
|
||||
"user5", "u5",
|
||||
"user6", "u6",
|
||||
"user7", "u7",
|
||||
"user8", "u8",
|
||||
"user9", "u9",
|
||||
"orig_pair", "op",
|
||||
"orig_colors", "oc",
|
||||
"initialize_color", "initc",
|
||||
"initialize_pair", "initp",
|
||||
"set_color_pair", "scp",
|
||||
"set_foreground", "setf",
|
||||
"set_background", "setb",
|
||||
"change_char_pitch", "cpi",
|
||||
"change_line_pitch", "lpi",
|
||||
"change_res_horz", "chr",
|
||||
"change_res_vert", "cvr",
|
||||
"define_char", "defc",
|
||||
"enter_doublewide_mode", "swidm",
|
||||
"enter_draft_quality", "sdrfq",
|
||||
"enter_italics_mode", "sitm",
|
||||
"enter_leftward_mode", "slm",
|
||||
"enter_micro_mode", "smicm",
|
||||
"enter_near_letter_quality", "snlq",
|
||||
"enter_normal_quality", "snrmq",
|
||||
"enter_shadow_mode", "sshm",
|
||||
"enter_subscript_mode", "ssubm",
|
||||
"enter_superscript_mode", "ssupm",
|
||||
"enter_upward_mode", "sum",
|
||||
"exit_doublewide_mode", "rwidm",
|
||||
"exit_italics_mode", "ritm",
|
||||
"exit_leftward_mode", "rlm",
|
||||
"exit_micro_mode", "rmicm",
|
||||
"exit_shadow_mode", "rshm",
|
||||
"exit_subscript_mode", "rsubm",
|
||||
"exit_superscript_mode", "rsupm",
|
||||
"exit_upward_mode", "rum",
|
||||
"micro_column_address", "mhpa",
|
||||
"micro_down", "mcud1",
|
||||
"micro_left", "mcub1",
|
||||
"micro_right", "mcuf1",
|
||||
"micro_row_address", "mvpa",
|
||||
"micro_up", "mcuu1",
|
||||
"order_of_pins", "porder",
|
||||
"parm_down_micro", "mcud",
|
||||
"parm_left_micro", "mcub",
|
||||
"parm_right_micro", "mcuf",
|
||||
"parm_up_micro", "mcuu",
|
||||
"select_char_set", "scs",
|
||||
"set_bottom_margin", "smgb",
|
||||
"set_bottom_margin_parm", "smgbp",
|
||||
"set_left_margin_parm", "smglp",
|
||||
"set_right_margin_parm", "smgrp",
|
||||
"set_top_margin", "smgt",
|
||||
"set_top_margin_parm", "smgtp",
|
||||
"start_bit_image", "sbim",
|
||||
"start_char_set_def", "scsd",
|
||||
"stop_bit_image", "rbim",
|
||||
"stop_char_set_def", "rcsd",
|
||||
"subscript_characters", "subcs",
|
||||
"superscript_characters", "supcs",
|
||||
"these_cause_cr", "docr",
|
||||
"zero_motion", "zerom",
|
||||
"char_set_names", "csnm",
|
||||
"key_mouse", "kmous",
|
||||
"mouse_info", "minfo",
|
||||
"req_mouse_pos", "reqmp",
|
||||
"get_mouse", "getm",
|
||||
"set_a_foreground", "setaf",
|
||||
"set_a_background", "setab",
|
||||
"pkey_plab", "pfxl",
|
||||
"device_type", "devt",
|
||||
"code_set_init", "csin",
|
||||
"set0_des_seq", "s0ds",
|
||||
"set1_des_seq", "s1ds",
|
||||
"set2_des_seq", "s2ds",
|
||||
"set3_des_seq", "s3ds",
|
||||
"set_lr_margin", "smglr",
|
||||
"set_tb_margin", "smgtb",
|
||||
"bit_image_repeat", "birep",
|
||||
"bit_image_newline", "binel",
|
||||
"bit_image_carriage_return", "bicr",
|
||||
"color_names", "colornm",
|
||||
"define_bit_image_region", "defbi",
|
||||
"end_bit_image_region", "endbi",
|
||||
"set_color_band", "setcolor",
|
||||
"set_page_length", "slines",
|
||||
"display_pc_char", "dispc",
|
||||
"enter_pc_charset_mode", "smpch",
|
||||
"exit_pc_charset_mode", "rmpch",
|
||||
"enter_scancode_mode", "smsc",
|
||||
"exit_scancode_mode", "rmsc",
|
||||
"pc_term_options", "pctrm",
|
||||
"scancode_escape", "scesc",
|
||||
"alt_scancode_esc", "scesa",
|
||||
"enter_horizontal_hl_mode", "ehhlm",
|
||||
"enter_left_hl_mode", "elhlm",
|
||||
"enter_low_hl_mode", "elohlm",
|
||||
"enter_right_hl_mode", "erhlm",
|
||||
"enter_top_hl_mode", "ethlm",
|
||||
"enter_vertical_hl_mode", "evhlm",
|
||||
"set_a_attributes", "sgr1",
|
||||
"set_pglen_inch", "slength",
|
||||
"termcap_init2", "",
|
||||
"termcap_reset", "",
|
||||
"linefeed_if_not_lf", "",
|
||||
"backspace_if_not_bs", "",
|
||||
"other_non_function_keys", "",
|
||||
"arrow_key_map", "",
|
||||
"acs_ulcorner", "",
|
||||
"acs_llcorner", "",
|
||||
"acs_urcorner", "",
|
||||
"acs_lrcorner", "",
|
||||
"acs_ltee", "",
|
||||
"acs_rtee", "",
|
||||
"acs_btee", "",
|
||||
"acs_ttee", "",
|
||||
"acs_hline", "",
|
||||
"acs_vline", "",
|
||||
"acs_plus", "",
|
||||
"memory_lock", "",
|
||||
"memory_unlock", "",
|
||||
"box_chars_1", "",
|
||||
}
|
238
vendor/github.com/Nvveen/Gotty/gotty.go
generated
vendored
Normal file
238
vendor/github.com/Nvveen/Gotty/gotty.go
generated
vendored
Normal file
@ -0,0 +1,238 @@
|
||||
// Copyright 2012 Neal van Veen. All rights reserved.
|
||||
// Usage of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Gotty is a Go-package for reading and parsing the terminfo database
|
||||
package gotty
|
||||
|
||||
// TODO add more concurrency to name lookup, look for more opportunities.
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Open a terminfo file by the name given and construct a TermInfo object.
|
||||
// If something went wrong reading the terminfo database file, an error is
|
||||
// returned.
|
||||
func OpenTermInfo(termName string) (*TermInfo, error) {
|
||||
var term *TermInfo
|
||||
var err error
|
||||
// Find the environment variables
|
||||
termloc := os.Getenv("TERMINFO")
|
||||
if len(termloc) == 0 {
|
||||
// Search like ncurses
|
||||
locations := []string{os.Getenv("HOME") + "/.terminfo/", "/etc/terminfo/",
|
||||
"/lib/terminfo/", "/usr/share/terminfo/"}
|
||||
var path string
|
||||
for _, str := range locations {
|
||||
// Construct path
|
||||
path = str + string(termName[0]) + "/" + termName
|
||||
// Check if path can be opened
|
||||
file, _ := os.Open(path)
|
||||
if file != nil {
|
||||
// Path can open, fall out and use current path
|
||||
file.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(path) > 0 {
|
||||
term, err = readTermInfo(path)
|
||||
} else {
|
||||
err = errors.New(fmt.Sprintf("No terminfo file(-location) found"))
|
||||
}
|
||||
}
|
||||
return term, err
|
||||
}
|
||||
|
||||
// Open a terminfo file from the environment variable containing the current
|
||||
// terminal name and construct a TermInfo object. If something went wrong
|
||||
// reading the terminfo database file, an error is returned.
|
||||
func OpenTermInfoEnv() (*TermInfo, error) {
|
||||
termenv := os.Getenv("TERM")
|
||||
return OpenTermInfo(termenv)
|
||||
}
|
||||
|
||||
// Return an attribute by the name attr provided. If none can be found,
|
||||
// an error is returned.
|
||||
func (term *TermInfo) GetAttribute(attr string) (stacker, error) {
|
||||
// Channel to store the main value in.
|
||||
var value stacker
|
||||
// Add a blocking WaitGroup
|
||||
var block sync.WaitGroup
|
||||
// Keep track of variable being written.
|
||||
written := false
|
||||
// Function to put into goroutine.
|
||||
f := func(ats interface{}) {
|
||||
var ok bool
|
||||
var v stacker
|
||||
// Switch on type of map to use and assign value to it.
|
||||
switch reflect.TypeOf(ats).Elem().Kind() {
|
||||
case reflect.Bool:
|
||||
v, ok = ats.(map[string]bool)[attr]
|
||||
case reflect.Int16:
|
||||
v, ok = ats.(map[string]int16)[attr]
|
||||
case reflect.String:
|
||||
v, ok = ats.(map[string]string)[attr]
|
||||
}
|
||||
// If ok, a value is found, so we can write.
|
||||
if ok {
|
||||
value = v
|
||||
written = true
|
||||
}
|
||||
// Goroutine is done
|
||||
block.Done()
|
||||
}
|
||||
block.Add(3)
|
||||
// Go for all 3 attribute lists.
|
||||
go f(term.boolAttributes)
|
||||
go f(term.numAttributes)
|
||||
go f(term.strAttributes)
|
||||
// Wait until every goroutine is done.
|
||||
block.Wait()
|
||||
// If a value has been written, return it.
|
||||
if written {
|
||||
return value, nil
|
||||
}
|
||||
// Otherwise, error.
|
||||
return nil, fmt.Errorf("Erorr finding attribute")
|
||||
}
|
||||
|
||||
// Return an attribute by the name attr provided. If none can be found,
|
||||
// an error is returned. A name is first converted to its termcap value.
|
||||
func (term *TermInfo) GetAttributeName(name string) (stacker, error) {
|
||||
tc := GetTermcapName(name)
|
||||
return term.GetAttribute(tc)
|
||||
}
|
||||
|
||||
// A utility function that finds and returns the termcap equivalent of a
|
||||
// variable name.
|
||||
func GetTermcapName(name string) string {
|
||||
// Termcap name
|
||||
var tc string
|
||||
// Blocking group
|
||||
var wait sync.WaitGroup
|
||||
// Function to put into a goroutine
|
||||
f := func(attrs []string) {
|
||||
// Find the string corresponding to the name
|
||||
for i, s := range attrs {
|
||||
if s == name {
|
||||
tc = attrs[i+1]
|
||||
}
|
||||
}
|
||||
// Goroutine is finished
|
||||
wait.Done()
|
||||
}
|
||||
wait.Add(3)
|
||||
// Go for all 3 attribute lists
|
||||
go f(BoolAttr[:])
|
||||
go f(NumAttr[:])
|
||||
go f(StrAttr[:])
|
||||
// Wait until every goroutine is done
|
||||
wait.Wait()
|
||||
// Return the termcap name
|
||||
return tc
|
||||
}
|
||||
|
||||
// This function takes a path to a terminfo file and reads it in binary
|
||||
// form to construct the actual TermInfo file.
|
||||
func readTermInfo(path string) (*TermInfo, error) {
|
||||
// Open the terminfo file
|
||||
file, err := os.Open(path)
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// magic, nameSize, boolSize, nrSNum, nrOffsetsStr, strSize
|
||||
// Header is composed of the magic 0432 octal number, size of the name
|
||||
// section, size of the boolean section, the amount of number values,
|
||||
// the number of offsets of strings, and the size of the string section.
|
||||
var header [6]int16
|
||||
// Byte array is used to read in byte values
|
||||
var byteArray []byte
|
||||
// Short array is used to read in short values
|
||||
var shArray []int16
|
||||
// TermInfo object to store values
|
||||
var term TermInfo
|
||||
|
||||
// Read in the header
|
||||
err = binary.Read(file, binary.LittleEndian, &header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If magic number isn't there or isn't correct, we have the wrong filetype
|
||||
if header[0] != 0432 {
|
||||
return nil, errors.New(fmt.Sprintf("Wrong filetype"))
|
||||
}
|
||||
|
||||
// Read in the names
|
||||
byteArray = make([]byte, header[1])
|
||||
err = binary.Read(file, binary.LittleEndian, &byteArray)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
term.Names = strings.Split(string(byteArray), "|")
|
||||
|
||||
// Read in the booleans
|
||||
byteArray = make([]byte, header[2])
|
||||
err = binary.Read(file, binary.LittleEndian, &byteArray)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
term.boolAttributes = make(map[string]bool)
|
||||
for i, b := range byteArray {
|
||||
if b == 1 {
|
||||
term.boolAttributes[BoolAttr[i*2+1]] = true
|
||||
}
|
||||
}
|
||||
// If the number of bytes read is not even, a byte for alignment is added
|
||||
if len(byteArray)%2 != 0 {
|
||||
err = binary.Read(file, binary.LittleEndian, make([]byte, 1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Read in shorts
|
||||
shArray = make([]int16, header[3])
|
||||
err = binary.Read(file, binary.LittleEndian, &shArray)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
term.numAttributes = make(map[string]int16)
|
||||
for i, n := range shArray {
|
||||
if n != 0377 && n > -1 {
|
||||
term.numAttributes[NumAttr[i*2+1]] = n
|
||||
}
|
||||
}
|
||||
|
||||
// Read the offsets into the short array
|
||||
shArray = make([]int16, header[4])
|
||||
err = binary.Read(file, binary.LittleEndian, &shArray)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Read the actual strings in the byte array
|
||||
byteArray = make([]byte, header[5])
|
||||
err = binary.Read(file, binary.LittleEndian, &byteArray)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
term.strAttributes = make(map[string]string)
|
||||
// We get an offset, and then iterate until the string is null-terminated
|
||||
for i, offset := range shArray {
|
||||
if offset > -1 {
|
||||
r := offset
|
||||
for ; byteArray[r] != 0; r++ {
|
||||
}
|
||||
term.strAttributes[StrAttr[i*2+1]] = string(byteArray[offset:r])
|
||||
}
|
||||
}
|
||||
return &term, nil
|
||||
}
|
362
vendor/github.com/Nvveen/Gotty/parser.go
generated
vendored
Normal file
362
vendor/github.com/Nvveen/Gotty/parser.go
generated
vendored
Normal file
@ -0,0 +1,362 @@
|
||||
// Copyright 2012 Neal van Veen. All rights reserved.
|
||||
// Usage of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package gotty
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var exp = [...]string{
|
||||
"%%",
|
||||
"%c",
|
||||
"%s",
|
||||
"%p(\\d)",
|
||||
"%P([A-z])",
|
||||
"%g([A-z])",
|
||||
"%'(.)'",
|
||||
"%{([0-9]+)}",
|
||||
"%l",
|
||||
"%\\+|%-|%\\*|%/|%m",
|
||||
"%&|%\\||%\\^",
|
||||
"%=|%>|%<",
|
||||
"%A|%O",
|
||||
"%!|%~",
|
||||
"%i",
|
||||
"%(:[\\ #\\-\\+]{0,4})?(\\d+\\.\\d+|\\d+)?[doxXs]",
|
||||
"%\\?(.*?);",
|
||||
}
|
||||
|
||||
var regex *regexp.Regexp
|
||||
var staticVar map[byte]stacker
|
||||
|
||||
// Parses the attribute that is received with name attr and parameters params.
|
||||
func (term *TermInfo) Parse(attr string, params ...interface{}) (string, error) {
|
||||
// Get the attribute name first.
|
||||
iface, err := term.GetAttribute(attr)
|
||||
str, ok := iface.(string)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !ok {
|
||||
return str, errors.New("Only string capabilities can be parsed.")
|
||||
}
|
||||
// Construct the hidden parser struct so we can use a recursive stack based
|
||||
// parser.
|
||||
ps := &parser{}
|
||||
// Dynamic variables only exist in this context.
|
||||
ps.dynamicVar = make(map[byte]stacker, 26)
|
||||
ps.parameters = make([]stacker, len(params))
|
||||
// Convert the parameters to insert them into the parser struct.
|
||||
for i, x := range params {
|
||||
ps.parameters[i] = x
|
||||
}
|
||||
// Recursively walk and return.
|
||||
result, err := ps.walk(str)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Parses the attribute that is received with name attr and parameters params.
|
||||
// Only works on full name of a capability that is given, which it uses to
|
||||
// search for the termcap name.
|
||||
func (term *TermInfo) ParseName(attr string, params ...interface{}) (string, error) {
|
||||
tc := GetTermcapName(attr)
|
||||
return term.Parse(tc, params)
|
||||
}
|
||||
|
||||
// Identify each token in a stack based manner and do the actual parsing.
|
||||
func (ps *parser) walk(attr string) (string, error) {
|
||||
// We use a buffer to get the modified string.
|
||||
var buf bytes.Buffer
|
||||
// Next, find and identify all tokens by their indices and strings.
|
||||
tokens := regex.FindAllStringSubmatch(attr, -1)
|
||||
if len(tokens) == 0 {
|
||||
return attr, nil
|
||||
}
|
||||
indices := regex.FindAllStringIndex(attr, -1)
|
||||
q := 0 // q counts the matches of one token
|
||||
// Iterate through the string per character.
|
||||
for i := 0; i < len(attr); i++ {
|
||||
// If the current position is an identified token, execute the following
|
||||
// steps.
|
||||
if q < len(indices) && i >= indices[q][0] && i < indices[q][1] {
|
||||
// Switch on token.
|
||||
switch {
|
||||
case tokens[q][0][:2] == "%%":
|
||||
// Literal percentage character.
|
||||
buf.WriteByte('%')
|
||||
case tokens[q][0][:2] == "%c":
|
||||
// Pop a character.
|
||||
c, err := ps.st.pop()
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
}
|
||||
buf.WriteByte(c.(byte))
|
||||
case tokens[q][0][:2] == "%s":
|
||||
// Pop a string.
|
||||
str, err := ps.st.pop()
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
}
|
||||
if _, ok := str.(string); !ok {
|
||||
return buf.String(), errors.New("Stack head is not a string")
|
||||
}
|
||||
buf.WriteString(str.(string))
|
||||
case tokens[q][0][:2] == "%p":
|
||||
// Push a parameter on the stack.
|
||||
index, err := strconv.ParseInt(tokens[q][1], 10, 8)
|
||||
index--
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
}
|
||||
if int(index) >= len(ps.parameters) {
|
||||
return buf.String(), errors.New("Parameters index out of bound")
|
||||
}
|
||||
ps.st.push(ps.parameters[index])
|
||||
case tokens[q][0][:2] == "%P":
|
||||
// Pop a variable from the stack as a dynamic or static variable.
|
||||
val, err := ps.st.pop()
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
}
|
||||
index := tokens[q][2]
|
||||
if len(index) > 1 {
|
||||
errorStr := fmt.Sprintf("%s is not a valid dynamic variables index",
|
||||
index)
|
||||
return buf.String(), errors.New(errorStr)
|
||||
}
|
||||
// Specify either dynamic or static.
|
||||
if index[0] >= 'a' && index[0] <= 'z' {
|
||||
ps.dynamicVar[index[0]] = val
|
||||
} else if index[0] >= 'A' && index[0] <= 'Z' {
|
||||
staticVar[index[0]] = val
|
||||
}
|
||||
case tokens[q][0][:2] == "%g":
|
||||
// Push a variable from the stack as a dynamic or static variable.
|
||||
index := tokens[q][3]
|
||||
if len(index) > 1 {
|
||||
errorStr := fmt.Sprintf("%s is not a valid static variables index",
|
||||
index)
|
||||
return buf.String(), errors.New(errorStr)
|
||||
}
|
||||
var val stacker
|
||||
if index[0] >= 'a' && index[0] <= 'z' {
|
||||
val = ps.dynamicVar[index[0]]
|
||||
} else if index[0] >= 'A' && index[0] <= 'Z' {
|
||||
val = staticVar[index[0]]
|
||||
}
|
||||
ps.st.push(val)
|
||||
case tokens[q][0][:2] == "%'":
|
||||
// Push a character constant.
|
||||
con := tokens[q][4]
|
||||
if len(con) > 1 {
|
||||
errorStr := fmt.Sprintf("%s is not a valid character constant", con)
|
||||
return buf.String(), errors.New(errorStr)
|
||||
}
|
||||
ps.st.push(con[0])
|
||||
case tokens[q][0][:2] == "%{":
|
||||
// Push an integer constant.
|
||||
con, err := strconv.ParseInt(tokens[q][5], 10, 32)
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
}
|
||||
ps.st.push(con)
|
||||
case tokens[q][0][:2] == "%l":
|
||||
// Push the length of the string that is popped from the stack.
|
||||
popStr, err := ps.st.pop()
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
}
|
||||
if _, ok := popStr.(string); !ok {
|
||||
errStr := fmt.Sprintf("Stack head is not a string")
|
||||
return buf.String(), errors.New(errStr)
|
||||
}
|
||||
ps.st.push(len(popStr.(string)))
|
||||
case tokens[q][0][:2] == "%?":
|
||||
// If-then-else construct. First, the whole string is identified and
|
||||
// then inside this substring, we can specify which parts to switch on.
|
||||
ifReg, _ := regexp.Compile("%\\?(.*)%t(.*)%e(.*);|%\\?(.*)%t(.*);")
|
||||
ifTokens := ifReg.FindStringSubmatch(tokens[q][0])
|
||||
var (
|
||||
ifStr string
|
||||
err error
|
||||
)
|
||||
// Parse the if-part to determine if-else.
|
||||
if len(ifTokens[1]) > 0 {
|
||||
ifStr, err = ps.walk(ifTokens[1])
|
||||
} else { // else
|
||||
ifStr, err = ps.walk(ifTokens[4])
|
||||
}
|
||||
// Return any errors
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
} else if len(ifStr) > 0 {
|
||||
// Self-defined limitation, not sure if this is correct, but didn't
|
||||
// seem like it.
|
||||
return buf.String(), errors.New("If-clause cannot print statements")
|
||||
}
|
||||
var thenStr string
|
||||
// Pop the first value that is set by parsing the if-clause.
|
||||
choose, err := ps.st.pop()
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
}
|
||||
// Switch to if or else.
|
||||
if choose.(int) == 0 && len(ifTokens[1]) > 0 {
|
||||
thenStr, err = ps.walk(ifTokens[3])
|
||||
} else if choose.(int) != 0 {
|
||||
if len(ifTokens[1]) > 0 {
|
||||
thenStr, err = ps.walk(ifTokens[2])
|
||||
} else {
|
||||
thenStr, err = ps.walk(ifTokens[5])
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
}
|
||||
buf.WriteString(thenStr)
|
||||
case tokens[q][0][len(tokens[q][0])-1] == 'd': // Fallthrough for printing
|
||||
fallthrough
|
||||
case tokens[q][0][len(tokens[q][0])-1] == 'o': // digits.
|
||||
fallthrough
|
||||
case tokens[q][0][len(tokens[q][0])-1] == 'x':
|
||||
fallthrough
|
||||
case tokens[q][0][len(tokens[q][0])-1] == 'X':
|
||||
fallthrough
|
||||
case tokens[q][0][len(tokens[q][0])-1] == 's':
|
||||
token := tokens[q][0]
|
||||
// Remove the : that comes before a flag.
|
||||
if token[1] == ':' {
|
||||
token = token[:1] + token[2:]
|
||||
}
|
||||
digit, err := ps.st.pop()
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
}
|
||||
// The rest is determined like the normal formatted prints.
|
||||
digitStr := fmt.Sprintf(token, digit.(int))
|
||||
buf.WriteString(digitStr)
|
||||
case tokens[q][0][:2] == "%i":
|
||||
// Increment the parameters by one.
|
||||
if len(ps.parameters) < 2 {
|
||||
return buf.String(), errors.New("Not enough parameters to increment.")
|
||||
}
|
||||
val1, val2 := ps.parameters[0].(int), ps.parameters[1].(int)
|
||||
val1++
|
||||
val2++
|
||||
ps.parameters[0], ps.parameters[1] = val1, val2
|
||||
default:
|
||||
// The rest of the tokens is a special case, where two values are
|
||||
// popped and then operated on by the token that comes after them.
|
||||
op1, err := ps.st.pop()
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
}
|
||||
op2, err := ps.st.pop()
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
}
|
||||
var result stacker
|
||||
switch tokens[q][0][:2] {
|
||||
case "%+":
|
||||
// Addition
|
||||
result = op2.(int) + op1.(int)
|
||||
case "%-":
|
||||
// Subtraction
|
||||
result = op2.(int) - op1.(int)
|
||||
case "%*":
|
||||
// Multiplication
|
||||
result = op2.(int) * op1.(int)
|
||||
case "%/":
|
||||
// Division
|
||||
result = op2.(int) / op1.(int)
|
||||
case "%m":
|
||||
// Modulo
|
||||
result = op2.(int) % op1.(int)
|
||||
case "%&":
|
||||
// Bitwise AND
|
||||
result = op2.(int) & op1.(int)
|
||||
case "%|":
|
||||
// Bitwise OR
|
||||
result = op2.(int) | op1.(int)
|
||||
case "%^":
|
||||
// Bitwise XOR
|
||||
result = op2.(int) ^ op1.(int)
|
||||
case "%=":
|
||||
// Equals
|
||||
result = op2 == op1
|
||||
case "%>":
|
||||
// Greater-than
|
||||
result = op2.(int) > op1.(int)
|
||||
case "%<":
|
||||
// Lesser-than
|
||||
result = op2.(int) < op1.(int)
|
||||
case "%A":
|
||||
// Logical AND
|
||||
result = op2.(bool) && op1.(bool)
|
||||
case "%O":
|
||||
// Logical OR
|
||||
result = op2.(bool) || op1.(bool)
|
||||
case "%!":
|
||||
// Logical complement
|
||||
result = !op1.(bool)
|
||||
case "%~":
|
||||
// Bitwise complement
|
||||
result = ^(op1.(int))
|
||||
}
|
||||
ps.st.push(result)
|
||||
}
|
||||
|
||||
i = indices[q][1] - 1
|
||||
q++
|
||||
} else {
|
||||
// We are not "inside" a token, so just skip until the end or the next
|
||||
// token, and add all characters to the buffer.
|
||||
j := i
|
||||
if q != len(indices) {
|
||||
for !(j >= indices[q][0] && j < indices[q][1]) {
|
||||
j++
|
||||
}
|
||||
} else {
|
||||
j = len(attr)
|
||||
}
|
||||
buf.WriteString(string(attr[i:j]))
|
||||
i = j
|
||||
}
|
||||
}
|
||||
// Return the buffer as a string.
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// Push a stacker-value onto the stack.
|
||||
func (st *stack) push(s stacker) {
|
||||
*st = append(*st, s)
|
||||
}
|
||||
|
||||
// Pop a stacker-value from the stack.
|
||||
func (st *stack) pop() (stacker, error) {
|
||||
if len(*st) == 0 {
|
||||
return nil, errors.New("Stack is empty.")
|
||||
}
|
||||
newStack := make(stack, len(*st)-1)
|
||||
val := (*st)[len(*st)-1]
|
||||
copy(newStack, (*st)[:len(*st)-1])
|
||||
*st = newStack
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// Initialize regexes and the static vars (that don't get changed between
|
||||
// calls.
|
||||
func init() {
|
||||
// Initialize the main regex.
|
||||
expStr := strings.Join(exp[:], "|")
|
||||
regex, _ = regexp.Compile(expStr)
|
||||
// Initialize the static variables.
|
||||
staticVar = make(map[byte]stacker, 26)
|
||||
}
|
23
vendor/github.com/Nvveen/Gotty/types.go
generated
vendored
Normal file
23
vendor/github.com/Nvveen/Gotty/types.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2012 Neal van Veen. All rights reserved.
|
||||
// Usage of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package gotty
|
||||
|
||||
type TermInfo struct {
|
||||
boolAttributes map[string]bool
|
||||
numAttributes map[string]int16
|
||||
strAttributes map[string]string
|
||||
// The various names of the TermInfo file.
|
||||
Names []string
|
||||
}
|
||||
|
||||
type stacker interface {
|
||||
}
|
||||
type stack []stacker
|
||||
|
||||
type parser struct {
|
||||
st stack
|
||||
parameters []stacker
|
||||
dynamicVar map[byte]stacker
|
||||
}
|
479
vendor/github.com/Sirupsen/logrus/README.md
generated
vendored
479
vendor/github.com/Sirupsen/logrus/README.md
generated
vendored
@ -1,479 +0,0 @@
|
||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/Sirupsen/logrus) [](https://godoc.org/github.com/Sirupsen/logrus)
|
||||
|
||||
**Seeing weird case-sensitive problems?** See [this
|
||||
issue](https://github.com/sirupsen/logrus/issues/451#issuecomment-264332021).
|
||||
This change has been reverted. I apologize for causing this. I greatly
|
||||
underestimated the impact this would have. Logrus strives for stability and
|
||||
backwards compatibility and failed to provide that.
|
||||
|
||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
|
||||
yet stable (pre 1.0). Logrus itself is completely stable and has been used in
|
||||
many large deployments. The core API is unlikely to change much but please
|
||||
version control your Logrus to make sure you aren't fetching latest `master` on
|
||||
every build.**
|
||||
|
||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||
plain text):
|
||||
|
||||

|
||||
|
||||
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
|
||||
or Splunk:
|
||||
|
||||
```json
|
||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||
|
||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||
|
||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||
```
|
||||
|
||||
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
||||
attached, the output is compatible with the
|
||||
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
||||
|
||||
```text
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||
exit status 1
|
||||
```
|
||||
|
||||
#### Example
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
```
|
||||
|
||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
|
||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||
want:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Log as JSON instead of the default ASCII formatter.
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
|
||||
// Output to stdout instead of the default stderr
|
||||
// Can be any io.Writer, see below for File example
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
// Only log the warning severity or above.
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
|
||||
// A common pattern is to re-use fields between logging statements by re-using
|
||||
// the logrus.Entry returned from WithFields()
|
||||
contextLogger := log.WithFields(log.Fields{
|
||||
"common": "this is a common field",
|
||||
"other": "I also should be logged always",
|
||||
})
|
||||
|
||||
contextLogger.Info("I'll be logged with common and other field")
|
||||
contextLogger.Info("Me too")
|
||||
}
|
||||
```
|
||||
|
||||
For more advanced usage such as logging to multiple locations from the same
|
||||
application, you can also create an instance of the `logrus` Logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Create a new instance of the logger. You can have any number of instances.
|
||||
var log = logrus.New()
|
||||
|
||||
func main() {
|
||||
// The API for setting attributes is a little different than the package level
|
||||
// exported logger. See Godoc.
|
||||
log.Out = os.Stdout
|
||||
|
||||
// You could set this to any `io.Writer` such as a file
|
||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||
// if err == nil {
|
||||
// log.Out = file
|
||||
// } else {
|
||||
// log.Info("Failed to log to file, using default stderr")
|
||||
// }
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
}
|
||||
```
|
||||
|
||||
#### Fields
|
||||
|
||||
Logrus encourages careful, structured logging though logging fields instead of
|
||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||
to send event %s to topic %s with key %d")`, you should log the much more
|
||||
discoverable:
|
||||
|
||||
```go
|
||||
log.WithFields(log.Fields{
|
||||
"event": event,
|
||||
"topic": topic,
|
||||
"key": key,
|
||||
}).Fatal("Failed to send event")
|
||||
```
|
||||
|
||||
We've found this API forces you to think about logging in a way that produces
|
||||
much more useful logging messages. We've been in countless situations where just
|
||||
a single added field to a log statement that was already there would've saved us
|
||||
hours. The `WithFields` call is optional.
|
||||
|
||||
In general, with Logrus using any of the `printf`-family functions should be
|
||||
seen as a hint you should add a field, however, you can still use the
|
||||
`printf`-family functions with Logrus.
|
||||
|
||||
#### Default Fields
|
||||
|
||||
Often it's helpful to have fields _always_ attached to log statements in an
|
||||
application or parts of one. For example, you may want to always log the
|
||||
`request_id` and `user_ip` in the context of a request. Instead of writing
|
||||
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
|
||||
every line, you can create a `logrus.Entry` to pass around instead:
|
||||
|
||||
```go
|
||||
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
|
||||
requestLogger.Info("something happened on that request") # will log request_id and user_ip
|
||||
requestLogger.Warn("something not great happened")
|
||||
```
|
||||
|
||||
#### Hooks
|
||||
|
||||
You can add hooks for logging levels. For example to send errors to an exception
|
||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||
multiple places simultaneously, e.g. syslog.
|
||||
|
||||
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
||||
`init`:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
|
||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
|
||||
|
||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
if err != nil {
|
||||
log.Error("Unable to connect to local syslog daemon")
|
||||
} else {
|
||||
log.AddHook(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
||||
|
||||
| Hook | Description |
|
||||
| ----- | ----------- |
|
||||
| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
|
||||
| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
|
||||
| [Amazon Kinesis](https://github.com/evalphobia/logrus_kinesis) | Hook for logging to [Amazon Kinesis](https://aws.amazon.com/kinesis/) |
|
||||
| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
|
||||
| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
||||
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
|
||||
| [Discordrus](https://github.com/kz/discordrus) | Hook for logging to [Discord](https://discordapp.com/) |
|
||||
| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
|
||||
| [Firehose](https://github.com/beaubrewer/firehose) | Hook for logging to [Amazon Firehose](https://aws.amazon.com/kinesis/firehose/)
|
||||
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
|
||||
| [Go-Slack](https://github.com/multiplay/go-slack) | Hook for logging to [Slack](https://slack.com) |
|
||||
| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
|
||||
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
|
||||
| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
|
||||
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
|
||||
| [Influxus] (http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB] (http://influxdata.com/) |
|
||||
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
|
||||
| [KafkaLogrus](https://github.com/goibibo/KafkaLogrus) | Hook for logging to kafka |
|
||||
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
|
||||
| [Logentries](https://github.com/jcftang/logentriesrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
||||
| [Logentrus](https://github.com/puddingfactory/logentrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
||||
| [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) |
|
||||
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
||||
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
|
||||
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
|
||||
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
|
||||
| [NATS-Hook](https://github.com/rybit/nats_logrus_hook) | Hook for logging to [NATS](https://nats.io) |
|
||||
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
|
||||
| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
|
||||
| [PostgreSQL](https://github.com/gemnasium/logrus-postgresql-hook) | Send logs to [PostgreSQL](http://postgresql.org) |
|
||||
| [Pushover](https://github.com/toorop/logrus_pushover) | Send error via [Pushover](https://pushover.net) |
|
||||
| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
|
||||
| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) |
|
||||
| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
|
||||
| [Scribe](https://github.com/sagar8192/logrus-scribe-hook) | Hook for logging to [Scribe](https://github.com/facebookarchive/scribe)|
|
||||
| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
|
||||
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
||||
| [Stackdriver](https://github.com/knq/sdhook) | Hook for logging to [Google Stackdriver](https://cloud.google.com/logging/) |
|
||||
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
||||
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
||||
| [TraceView](https://github.com/evalphobia/logrus_appneta) | Hook for logging to [AppNeta TraceView](https://www.appneta.com/products/traceview/) |
|
||||
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
|
||||
| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash |
|
||||
|
||||
#### Level logging
|
||||
|
||||
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
||||
|
||||
```go
|
||||
log.Debug("Useful debugging information.")
|
||||
log.Info("Something noteworthy happened!")
|
||||
log.Warn("You should probably take a look at this.")
|
||||
log.Error("Something failed but I'm not quitting.")
|
||||
// Calls os.Exit(1) after logging
|
||||
log.Fatal("Bye.")
|
||||
// Calls panic() after logging
|
||||
log.Panic("I'm bailing.")
|
||||
```
|
||||
|
||||
You can set the logging level on a `Logger`, then it will only log entries with
|
||||
that severity or anything above it:
|
||||
|
||||
```go
|
||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||
log.SetLevel(log.InfoLevel)
|
||||
```
|
||||
|
||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||
environment if your application has that.
|
||||
|
||||
#### Entries
|
||||
|
||||
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||
automatically added to all logging events:
|
||||
|
||||
1. `time`. The timestamp when the entry was created.
|
||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||
the `AddFields` call. E.g. `Failed to send event.`
|
||||
3. `level`. The logging level. E.g. `info`.
|
||||
|
||||
#### Environments
|
||||
|
||||
Logrus has no notion of environment.
|
||||
|
||||
If you wish for hooks and formatters to only be used in specific environments,
|
||||
you should handle that yourself. For example, if your application has a global
|
||||
variable `Environment`, which is a string representation of the environment you
|
||||
could do:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
init() {
|
||||
// do something here to set environment depending on an environment variable
|
||||
// or command-line flag
|
||||
if Environment == "production" {
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
} else {
|
||||
// The TextFormatter is default, you don't actually have to do this.
|
||||
log.SetFormatter(&log.TextFormatter{})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This configuration is how `logrus` was intended to be used, but JSON in
|
||||
production is mostly only useful if you do log aggregation with tools like
|
||||
Splunk or Logstash.
|
||||
|
||||
#### Formatters
|
||||
|
||||
The built-in logging formatters are:
|
||||
|
||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||
without colors.
|
||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||
field to `true`. To force no colored output even if there is a TTY set the
|
||||
`DisableColors` field to `true`. For Windows, see
|
||||
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
|
||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
|
||||
|
||||
Third party logging formatters:
|
||||
|
||||
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
||||
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
||||
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||
|
||||
You can define your formatter by implementing the `Formatter` interface,
|
||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||
default ones (see Entries section above):
|
||||
|
||||
```go
|
||||
type MyJSONFormatter struct {
|
||||
}
|
||||
|
||||
log.SetFormatter(new(MyJSONFormatter))
|
||||
|
||||
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
// Note this doesn't include Time, Level and Message which are available on
|
||||
// the Entry. Consult `godoc` on information about those fields or read the
|
||||
// source of the official loggers.
|
||||
serialized, err := json.Marshal(entry.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Logger as an `io.Writer`
|
||||
|
||||
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
||||
|
||||
```go
|
||||
w := logger.Writer()
|
||||
defer w.Close()
|
||||
|
||||
srv := http.Server{
|
||||
// create a stdlib log.Logger that writes to
|
||||
// logrus.Logger.
|
||||
ErrorLog: log.New(w, "", 0),
|
||||
}
|
||||
```
|
||||
|
||||
Each line written to that writer will be printed the usual way, using formatters
|
||||
and hooks. The level for those entries is `info`.
|
||||
|
||||
This means that we can override the standard library logger easily:
|
||||
|
||||
```go
|
||||
logger := logrus.New()
|
||||
logger.Formatter = &logrus.JSONFormatter{}
|
||||
|
||||
// Use logrus for standard log output
|
||||
// Note that `log` here references stdlib's log
|
||||
// Not logrus imported under the name `log`.
|
||||
log.SetOutput(logger.Writer())
|
||||
```
|
||||
|
||||
#### Rotation
|
||||
|
||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||
external program (like `logrotate(8)`) that can compress and delete old log
|
||||
entries. It should not be a feature of the application-level logger.
|
||||
|
||||
#### Tools
|
||||
|
||||
| Tool | Description |
|
||||
| ---- | ----------- |
|
||||
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
|
||||
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
||||
|
||||
#### Testing
|
||||
|
||||
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
|
||||
|
||||
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just add the `test` hook
|
||||
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
||||
|
||||
```go
|
||||
logger, hook := NewNullLogger()
|
||||
logger.Error("Hello error")
|
||||
|
||||
assert.Equal(1, len(hook.Entries))
|
||||
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
|
||||
assert.Equal("Hello error", hook.LastEntry().Message)
|
||||
|
||||
hook.Reset()
|
||||
assert.Nil(hook.LastEntry())
|
||||
```
|
||||
|
||||
#### Fatal handlers
|
||||
|
||||
Logrus can register one or more functions that will be called when any `fatal`
|
||||
level message is logged. The registered handlers will be executed before
|
||||
logrus performs a `os.Exit(1)`. This behavior may be helpful if callers need
|
||||
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
|
||||
|
||||
```
|
||||
...
|
||||
handler := func() {
|
||||
// gracefully shutdown something...
|
||||
}
|
||||
logrus.RegisterExitHandler(handler)
|
||||
...
|
||||
```
|
||||
|
||||
#### Thread safety
|
||||
|
||||
By default Logger is protected by mutex for concurrent writes, this mutex is invoked when calling hooks and writing logs.
|
||||
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
|
||||
|
||||
Situation when locking is not needed includes:
|
||||
|
||||
* You have no hooks registered, or hooks calling is already thread-safe.
|
||||
|
||||
* Writing to logger.Out is already thread-safe, for example:
|
||||
|
||||
1) logger.Out is protected by locks.
|
||||
|
||||
2) logger.Out is a os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allow multi-thread/multi-process writing)
|
||||
|
||||
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
27
vendor/github.com/agl/ed25519/LICENSE
generated
vendored
Normal file
27
vendor/github.com/agl/ed25519/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
127
vendor/github.com/agl/ed25519/ed25519.go
generated
vendored
Normal file
127
vendor/github.com/agl/ed25519/ed25519.go
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ed25519 implements the Ed25519 signature algorithm. See
|
||||
// http://ed25519.cr.yp.to/.
|
||||
package ed25519
|
||||
|
||||
// This code is a port of the public domain, "ref10" implementation of ed25519
|
||||
// from SUPERCOP.
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"crypto/subtle"
|
||||
"io"
|
||||
|
||||
"github.com/agl/ed25519/edwards25519"
|
||||
)
|
||||
|
||||
const (
|
||||
PublicKeySize = 32
|
||||
PrivateKeySize = 64
|
||||
SignatureSize = 64
|
||||
)
|
||||
|
||||
// GenerateKey generates a public/private key pair using randomness from rand.
|
||||
func GenerateKey(rand io.Reader) (publicKey *[PublicKeySize]byte, privateKey *[PrivateKeySize]byte, err error) {
|
||||
privateKey = new([64]byte)
|
||||
publicKey = new([32]byte)
|
||||
_, err = io.ReadFull(rand, privateKey[:32])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
h := sha512.New()
|
||||
h.Write(privateKey[:32])
|
||||
digest := h.Sum(nil)
|
||||
|
||||
digest[0] &= 248
|
||||
digest[31] &= 127
|
||||
digest[31] |= 64
|
||||
|
||||
var A edwards25519.ExtendedGroupElement
|
||||
var hBytes [32]byte
|
||||
copy(hBytes[:], digest)
|
||||
edwards25519.GeScalarMultBase(&A, &hBytes)
|
||||
A.ToBytes(publicKey)
|
||||
|
||||
copy(privateKey[32:], publicKey[:])
|
||||
return
|
||||
}
|
||||
|
||||
// Sign signs the message with privateKey and returns a signature.
|
||||
func Sign(privateKey *[PrivateKeySize]byte, message []byte) *[SignatureSize]byte {
|
||||
h := sha512.New()
|
||||
h.Write(privateKey[:32])
|
||||
|
||||
var digest1, messageDigest, hramDigest [64]byte
|
||||
var expandedSecretKey [32]byte
|
||||
h.Sum(digest1[:0])
|
||||
copy(expandedSecretKey[:], digest1[:])
|
||||
expandedSecretKey[0] &= 248
|
||||
expandedSecretKey[31] &= 63
|
||||
expandedSecretKey[31] |= 64
|
||||
|
||||
h.Reset()
|
||||
h.Write(digest1[32:])
|
||||
h.Write(message)
|
||||
h.Sum(messageDigest[:0])
|
||||
|
||||
var messageDigestReduced [32]byte
|
||||
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
|
||||
var R edwards25519.ExtendedGroupElement
|
||||
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
|
||||
|
||||
var encodedR [32]byte
|
||||
R.ToBytes(&encodedR)
|
||||
|
||||
h.Reset()
|
||||
h.Write(encodedR[:])
|
||||
h.Write(privateKey[32:])
|
||||
h.Write(message)
|
||||
h.Sum(hramDigest[:0])
|
||||
var hramDigestReduced [32]byte
|
||||
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
|
||||
|
||||
var s [32]byte
|
||||
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
|
||||
|
||||
signature := new([64]byte)
|
||||
copy(signature[:], encodedR[:])
|
||||
copy(signature[32:], s[:])
|
||||
return signature
|
||||
}
|
||||
|
||||
// Verify returns true iff sig is a valid signature of message by publicKey.
|
||||
func Verify(publicKey *[PublicKeySize]byte, message []byte, sig *[SignatureSize]byte) bool {
|
||||
if sig[63]&224 != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var A edwards25519.ExtendedGroupElement
|
||||
if !A.FromBytes(publicKey) {
|
||||
return false
|
||||
}
|
||||
edwards25519.FeNeg(&A.X, &A.X)
|
||||
edwards25519.FeNeg(&A.T, &A.T)
|
||||
|
||||
h := sha512.New()
|
||||
h.Write(sig[:32])
|
||||
h.Write(publicKey[:])
|
||||
h.Write(message)
|
||||
var digest [64]byte
|
||||
h.Sum(digest[:0])
|
||||
|
||||
var hReduced [32]byte
|
||||
edwards25519.ScReduce(&hReduced, &digest)
|
||||
|
||||
var R edwards25519.ProjectiveGroupElement
|
||||
var b [32]byte
|
||||
copy(b[:], sig[32:])
|
||||
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b)
|
||||
|
||||
var checkR [32]byte
|
||||
R.ToBytes(&checkR)
|
||||
return subtle.ConstantTimeCompare(sig[:32], checkR[:]) == 1
|
||||
}
|
1411
vendor/github.com/agl/ed25519/edwards25519/const.go
generated
vendored
Normal file
1411
vendor/github.com/agl/ed25519/edwards25519/const.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1773
vendor/github.com/agl/ed25519/edwards25519/edwards25519.go
generated
vendored
Normal file
1773
vendor/github.com/agl/ed25519/edwards25519/edwards25519.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
131
vendor/github.com/docker/distribution/README.md
generated
vendored
131
vendor/github.com/docker/distribution/README.md
generated
vendored
@ -1,131 +0,0 @@
|
||||
# Distribution
|
||||
|
||||
The Docker toolset to pack, ship, store, and deliver content.
|
||||
|
||||
This repository's main product is the Docker Registry 2.0 implementation
|
||||
for storing and distributing Docker images. It supersedes the
|
||||
[docker/docker-registry](https://github.com/docker/docker-registry)
|
||||
project with a new API design, focused around security and performance.
|
||||
|
||||
<img src="https://www.docker.com/sites/default/files/oyster-registry-3.png" width=200px/>
|
||||
|
||||
[](https://circleci.com/gh/docker/distribution/tree/master)
|
||||
[](https://godoc.org/github.com/docker/distribution)
|
||||
|
||||
This repository contains the following components:
|
||||
|
||||
|**Component** |Description |
|
||||
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **registry** | An implementation of the [Docker Registry HTTP API V2](docs/spec/api.md) for use with docker 1.6+. |
|
||||
| **libraries** | A rich set of libraries for interacting with distribution components. Please see [godoc](https://godoc.org/github.com/docker/distribution) for details. **Note**: These libraries are **unstable**. |
|
||||
| **specifications** | _Distribution_ related specifications are available in [docs/spec](docs/spec) |
|
||||
| **documentation** | Docker's full documentation set is available at [docs.docker.com](https://docs.docker.com). This repository [contains the subset](docs/) related just to the registry. |
|
||||
|
||||
### How does this integrate with Docker engine?
|
||||
|
||||
This project should provide an implementation to a V2 API for use in the [Docker
|
||||
core project](https://github.com/docker/docker). The API should be embeddable
|
||||
and simplify the process of securely pulling and pushing content from `docker`
|
||||
daemons.
|
||||
|
||||
### What are the long term goals of the Distribution project?
|
||||
|
||||
The _Distribution_ project has the further long term goal of providing a
|
||||
secure tool chain for distributing content. The specifications, APIs and tools
|
||||
should be as useful with Docker as they are without.
|
||||
|
||||
Our goal is to design a professional grade and extensible content distribution
|
||||
system that allow users to:
|
||||
|
||||
* Enjoy an efficient, secured and reliable way to store, manage, package and
|
||||
exchange content
|
||||
* Hack/roll their own on top of healthy open-source components
|
||||
* Implement their own home made solution through good specs, and solid
|
||||
extensions mechanism.
|
||||
|
||||
## More about Registry 2.0
|
||||
|
||||
The new registry implementation provides the following benefits:
|
||||
|
||||
- faster push and pull
|
||||
- new, more efficient implementation
|
||||
- simplified deployment
|
||||
- pluggable storage backend
|
||||
- webhook notifications
|
||||
|
||||
For information on upcoming functionality, please see [ROADMAP.md](ROADMAP.md).
|
||||
|
||||
### Who needs to deploy a registry?
|
||||
|
||||
By default, Docker users pull images from Docker's public registry instance.
|
||||
[Installing Docker](https://docs.docker.com/engine/installation/) gives users this
|
||||
ability. Users can also push images to a repository on Docker's public registry,
|
||||
if they have a [Docker Hub](https://hub.docker.com/) account.
|
||||
|
||||
For some users and even companies, this default behavior is sufficient. For
|
||||
others, it is not.
|
||||
|
||||
For example, users with their own software products may want to maintain a
|
||||
registry for private, company images. Also, you may wish to deploy your own
|
||||
image repository for images used to test or in continuous integration. For these
|
||||
use cases and others, [deploying your own registry instance](https://github.com/docker/docker.github.io/blob/master/registry/deploying.md)
|
||||
may be the better choice.
|
||||
|
||||
### Migration to Registry 2.0
|
||||
|
||||
For those who have previously deployed their own registry based on the Registry
|
||||
1.0 implementation and wish to deploy a Registry 2.0 while retaining images,
|
||||
data migration is required. A tool to assist with migration efforts has been
|
||||
created. For more information see [docker/migrator]
|
||||
(https://github.com/docker/migrator).
|
||||
|
||||
## Contribute
|
||||
|
||||
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute
|
||||
issues, fixes, and patches to this project. If you are contributing code, see
|
||||
the instructions for [building a development environment](BUILDING.md).
|
||||
|
||||
## Support
|
||||
|
||||
If any issues are encountered while using the _Distribution_ project, several
|
||||
avenues are available for support:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th align="left">
|
||||
IRC
|
||||
</th>
|
||||
<td>
|
||||
#docker-distribution on FreeNode
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th align="left">
|
||||
Issue Tracker
|
||||
</th>
|
||||
<td>
|
||||
github.com/docker/distribution/issues
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th align="left">
|
||||
Google Groups
|
||||
</th>
|
||||
<td>
|
||||
https://groups.google.com/a/dockerproject.org/forum/#!forum/distribution
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th align="left">
|
||||
Mailing List
|
||||
</th>
|
||||
<td>
|
||||
docker@dockerproject.org
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This project is distributed under [Apache License, Version 2.0](LICENSE).
|
257
vendor/github.com/docker/distribution/blobs.go
generated
vendored
Normal file
257
vendor/github.com/docker/distribution/blobs.go
generated
vendored
Normal file
@ -0,0 +1,257 @@
|
||||
package distribution
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrBlobExists returned when blob already exists
|
||||
ErrBlobExists = errors.New("blob exists")
|
||||
|
||||
// ErrBlobDigestUnsupported when blob digest is an unsupported version.
|
||||
ErrBlobDigestUnsupported = errors.New("unsupported blob digest")
|
||||
|
||||
// ErrBlobUnknown when blob is not found.
|
||||
ErrBlobUnknown = errors.New("unknown blob")
|
||||
|
||||
// ErrBlobUploadUnknown returned when upload is not found.
|
||||
ErrBlobUploadUnknown = errors.New("blob upload unknown")
|
||||
|
||||
// ErrBlobInvalidLength returned when the blob has an expected length on
|
||||
// commit, meaning mismatched with the descriptor or an invalid value.
|
||||
ErrBlobInvalidLength = errors.New("blob invalid length")
|
||||
)
|
||||
|
||||
// ErrBlobInvalidDigest returned when digest check fails.
|
||||
type ErrBlobInvalidDigest struct {
|
||||
Digest digest.Digest
|
||||
Reason error
|
||||
}
|
||||
|
||||
func (err ErrBlobInvalidDigest) Error() string {
|
||||
return fmt.Sprintf("invalid digest for referenced layer: %v, %v",
|
||||
err.Digest, err.Reason)
|
||||
}
|
||||
|
||||
// ErrBlobMounted returned when a blob is mounted from another repository
|
||||
// instead of initiating an upload session.
|
||||
type ErrBlobMounted struct {
|
||||
From reference.Canonical
|
||||
Descriptor Descriptor
|
||||
}
|
||||
|
||||
func (err ErrBlobMounted) Error() string {
|
||||
return fmt.Sprintf("blob mounted from: %v to: %v",
|
||||
err.From, err.Descriptor)
|
||||
}
|
||||
|
||||
// Descriptor describes targeted content. Used in conjunction with a blob
|
||||
// store, a descriptor can be used to fetch, store and target any kind of
|
||||
// blob. The struct also describes the wire protocol format. Fields should
|
||||
// only be added but never changed.
|
||||
type Descriptor struct {
|
||||
// MediaType describe the type of the content. All text based formats are
|
||||
// encoded as utf-8.
|
||||
MediaType string `json:"mediaType,omitempty"`
|
||||
|
||||
// Size in bytes of content.
|
||||
Size int64 `json:"size,omitempty"`
|
||||
|
||||
// Digest uniquely identifies the content. A byte stream can be verified
|
||||
// against against this digest.
|
||||
Digest digest.Digest `json:"digest,omitempty"`
|
||||
|
||||
// URLs contains the source URLs of this content.
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
|
||||
// NOTE: Before adding a field here, please ensure that all
|
||||
// other options have been exhausted. Much of the type relationships
|
||||
// depend on the simplicity of this type.
|
||||
}
|
||||
|
||||
// Descriptor returns the descriptor, to make it satisfy the Describable
|
||||
// interface. Note that implementations of Describable are generally objects
|
||||
// which can be described, not simply descriptors; this exception is in place
|
||||
// to make it more convenient to pass actual descriptors to functions that
|
||||
// expect Describable objects.
|
||||
func (d Descriptor) Descriptor() Descriptor {
|
||||
return d
|
||||
}
|
||||
|
||||
// BlobStatter makes blob descriptors available by digest. The service may
|
||||
// provide a descriptor of a different digest if the provided digest is not
|
||||
// canonical.
|
||||
type BlobStatter interface {
|
||||
// Stat provides metadata about a blob identified by the digest. If the
|
||||
// blob is unknown to the describer, ErrBlobUnknown will be returned.
|
||||
Stat(ctx context.Context, dgst digest.Digest) (Descriptor, error)
|
||||
}
|
||||
|
||||
// BlobDeleter enables deleting blobs from storage.
|
||||
type BlobDeleter interface {
|
||||
Delete(ctx context.Context, dgst digest.Digest) error
|
||||
}
|
||||
|
||||
// BlobEnumerator enables iterating over blobs from storage
|
||||
type BlobEnumerator interface {
|
||||
Enumerate(ctx context.Context, ingester func(dgst digest.Digest) error) error
|
||||
}
|
||||
|
||||
// BlobDescriptorService manages metadata about a blob by digest. Most
|
||||
// implementations will not expose such an interface explicitly. Such mappings
|
||||
// should be maintained by interacting with the BlobIngester. Hence, this is
|
||||
// left off of BlobService and BlobStore.
|
||||
type BlobDescriptorService interface {
|
||||
BlobStatter
|
||||
|
||||
// SetDescriptor assigns the descriptor to the digest. The provided digest and
|
||||
// the digest in the descriptor must map to identical content but they may
|
||||
// differ on their algorithm. The descriptor must have the canonical
|
||||
// digest of the content and the digest algorithm must match the
|
||||
// annotators canonical algorithm.
|
||||
//
|
||||
// Such a facility can be used to map blobs between digest domains, with
|
||||
// the restriction that the algorithm of the descriptor must match the
|
||||
// canonical algorithm (ie sha256) of the annotator.
|
||||
SetDescriptor(ctx context.Context, dgst digest.Digest, desc Descriptor) error
|
||||
|
||||
// Clear enables descriptors to be unlinked
|
||||
Clear(ctx context.Context, dgst digest.Digest) error
|
||||
}
|
||||
|
||||
// BlobDescriptorServiceFactory creates middleware for BlobDescriptorService.
|
||||
type BlobDescriptorServiceFactory interface {
|
||||
BlobAccessController(svc BlobDescriptorService) BlobDescriptorService
|
||||
}
|
||||
|
||||
// ReadSeekCloser is the primary reader type for blob data, combining
|
||||
// io.ReadSeeker with io.Closer.
|
||||
type ReadSeekCloser interface {
|
||||
io.ReadSeeker
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// BlobProvider describes operations for getting blob data.
|
||||
type BlobProvider interface {
|
||||
// Get returns the entire blob identified by digest along with the descriptor.
|
||||
Get(ctx context.Context, dgst digest.Digest) ([]byte, error)
|
||||
|
||||
// Open provides a ReadSeekCloser to the blob identified by the provided
|
||||
// descriptor. If the blob is not known to the service, an error will be
|
||||
// returned.
|
||||
Open(ctx context.Context, dgst digest.Digest) (ReadSeekCloser, error)
|
||||
}
|
||||
|
||||
// BlobServer can serve blobs via http.
|
||||
type BlobServer interface {
|
||||
// ServeBlob attempts to serve the blob, identifed by dgst, via http. The
|
||||
// service may decide to redirect the client elsewhere or serve the data
|
||||
// directly.
|
||||
//
|
||||
// This handler only issues successful responses, such as 2xx or 3xx,
|
||||
// meaning it serves data or issues a redirect. If the blob is not
|
||||
// available, an error will be returned and the caller may still issue a
|
||||
// response.
|
||||
//
|
||||
// The implementation may serve the same blob from a different digest
|
||||
// domain. The appropriate headers will be set for the blob, unless they
|
||||
// have already been set by the caller.
|
||||
ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error
|
||||
}
|
||||
|
||||
// BlobIngester ingests blob data.
|
||||
type BlobIngester interface {
|
||||
// Put inserts the content p into the blob service, returning a descriptor
|
||||
// or an error.
|
||||
Put(ctx context.Context, mediaType string, p []byte) (Descriptor, error)
|
||||
|
||||
// Create allocates a new blob writer to add a blob to this service. The
|
||||
// returned handle can be written to and later resumed using an opaque
|
||||
// identifier. With this approach, one can Close and Resume a BlobWriter
|
||||
// multiple times until the BlobWriter is committed or cancelled.
|
||||
Create(ctx context.Context, options ...BlobCreateOption) (BlobWriter, error)
|
||||
|
||||
// Resume attempts to resume a write to a blob, identified by an id.
|
||||
Resume(ctx context.Context, id string) (BlobWriter, error)
|
||||
}
|
||||
|
||||
// BlobCreateOption is a general extensible function argument for blob creation
|
||||
// methods. A BlobIngester may choose to honor any or none of the given
|
||||
// BlobCreateOptions, which can be specific to the implementation of the
|
||||
// BlobIngester receiving them.
|
||||
// TODO (brianbland): unify this with ManifestServiceOption in the future
|
||||
type BlobCreateOption interface {
|
||||
Apply(interface{}) error
|
||||
}
|
||||
|
||||
// CreateOptions is a collection of blob creation modifiers relevant to general
|
||||
// blob storage intended to be configured by the BlobCreateOption.Apply method.
|
||||
type CreateOptions struct {
|
||||
Mount struct {
|
||||
ShouldMount bool
|
||||
From reference.Canonical
|
||||
// Stat allows to pass precalculated descriptor to link and return.
|
||||
// Blob access check will be skipped if set.
|
||||
Stat *Descriptor
|
||||
}
|
||||
}
|
||||
|
||||
// BlobWriter provides a handle for inserting data into a blob store.
|
||||
// Instances should be obtained from BlobWriteService.Writer and
|
||||
// BlobWriteService.Resume. If supported by the store, a writer can be
|
||||
// recovered with the id.
|
||||
type BlobWriter interface {
|
||||
io.WriteCloser
|
||||
io.ReaderFrom
|
||||
|
||||
// Size returns the number of bytes written to this blob.
|
||||
Size() int64
|
||||
|
||||
// ID returns the identifier for this writer. The ID can be used with the
|
||||
// Blob service to later resume the write.
|
||||
ID() string
|
||||
|
||||
// StartedAt returns the time this blob write was started.
|
||||
StartedAt() time.Time
|
||||
|
||||
// Commit completes the blob writer process. The content is verified
|
||||
// against the provided provisional descriptor, which may result in an
|
||||
// error. Depending on the implementation, written data may be validated
|
||||
// against the provisional descriptor fields. If MediaType is not present,
|
||||
// the implementation may reject the commit or assign "application/octet-
|
||||
// stream" to the blob. The returned descriptor may have a different
|
||||
// digest depending on the blob store, referred to as the canonical
|
||||
// descriptor.
|
||||
Commit(ctx context.Context, provisional Descriptor) (canonical Descriptor, err error)
|
||||
|
||||
// Cancel ends the blob write without storing any data and frees any
|
||||
// associated resources. Any data written thus far will be lost. Cancel
|
||||
// implementations should allow multiple calls even after a commit that
|
||||
// result in a no-op. This allows use of Cancel in a defer statement,
|
||||
// increasing the assurance that it is correctly called.
|
||||
Cancel(ctx context.Context) error
|
||||
}
|
||||
|
||||
// BlobService combines the operations to access, read and write blobs. This
|
||||
// can be used to describe remote blob services.
|
||||
type BlobService interface {
|
||||
BlobStatter
|
||||
BlobProvider
|
||||
BlobIngester
|
||||
}
|
||||
|
||||
// BlobStore represent the entire suite of blob related operations. Such an
|
||||
// implementation can access, read, write, delete and serve blobs.
|
||||
type BlobStore interface {
|
||||
BlobService
|
||||
BlobServer
|
||||
BlobDeleter
|
||||
}
|
85
vendor/github.com/docker/distribution/context/context.go
generated
vendored
Normal file
85
vendor/github.com/docker/distribution/context/context.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/docker/distribution/uuid"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Context is a copy of Context from the golang.org/x/net/context package.
|
||||
type Context interface {
|
||||
context.Context
|
||||
}
|
||||
|
||||
// instanceContext is a context that provides only an instance id. It is
|
||||
// provided as the main background context.
|
||||
type instanceContext struct {
|
||||
Context
|
||||
id string // id of context, logged as "instance.id"
|
||||
once sync.Once // once protect generation of the id
|
||||
}
|
||||
|
||||
func (ic *instanceContext) Value(key interface{}) interface{} {
|
||||
if key == "instance.id" {
|
||||
ic.once.Do(func() {
|
||||
// We want to lazy initialize the UUID such that we don't
|
||||
// call a random generator from the package initialization
|
||||
// code. For various reasons random could not be available
|
||||
// https://github.com/docker/distribution/issues/782
|
||||
ic.id = uuid.Generate().String()
|
||||
})
|
||||
return ic.id
|
||||
}
|
||||
|
||||
return ic.Context.Value(key)
|
||||
}
|
||||
|
||||
var background = &instanceContext{
|
||||
Context: context.Background(),
|
||||
}
|
||||
|
||||
// Background returns a non-nil, empty Context. The background context
|
||||
// provides a single key, "instance.id" that is globally unique to the
|
||||
// process.
|
||||
func Background() Context {
|
||||
return background
|
||||
}
|
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val. Use context Values only for request-scoped data that transits processes
|
||||
// and APIs, not for passing optional parameters to functions.
|
||||
func WithValue(parent Context, key, val interface{}) Context {
|
||||
return context.WithValue(parent, key, val)
|
||||
}
|
||||
|
||||
// stringMapContext is a simple context implementation that checks a map for a
|
||||
// key, falling back to a parent if not present.
|
||||
type stringMapContext struct {
|
||||
context.Context
|
||||
m map[string]interface{}
|
||||
}
|
||||
|
||||
// WithValues returns a context that proxies lookups through a map. Only
|
||||
// supports string keys.
|
||||
func WithValues(ctx context.Context, m map[string]interface{}) context.Context {
|
||||
mo := make(map[string]interface{}, len(m)) // make our own copy.
|
||||
for k, v := range m {
|
||||
mo[k] = v
|
||||
}
|
||||
|
||||
return stringMapContext{
|
||||
Context: ctx,
|
||||
m: mo,
|
||||
}
|
||||
}
|
||||
|
||||
func (smc stringMapContext) Value(key interface{}) interface{} {
|
||||
if ks, ok := key.(string); ok {
|
||||
if v, ok := smc.m[ks]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return smc.Context.Value(key)
|
||||
}
|
89
vendor/github.com/docker/distribution/context/doc.go
generated
vendored
Normal file
89
vendor/github.com/docker/distribution/context/doc.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
// Package context provides several utilities for working with
|
||||
// golang.org/x/net/context in http requests. Primarily, the focus is on
|
||||
// logging relevant request information but this package is not limited to
|
||||
// that purpose.
|
||||
//
|
||||
// The easiest way to get started is to get the background context:
|
||||
//
|
||||
// ctx := context.Background()
|
||||
//
|
||||
// The returned context should be passed around your application and be the
|
||||
// root of all other context instances. If the application has a version, this
|
||||
// line should be called before anything else:
|
||||
//
|
||||
// ctx := context.WithVersion(context.Background(), version)
|
||||
//
|
||||
// The above will store the version in the context and will be available to
|
||||
// the logger.
|
||||
//
|
||||
// Logging
|
||||
//
|
||||
// The most useful aspect of this package is GetLogger. This function takes
|
||||
// any context.Context interface and returns the current logger from the
|
||||
// context. Canonical usage looks like this:
|
||||
//
|
||||
// GetLogger(ctx).Infof("something interesting happened")
|
||||
//
|
||||
// GetLogger also takes optional key arguments. The keys will be looked up in
|
||||
// the context and reported with the logger. The following example would
|
||||
// return a logger that prints the version with each log message:
|
||||
//
|
||||
// ctx := context.Context(context.Background(), "version", version)
|
||||
// GetLogger(ctx, "version").Infof("this log message has a version field")
|
||||
//
|
||||
// The above would print out a log message like this:
|
||||
//
|
||||
// INFO[0000] this log message has a version field version=v2.0.0-alpha.2.m
|
||||
//
|
||||
// When used with WithLogger, we gain the ability to decorate the context with
|
||||
// loggers that have information from disparate parts of the call stack.
|
||||
// Following from the version example, we can build a new context with the
|
||||
// configured logger such that we always print the version field:
|
||||
//
|
||||
// ctx = WithLogger(ctx, GetLogger(ctx, "version"))
|
||||
//
|
||||
// Since the logger has been pushed to the context, we can now get the version
|
||||
// field for free with our log messages. Future calls to GetLogger on the new
|
||||
// context will have the version field:
|
||||
//
|
||||
// GetLogger(ctx).Infof("this log message has a version field")
|
||||
//
|
||||
// This becomes more powerful when we start stacking loggers. Let's say we
|
||||
// have the version logger from above but also want a request id. Using the
|
||||
// context above, in our request scoped function, we place another logger in
|
||||
// the context:
|
||||
//
|
||||
// ctx = context.WithValue(ctx, "http.request.id", "unique id") // called when building request context
|
||||
// ctx = WithLogger(ctx, GetLogger(ctx, "http.request.id"))
|
||||
//
|
||||
// When GetLogger is called on the new context, "http.request.id" will be
|
||||
// included as a logger field, along with the original "version" field:
|
||||
//
|
||||
// INFO[0000] this log message has a version field http.request.id=unique id version=v2.0.0-alpha.2.m
|
||||
//
|
||||
// Note that this only affects the new context, the previous context, with the
|
||||
// version field, can be used independently. Put another way, the new logger,
|
||||
// added to the request context, is unique to that context and can have
|
||||
// request scoped varaibles.
|
||||
//
|
||||
// HTTP Requests
|
||||
//
|
||||
// This package also contains several methods for working with http requests.
|
||||
// The concepts are very similar to those described above. We simply place the
|
||||
// request in the context using WithRequest. This makes the request variables
|
||||
// available. GetRequestLogger can then be called to get request specific
|
||||
// variables in a log line:
|
||||
//
|
||||
// ctx = WithRequest(ctx, req)
|
||||
// GetRequestLogger(ctx).Infof("request variables")
|
||||
//
|
||||
// Like above, if we want to include the request data in all log messages in
|
||||
// the context, we push the logger to a new context and use that one:
|
||||
//
|
||||
// ctx = WithLogger(ctx, GetRequestLogger(ctx))
|
||||
//
|
||||
// The concept is fairly powerful and ensures that calls throughout the stack
|
||||
// can be traced in log messages. Using the fields like "http.request.id", one
|
||||
// can analyze call flow for a particular request with a simple grep of the
|
||||
// logs.
|
||||
package context
|
366
vendor/github.com/docker/distribution/context/http.go
generated
vendored
Normal file
366
vendor/github.com/docker/distribution/context/http.go
generated
vendored
Normal file
@ -0,0 +1,366 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Common errors used with this package.
|
||||
var (
|
||||
ErrNoRequestContext = errors.New("no http request in context")
|
||||
ErrNoResponseWriterContext = errors.New("no http response in context")
|
||||
)
|
||||
|
||||
func parseIP(ipStr string) net.IP {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
log.Warnf("invalid remote IP address: %q", ipStr)
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// RemoteAddr extracts the remote address of the request, taking into
|
||||
// account proxy headers.
|
||||
func RemoteAddr(r *http.Request) string {
|
||||
if prior := r.Header.Get("X-Forwarded-For"); prior != "" {
|
||||
proxies := strings.Split(prior, ",")
|
||||
if len(proxies) > 0 {
|
||||
remoteAddr := strings.Trim(proxies[0], " ")
|
||||
if parseIP(remoteAddr) != nil {
|
||||
return remoteAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
// X-Real-Ip is less supported, but worth checking in the
|
||||
// absence of X-Forwarded-For
|
||||
if realIP := r.Header.Get("X-Real-Ip"); realIP != "" {
|
||||
if parseIP(realIP) != nil {
|
||||
return realIP
|
||||
}
|
||||
}
|
||||
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
// RemoteIP extracts the remote IP of the request, taking into
|
||||
// account proxy headers.
|
||||
func RemoteIP(r *http.Request) string {
|
||||
addr := RemoteAddr(r)
|
||||
|
||||
// Try parsing it as "IP:port"
|
||||
if ip, _, err := net.SplitHostPort(addr); err == nil {
|
||||
return ip
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
// WithRequest places the request on the context. The context of the request
|
||||
// is assigned a unique id, available at "http.request.id". The request itself
|
||||
// is available at "http.request". Other common attributes are available under
|
||||
// the prefix "http.request.". If a request is already present on the context,
|
||||
// this method will panic.
|
||||
func WithRequest(ctx Context, r *http.Request) Context {
|
||||
if ctx.Value("http.request") != nil {
|
||||
// NOTE(stevvooe): This needs to be considered a programming error. It
|
||||
// is unlikely that we'd want to have more than one request in
|
||||
// context.
|
||||
panic("only one request per context")
|
||||
}
|
||||
|
||||
return &httpRequestContext{
|
||||
Context: ctx,
|
||||
startedAt: time.Now(),
|
||||
id: uuid.Generate().String(),
|
||||
r: r,
|
||||
}
|
||||
}
|
||||
|
||||
// GetRequest returns the http request in the given context. Returns
|
||||
// ErrNoRequestContext if the context does not have an http request associated
|
||||
// with it.
|
||||
func GetRequest(ctx Context) (*http.Request, error) {
|
||||
if r, ok := ctx.Value("http.request").(*http.Request); r != nil && ok {
|
||||
return r, nil
|
||||
}
|
||||
return nil, ErrNoRequestContext
|
||||
}
|
||||
|
||||
// GetRequestID attempts to resolve the current request id, if possible. An
|
||||
// error is return if it is not available on the context.
|
||||
func GetRequestID(ctx Context) string {
|
||||
return GetStringValue(ctx, "http.request.id")
|
||||
}
|
||||
|
||||
// WithResponseWriter returns a new context and response writer that makes
|
||||
// interesting response statistics available within the context.
|
||||
func WithResponseWriter(ctx Context, w http.ResponseWriter) (Context, http.ResponseWriter) {
|
||||
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
||||
irwCN := &instrumentedResponseWriterCN{
|
||||
instrumentedResponseWriter: instrumentedResponseWriter{
|
||||
ResponseWriter: w,
|
||||
Context: ctx,
|
||||
},
|
||||
CloseNotifier: closeNotifier,
|
||||
}
|
||||
|
||||
return irwCN, irwCN
|
||||
}
|
||||
|
||||
irw := instrumentedResponseWriter{
|
||||
ResponseWriter: w,
|
||||
Context: ctx,
|
||||
}
|
||||
return &irw, &irw
|
||||
}
|
||||
|
||||
// GetResponseWriter returns the http.ResponseWriter from the provided
|
||||
// context. If not present, ErrNoResponseWriterContext is returned. The
|
||||
// returned instance provides instrumentation in the context.
|
||||
func GetResponseWriter(ctx Context) (http.ResponseWriter, error) {
|
||||
v := ctx.Value("http.response")
|
||||
|
||||
rw, ok := v.(http.ResponseWriter)
|
||||
if !ok || rw == nil {
|
||||
return nil, ErrNoResponseWriterContext
|
||||
}
|
||||
|
||||
return rw, nil
|
||||
}
|
||||
|
||||
// getVarsFromRequest let's us change request vars implementation for testing
|
||||
// and maybe future changes.
|
||||
var getVarsFromRequest = mux.Vars
|
||||
|
||||
// WithVars extracts gorilla/mux vars and makes them available on the returned
|
||||
// context. Variables are available at keys with the prefix "vars.". For
|
||||
// example, if looking for the variable "name", it can be accessed as
|
||||
// "vars.name". Implementations that are accessing values need not know that
|
||||
// the underlying context is implemented with gorilla/mux vars.
|
||||
func WithVars(ctx Context, r *http.Request) Context {
|
||||
return &muxVarsContext{
|
||||
Context: ctx,
|
||||
vars: getVarsFromRequest(r),
|
||||
}
|
||||
}
|
||||
|
||||
// GetRequestLogger returns a logger that contains fields from the request in
|
||||
// the current context. If the request is not available in the context, no
|
||||
// fields will display. Request loggers can safely be pushed onto the context.
|
||||
func GetRequestLogger(ctx Context) Logger {
|
||||
return GetLogger(ctx,
|
||||
"http.request.id",
|
||||
"http.request.method",
|
||||
"http.request.host",
|
||||
"http.request.uri",
|
||||
"http.request.referer",
|
||||
"http.request.useragent",
|
||||
"http.request.remoteaddr",
|
||||
"http.request.contenttype")
|
||||
}
|
||||
|
||||
// GetResponseLogger reads the current response stats and builds a logger.
|
||||
// Because the values are read at call time, pushing a logger returned from
|
||||
// this function on the context will lead to missing or invalid data. Only
|
||||
// call this at the end of a request, after the response has been written.
|
||||
func GetResponseLogger(ctx Context) Logger {
|
||||
l := getLogrusLogger(ctx,
|
||||
"http.response.written",
|
||||
"http.response.status",
|
||||
"http.response.contenttype")
|
||||
|
||||
duration := Since(ctx, "http.request.startedat")
|
||||
|
||||
if duration > 0 {
|
||||
l = l.WithField("http.response.duration", duration.String())
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// httpRequestContext makes information about a request available to context.
|
||||
type httpRequestContext struct {
|
||||
Context
|
||||
|
||||
startedAt time.Time
|
||||
id string
|
||||
r *http.Request
|
||||
}
|
||||
|
||||
// Value returns a keyed element of the request for use in the context. To get
|
||||
// the request itself, query "request". For other components, access them as
|
||||
// "request.<component>". For example, r.RequestURI
|
||||
func (ctx *httpRequestContext) Value(key interface{}) interface{} {
|
||||
if keyStr, ok := key.(string); ok {
|
||||
if keyStr == "http.request" {
|
||||
return ctx.r
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(keyStr, "http.request.") {
|
||||
goto fallback
|
||||
}
|
||||
|
||||
parts := strings.Split(keyStr, ".")
|
||||
|
||||
if len(parts) != 3 {
|
||||
goto fallback
|
||||
}
|
||||
|
||||
switch parts[2] {
|
||||
case "uri":
|
||||
return ctx.r.RequestURI
|
||||
case "remoteaddr":
|
||||
return RemoteAddr(ctx.r)
|
||||
case "method":
|
||||
return ctx.r.Method
|
||||
case "host":
|
||||
return ctx.r.Host
|
||||
case "referer":
|
||||
referer := ctx.r.Referer()
|
||||
if referer != "" {
|
||||
return referer
|
||||
}
|
||||
case "useragent":
|
||||
return ctx.r.UserAgent()
|
||||
case "id":
|
||||
return ctx.id
|
||||
case "startedat":
|
||||
return ctx.startedAt
|
||||
case "contenttype":
|
||||
ct := ctx.r.Header.Get("Content-Type")
|
||||
if ct != "" {
|
||||
return ct
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fallback:
|
||||
return ctx.Context.Value(key)
|
||||
}
|
||||
|
||||
type muxVarsContext struct {
|
||||
Context
|
||||
vars map[string]string
|
||||
}
|
||||
|
||||
func (ctx *muxVarsContext) Value(key interface{}) interface{} {
|
||||
if keyStr, ok := key.(string); ok {
|
||||
if keyStr == "vars" {
|
||||
return ctx.vars
|
||||
}
|
||||
|
||||
if strings.HasPrefix(keyStr, "vars.") {
|
||||
keyStr = strings.TrimPrefix(keyStr, "vars.")
|
||||
}
|
||||
|
||||
if v, ok := ctx.vars[keyStr]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.Context.Value(key)
|
||||
}
|
||||
|
||||
// instrumentedResponseWriterCN provides response writer information in a
|
||||
// context. It implements http.CloseNotifier so that users can detect
|
||||
// early disconnects.
|
||||
type instrumentedResponseWriterCN struct {
|
||||
instrumentedResponseWriter
|
||||
http.CloseNotifier
|
||||
}
|
||||
|
||||
// instrumentedResponseWriter provides response writer information in a
|
||||
// context. This variant is only used in the case where CloseNotifier is not
|
||||
// implemented by the parent ResponseWriter.
|
||||
type instrumentedResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
Context
|
||||
|
||||
mu sync.Mutex
|
||||
status int
|
||||
written int64
|
||||
}
|
||||
|
||||
func (irw *instrumentedResponseWriter) Write(p []byte) (n int, err error) {
|
||||
n, err = irw.ResponseWriter.Write(p)
|
||||
|
||||
irw.mu.Lock()
|
||||
irw.written += int64(n)
|
||||
|
||||
// Guess the likely status if not set.
|
||||
if irw.status == 0 {
|
||||
irw.status = http.StatusOK
|
||||
}
|
||||
|
||||
irw.mu.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (irw *instrumentedResponseWriter) WriteHeader(status int) {
|
||||
irw.ResponseWriter.WriteHeader(status)
|
||||
|
||||
irw.mu.Lock()
|
||||
irw.status = status
|
||||
irw.mu.Unlock()
|
||||
}
|
||||
|
||||
func (irw *instrumentedResponseWriter) Flush() {
|
||||
if flusher, ok := irw.ResponseWriter.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} {
|
||||
if keyStr, ok := key.(string); ok {
|
||||
if keyStr == "http.response" {
|
||||
return irw
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(keyStr, "http.response.") {
|
||||
goto fallback
|
||||
}
|
||||
|
||||
parts := strings.Split(keyStr, ".")
|
||||
|
||||
if len(parts) != 3 {
|
||||
goto fallback
|
||||
}
|
||||
|
||||
irw.mu.Lock()
|
||||
defer irw.mu.Unlock()
|
||||
|
||||
switch parts[2] {
|
||||
case "written":
|
||||
return irw.written
|
||||
case "status":
|
||||
return irw.status
|
||||
case "contenttype":
|
||||
contentType := irw.Header().Get("Content-Type")
|
||||
if contentType != "" {
|
||||
return contentType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fallback:
|
||||
return irw.Context.Value(key)
|
||||
}
|
||||
|
||||
func (irw *instrumentedResponseWriterCN) Value(key interface{}) interface{} {
|
||||
if keyStr, ok := key.(string); ok {
|
||||
if keyStr == "http.response" {
|
||||
return irw
|
||||
}
|
||||
}
|
||||
|
||||
return irw.instrumentedResponseWriter.Value(key)
|
||||
}
|
116
vendor/github.com/docker/distribution/context/logger.go
generated
vendored
Normal file
116
vendor/github.com/docker/distribution/context/logger.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Logger provides a leveled-logging interface.
|
||||
type Logger interface {
|
||||
// standard logger methods
|
||||
Print(args ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
Println(args ...interface{})
|
||||
|
||||
Fatal(args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Fatalln(args ...interface{})
|
||||
|
||||
Panic(args ...interface{})
|
||||
Panicf(format string, args ...interface{})
|
||||
Panicln(args ...interface{})
|
||||
|
||||
// Leveled methods, from logrus
|
||||
Debug(args ...interface{})
|
||||
Debugf(format string, args ...interface{})
|
||||
Debugln(args ...interface{})
|
||||
|
||||
Error(args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Errorln(args ...interface{})
|
||||
|
||||
Info(args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Infoln(args ...interface{})
|
||||
|
||||
Warn(args ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
Warnln(args ...interface{})
|
||||
}
|
||||
|
||||
// WithLogger creates a new context with provided logger.
|
||||
func WithLogger(ctx Context, logger Logger) Context {
|
||||
return WithValue(ctx, "logger", logger)
|
||||
}
|
||||
|
||||
// GetLoggerWithField returns a logger instance with the specified field key
|
||||
// and value without affecting the context. Extra specified keys will be
|
||||
// resolved from the context.
|
||||
func GetLoggerWithField(ctx Context, key, value interface{}, keys ...interface{}) Logger {
|
||||
return getLogrusLogger(ctx, keys...).WithField(fmt.Sprint(key), value)
|
||||
}
|
||||
|
||||
// GetLoggerWithFields returns a logger instance with the specified fields
|
||||
// without affecting the context. Extra specified keys will be resolved from
|
||||
// the context.
|
||||
func GetLoggerWithFields(ctx Context, fields map[interface{}]interface{}, keys ...interface{}) Logger {
|
||||
// must convert from interface{} -> interface{} to string -> interface{} for logrus.
|
||||
lfields := make(logrus.Fields, len(fields))
|
||||
for key, value := range fields {
|
||||
lfields[fmt.Sprint(key)] = value
|
||||
}
|
||||
|
||||
return getLogrusLogger(ctx, keys...).WithFields(lfields)
|
||||
}
|
||||
|
||||
// GetLogger returns the logger from the current context, if present. If one
|
||||
// or more keys are provided, they will be resolved on the context and
|
||||
// included in the logger. While context.Value takes an interface, any key
|
||||
// argument passed to GetLogger will be passed to fmt.Sprint when expanded as
|
||||
// a logging key field. If context keys are integer constants, for example,
|
||||
// its recommended that a String method is implemented.
|
||||
func GetLogger(ctx Context, keys ...interface{}) Logger {
|
||||
return getLogrusLogger(ctx, keys...)
|
||||
}
|
||||
|
||||
// GetLogrusLogger returns the logrus logger for the context. If one more keys
|
||||
// are provided, they will be resolved on the context and included in the
|
||||
// logger. Only use this function if specific logrus functionality is
|
||||
// required.
|
||||
func getLogrusLogger(ctx Context, keys ...interface{}) *logrus.Entry {
|
||||
var logger *logrus.Entry
|
||||
|
||||
// Get a logger, if it is present.
|
||||
loggerInterface := ctx.Value("logger")
|
||||
if loggerInterface != nil {
|
||||
if lgr, ok := loggerInterface.(*logrus.Entry); ok {
|
||||
logger = lgr
|
||||
}
|
||||
}
|
||||
|
||||
if logger == nil {
|
||||
fields := logrus.Fields{}
|
||||
|
||||
// Fill in the instance id, if we have it.
|
||||
instanceID := ctx.Value("instance.id")
|
||||
if instanceID != nil {
|
||||
fields["instance.id"] = instanceID
|
||||
}
|
||||
|
||||
fields["go.version"] = runtime.Version()
|
||||
// If no logger is found, just return the standard logger.
|
||||
logger = logrus.StandardLogger().WithFields(fields)
|
||||
}
|
||||
|
||||
fields := logrus.Fields{}
|
||||
for _, key := range keys {
|
||||
v := ctx.Value(key)
|
||||
if v != nil {
|
||||
fields[fmt.Sprint(key)] = v
|
||||
}
|
||||
}
|
||||
|
||||
return logger.WithFields(fields)
|
||||
}
|
104
vendor/github.com/docker/distribution/context/trace.go
generated
vendored
Normal file
104
vendor/github.com/docker/distribution/context/trace.go
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/uuid"
|
||||
)
|
||||
|
||||
// WithTrace allocates a traced timing span in a new context. This allows a
|
||||
// caller to track the time between calling WithTrace and the returned done
|
||||
// function. When the done function is called, a log message is emitted with a
|
||||
// "trace.duration" field, corresponding to the elapsed time and a
|
||||
// "trace.func" field, corresponding to the function that called WithTrace.
|
||||
//
|
||||
// The logging keys "trace.id" and "trace.parent.id" are provided to implement
|
||||
// dapper-like tracing. This function should be complemented with a WithSpan
|
||||
// method that could be used for tracing distributed RPC calls.
|
||||
//
|
||||
// The main benefit of this function is to post-process log messages or
|
||||
// intercept them in a hook to provide timing data. Trace ids and parent ids
|
||||
// can also be linked to provide call tracing, if so required.
|
||||
//
|
||||
// Here is an example of the usage:
|
||||
//
|
||||
// func timedOperation(ctx Context) {
|
||||
// ctx, done := WithTrace(ctx)
|
||||
// defer done("this will be the log message")
|
||||
// // ... function body ...
|
||||
// }
|
||||
//
|
||||
// If the function ran for roughly 1s, such a usage would emit a log message
|
||||
// as follows:
|
||||
//
|
||||
// INFO[0001] this will be the log message trace.duration=1.004575763s trace.func=github.com/docker/distribution/context.traceOperation trace.id=<id> ...
|
||||
//
|
||||
// Notice that the function name is automatically resolved, along with the
|
||||
// package and a trace id is emitted that can be linked with parent ids.
|
||||
func WithTrace(ctx Context) (Context, func(format string, a ...interface{})) {
|
||||
if ctx == nil {
|
||||
ctx = Background()
|
||||
}
|
||||
|
||||
pc, file, line, _ := runtime.Caller(1)
|
||||
f := runtime.FuncForPC(pc)
|
||||
ctx = &traced{
|
||||
Context: ctx,
|
||||
id: uuid.Generate().String(),
|
||||
start: time.Now(),
|
||||
parent: GetStringValue(ctx, "trace.id"),
|
||||
fnname: f.Name(),
|
||||
file: file,
|
||||
line: line,
|
||||
}
|
||||
|
||||
return ctx, func(format string, a ...interface{}) {
|
||||
GetLogger(ctx,
|
||||
"trace.duration",
|
||||
"trace.id",
|
||||
"trace.parent.id",
|
||||
"trace.func",
|
||||
"trace.file",
|
||||
"trace.line").
|
||||
Debugf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// traced represents a context that is traced for function call timing. It
|
||||
// also provides fast lookup for the various attributes that are available on
|
||||
// the trace.
|
||||
type traced struct {
|
||||
Context
|
||||
id string
|
||||
parent string
|
||||
start time.Time
|
||||
fnname string
|
||||
file string
|
||||
line int
|
||||
}
|
||||
|
||||
func (ts *traced) Value(key interface{}) interface{} {
|
||||
switch key {
|
||||
case "trace.start":
|
||||
return ts.start
|
||||
case "trace.duration":
|
||||
return time.Since(ts.start)
|
||||
case "trace.id":
|
||||
return ts.id
|
||||
case "trace.parent.id":
|
||||
if ts.parent == "" {
|
||||
return nil // must return nil to signal no parent.
|
||||
}
|
||||
|
||||
return ts.parent
|
||||
case "trace.func":
|
||||
return ts.fnname
|
||||
case "trace.file":
|
||||
return ts.file
|
||||
case "trace.line":
|
||||
return ts.line
|
||||
}
|
||||
|
||||
return ts.Context.Value(key)
|
||||
}
|
24
vendor/github.com/docker/distribution/context/util.go
generated
vendored
Normal file
24
vendor/github.com/docker/distribution/context/util.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Since looks up key, which should be a time.Time, and returns the duration
|
||||
// since that time. If the key is not found, the value returned will be zero.
|
||||
// This is helpful when inferring metrics related to context execution times.
|
||||
func Since(ctx Context, key interface{}) time.Duration {
|
||||
if startedAt, ok := ctx.Value(key).(time.Time); ok {
|
||||
return time.Since(startedAt)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetStringValue returns a string value from the context. The empty string
|
||||
// will be returned if not found.
|
||||
func GetStringValue(ctx Context, key interface{}) (value string) {
|
||||
if valuev, ok := ctx.Value(key).(string); ok {
|
||||
value = valuev
|
||||
}
|
||||
return value
|
||||
}
|
16
vendor/github.com/docker/distribution/context/version.go
generated
vendored
Normal file
16
vendor/github.com/docker/distribution/context/version.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
package context
|
||||
|
||||
// WithVersion stores the application version in the context. The new context
|
||||
// gets a logger to ensure log messages are marked with the application
|
||||
// version.
|
||||
func WithVersion(ctx Context, version string) Context {
|
||||
ctx = WithValue(ctx, "version", version)
|
||||
// push a new logger onto the stack
|
||||
return WithLogger(ctx, GetLogger(ctx, "version"))
|
||||
}
|
||||
|
||||
// GetVersion returns the application version from the context. An empty
|
||||
// string may returned if the version was not set on the context.
|
||||
func GetVersion(ctx Context) string {
|
||||
return GetStringValue(ctx, "version")
|
||||
}
|
7
vendor/github.com/docker/distribution/doc.go
generated
vendored
Normal file
7
vendor/github.com/docker/distribution/doc.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// Package distribution will define the interfaces for the components of
|
||||
// docker distribution. The goal is to allow users to reliably package, ship
|
||||
// and store content related to docker images.
|
||||
//
|
||||
// This is currently a work in progress. More details are available in the
|
||||
// README.md.
|
||||
package distribution
|
115
vendor/github.com/docker/distribution/errors.go
generated
vendored
Normal file
115
vendor/github.com/docker/distribution/errors.go
generated
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
package distribution
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// ErrAccessDenied is returned when an access to a requested resource is
|
||||
// denied.
|
||||
var ErrAccessDenied = errors.New("access denied")
|
||||
|
||||
// ErrManifestNotModified is returned when a conditional manifest GetByTag
|
||||
// returns nil due to the client indicating it has the latest version
|
||||
var ErrManifestNotModified = errors.New("manifest not modified")
|
||||
|
||||
// ErrUnsupported is returned when an unimplemented or unsupported action is
|
||||
// performed
|
||||
var ErrUnsupported = errors.New("operation unsupported")
|
||||
|
||||
// ErrTagUnknown is returned if the given tag is not known by the tag service
|
||||
type ErrTagUnknown struct {
|
||||
Tag string
|
||||
}
|
||||
|
||||
func (err ErrTagUnknown) Error() string {
|
||||
return fmt.Sprintf("unknown tag=%s", err.Tag)
|
||||
}
|
||||
|
||||
// ErrRepositoryUnknown is returned if the named repository is not known by
|
||||
// the registry.
|
||||
type ErrRepositoryUnknown struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (err ErrRepositoryUnknown) Error() string {
|
||||
return fmt.Sprintf("unknown repository name=%s", err.Name)
|
||||
}
|
||||
|
||||
// ErrRepositoryNameInvalid should be used to denote an invalid repository
|
||||
// name. Reason may set, indicating the cause of invalidity.
|
||||
type ErrRepositoryNameInvalid struct {
|
||||
Name string
|
||||
Reason error
|
||||
}
|
||||
|
||||
func (err ErrRepositoryNameInvalid) Error() string {
|
||||
return fmt.Sprintf("repository name %q invalid: %v", err.Name, err.Reason)
|
||||
}
|
||||
|
||||
// ErrManifestUnknown is returned if the manifest is not known by the
|
||||
// registry.
|
||||
type ErrManifestUnknown struct {
|
||||
Name string
|
||||
Tag string
|
||||
}
|
||||
|
||||
func (err ErrManifestUnknown) Error() string {
|
||||
return fmt.Sprintf("unknown manifest name=%s tag=%s", err.Name, err.Tag)
|
||||
}
|
||||
|
||||
// ErrManifestUnknownRevision is returned when a manifest cannot be found by
|
||||
// revision within a repository.
|
||||
type ErrManifestUnknownRevision struct {
|
||||
Name string
|
||||
Revision digest.Digest
|
||||
}
|
||||
|
||||
func (err ErrManifestUnknownRevision) Error() string {
|
||||
return fmt.Sprintf("unknown manifest name=%s revision=%s", err.Name, err.Revision)
|
||||
}
|
||||
|
||||
// ErrManifestUnverified is returned when the registry is unable to verify
|
||||
// the manifest.
|
||||
type ErrManifestUnverified struct{}
|
||||
|
||||
func (ErrManifestUnverified) Error() string {
|
||||
return fmt.Sprintf("unverified manifest")
|
||||
}
|
||||
|
||||
// ErrManifestVerification provides a type to collect errors encountered
|
||||
// during manifest verification. Currently, it accepts errors of all types,
|
||||
// but it may be narrowed to those involving manifest verification.
|
||||
type ErrManifestVerification []error
|
||||
|
||||
func (errs ErrManifestVerification) Error() string {
|
||||
var parts []string
|
||||
for _, err := range errs {
|
||||
parts = append(parts, err.Error())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("errors verifying manifest: %v", strings.Join(parts, ","))
|
||||
}
|
||||
|
||||
// ErrManifestBlobUnknown returned when a referenced blob cannot be found.
|
||||
type ErrManifestBlobUnknown struct {
|
||||
Digest digest.Digest
|
||||
}
|
||||
|
||||
func (err ErrManifestBlobUnknown) Error() string {
|
||||
return fmt.Sprintf("unknown blob %v on manifest", err.Digest)
|
||||
}
|
||||
|
||||
// ErrManifestNameInvalid should be used to denote an invalid manifest
|
||||
// name. Reason may set, indicating the cause of invalidity.
|
||||
type ErrManifestNameInvalid struct {
|
||||
Name string
|
||||
Reason error
|
||||
}
|
||||
|
||||
func (err ErrManifestNameInvalid) Error() string {
|
||||
return fmt.Sprintf("manifest name %q invalid: %v", err.Name, err.Reason)
|
||||
}
|
125
vendor/github.com/docker/distribution/manifests.go
generated
vendored
Normal file
125
vendor/github.com/docker/distribution/manifests.go
generated
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
package distribution
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// Manifest represents a registry object specifying a set of
|
||||
// references and an optional target
|
||||
type Manifest interface {
|
||||
// References returns a list of objects which make up this manifest.
|
||||
// A reference is anything which can be represented by a
|
||||
// distribution.Descriptor. These can consist of layers, resources or other
|
||||
// manifests.
|
||||
//
|
||||
// While no particular order is required, implementations should return
|
||||
// them from highest to lowest priority. For example, one might want to
|
||||
// return the base layer before the top layer.
|
||||
References() []Descriptor
|
||||
|
||||
// Payload provides the serialized format of the manifest, in addition to
|
||||
// the media type.
|
||||
Payload() (mediaType string, payload []byte, err error)
|
||||
}
|
||||
|
||||
// ManifestBuilder creates a manifest allowing one to include dependencies.
|
||||
// Instances can be obtained from a version-specific manifest package. Manifest
|
||||
// specific data is passed into the function which creates the builder.
|
||||
type ManifestBuilder interface {
|
||||
// Build creates the manifest from his builder.
|
||||
Build(ctx context.Context) (Manifest, error)
|
||||
|
||||
// References returns a list of objects which have been added to this
|
||||
// builder. The dependencies are returned in the order they were added,
|
||||
// which should be from base to head.
|
||||
References() []Descriptor
|
||||
|
||||
// AppendReference includes the given object in the manifest after any
|
||||
// existing dependencies. If the add fails, such as when adding an
|
||||
// unsupported dependency, an error may be returned.
|
||||
//
|
||||
// The destination of the reference is dependent on the manifest type and
|
||||
// the dependency type.
|
||||
AppendReference(dependency Describable) error
|
||||
}
|
||||
|
||||
// ManifestService describes operations on image manifests.
|
||||
type ManifestService interface {
|
||||
// Exists returns true if the manifest exists.
|
||||
Exists(ctx context.Context, dgst digest.Digest) (bool, error)
|
||||
|
||||
// Get retrieves the manifest specified by the given digest
|
||||
Get(ctx context.Context, dgst digest.Digest, options ...ManifestServiceOption) (Manifest, error)
|
||||
|
||||
// Put creates or updates the given manifest returning the manifest digest
|
||||
Put(ctx context.Context, manifest Manifest, options ...ManifestServiceOption) (digest.Digest, error)
|
||||
|
||||
// Delete removes the manifest specified by the given digest. Deleting
|
||||
// a manifest that doesn't exist will return ErrManifestNotFound
|
||||
Delete(ctx context.Context, dgst digest.Digest) error
|
||||
}
|
||||
|
||||
// ManifestEnumerator enables iterating over manifests
|
||||
type ManifestEnumerator interface {
|
||||
// Enumerate calls ingester for each manifest.
|
||||
Enumerate(ctx context.Context, ingester func(digest.Digest) error) error
|
||||
}
|
||||
|
||||
// Describable is an interface for descriptors
|
||||
type Describable interface {
|
||||
Descriptor() Descriptor
|
||||
}
|
||||
|
||||
// ManifestMediaTypes returns the supported media types for manifests.
|
||||
func ManifestMediaTypes() (mediaTypes []string) {
|
||||
for t := range mappings {
|
||||
if t != "" {
|
||||
mediaTypes = append(mediaTypes, t)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalFunc implements manifest unmarshalling a given MediaType
|
||||
type UnmarshalFunc func([]byte) (Manifest, Descriptor, error)
|
||||
|
||||
var mappings = make(map[string]UnmarshalFunc, 0)
|
||||
|
||||
// UnmarshalManifest looks up manifest unmarshal functions based on
|
||||
// MediaType
|
||||
func UnmarshalManifest(ctHeader string, p []byte) (Manifest, Descriptor, error) {
|
||||
// Need to look up by the actual media type, not the raw contents of
|
||||
// the header. Strip semicolons and anything following them.
|
||||
var mediaType string
|
||||
if ctHeader != "" {
|
||||
var err error
|
||||
mediaType, _, err = mime.ParseMediaType(ctHeader)
|
||||
if err != nil {
|
||||
return nil, Descriptor{}, err
|
||||
}
|
||||
}
|
||||
|
||||
unmarshalFunc, ok := mappings[mediaType]
|
||||
if !ok {
|
||||
unmarshalFunc, ok = mappings[""]
|
||||
if !ok {
|
||||
return nil, Descriptor{}, fmt.Errorf("unsupported manifest media type and no default available: %s", mediaType)
|
||||
}
|
||||
}
|
||||
|
||||
return unmarshalFunc(p)
|
||||
}
|
||||
|
||||
// RegisterManifestSchema registers an UnmarshalFunc for a given schema type. This
|
||||
// should be called from specific
|
||||
func RegisterManifestSchema(mediaType string, u UnmarshalFunc) error {
|
||||
if _, ok := mappings[mediaType]; ok {
|
||||
return fmt.Errorf("manifest media type registration would overwrite existing: %s", mediaType)
|
||||
}
|
||||
mappings[mediaType] = u
|
||||
return nil
|
||||
}
|
97
vendor/github.com/docker/distribution/registry.go
generated
vendored
Normal file
97
vendor/github.com/docker/distribution/registry.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
package distribution
|
||||
|
||||
import (
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/reference"
|
||||
)
|
||||
|
||||
// Scope defines the set of items that match a namespace.
|
||||
type Scope interface {
|
||||
// Contains returns true if the name belongs to the namespace.
|
||||
Contains(name string) bool
|
||||
}
|
||||
|
||||
type fullScope struct{}
|
||||
|
||||
func (f fullScope) Contains(string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GlobalScope represents the full namespace scope which contains
|
||||
// all other scopes.
|
||||
var GlobalScope = Scope(fullScope{})
|
||||
|
||||
// Namespace represents a collection of repositories, addressable by name.
|
||||
// Generally, a namespace is backed by a set of one or more services,
|
||||
// providing facilities such as registry access, trust, and indexing.
|
||||
type Namespace interface {
|
||||
// Scope describes the names that can be used with this Namespace. The
|
||||
// global namespace will have a scope that matches all names. The scope
|
||||
// effectively provides an identity for the namespace.
|
||||
Scope() Scope
|
||||
|
||||
// Repository should return a reference to the named repository. The
|
||||
// registry may or may not have the repository but should always return a
|
||||
// reference.
|
||||
Repository(ctx context.Context, name reference.Named) (Repository, error)
|
||||
|
||||
// Repositories fills 'repos' with a lexicographically sorted catalog of repositories
|
||||
// up to the size of 'repos' and returns the value 'n' for the number of entries
|
||||
// which were filled. 'last' contains an offset in the catalog, and 'err' will be
|
||||
// set to io.EOF if there are no more entries to obtain.
|
||||
Repositories(ctx context.Context, repos []string, last string) (n int, err error)
|
||||
|
||||
// Blobs returns a blob enumerator to access all blobs
|
||||
Blobs() BlobEnumerator
|
||||
|
||||
// BlobStatter returns a BlobStatter to control
|
||||
BlobStatter() BlobStatter
|
||||
}
|
||||
|
||||
// RepositoryEnumerator describes an operation to enumerate repositories
|
||||
type RepositoryEnumerator interface {
|
||||
Enumerate(ctx context.Context, ingester func(string) error) error
|
||||
}
|
||||
|
||||
// ManifestServiceOption is a function argument for Manifest Service methods
|
||||
type ManifestServiceOption interface {
|
||||
Apply(ManifestService) error
|
||||
}
|
||||
|
||||
// WithTag allows a tag to be passed into Put
|
||||
func WithTag(tag string) ManifestServiceOption {
|
||||
return WithTagOption{tag}
|
||||
}
|
||||
|
||||
// WithTagOption holds a tag
|
||||
type WithTagOption struct{ Tag string }
|
||||
|
||||
// Apply conforms to the ManifestServiceOption interface
|
||||
func (o WithTagOption) Apply(m ManifestService) error {
|
||||
// no implementation
|
||||
return nil
|
||||
}
|
||||
|
||||
// Repository is a named collection of manifests and layers.
|
||||
type Repository interface {
|
||||
// Named returns the name of the repository.
|
||||
Named() reference.Named
|
||||
|
||||
// Manifests returns a reference to this repository's manifest service.
|
||||
// with the supplied options applied.
|
||||
Manifests(ctx context.Context, options ...ManifestServiceOption) (ManifestService, error)
|
||||
|
||||
// Blobs returns a reference to this repository's blob service.
|
||||
Blobs(ctx context.Context) BlobStore
|
||||
|
||||
// TODO(stevvooe): The above BlobStore return can probably be relaxed to
|
||||
// be a BlobService for use with clients. This will allow such
|
||||
// implementations to avoid implementing ServeBlob.
|
||||
|
||||
// Tags returns a reference to this repositories tag service
|
||||
Tags(ctx context.Context) TagService
|
||||
}
|
||||
|
||||
// TODO(stevvooe): Must add close methods to all these. May want to change the
|
||||
// way instances are created to better reflect internal dependency
|
||||
// relationships.
|
267
vendor/github.com/docker/distribution/registry/api/errcode/errors.go
generated
vendored
Normal file
267
vendor/github.com/docker/distribution/registry/api/errcode/errors.go
generated
vendored
Normal file
@ -0,0 +1,267 @@
|
||||
package errcode
|
||||
|
||||
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.(type) {
|
||||
case ErrorCode:
|
||||
err = daErr.(ErrorCode).WithDetail(nil)
|
||||
case Error:
|
||||
err = daErr.(Error)
|
||||
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
|
||||
}
|
44
vendor/github.com/docker/distribution/registry/api/errcode/handler.go
generated
vendored
Normal file
44
vendor/github.com/docker/distribution/registry/api/errcode/handler.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
package errcode
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ServeJSON attempts to serve the errcode in a JSON envelope. It marshals err
|
||||
// and sets the content-type header to 'application/json'. It will handle
|
||||
// ErrorCoder and Errors, and if necessary will create an envelope.
|
||||
func ServeJSON(w http.ResponseWriter, err error) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
var sc int
|
||||
|
||||
switch errs := err.(type) {
|
||||
case Errors:
|
||||
if len(errs) < 1 {
|
||||
break
|
||||
}
|
||||
|
||||
if err, ok := errs[0].(ErrorCoder); ok {
|
||||
sc = err.ErrorCode().Descriptor().HTTPStatusCode
|
||||
}
|
||||
case ErrorCoder:
|
||||
sc = errs.ErrorCode().Descriptor().HTTPStatusCode
|
||||
err = Errors{err} // create an envelope.
|
||||
default:
|
||||
// We just have an unhandled error type, so just place in an envelope
|
||||
// and move along.
|
||||
err = Errors{err}
|
||||
}
|
||||
|
||||
if sc == 0 {
|
||||
sc = http.StatusInternalServerError
|
||||
}
|
||||
|
||||
w.WriteHeader(sc)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
138
vendor/github.com/docker/distribution/registry/api/errcode/register.go
generated
vendored
Normal file
138
vendor/github.com/docker/distribution/registry/api/errcode/register.go
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
package errcode
|
||||
|
||||
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
|
||||
}
|
1596
vendor/github.com/docker/distribution/registry/api/v2/descriptors.go
generated
vendored
Normal file
1596
vendor/github.com/docker/distribution/registry/api/v2/descriptors.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
vendor/github.com/docker/distribution/registry/api/v2/doc.go
generated
vendored
Normal file
9
vendor/github.com/docker/distribution/registry/api/v2/doc.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
// Package v2 describes routes, urls and the error codes used in the Docker
|
||||
// Registry JSON HTTP API V2. In addition to declarations, descriptors are
|
||||
// provided for routes and error codes that can be used for implementation and
|
||||
// automatically generating documentation.
|
||||
//
|
||||
// Definitions here are considered to be locked down for the V2 registry api.
|
||||
// Any changes must be considered carefully and should not proceed without a
|
||||
// change proposal in docker core.
|
||||
package v2
|
136
vendor/github.com/docker/distribution/registry/api/v2/errors.go
generated
vendored
Normal file
136
vendor/github.com/docker/distribution/registry/api/v2/errors.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
)
|
||||
|
||||
const errGroup = "registry.api.v2"
|
||||
|
||||
var (
|
||||
// ErrorCodeDigestInvalid is returned when uploading a blob if the
|
||||
// provided digest does not match the blob contents.
|
||||
ErrorCodeDigestInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "DIGEST_INVALID",
|
||||
Message: "provided digest did not match uploaded content",
|
||||
Description: `When a blob is uploaded, the registry will check that
|
||||
the content matches the digest provided by the client. The error may
|
||||
include a detail structure with the key "digest", including the
|
||||
invalid digest string. This error may also be returned when a manifest
|
||||
includes an invalid layer digest.`,
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
})
|
||||
|
||||
// ErrorCodeSizeInvalid is returned when uploading a blob if the provided
|
||||
ErrorCodeSizeInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "SIZE_INVALID",
|
||||
Message: "provided length did not match content length",
|
||||
Description: `When a layer is uploaded, the provided size will be
|
||||
checked against the uploaded content. If they do not match, this error
|
||||
will be returned.`,
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
})
|
||||
|
||||
// ErrorCodeNameInvalid is returned when the name in the manifest does not
|
||||
// match the provided name.
|
||||
ErrorCodeNameInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "NAME_INVALID",
|
||||
Message: "invalid repository name",
|
||||
Description: `Invalid repository name encountered either during
|
||||
manifest validation or any API operation.`,
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
})
|
||||
|
||||
// ErrorCodeTagInvalid is returned when the tag in the manifest does not
|
||||
// match the provided tag.
|
||||
ErrorCodeTagInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "TAG_INVALID",
|
||||
Message: "manifest tag did not match URI",
|
||||
Description: `During a manifest upload, if the tag in the manifest
|
||||
does not match the uri tag, this error will be returned.`,
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
})
|
||||
|
||||
// ErrorCodeNameUnknown when the repository name is not known.
|
||||
ErrorCodeNameUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "NAME_UNKNOWN",
|
||||
Message: "repository name not known to registry",
|
||||
Description: `This is returned if the name used during an operation is
|
||||
unknown to the registry.`,
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
})
|
||||
|
||||
// ErrorCodeManifestUnknown returned when image manifest is unknown.
|
||||
ErrorCodeManifestUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "MANIFEST_UNKNOWN",
|
||||
Message: "manifest unknown",
|
||||
Description: `This error is returned when the manifest, identified by
|
||||
name and tag is unknown to the repository.`,
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
})
|
||||
|
||||
// ErrorCodeManifestInvalid returned when an image manifest is invalid,
|
||||
// typically during a PUT operation. This error encompasses all errors
|
||||
// encountered during manifest validation that aren't signature errors.
|
||||
ErrorCodeManifestInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "MANIFEST_INVALID",
|
||||
Message: "manifest invalid",
|
||||
Description: `During upload, manifests undergo several checks ensuring
|
||||
validity. If those checks fail, this error may be returned, unless a
|
||||
more specific error is included. The detail will contain information
|
||||
the failed validation.`,
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
})
|
||||
|
||||
// ErrorCodeManifestUnverified is returned when the manifest fails
|
||||
// signature verification.
|
||||
ErrorCodeManifestUnverified = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "MANIFEST_UNVERIFIED",
|
||||
Message: "manifest failed signature verification",
|
||||
Description: `During manifest upload, if the manifest fails signature
|
||||
verification, this error will be returned.`,
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
})
|
||||
|
||||
// ErrorCodeManifestBlobUnknown is returned when a manifest blob is
|
||||
// unknown to the registry.
|
||||
ErrorCodeManifestBlobUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "MANIFEST_BLOB_UNKNOWN",
|
||||
Message: "blob unknown to registry",
|
||||
Description: `This error may be returned when a manifest blob is
|
||||
unknown to the registry.`,
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
})
|
||||
|
||||
// ErrorCodeBlobUnknown is returned when a blob is unknown to the
|
||||
// registry. This can happen when the manifest references a nonexistent
|
||||
// layer or the result is not found by a blob fetch.
|
||||
ErrorCodeBlobUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "BLOB_UNKNOWN",
|
||||
Message: "blob unknown to registry",
|
||||
Description: `This error may be returned when a blob is unknown to the
|
||||
registry in a specified repository. This can be returned with a
|
||||
standard get or if a manifest references an unknown layer during
|
||||
upload.`,
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
})
|
||||
|
||||
// ErrorCodeBlobUploadUnknown is returned when an upload is unknown.
|
||||
ErrorCodeBlobUploadUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "BLOB_UPLOAD_UNKNOWN",
|
||||
Message: "blob upload unknown to registry",
|
||||
Description: `If a blob upload has been cancelled or was never
|
||||
started, this error code may be returned.`,
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
})
|
||||
|
||||
// ErrorCodeBlobUploadInvalid is returned when an upload is invalid.
|
||||
ErrorCodeBlobUploadInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "BLOB_UPLOAD_INVALID",
|
||||
Message: "blob upload invalid",
|
||||
Description: `The blob upload encountered an error and can no
|
||||
longer proceed.`,
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
})
|
||||
)
|
161
vendor/github.com/docker/distribution/registry/api/v2/headerparser.go
generated
vendored
Normal file
161
vendor/github.com/docker/distribution/registry/api/v2/headerparser.go
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
// according to rfc7230
|
||||
reToken = regexp.MustCompile(`^[^"(),/:;<=>?@[\]{}[:space:][:cntrl:]]+`)
|
||||
reQuotedValue = regexp.MustCompile(`^[^\\"]+`)
|
||||
reEscapedCharacter = regexp.MustCompile(`^[[:blank:][:graph:]]`)
|
||||
)
|
||||
|
||||
// parseForwardedHeader is a benevolent parser of Forwarded header defined in rfc7239. The header contains
|
||||
// a comma-separated list of forwarding key-value pairs. Each list element is set by single proxy. The
|
||||
// function parses only the first element of the list, which is set by the very first proxy. It returns a map
|
||||
// of corresponding key-value pairs and an unparsed slice of the input string.
|
||||
//
|
||||
// Examples of Forwarded header values:
|
||||
//
|
||||
// 1. Forwarded: For=192.0.2.43; Proto=https,For="[2001:db8:cafe::17]",For=unknown
|
||||
// 2. Forwarded: for="192.0.2.43:443"; host="registry.example.org", for="10.10.05.40:80"
|
||||
//
|
||||
// The first will be parsed into {"for": "192.0.2.43", "proto": "https"} while the second into
|
||||
// {"for": "192.0.2.43:443", "host": "registry.example.org"}.
|
||||
func parseForwardedHeader(forwarded string) (map[string]string, string, error) {
|
||||
// Following are states of forwarded header parser. Any state could transition to a failure.
|
||||
const (
|
||||
// terminating state; can transition to Parameter
|
||||
stateElement = iota
|
||||
// terminating state; can transition to KeyValueDelimiter
|
||||
stateParameter
|
||||
// can transition to Value
|
||||
stateKeyValueDelimiter
|
||||
// can transition to one of { QuotedValue, PairEnd }
|
||||
stateValue
|
||||
// can transition to one of { EscapedCharacter, PairEnd }
|
||||
stateQuotedValue
|
||||
// can transition to one of { QuotedValue }
|
||||
stateEscapedCharacter
|
||||
// terminating state; can transition to one of { Parameter, Element }
|
||||
statePairEnd
|
||||
)
|
||||
|
||||
var (
|
||||
parameter string
|
||||
value string
|
||||
parse = forwarded[:]
|
||||
res = map[string]string{}
|
||||
state = stateElement
|
||||
)
|
||||
|
||||
Loop:
|
||||
for {
|
||||
// skip spaces unless in quoted value
|
||||
if state != stateQuotedValue && state != stateEscapedCharacter {
|
||||
parse = strings.TrimLeftFunc(parse, unicode.IsSpace)
|
||||
}
|
||||
|
||||
if len(parse) == 0 {
|
||||
if state != stateElement && state != statePairEnd && state != stateParameter {
|
||||
return nil, parse, fmt.Errorf("unexpected end of input")
|
||||
}
|
||||
// terminating
|
||||
break
|
||||
}
|
||||
|
||||
switch state {
|
||||
// terminate at list element delimiter
|
||||
case stateElement:
|
||||
if parse[0] == ',' {
|
||||
parse = parse[1:]
|
||||
break Loop
|
||||
}
|
||||
state = stateParameter
|
||||
|
||||
// parse parameter (the key of key-value pair)
|
||||
case stateParameter:
|
||||
match := reToken.FindString(parse)
|
||||
if len(match) == 0 {
|
||||
return nil, parse, fmt.Errorf("failed to parse token at position %d", len(forwarded)-len(parse))
|
||||
}
|
||||
parameter = strings.ToLower(match)
|
||||
parse = parse[len(match):]
|
||||
state = stateKeyValueDelimiter
|
||||
|
||||
// parse '='
|
||||
case stateKeyValueDelimiter:
|
||||
if parse[0] != '=' {
|
||||
return nil, parse, fmt.Errorf("expected '=', not '%c' at position %d", parse[0], len(forwarded)-len(parse))
|
||||
}
|
||||
parse = parse[1:]
|
||||
state = stateValue
|
||||
|
||||
// parse value or quoted value
|
||||
case stateValue:
|
||||
if parse[0] == '"' {
|
||||
parse = parse[1:]
|
||||
state = stateQuotedValue
|
||||
} else {
|
||||
value = reToken.FindString(parse)
|
||||
if len(value) == 0 {
|
||||
return nil, parse, fmt.Errorf("failed to parse value at position %d", len(forwarded)-len(parse))
|
||||
}
|
||||
if _, exists := res[parameter]; exists {
|
||||
return nil, parse, fmt.Errorf("duplicate parameter %q at position %d", parameter, len(forwarded)-len(parse))
|
||||
}
|
||||
res[parameter] = value
|
||||
parse = parse[len(value):]
|
||||
value = ""
|
||||
state = statePairEnd
|
||||
}
|
||||
|
||||
// parse a part of quoted value until the first backslash
|
||||
case stateQuotedValue:
|
||||
match := reQuotedValue.FindString(parse)
|
||||
value += match
|
||||
parse = parse[len(match):]
|
||||
switch {
|
||||
case len(parse) == 0:
|
||||
return nil, parse, fmt.Errorf("unterminated quoted string")
|
||||
case parse[0] == '"':
|
||||
res[parameter] = value
|
||||
value = ""
|
||||
parse = parse[1:]
|
||||
state = statePairEnd
|
||||
case parse[0] == '\\':
|
||||
parse = parse[1:]
|
||||
state = stateEscapedCharacter
|
||||
}
|
||||
|
||||
// parse escaped character in a quoted string, ignore the backslash
|
||||
// transition back to QuotedValue state
|
||||
case stateEscapedCharacter:
|
||||
c := reEscapedCharacter.FindString(parse)
|
||||
if len(c) == 0 {
|
||||
return nil, parse, fmt.Errorf("invalid escape sequence at position %d", len(forwarded)-len(parse)-1)
|
||||
}
|
||||
value += c
|
||||
parse = parse[1:]
|
||||
state = stateQuotedValue
|
||||
|
||||
// expect either a new key-value pair, new list or end of input
|
||||
case statePairEnd:
|
||||
switch parse[0] {
|
||||
case ';':
|
||||
parse = parse[1:]
|
||||
state = stateParameter
|
||||
case ',':
|
||||
state = stateElement
|
||||
default:
|
||||
return nil, parse, fmt.Errorf("expected ',' or ';', not %c at position %d", parse[0], len(forwarded)-len(parse))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res, parse, nil
|
||||
}
|
49
vendor/github.com/docker/distribution/registry/api/v2/routes.go
generated
vendored
Normal file
49
vendor/github.com/docker/distribution/registry/api/v2/routes.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package v2
|
||||
|
||||
import "github.com/gorilla/mux"
|
||||
|
||||
// The following are definitions of the name under which all V2 routes are
|
||||
// registered. These symbols can be used to look up a route based on the name.
|
||||
const (
|
||||
RouteNameBase = "base"
|
||||
RouteNameManifest = "manifest"
|
||||
RouteNameTags = "tags"
|
||||
RouteNameBlob = "blob"
|
||||
RouteNameBlobUpload = "blob-upload"
|
||||
RouteNameBlobUploadChunk = "blob-upload-chunk"
|
||||
RouteNameCatalog = "catalog"
|
||||
)
|
||||
|
||||
var allEndpoints = []string{
|
||||
RouteNameManifest,
|
||||
RouteNameCatalog,
|
||||
RouteNameTags,
|
||||
RouteNameBlob,
|
||||
RouteNameBlobUpload,
|
||||
RouteNameBlobUploadChunk,
|
||||
}
|
||||
|
||||
// Router builds a gorilla router with named routes for the various API
|
||||
// methods. This can be used directly by both server implementations and
|
||||
// clients.
|
||||
func Router() *mux.Router {
|
||||
return RouterWithPrefix("")
|
||||
}
|
||||
|
||||
// RouterWithPrefix builds a gorilla router with a configured prefix
|
||||
// on all routes.
|
||||
func RouterWithPrefix(prefix string) *mux.Router {
|
||||
rootRouter := mux.NewRouter()
|
||||
router := rootRouter
|
||||
if prefix != "" {
|
||||
router = router.PathPrefix(prefix).Subrouter()
|
||||
}
|
||||
|
||||
router.StrictSlash(true)
|
||||
|
||||
for _, descriptor := range routeDescriptors {
|
||||
router.Path(descriptor.Path).Name(descriptor.Name)
|
||||
}
|
||||
|
||||
return rootRouter
|
||||
}
|
314
vendor/github.com/docker/distribution/registry/api/v2/urls.go
generated
vendored
Normal file
314
vendor/github.com/docker/distribution/registry/api/v2/urls.go
generated
vendored
Normal file
@ -0,0 +1,314 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// URLBuilder creates registry API urls from a single base endpoint. It can be
|
||||
// used to create urls for use in a registry client or server.
|
||||
//
|
||||
// All urls will be created from the given base, including the api version.
|
||||
// For example, if a root of "/foo/" is provided, urls generated will be fall
|
||||
// under "/foo/v2/...". Most application will only provide a schema, host and
|
||||
// port, such as "https://localhost:5000/".
|
||||
type URLBuilder struct {
|
||||
root *url.URL // url root (ie http://localhost/)
|
||||
router *mux.Router
|
||||
relative bool
|
||||
}
|
||||
|
||||
// NewURLBuilder creates a URLBuilder with provided root url object.
|
||||
func NewURLBuilder(root *url.URL, relative bool) *URLBuilder {
|
||||
return &URLBuilder{
|
||||
root: root,
|
||||
router: Router(),
|
||||
relative: relative,
|
||||
}
|
||||
}
|
||||
|
||||
// NewURLBuilderFromString workes identically to NewURLBuilder except it takes
|
||||
// a string argument for the root, returning an error if it is not a valid
|
||||
// url.
|
||||
func NewURLBuilderFromString(root string, relative bool) (*URLBuilder, error) {
|
||||
u, err := url.Parse(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewURLBuilder(u, relative), nil
|
||||
}
|
||||
|
||||
// NewURLBuilderFromRequest uses information from an *http.Request to
|
||||
// construct the root url.
|
||||
func NewURLBuilderFromRequest(r *http.Request, relative bool) *URLBuilder {
|
||||
var scheme string
|
||||
|
||||
forwardedProto := r.Header.Get("X-Forwarded-Proto")
|
||||
// TODO: log the error
|
||||
forwardedHeader, _, _ := parseForwardedHeader(r.Header.Get("Forwarded"))
|
||||
|
||||
switch {
|
||||
case len(forwardedProto) > 0:
|
||||
scheme = forwardedProto
|
||||
case len(forwardedHeader["proto"]) > 0:
|
||||
scheme = forwardedHeader["proto"]
|
||||
case r.TLS != nil:
|
||||
scheme = "https"
|
||||
case len(r.URL.Scheme) > 0:
|
||||
scheme = r.URL.Scheme
|
||||
default:
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
host := r.Host
|
||||
|
||||
if forwardedHost := r.Header.Get("X-Forwarded-Host"); len(forwardedHost) > 0 {
|
||||
// According to the Apache mod_proxy docs, X-Forwarded-Host can be a
|
||||
// comma-separated list of hosts, to which each proxy appends the
|
||||
// requested host. We want to grab the first from this comma-separated
|
||||
// list.
|
||||
hosts := strings.SplitN(forwardedHost, ",", 2)
|
||||
host = strings.TrimSpace(hosts[0])
|
||||
} else if addr, exists := forwardedHeader["for"]; exists {
|
||||
host = addr
|
||||
} else if h, exists := forwardedHeader["host"]; exists {
|
||||
host = h
|
||||
}
|
||||
|
||||
portLessHost, port := host, ""
|
||||
if !isIPv6Address(portLessHost) {
|
||||
// with go 1.6, this would treat the last part of IPv6 address as a port
|
||||
portLessHost, port, _ = net.SplitHostPort(host)
|
||||
}
|
||||
if forwardedPort := r.Header.Get("X-Forwarded-Port"); len(port) == 0 && len(forwardedPort) > 0 {
|
||||
ports := strings.SplitN(forwardedPort, ",", 2)
|
||||
forwardedPort = strings.TrimSpace(ports[0])
|
||||
if _, err := strconv.ParseInt(forwardedPort, 10, 32); err == nil {
|
||||
port = forwardedPort
|
||||
}
|
||||
}
|
||||
|
||||
if len(portLessHost) > 0 {
|
||||
host = portLessHost
|
||||
}
|
||||
if len(port) > 0 {
|
||||
// remove enclosing brackets of ipv6 address otherwise they will be duplicated
|
||||
if len(host) > 1 && host[0] == '[' && host[len(host)-1] == ']' {
|
||||
host = host[1 : len(host)-1]
|
||||
}
|
||||
// JoinHostPort properly encloses ipv6 addresses in square brackets
|
||||
host = net.JoinHostPort(host, port)
|
||||
} else if isIPv6Address(host) && host[0] != '[' {
|
||||
// ipv6 needs to be enclosed in square brackets in urls
|
||||
host = "[" + host + "]"
|
||||
}
|
||||
|
||||
basePath := routeDescriptorsMap[RouteNameBase].Path
|
||||
|
||||
requestPath := r.URL.Path
|
||||
index := strings.Index(requestPath, basePath)
|
||||
|
||||
u := &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: host,
|
||||
}
|
||||
|
||||
if index > 0 {
|
||||
// N.B. index+1 is important because we want to include the trailing /
|
||||
u.Path = requestPath[0 : index+1]
|
||||
}
|
||||
|
||||
return NewURLBuilder(u, relative)
|
||||
}
|
||||
|
||||
// BuildBaseURL constructs a base url for the API, typically just "/v2/".
|
||||
func (ub *URLBuilder) BuildBaseURL() (string, error) {
|
||||
route := ub.cloneRoute(RouteNameBase)
|
||||
|
||||
baseURL, err := route.URL()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return baseURL.String(), nil
|
||||
}
|
||||
|
||||
// BuildCatalogURL constructs a url get a catalog of repositories
|
||||
func (ub *URLBuilder) BuildCatalogURL(values ...url.Values) (string, error) {
|
||||
route := ub.cloneRoute(RouteNameCatalog)
|
||||
|
||||
catalogURL, err := route.URL()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return appendValuesURL(catalogURL, values...).String(), nil
|
||||
}
|
||||
|
||||
// BuildTagsURL constructs a url to list the tags in the named repository.
|
||||
func (ub *URLBuilder) BuildTagsURL(name reference.Named) (string, error) {
|
||||
route := ub.cloneRoute(RouteNameTags)
|
||||
|
||||
tagsURL, err := route.URL("name", name.Name())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tagsURL.String(), nil
|
||||
}
|
||||
|
||||
// BuildManifestURL constructs a url for the manifest identified by name and
|
||||
// reference. The argument reference may be either a tag or digest.
|
||||
func (ub *URLBuilder) BuildManifestURL(ref reference.Named) (string, error) {
|
||||
route := ub.cloneRoute(RouteNameManifest)
|
||||
|
||||
tagOrDigest := ""
|
||||
switch v := ref.(type) {
|
||||
case reference.Tagged:
|
||||
tagOrDigest = v.Tag()
|
||||
case reference.Digested:
|
||||
tagOrDigest = v.Digest().String()
|
||||
}
|
||||
|
||||
manifestURL, err := route.URL("name", ref.Name(), "reference", tagOrDigest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return manifestURL.String(), nil
|
||||
}
|
||||
|
||||
// BuildBlobURL constructs the url for the blob identified by name and dgst.
|
||||
func (ub *URLBuilder) BuildBlobURL(ref reference.Canonical) (string, error) {
|
||||
route := ub.cloneRoute(RouteNameBlob)
|
||||
|
||||
layerURL, err := route.URL("name", ref.Name(), "digest", ref.Digest().String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return layerURL.String(), nil
|
||||
}
|
||||
|
||||
// BuildBlobUploadURL constructs a url to begin a blob upload in the
|
||||
// repository identified by name.
|
||||
func (ub *URLBuilder) BuildBlobUploadURL(name reference.Named, values ...url.Values) (string, error) {
|
||||
route := ub.cloneRoute(RouteNameBlobUpload)
|
||||
|
||||
uploadURL, err := route.URL("name", name.Name())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return appendValuesURL(uploadURL, values...).String(), nil
|
||||
}
|
||||
|
||||
// BuildBlobUploadChunkURL constructs a url for the upload identified by uuid,
|
||||
// including any url values. This should generally not be used by clients, as
|
||||
// this url is provided by server implementations during the blob upload
|
||||
// process.
|
||||
func (ub *URLBuilder) BuildBlobUploadChunkURL(name reference.Named, uuid string, values ...url.Values) (string, error) {
|
||||
route := ub.cloneRoute(RouteNameBlobUploadChunk)
|
||||
|
||||
uploadURL, err := route.URL("name", name.Name(), "uuid", uuid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return appendValuesURL(uploadURL, values...).String(), nil
|
||||
}
|
||||
|
||||
// clondedRoute returns a clone of the named route from the router. Routes
|
||||
// must be cloned to avoid modifying them during url generation.
|
||||
func (ub *URLBuilder) cloneRoute(name string) clonedRoute {
|
||||
route := new(mux.Route)
|
||||
root := new(url.URL)
|
||||
|
||||
*route = *ub.router.GetRoute(name) // clone the route
|
||||
*root = *ub.root
|
||||
|
||||
return clonedRoute{Route: route, root: root, relative: ub.relative}
|
||||
}
|
||||
|
||||
type clonedRoute struct {
|
||||
*mux.Route
|
||||
root *url.URL
|
||||
relative bool
|
||||
}
|
||||
|
||||
func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) {
|
||||
routeURL, err := cr.Route.URL(pairs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cr.relative {
|
||||
return routeURL, nil
|
||||
}
|
||||
|
||||
if routeURL.Scheme == "" && routeURL.User == nil && routeURL.Host == "" {
|
||||
routeURL.Path = routeURL.Path[1:]
|
||||
}
|
||||
|
||||
url := cr.root.ResolveReference(routeURL)
|
||||
url.Scheme = cr.root.Scheme
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// appendValuesURL appends the parameters to the url.
|
||||
func appendValuesURL(u *url.URL, values ...url.Values) *url.URL {
|
||||
merged := u.Query()
|
||||
|
||||
for _, v := range values {
|
||||
for k, vv := range v {
|
||||
merged[k] = append(merged[k], vv...)
|
||||
}
|
||||
}
|
||||
|
||||
u.RawQuery = merged.Encode()
|
||||
return u
|
||||
}
|
||||
|
||||
// appendValues appends the parameters to the url. Panics if the string is not
|
||||
// a url.
|
||||
func appendValues(u string, values ...url.Values) string {
|
||||
up, err := url.Parse(u)
|
||||
|
||||
if err != nil {
|
||||
panic(err) // should never happen
|
||||
}
|
||||
|
||||
return appendValuesURL(up, values...).String()
|
||||
}
|
||||
|
||||
// isIPv6Address returns true if given string is a valid IPv6 address. No port is allowed. The address may be
|
||||
// enclosed in square brackets.
|
||||
func isIPv6Address(host string) bool {
|
||||
if len(host) > 1 && host[0] == '[' && host[len(host)-1] == ']' {
|
||||
host = host[1 : len(host)-1]
|
||||
}
|
||||
// The IPv6 scoped addressing zone identifier starts after the last percent sign.
|
||||
if i := strings.LastIndexByte(host, '%'); i > 0 {
|
||||
host = host[:i]
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
if ip.To16() == nil {
|
||||
return false
|
||||
}
|
||||
if ip.To4() == nil {
|
||||
return true
|
||||
}
|
||||
// dot can be present in ipv4-mapped address, it needs to come after a colon though
|
||||
i := strings.IndexAny(host, ":.")
|
||||
return i >= 0 && host[i] == ':'
|
||||
}
|
58
vendor/github.com/docker/distribution/registry/client/auth/api_version.go
generated
vendored
Normal file
58
vendor/github.com/docker/distribution/registry/client/auth/api_version.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// APIVersion represents a version of an API including its
|
||||
// type and version number.
|
||||
type APIVersion struct {
|
||||
// Type refers to the name of a specific API specification
|
||||
// such as "registry"
|
||||
Type string
|
||||
|
||||
// Version is the version of the API specification implemented,
|
||||
// This may omit the revision number and only include
|
||||
// the major and minor version, such as "2.0"
|
||||
Version string
|
||||
}
|
||||
|
||||
// String returns the string formatted API Version
|
||||
func (v APIVersion) String() string {
|
||||
return v.Type + "/" + v.Version
|
||||
}
|
||||
|
||||
// APIVersions gets the API versions out of an HTTP response using the provided
|
||||
// version header as the key for the HTTP header.
|
||||
func APIVersions(resp *http.Response, versionHeader string) []APIVersion {
|
||||
versions := []APIVersion{}
|
||||
if versionHeader != "" {
|
||||
for _, supportedVersions := range resp.Header[http.CanonicalHeaderKey(versionHeader)] {
|
||||
for _, version := range strings.Fields(supportedVersions) {
|
||||
versions = append(versions, ParseAPIVersion(version))
|
||||
}
|
||||
}
|
||||
}
|
||||
return versions
|
||||
}
|
||||
|
||||
// ParseAPIVersion parses an API version string into an APIVersion
|
||||
// Format (Expected, not enforced):
|
||||
// API version string = <API type> '/' <API version>
|
||||
// API type = [a-z][a-z0-9]*
|
||||
// API version = [0-9]+(\.[0-9]+)?
|
||||
// TODO(dmcgowan): Enforce format, add error condition, remove unknown type
|
||||
func ParseAPIVersion(versionStr string) APIVersion {
|
||||
idx := strings.IndexRune(versionStr, '/')
|
||||
if idx == -1 {
|
||||
return APIVersion{
|
||||
Type: "unknown",
|
||||
Version: versionStr,
|
||||
}
|
||||
}
|
||||
return APIVersion{
|
||||
Type: strings.ToLower(versionStr[:idx]),
|
||||
Version: versionStr[idx+1:],
|
||||
}
|
||||
}
|
27
vendor/github.com/docker/distribution/registry/client/auth/challenge/addr.go
generated
vendored
Normal file
27
vendor/github.com/docker/distribution/registry/client/auth/challenge/addr.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
package challenge
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FROM: https://golang.org/src/net/http/http.go
|
||||
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
|
||||
// return true if the string includes a port.
|
||||
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
|
||||
|
||||
// FROM: http://golang.org/src/net/http/transport.go
|
||||
var portMap = map[string]string{
|
||||
"http": "80",
|
||||
"https": "443",
|
||||
}
|
||||
|
||||
// canonicalAddr returns url.Host but always with a ":port" suffix
|
||||
// FROM: http://golang.org/src/net/http/transport.go
|
||||
func canonicalAddr(url *url.URL) string {
|
||||
addr := url.Host
|
||||
if !hasPort(addr) {
|
||||
return addr + ":" + portMap[url.Scheme]
|
||||
}
|
||||
return addr
|
||||
}
|
237
vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge.go
generated
vendored
Normal file
237
vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge.go
generated
vendored
Normal file
@ -0,0 +1,237 @@
|
||||
package challenge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Challenge carries information from a WWW-Authenticate response header.
|
||||
// See RFC 2617.
|
||||
type Challenge struct {
|
||||
// Scheme is the auth-scheme according to RFC 2617
|
||||
Scheme string
|
||||
|
||||
// Parameters are the auth-params according to RFC 2617
|
||||
Parameters map[string]string
|
||||
}
|
||||
|
||||
// Manager manages the challenges for endpoints.
|
||||
// The challenges are pulled out of HTTP responses. Only
|
||||
// responses which expect challenges should be added to
|
||||
// the manager, since a non-unauthorized request will be
|
||||
// viewed as not requiring challenges.
|
||||
type Manager interface {
|
||||
// GetChallenges returns the challenges for the given
|
||||
// endpoint URL.
|
||||
GetChallenges(endpoint url.URL) ([]Challenge, error)
|
||||
|
||||
// AddResponse adds the response to the challenge
|
||||
// manager. The challenges will be parsed out of
|
||||
// the WWW-Authenicate headers and added to the
|
||||
// URL which was produced the response. If the
|
||||
// response was authorized, any challenges for the
|
||||
// endpoint will be cleared.
|
||||
AddResponse(resp *http.Response) error
|
||||
}
|
||||
|
||||
// NewSimpleManager returns an instance of
|
||||
// Manger which only maps endpoints to challenges
|
||||
// based on the responses which have been added the
|
||||
// manager. The simple manager will make no attempt to
|
||||
// perform requests on the endpoints or cache the responses
|
||||
// to a backend.
|
||||
func NewSimpleManager() Manager {
|
||||
return &simpleManager{
|
||||
Challanges: make(map[string][]Challenge),
|
||||
}
|
||||
}
|
||||
|
||||
type simpleManager struct {
|
||||
sync.RWMutex
|
||||
Challanges map[string][]Challenge
|
||||
}
|
||||
|
||||
func normalizeURL(endpoint *url.URL) {
|
||||
endpoint.Host = strings.ToLower(endpoint.Host)
|
||||
endpoint.Host = canonicalAddr(endpoint)
|
||||
}
|
||||
|
||||
func (m *simpleManager) GetChallenges(endpoint url.URL) ([]Challenge, error) {
|
||||
normalizeURL(&endpoint)
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
challenges := m.Challanges[endpoint.String()]
|
||||
return challenges, nil
|
||||
}
|
||||
|
||||
func (m *simpleManager) AddResponse(resp *http.Response) error {
|
||||
challenges := ResponseChallenges(resp)
|
||||
if resp.Request == nil {
|
||||
return fmt.Errorf("missing request reference")
|
||||
}
|
||||
urlCopy := url.URL{
|
||||
Path: resp.Request.URL.Path,
|
||||
Host: resp.Request.URL.Host,
|
||||
Scheme: resp.Request.URL.Scheme,
|
||||
}
|
||||
normalizeURL(&urlCopy)
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
m.Challanges[urlCopy.String()] = challenges
|
||||
return nil
|
||||
}
|
||||
|
||||
// Octet types from RFC 2616.
|
||||
type octetType byte
|
||||
|
||||
var octetTypes [256]octetType
|
||||
|
||||
const (
|
||||
isToken octetType = 1 << iota
|
||||
isSpace
|
||||
)
|
||||
|
||||
func init() {
|
||||
// OCTET = <any 8-bit sequence of data>
|
||||
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
||||
// CR = <US-ASCII CR, carriage return (13)>
|
||||
// LF = <US-ASCII LF, linefeed (10)>
|
||||
// SP = <US-ASCII SP, space (32)>
|
||||
// HT = <US-ASCII HT, horizontal-tab (9)>
|
||||
// <"> = <US-ASCII double-quote mark (34)>
|
||||
// CRLF = CR LF
|
||||
// LWS = [CRLF] 1*( SP | HT )
|
||||
// TEXT = <any OCTET except CTLs, but including LWS>
|
||||
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
||||
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
||||
// token = 1*<any CHAR except CTLs or separators>
|
||||
// qdtext = <any TEXT except <">>
|
||||
|
||||
for c := 0; c < 256; c++ {
|
||||
var t octetType
|
||||
isCtl := c <= 31 || c == 127
|
||||
isChar := 0 <= c && c <= 127
|
||||
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
||||
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
||||
t |= isSpace
|
||||
}
|
||||
if isChar && !isCtl && !isSeparator {
|
||||
t |= isToken
|
||||
}
|
||||
octetTypes[c] = t
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseChallenges returns a list of authorization challenges
|
||||
// for the given http Response. Challenges are only checked if
|
||||
// the response status code was a 401.
|
||||
func ResponseChallenges(resp *http.Response) []Challenge {
|
||||
if resp.StatusCode == http.StatusUnauthorized {
|
||||
// Parse the WWW-Authenticate Header and store the challenges
|
||||
// on this endpoint object.
|
||||
return parseAuthHeader(resp.Header)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseAuthHeader(header http.Header) []Challenge {
|
||||
challenges := []Challenge{}
|
||||
for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] {
|
||||
v, p := parseValueAndParams(h)
|
||||
if v != "" {
|
||||
challenges = append(challenges, Challenge{Scheme: v, Parameters: p})
|
||||
}
|
||||
}
|
||||
return challenges
|
||||
}
|
||||
|
||||
func parseValueAndParams(header string) (value string, params map[string]string) {
|
||||
params = make(map[string]string)
|
||||
value, s := expectToken(header)
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
value = strings.ToLower(value)
|
||||
s = "," + skipSpace(s)
|
||||
for strings.HasPrefix(s, ",") {
|
||||
var pkey string
|
||||
pkey, s = expectToken(skipSpace(s[1:]))
|
||||
if pkey == "" {
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(s, "=") {
|
||||
return
|
||||
}
|
||||
var pvalue string
|
||||
pvalue, s = expectTokenOrQuoted(s[1:])
|
||||
if pvalue == "" {
|
||||
return
|
||||
}
|
||||
pkey = strings.ToLower(pkey)
|
||||
params[pkey] = pvalue
|
||||
s = skipSpace(s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func skipSpace(s string) (rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isSpace == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[i:]
|
||||
}
|
||||
|
||||
func expectToken(s string) (token, rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isToken == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[:i], s[i:]
|
||||
}
|
||||
|
||||
func expectTokenOrQuoted(s string) (value string, rest string) {
|
||||
if !strings.HasPrefix(s, "\"") {
|
||||
return expectToken(s)
|
||||
}
|
||||
s = s[1:]
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '"':
|
||||
return s[:i], s[i+1:]
|
||||
case '\\':
|
||||
p := make([]byte, len(s)-1)
|
||||
j := copy(p, s[:i])
|
||||
escape := true
|
||||
for i = i + 1; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case escape:
|
||||
escape = false
|
||||
p[j] = b
|
||||
j++
|
||||
case b == '\\':
|
||||
escape = true
|
||||
case b == '"':
|
||||
return string(p[:j]), s[i+1:]
|
||||
default:
|
||||
p[j] = b
|
||||
j++
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
505
vendor/github.com/docker/distribution/registry/client/auth/session.go
generated
vendored
Normal file
505
vendor/github.com/docker/distribution/registry/client/auth/session.go
generated
vendored
Normal file
@ -0,0 +1,505 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/registry/client"
|
||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoBasicAuthCredentials is returned if a request can't be authorized with
|
||||
// basic auth due to lack of credentials.
|
||||
ErrNoBasicAuthCredentials = errors.New("no basic auth credentials")
|
||||
|
||||
// ErrNoToken is returned if a request is successful but the body does not
|
||||
// contain an authorization token.
|
||||
ErrNoToken = errors.New("authorization server did not include a token in the response")
|
||||
)
|
||||
|
||||
const defaultClientID = "registry-client"
|
||||
|
||||
// AuthenticationHandler is an interface for authorizing a request from
|
||||
// params from a "WWW-Authenicate" header for a single scheme.
|
||||
type AuthenticationHandler interface {
|
||||
// Scheme returns the scheme as expected from the "WWW-Authenicate" header.
|
||||
Scheme() string
|
||||
|
||||
// AuthorizeRequest adds the authorization header to a request (if needed)
|
||||
// using the parameters from "WWW-Authenticate" method. The parameters
|
||||
// values depend on the scheme.
|
||||
AuthorizeRequest(req *http.Request, params map[string]string) error
|
||||
}
|
||||
|
||||
// CredentialStore is an interface for getting credentials for
|
||||
// a given URL
|
||||
type CredentialStore interface {
|
||||
// Basic returns basic auth for the given URL
|
||||
Basic(*url.URL) (string, string)
|
||||
|
||||
// RefreshToken returns a refresh token for the
|
||||
// given URL and service
|
||||
RefreshToken(*url.URL, string) string
|
||||
|
||||
// SetRefreshToken sets the refresh token if none
|
||||
// is provided for the given url and service
|
||||
SetRefreshToken(realm *url.URL, service, token string)
|
||||
}
|
||||
|
||||
// NewAuthorizer creates an authorizer which can handle multiple authentication
|
||||
// schemes. The handlers are tried in order, the higher priority authentication
|
||||
// methods should be first. The challengeMap holds a list of challenges for
|
||||
// a given root API endpoint (for example "https://registry-1.docker.io/v2/").
|
||||
func NewAuthorizer(manager challenge.Manager, handlers ...AuthenticationHandler) transport.RequestModifier {
|
||||
return &endpointAuthorizer{
|
||||
challenges: manager,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
type endpointAuthorizer struct {
|
||||
challenges challenge.Manager
|
||||
handlers []AuthenticationHandler
|
||||
transport http.RoundTripper
|
||||
}
|
||||
|
||||
func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error {
|
||||
pingPath := req.URL.Path
|
||||
if v2Root := strings.Index(req.URL.Path, "/v2/"); v2Root != -1 {
|
||||
pingPath = pingPath[:v2Root+4]
|
||||
} else if v1Root := strings.Index(req.URL.Path, "/v1/"); v1Root != -1 {
|
||||
pingPath = pingPath[:v1Root] + "/v2/"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
ping := url.URL{
|
||||
Host: req.URL.Host,
|
||||
Scheme: req.URL.Scheme,
|
||||
Path: pingPath,
|
||||
}
|
||||
|
||||
challenges, err := ea.challenges.GetChallenges(ping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(challenges) > 0 {
|
||||
for _, handler := range ea.handlers {
|
||||
for _, c := range challenges {
|
||||
if c.Scheme != handler.Scheme() {
|
||||
continue
|
||||
}
|
||||
if err := handler.AuthorizeRequest(req, c.Parameters); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is the minimum duration a token can last (in seconds).
|
||||
// A token must not live less than 60 seconds because older versions
|
||||
// of the Docker client didn't read their expiration from the token
|
||||
// response and assumed 60 seconds. So to remain compatible with
|
||||
// those implementations, a token must live at least this long.
|
||||
const minimumTokenLifetimeSeconds = 60
|
||||
|
||||
// Private interface for time used by this package to enable tests to provide their own implementation.
|
||||
type clock interface {
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
type tokenHandler struct {
|
||||
header http.Header
|
||||
creds CredentialStore
|
||||
transport http.RoundTripper
|
||||
clock clock
|
||||
|
||||
offlineAccess bool
|
||||
forceOAuth bool
|
||||
clientID string
|
||||
scopes []Scope
|
||||
|
||||
tokenLock sync.Mutex
|
||||
tokenCache string
|
||||
tokenExpiration time.Time
|
||||
}
|
||||
|
||||
// Scope is a type which is serializable to a string
|
||||
// using the allow scope grammar.
|
||||
type Scope interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
// RepositoryScope represents a token scope for access
|
||||
// to a repository.
|
||||
type RepositoryScope struct {
|
||||
Repository string
|
||||
Class string
|
||||
Actions []string
|
||||
}
|
||||
|
||||
// String returns the string representation of the repository
|
||||
// using the scope grammar
|
||||
func (rs RepositoryScope) String() string {
|
||||
repoType := "repository"
|
||||
// Keep existing format for image class to maintain backwards compatibility
|
||||
// with authorization servers which do not support the expanded grammar.
|
||||
if rs.Class != "" && rs.Class != "image" {
|
||||
repoType = fmt.Sprintf("%s(%s)", repoType, rs.Class)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%s", repoType, rs.Repository, strings.Join(rs.Actions, ","))
|
||||
}
|
||||
|
||||
// RegistryScope represents a token scope for access
|
||||
// to resources in the registry.
|
||||
type RegistryScope struct {
|
||||
Name string
|
||||
Actions []string
|
||||
}
|
||||
|
||||
// String returns the string representation of the user
|
||||
// using the scope grammar
|
||||
func (rs RegistryScope) String() string {
|
||||
return fmt.Sprintf("registry:%s:%s", rs.Name, strings.Join(rs.Actions, ","))
|
||||
}
|
||||
|
||||
// TokenHandlerOptions is used to configure a new token handler
|
||||
type TokenHandlerOptions struct {
|
||||
Transport http.RoundTripper
|
||||
Credentials CredentialStore
|
||||
|
||||
OfflineAccess bool
|
||||
ForceOAuth bool
|
||||
ClientID string
|
||||
Scopes []Scope
|
||||
}
|
||||
|
||||
// An implementation of clock for providing real time data.
|
||||
type realClock struct{}
|
||||
|
||||
// Now implements clock
|
||||
func (realClock) Now() time.Time { return time.Now() }
|
||||
|
||||
// NewTokenHandler creates a new AuthenicationHandler which supports
|
||||
// fetching tokens from a remote token server.
|
||||
func NewTokenHandler(transport http.RoundTripper, creds CredentialStore, scope string, actions ...string) AuthenticationHandler {
|
||||
// Create options...
|
||||
return NewTokenHandlerWithOptions(TokenHandlerOptions{
|
||||
Transport: transport,
|
||||
Credentials: creds,
|
||||
Scopes: []Scope{
|
||||
RepositoryScope{
|
||||
Repository: scope,
|
||||
Actions: actions,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// NewTokenHandlerWithOptions creates a new token handler using the provided
|
||||
// options structure.
|
||||
func NewTokenHandlerWithOptions(options TokenHandlerOptions) AuthenticationHandler {
|
||||
handler := &tokenHandler{
|
||||
transport: options.Transport,
|
||||
creds: options.Credentials,
|
||||
offlineAccess: options.OfflineAccess,
|
||||
forceOAuth: options.ForceOAuth,
|
||||
clientID: options.ClientID,
|
||||
scopes: options.Scopes,
|
||||
clock: realClock{},
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func (th *tokenHandler) client() *http.Client {
|
||||
return &http.Client{
|
||||
Transport: th.transport,
|
||||
Timeout: 15 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func (th *tokenHandler) Scheme() string {
|
||||
return "bearer"
|
||||
}
|
||||
|
||||
func (th *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
|
||||
var additionalScopes []string
|
||||
if fromParam := req.URL.Query().Get("from"); fromParam != "" {
|
||||
additionalScopes = append(additionalScopes, RepositoryScope{
|
||||
Repository: fromParam,
|
||||
Actions: []string{"pull"},
|
||||
}.String())
|
||||
}
|
||||
|
||||
token, err := th.getToken(params, additionalScopes...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (th *tokenHandler) getToken(params map[string]string, additionalScopes ...string) (string, error) {
|
||||
th.tokenLock.Lock()
|
||||
defer th.tokenLock.Unlock()
|
||||
scopes := make([]string, 0, len(th.scopes)+len(additionalScopes))
|
||||
for _, scope := range th.scopes {
|
||||
scopes = append(scopes, scope.String())
|
||||
}
|
||||
var addedScopes bool
|
||||
for _, scope := range additionalScopes {
|
||||
scopes = append(scopes, scope)
|
||||
addedScopes = true
|
||||
}
|
||||
|
||||
now := th.clock.Now()
|
||||
if now.After(th.tokenExpiration) || addedScopes {
|
||||
token, expiration, err := th.fetchToken(params, scopes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// do not update cache for added scope tokens
|
||||
if !addedScopes {
|
||||
th.tokenCache = token
|
||||
th.tokenExpiration = expiration
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
return th.tokenCache, nil
|
||||
}
|
||||
|
||||
type postTokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
func (th *tokenHandler) fetchTokenWithOAuth(realm *url.URL, refreshToken, service string, scopes []string) (token string, expiration time.Time, err error) {
|
||||
form := url.Values{}
|
||||
form.Set("scope", strings.Join(scopes, " "))
|
||||
form.Set("service", service)
|
||||
|
||||
clientID := th.clientID
|
||||
if clientID == "" {
|
||||
// Use default client, this is a required field
|
||||
clientID = defaultClientID
|
||||
}
|
||||
form.Set("client_id", clientID)
|
||||
|
||||
if refreshToken != "" {
|
||||
form.Set("grant_type", "refresh_token")
|
||||
form.Set("refresh_token", refreshToken)
|
||||
} else if th.creds != nil {
|
||||
form.Set("grant_type", "password")
|
||||
username, password := th.creds.Basic(realm)
|
||||
form.Set("username", username)
|
||||
form.Set("password", password)
|
||||
|
||||
// attempt to get a refresh token
|
||||
form.Set("access_type", "offline")
|
||||
} else {
|
||||
// refuse to do oauth without a grant type
|
||||
return "", time.Time{}, fmt.Errorf("no supported grant type")
|
||||
}
|
||||
|
||||
resp, err := th.client().PostForm(realm.String(), form)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !client.SuccessStatus(resp.StatusCode) {
|
||||
err := client.HandleErrorResponse(resp)
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
|
||||
var tr postTokenResponse
|
||||
if err = decoder.Decode(&tr); err != nil {
|
||||
return "", time.Time{}, fmt.Errorf("unable to decode token response: %s", err)
|
||||
}
|
||||
|
||||
if tr.RefreshToken != "" && tr.RefreshToken != refreshToken {
|
||||
th.creds.SetRefreshToken(realm, service, tr.RefreshToken)
|
||||
}
|
||||
|
||||
if tr.ExpiresIn < minimumTokenLifetimeSeconds {
|
||||
// The default/minimum lifetime.
|
||||
tr.ExpiresIn = minimumTokenLifetimeSeconds
|
||||
logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
|
||||
}
|
||||
|
||||
if tr.IssuedAt.IsZero() {
|
||||
// issued_at is optional in the token response.
|
||||
tr.IssuedAt = th.clock.Now().UTC()
|
||||
}
|
||||
|
||||
return tr.AccessToken, tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second), nil
|
||||
}
|
||||
|
||||
type getTokenResponse struct {
|
||||
Token string `json:"token"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
func (th *tokenHandler) fetchTokenWithBasicAuth(realm *url.URL, service string, scopes []string) (token string, expiration time.Time, err error) {
|
||||
|
||||
req, err := http.NewRequest("GET", realm.String(), nil)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
reqParams := req.URL.Query()
|
||||
|
||||
if service != "" {
|
||||
reqParams.Add("service", service)
|
||||
}
|
||||
|
||||
for _, scope := range scopes {
|
||||
reqParams.Add("scope", scope)
|
||||
}
|
||||
|
||||
if th.offlineAccess {
|
||||
reqParams.Add("offline_token", "true")
|
||||
clientID := th.clientID
|
||||
if clientID == "" {
|
||||
clientID = defaultClientID
|
||||
}
|
||||
reqParams.Add("client_id", clientID)
|
||||
}
|
||||
|
||||
if th.creds != nil {
|
||||
username, password := th.creds.Basic(realm)
|
||||
if username != "" && password != "" {
|
||||
reqParams.Add("account", username)
|
||||
req.SetBasicAuth(username, password)
|
||||
}
|
||||
}
|
||||
|
||||
req.URL.RawQuery = reqParams.Encode()
|
||||
|
||||
resp, err := th.client().Do(req)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !client.SuccessStatus(resp.StatusCode) {
|
||||
err := client.HandleErrorResponse(resp)
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
|
||||
var tr getTokenResponse
|
||||
if err = decoder.Decode(&tr); err != nil {
|
||||
return "", time.Time{}, fmt.Errorf("unable to decode token response: %s", err)
|
||||
}
|
||||
|
||||
if tr.RefreshToken != "" && th.creds != nil {
|
||||
th.creds.SetRefreshToken(realm, service, tr.RefreshToken)
|
||||
}
|
||||
|
||||
// `access_token` is equivalent to `token` and if both are specified
|
||||
// the choice is undefined. Canonicalize `access_token` by sticking
|
||||
// things in `token`.
|
||||
if tr.AccessToken != "" {
|
||||
tr.Token = tr.AccessToken
|
||||
}
|
||||
|
||||
if tr.Token == "" {
|
||||
return "", time.Time{}, ErrNoToken
|
||||
}
|
||||
|
||||
if tr.ExpiresIn < minimumTokenLifetimeSeconds {
|
||||
// The default/minimum lifetime.
|
||||
tr.ExpiresIn = minimumTokenLifetimeSeconds
|
||||
logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
|
||||
}
|
||||
|
||||
if tr.IssuedAt.IsZero() {
|
||||
// issued_at is optional in the token response.
|
||||
tr.IssuedAt = th.clock.Now().UTC()
|
||||
}
|
||||
|
||||
return tr.Token, tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second), nil
|
||||
}
|
||||
|
||||
func (th *tokenHandler) fetchToken(params map[string]string, scopes []string) (token string, expiration time.Time, err error) {
|
||||
realm, ok := params["realm"]
|
||||
if !ok {
|
||||
return "", time.Time{}, errors.New("no realm specified for token auth challenge")
|
||||
}
|
||||
|
||||
// TODO(dmcgowan): Handle empty scheme and relative realm
|
||||
realmURL, err := url.Parse(realm)
|
||||
if err != nil {
|
||||
return "", time.Time{}, fmt.Errorf("invalid token auth challenge realm: %s", err)
|
||||
}
|
||||
|
||||
service := params["service"]
|
||||
|
||||
var refreshToken string
|
||||
|
||||
if th.creds != nil {
|
||||
refreshToken = th.creds.RefreshToken(realmURL, service)
|
||||
}
|
||||
|
||||
if refreshToken != "" || th.forceOAuth {
|
||||
return th.fetchTokenWithOAuth(realmURL, refreshToken, service, scopes)
|
||||
}
|
||||
|
||||
return th.fetchTokenWithBasicAuth(realmURL, service, scopes)
|
||||
}
|
||||
|
||||
type basicHandler struct {
|
||||
creds CredentialStore
|
||||
}
|
||||
|
||||
// NewBasicHandler creaters a new authentiation handler which adds
|
||||
// basic authentication credentials to a request.
|
||||
func NewBasicHandler(creds CredentialStore) AuthenticationHandler {
|
||||
return &basicHandler{
|
||||
creds: creds,
|
||||
}
|
||||
}
|
||||
|
||||
func (*basicHandler) Scheme() string {
|
||||
return "basic"
|
||||
}
|
||||
|
||||
func (bh *basicHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
|
||||
if bh.creds != nil {
|
||||
username, password := bh.creds.Basic(req.URL)
|
||||
if username != "" && password != "" {
|
||||
req.SetBasicAuth(username, password)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrNoBasicAuthCredentials
|
||||
}
|
162
vendor/github.com/docker/distribution/registry/client/blob_writer.go
generated
vendored
Normal file
162
vendor/github.com/docker/distribution/registry/client/blob_writer.go
generated
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/context"
|
||||
)
|
||||
|
||||
type httpBlobUpload struct {
|
||||
statter distribution.BlobStatter
|
||||
client *http.Client
|
||||
|
||||
uuid string
|
||||
startedAt time.Time
|
||||
|
||||
location string // always the last value of the location header.
|
||||
offset int64
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (hbu *httpBlobUpload) Reader() (io.ReadCloser, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
func (hbu *httpBlobUpload) handleErrorResponse(resp *http.Response) error {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return distribution.ErrBlobUploadUnknown
|
||||
}
|
||||
return HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func (hbu *httpBlobUpload) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
req, err := http.NewRequest("PATCH", hbu.location, ioutil.NopCloser(r))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer req.Body.Close()
|
||||
|
||||
resp, err := hbu.client.Do(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !SuccessStatus(resp.StatusCode) {
|
||||
return 0, hbu.handleErrorResponse(resp)
|
||||
}
|
||||
|
||||
hbu.uuid = resp.Header.Get("Docker-Upload-UUID")
|
||||
hbu.location, err = sanitizeLocation(resp.Header.Get("Location"), hbu.location)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
rng := resp.Header.Get("Range")
|
||||
var start, end int64
|
||||
if n, err := fmt.Sscanf(rng, "%d-%d", &start, &end); err != nil {
|
||||
return 0, err
|
||||
} else if n != 2 || end < start {
|
||||
return 0, fmt.Errorf("bad range format: %s", rng)
|
||||
}
|
||||
|
||||
return (end - start + 1), nil
|
||||
|
||||
}
|
||||
|
||||
func (hbu *httpBlobUpload) Write(p []byte) (n int, err error) {
|
||||
req, err := http.NewRequest("PATCH", hbu.location, bytes.NewReader(p))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("%d-%d", hbu.offset, hbu.offset+int64(len(p)-1)))
|
||||
req.Header.Set("Content-Length", fmt.Sprintf("%d", len(p)))
|
||||
req.Header.Set("Content-Type", "application/octet-stream")
|
||||
|
||||
resp, err := hbu.client.Do(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !SuccessStatus(resp.StatusCode) {
|
||||
return 0, hbu.handleErrorResponse(resp)
|
||||
}
|
||||
|
||||
hbu.uuid = resp.Header.Get("Docker-Upload-UUID")
|
||||
hbu.location, err = sanitizeLocation(resp.Header.Get("Location"), hbu.location)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
rng := resp.Header.Get("Range")
|
||||
var start, end int
|
||||
if n, err := fmt.Sscanf(rng, "%d-%d", &start, &end); err != nil {
|
||||
return 0, err
|
||||
} else if n != 2 || end < start {
|
||||
return 0, fmt.Errorf("bad range format: %s", rng)
|
||||
}
|
||||
|
||||
return (end - start + 1), nil
|
||||
|
||||
}
|
||||
|
||||
func (hbu *httpBlobUpload) Size() int64 {
|
||||
return hbu.offset
|
||||
}
|
||||
|
||||
func (hbu *httpBlobUpload) ID() string {
|
||||
return hbu.uuid
|
||||
}
|
||||
|
||||
func (hbu *httpBlobUpload) StartedAt() time.Time {
|
||||
return hbu.startedAt
|
||||
}
|
||||
|
||||
func (hbu *httpBlobUpload) Commit(ctx context.Context, desc distribution.Descriptor) (distribution.Descriptor, error) {
|
||||
// TODO(dmcgowan): Check if already finished, if so just fetch
|
||||
req, err := http.NewRequest("PUT", hbu.location, nil)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
|
||||
values := req.URL.Query()
|
||||
values.Set("digest", desc.Digest.String())
|
||||
req.URL.RawQuery = values.Encode()
|
||||
|
||||
resp, err := hbu.client.Do(req)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !SuccessStatus(resp.StatusCode) {
|
||||
return distribution.Descriptor{}, hbu.handleErrorResponse(resp)
|
||||
}
|
||||
|
||||
return hbu.statter.Stat(ctx, desc.Digest)
|
||||
}
|
||||
|
||||
func (hbu *httpBlobUpload) Cancel(ctx context.Context) error {
|
||||
req, err := http.NewRequest("DELETE", hbu.location, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := hbu.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound || SuccessStatus(resp.StatusCode) {
|
||||
return nil
|
||||
}
|
||||
return hbu.handleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func (hbu *httpBlobUpload) Close() error {
|
||||
hbu.closed = true
|
||||
return nil
|
||||
}
|
139
vendor/github.com/docker/distribution/registry/client/errors.go
generated
vendored
Normal file
139
vendor/github.com/docker/distribution/registry/client/errors.go
generated
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||
)
|
||||
|
||||
// ErrNoErrorsInBody is returned when an HTTP response body parses to an empty
|
||||
// errcode.Errors slice.
|
||||
var ErrNoErrorsInBody = errors.New("no error details found in HTTP response body")
|
||||
|
||||
// UnexpectedHTTPStatusError is returned when an unexpected HTTP status is
|
||||
// returned when making a registry api call.
|
||||
type UnexpectedHTTPStatusError struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
func (e *UnexpectedHTTPStatusError) Error() string {
|
||||
return fmt.Sprintf("received unexpected HTTP status: %s", e.Status)
|
||||
}
|
||||
|
||||
// UnexpectedHTTPResponseError is returned when an expected HTTP status code
|
||||
// is returned, but the content was unexpected and failed to be parsed.
|
||||
type UnexpectedHTTPResponseError struct {
|
||||
ParseErr error
|
||||
StatusCode int
|
||||
Response []byte
|
||||
}
|
||||
|
||||
func (e *UnexpectedHTTPResponseError) Error() string {
|
||||
return fmt.Sprintf("error parsing HTTP %d response body: %s: %q", e.StatusCode, e.ParseErr.Error(), string(e.Response))
|
||||
}
|
||||
|
||||
func parseHTTPErrorResponse(statusCode int, r io.Reader) error {
|
||||
var errors errcode.Errors
|
||||
body, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For backward compatibility, handle irregularly formatted
|
||||
// messages that contain a "details" field.
|
||||
var detailsErr struct {
|
||||
Details string `json:"details"`
|
||||
}
|
||||
err = json.Unmarshal(body, &detailsErr)
|
||||
if err == nil && detailsErr.Details != "" {
|
||||
switch statusCode {
|
||||
case http.StatusUnauthorized:
|
||||
return errcode.ErrorCodeUnauthorized.WithMessage(detailsErr.Details)
|
||||
case http.StatusTooManyRequests:
|
||||
return errcode.ErrorCodeTooManyRequests.WithMessage(detailsErr.Details)
|
||||
default:
|
||||
return errcode.ErrorCodeUnknown.WithMessage(detailsErr.Details)
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &errors); err != nil {
|
||||
return &UnexpectedHTTPResponseError{
|
||||
ParseErr: err,
|
||||
StatusCode: statusCode,
|
||||
Response: body,
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) == 0 {
|
||||
// If there was no error specified in the body, return
|
||||
// UnexpectedHTTPResponseError.
|
||||
return &UnexpectedHTTPResponseError{
|
||||
ParseErr: ErrNoErrorsInBody,
|
||||
StatusCode: statusCode,
|
||||
Response: body,
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func makeErrorList(err error) []error {
|
||||
if errL, ok := err.(errcode.Errors); ok {
|
||||
return []error(errL)
|
||||
}
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
func mergeErrors(err1, err2 error) error {
|
||||
return errcode.Errors(append(makeErrorList(err1), makeErrorList(err2)...))
|
||||
}
|
||||
|
||||
// HandleErrorResponse returns error parsed from HTTP response for an
|
||||
// unsuccessful HTTP response code (in the range 400 - 499 inclusive). An
|
||||
// UnexpectedHTTPStatusError returned for response code outside of expected
|
||||
// range.
|
||||
func HandleErrorResponse(resp *http.Response) error {
|
||||
if resp.StatusCode >= 400 && resp.StatusCode < 500 {
|
||||
// Check for OAuth errors within the `WWW-Authenticate` header first
|
||||
// See https://tools.ietf.org/html/rfc6750#section-3
|
||||
for _, c := range challenge.ResponseChallenges(resp) {
|
||||
if c.Scheme == "bearer" {
|
||||
var err errcode.Error
|
||||
// codes defined at https://tools.ietf.org/html/rfc6750#section-3.1
|
||||
switch c.Parameters["error"] {
|
||||
case "invalid_token":
|
||||
err.Code = errcode.ErrorCodeUnauthorized
|
||||
case "insufficient_scope":
|
||||
err.Code = errcode.ErrorCodeDenied
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if description := c.Parameters["error_description"]; description != "" {
|
||||
err.Message = description
|
||||
} else {
|
||||
err.Message = err.Code.Message()
|
||||
}
|
||||
|
||||
return mergeErrors(err, parseHTTPErrorResponse(resp.StatusCode, resp.Body))
|
||||
}
|
||||
}
|
||||
err := parseHTTPErrorResponse(resp.StatusCode, resp.Body)
|
||||
if uErr, ok := err.(*UnexpectedHTTPResponseError); ok && resp.StatusCode == 401 {
|
||||
return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return &UnexpectedHTTPStatusError{Status: resp.Status}
|
||||
}
|
||||
|
||||
// SuccessStatus returns true if the argument is a successful HTTP response
|
||||
// code (in the range 200 - 399 inclusive).
|
||||
func SuccessStatus(status int) bool {
|
||||
return status >= 200 && status <= 399
|
||||
}
|
853
vendor/github.com/docker/distribution/registry/client/repository.go
generated
vendored
Normal file
853
vendor/github.com/docker/distribution/registry/client/repository.go
generated
vendored
Normal file
@ -0,0 +1,853 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/distribution/registry/api/v2"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/distribution/registry/storage/cache"
|
||||
"github.com/docker/distribution/registry/storage/cache/memory"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// Registry provides an interface for calling Repositories, which returns a catalog of repositories.
|
||||
type Registry interface {
|
||||
Repositories(ctx context.Context, repos []string, last string) (n int, err error)
|
||||
}
|
||||
|
||||
// checkHTTPRedirect is a callback that can manipulate redirected HTTP
|
||||
// requests. It is used to preserve Accept and Range headers.
|
||||
func checkHTTPRedirect(req *http.Request, via []*http.Request) error {
|
||||
if len(via) >= 10 {
|
||||
return errors.New("stopped after 10 redirects")
|
||||
}
|
||||
|
||||
if len(via) > 0 {
|
||||
for headerName, headerVals := range via[0].Header {
|
||||
if headerName != "Accept" && headerName != "Range" {
|
||||
continue
|
||||
}
|
||||
for _, val := range headerVals {
|
||||
// Don't add to redirected request if redirected
|
||||
// request already has a header with the same
|
||||
// name and value.
|
||||
hasValue := false
|
||||
for _, existingVal := range req.Header[headerName] {
|
||||
if existingVal == val {
|
||||
hasValue = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasValue {
|
||||
req.Header.Add(headerName, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewRegistry creates a registry namespace which can be used to get a listing of repositories
|
||||
func NewRegistry(ctx context.Context, baseURL string, transport http.RoundTripper) (Registry, error) {
|
||||
ub, err := v2.NewURLBuilderFromString(baseURL, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: 1 * time.Minute,
|
||||
CheckRedirect: checkHTTPRedirect,
|
||||
}
|
||||
|
||||
return ®istry{
|
||||
client: client,
|
||||
ub: ub,
|
||||
context: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type registry struct {
|
||||
client *http.Client
|
||||
ub *v2.URLBuilder
|
||||
context context.Context
|
||||
}
|
||||
|
||||
// Repositories returns a lexigraphically sorted catalog given a base URL. The 'entries' slice will be filled up to the size
|
||||
// of the slice, starting at the value provided in 'last'. The number of entries will be returned along with io.EOF if there
|
||||
// are no more entries
|
||||
func (r *registry) Repositories(ctx context.Context, entries []string, last string) (int, error) {
|
||||
var numFilled int
|
||||
var returnErr error
|
||||
|
||||
values := buildCatalogValues(len(entries), last)
|
||||
u, err := r.ub.BuildCatalogURL(values)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
resp, err := r.client.Get(u)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
var ctlg struct {
|
||||
Repositories []string `json:"repositories"`
|
||||
}
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
|
||||
if err := decoder.Decode(&ctlg); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for cnt := range ctlg.Repositories {
|
||||
entries[cnt] = ctlg.Repositories[cnt]
|
||||
}
|
||||
numFilled = len(ctlg.Repositories)
|
||||
|
||||
link := resp.Header.Get("Link")
|
||||
if link == "" {
|
||||
returnErr = io.EOF
|
||||
}
|
||||
} else {
|
||||
return 0, HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
return numFilled, returnErr
|
||||
}
|
||||
|
||||
// NewRepository creates a new Repository for the given repository name and base URL.
|
||||
func NewRepository(ctx context.Context, name reference.Named, baseURL string, transport http.RoundTripper) (distribution.Repository, error) {
|
||||
ub, err := v2.NewURLBuilderFromString(baseURL, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
CheckRedirect: checkHTTPRedirect,
|
||||
// TODO(dmcgowan): create cookie jar
|
||||
}
|
||||
|
||||
return &repository{
|
||||
client: client,
|
||||
ub: ub,
|
||||
name: name,
|
||||
context: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type repository struct {
|
||||
client *http.Client
|
||||
ub *v2.URLBuilder
|
||||
context context.Context
|
||||
name reference.Named
|
||||
}
|
||||
|
||||
func (r *repository) Named() reference.Named {
|
||||
return r.name
|
||||
}
|
||||
|
||||
func (r *repository) Blobs(ctx context.Context) distribution.BlobStore {
|
||||
statter := &blobStatter{
|
||||
name: r.name,
|
||||
ub: r.ub,
|
||||
client: r.client,
|
||||
}
|
||||
return &blobs{
|
||||
name: r.name,
|
||||
ub: r.ub,
|
||||
client: r.client,
|
||||
statter: cache.NewCachedBlobStatter(memory.NewInMemoryBlobDescriptorCacheProvider(), statter),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
|
||||
// todo(richardscothern): options should be sent over the wire
|
||||
return &manifests{
|
||||
name: r.name,
|
||||
ub: r.ub,
|
||||
client: r.client,
|
||||
etags: make(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *repository) Tags(ctx context.Context) distribution.TagService {
|
||||
return &tags{
|
||||
client: r.client,
|
||||
ub: r.ub,
|
||||
context: r.context,
|
||||
name: r.Named(),
|
||||
}
|
||||
}
|
||||
|
||||
// tags implements remote tagging operations.
|
||||
type tags struct {
|
||||
client *http.Client
|
||||
ub *v2.URLBuilder
|
||||
context context.Context
|
||||
name reference.Named
|
||||
}
|
||||
|
||||
// All returns all tags
|
||||
func (t *tags) All(ctx context.Context) ([]string, error) {
|
||||
var tags []string
|
||||
|
||||
u, err := t.ub.BuildTagsURL(t.name)
|
||||
if err != nil {
|
||||
return tags, err
|
||||
}
|
||||
|
||||
for {
|
||||
resp, err := t.client.Get(u)
|
||||
if err != nil {
|
||||
return tags, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return tags, err
|
||||
}
|
||||
|
||||
tagsResponse := struct {
|
||||
Tags []string `json:"tags"`
|
||||
}{}
|
||||
if err := json.Unmarshal(b, &tagsResponse); err != nil {
|
||||
return tags, err
|
||||
}
|
||||
tags = append(tags, tagsResponse.Tags...)
|
||||
if link := resp.Header.Get("Link"); link != "" {
|
||||
u = strings.Trim(strings.Split(link, ";")[0], "<>")
|
||||
} else {
|
||||
return tags, nil
|
||||
}
|
||||
} else {
|
||||
return tags, HandleErrorResponse(resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func descriptorFromResponse(response *http.Response) (distribution.Descriptor, error) {
|
||||
desc := distribution.Descriptor{}
|
||||
headers := response.Header
|
||||
|
||||
ctHeader := headers.Get("Content-Type")
|
||||
if ctHeader == "" {
|
||||
return distribution.Descriptor{}, errors.New("missing or empty Content-Type header")
|
||||
}
|
||||
desc.MediaType = ctHeader
|
||||
|
||||
digestHeader := headers.Get("Docker-Content-Digest")
|
||||
if digestHeader == "" {
|
||||
bytes, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
_, desc, err := distribution.UnmarshalManifest(ctHeader, bytes)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
return desc, nil
|
||||
}
|
||||
|
||||
dgst, err := digest.Parse(digestHeader)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
desc.Digest = dgst
|
||||
|
||||
lengthHeader := headers.Get("Content-Length")
|
||||
if lengthHeader == "" {
|
||||
return distribution.Descriptor{}, errors.New("missing or empty Content-Length header")
|
||||
}
|
||||
length, err := strconv.ParseInt(lengthHeader, 10, 64)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
desc.Size = length
|
||||
|
||||
return desc, nil
|
||||
|
||||
}
|
||||
|
||||
// Get issues a HEAD request for a Manifest against its named endpoint in order
|
||||
// to construct a descriptor for the tag. If the registry doesn't support HEADing
|
||||
// a manifest, fallback to GET.
|
||||
func (t *tags) Get(ctx context.Context, tag string) (distribution.Descriptor, error) {
|
||||
ref, err := reference.WithTag(t.name, tag)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
u, err := t.ub.BuildManifestURL(ref)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
|
||||
newRequest := func(method string) (*http.Response, error) {
|
||||
req, err := http.NewRequest(method, u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, t := range distribution.ManifestMediaTypes() {
|
||||
req.Header.Add("Accept", t)
|
||||
}
|
||||
resp, err := t.client.Do(req)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp, err := newRequest("HEAD")
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
switch {
|
||||
case resp.StatusCode >= 200 && resp.StatusCode < 400:
|
||||
return descriptorFromResponse(resp)
|
||||
default:
|
||||
// if the response is an error - there will be no body to decode.
|
||||
// Issue a GET request:
|
||||
// - for data from a server that does not handle HEAD
|
||||
// - to get error details in case of a failure
|
||||
resp, err = newRequest("GET")
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 400 {
|
||||
return descriptorFromResponse(resp)
|
||||
}
|
||||
return distribution.Descriptor{}, HandleErrorResponse(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tags) Lookup(ctx context.Context, digest distribution.Descriptor) ([]string, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (t *tags) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (t *tags) Untag(ctx context.Context, tag string) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
type manifests struct {
|
||||
name reference.Named
|
||||
ub *v2.URLBuilder
|
||||
client *http.Client
|
||||
etags map[string]string
|
||||
}
|
||||
|
||||
func (ms *manifests) Exists(ctx context.Context, dgst digest.Digest) (bool, error) {
|
||||
ref, err := reference.WithDigest(ms.name, dgst)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
u, err := ms.ub.BuildManifestURL(ref)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
resp, err := ms.client.Head(u)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
return true, nil
|
||||
} else if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
// AddEtagToTag allows a client to supply an eTag to Get which will be
|
||||
// used for a conditional HTTP request. If the eTag matches, a nil manifest
|
||||
// and ErrManifestNotModified error will be returned. etag is automatically
|
||||
// quoted when added to this map.
|
||||
func AddEtagToTag(tag, etag string) distribution.ManifestServiceOption {
|
||||
return etagOption{tag, etag}
|
||||
}
|
||||
|
||||
type etagOption struct{ tag, etag string }
|
||||
|
||||
func (o etagOption) Apply(ms distribution.ManifestService) error {
|
||||
if ms, ok := ms.(*manifests); ok {
|
||||
ms.etags[o.tag] = fmt.Sprintf(`"%s"`, o.etag)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("etag options is a client-only option")
|
||||
}
|
||||
|
||||
// ReturnContentDigest allows a client to set a the content digest on
|
||||
// a successful request from the 'Docker-Content-Digest' header. This
|
||||
// returned digest is represents the digest which the registry uses
|
||||
// to refer to the content and can be used to delete the content.
|
||||
func ReturnContentDigest(dgst *digest.Digest) distribution.ManifestServiceOption {
|
||||
return contentDigestOption{dgst}
|
||||
}
|
||||
|
||||
type contentDigestOption struct{ digest *digest.Digest }
|
||||
|
||||
func (o contentDigestOption) Apply(ms distribution.ManifestService) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
|
||||
var (
|
||||
digestOrTag string
|
||||
ref reference.Named
|
||||
err error
|
||||
contentDgst *digest.Digest
|
||||
)
|
||||
|
||||
for _, option := range options {
|
||||
if opt, ok := option.(distribution.WithTagOption); ok {
|
||||
digestOrTag = opt.Tag
|
||||
ref, err = reference.WithTag(ms.name, opt.Tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if opt, ok := option.(contentDigestOption); ok {
|
||||
contentDgst = opt.digest
|
||||
} else {
|
||||
err := option.Apply(ms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if digestOrTag == "" {
|
||||
digestOrTag = dgst.String()
|
||||
ref, err = reference.WithDigest(ms.name, dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
u, err := ms.ub.BuildManifestURL(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, t := range distribution.ManifestMediaTypes() {
|
||||
req.Header.Add("Accept", t)
|
||||
}
|
||||
|
||||
if _, ok := ms.etags[digestOrTag]; ok {
|
||||
req.Header.Set("If-None-Match", ms.etags[digestOrTag])
|
||||
}
|
||||
|
||||
resp, err := ms.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusNotModified {
|
||||
return nil, distribution.ErrManifestNotModified
|
||||
} else if SuccessStatus(resp.StatusCode) {
|
||||
if contentDgst != nil {
|
||||
dgst, err := digest.Parse(resp.Header.Get("Docker-Content-Digest"))
|
||||
if err == nil {
|
||||
*contentDgst = dgst
|
||||
}
|
||||
}
|
||||
mt := resp.Header.Get("Content-Type")
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, _, err := distribution.UnmarshalManifest(mt, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
return nil, HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
// Put puts a manifest. A tag can be specified using an options parameter which uses some shared state to hold the
|
||||
// tag name in order to build the correct upload URL.
|
||||
func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
|
||||
ref := ms.name
|
||||
var tagged bool
|
||||
|
||||
for _, option := range options {
|
||||
if opt, ok := option.(distribution.WithTagOption); ok {
|
||||
var err error
|
||||
ref, err = reference.WithTag(ref, opt.Tag)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tagged = true
|
||||
} else {
|
||||
err := option.Apply(ms)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
mediaType, p, err := m.Payload()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !tagged {
|
||||
// generate a canonical digest and Put by digest
|
||||
_, d, err := distribution.UnmarshalManifest(mediaType, p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ref, err = reference.WithDigest(ref, d.Digest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
manifestURL, err := ms.ub.BuildManifestURL(ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
putRequest, err := http.NewRequest("PUT", manifestURL, bytes.NewReader(p))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
putRequest.Header.Set("Content-Type", mediaType)
|
||||
|
||||
resp, err := ms.client.Do(putRequest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
dgstHeader := resp.Header.Get("Docker-Content-Digest")
|
||||
dgst, err := digest.Parse(dgstHeader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return dgst, nil
|
||||
}
|
||||
|
||||
return "", HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func (ms *manifests) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||
ref, err := reference.WithDigest(ms.name, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u, err := ms.ub.BuildManifestURL(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := ms.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
return nil
|
||||
}
|
||||
return HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
// todo(richardscothern): Restore interface and implementation with merge of #1050
|
||||
/*func (ms *manifests) Enumerate(ctx context.Context, manifests []distribution.Manifest, last distribution.Manifest) (n int, err error) {
|
||||
panic("not supported")
|
||||
}*/
|
||||
|
||||
type blobs struct {
|
||||
name reference.Named
|
||||
ub *v2.URLBuilder
|
||||
client *http.Client
|
||||
|
||||
statter distribution.BlobDescriptorService
|
||||
distribution.BlobDeleter
|
||||
}
|
||||
|
||||
func sanitizeLocation(location, base string) (string, error) {
|
||||
baseURL, err := url.Parse(base)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
locationURL, err := url.Parse(location)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return baseURL.ResolveReference(locationURL).String(), nil
|
||||
}
|
||||
|
||||
func (bs *blobs) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
||||
return bs.statter.Stat(ctx, dgst)
|
||||
|
||||
}
|
||||
|
||||
func (bs *blobs) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
|
||||
reader, err := bs.Open(ctx, dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
return ioutil.ReadAll(reader)
|
||||
}
|
||||
|
||||
func (bs *blobs) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) {
|
||||
ref, err := reference.WithDigest(bs.name, dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blobURL, err := bs.ub.BuildBlobURL(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return transport.NewHTTPReadSeeker(bs.client, blobURL,
|
||||
func(resp *http.Response) error {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return distribution.ErrBlobUnknown
|
||||
}
|
||||
return HandleErrorResponse(resp)
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (bs *blobs) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (bs *blobs) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) {
|
||||
writer, err := bs.Create(ctx)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
dgstr := digest.Canonical.Digester()
|
||||
n, err := io.Copy(writer, io.TeeReader(bytes.NewReader(p), dgstr.Hash()))
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
if n < int64(len(p)) {
|
||||
return distribution.Descriptor{}, fmt.Errorf("short copy: wrote %d of %d", n, len(p))
|
||||
}
|
||||
|
||||
desc := distribution.Descriptor{
|
||||
MediaType: mediaType,
|
||||
Size: int64(len(p)),
|
||||
Digest: dgstr.Digest(),
|
||||
}
|
||||
|
||||
return writer.Commit(ctx, desc)
|
||||
}
|
||||
|
||||
type optionFunc func(interface{}) error
|
||||
|
||||
func (f optionFunc) Apply(v interface{}) error {
|
||||
return f(v)
|
||||
}
|
||||
|
||||
// WithMountFrom returns a BlobCreateOption which designates that the blob should be
|
||||
// mounted from the given canonical reference.
|
||||
func WithMountFrom(ref reference.Canonical) distribution.BlobCreateOption {
|
||||
return optionFunc(func(v interface{}) error {
|
||||
opts, ok := v.(*distribution.CreateOptions)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected options type: %T", v)
|
||||
}
|
||||
|
||||
opts.Mount.ShouldMount = true
|
||||
opts.Mount.From = ref
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (bs *blobs) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) {
|
||||
var opts distribution.CreateOptions
|
||||
|
||||
for _, option := range options {
|
||||
err := option.Apply(&opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var values []url.Values
|
||||
|
||||
if opts.Mount.ShouldMount {
|
||||
values = append(values, url.Values{"from": {opts.Mount.From.Name()}, "mount": {opts.Mount.From.Digest().String()}})
|
||||
}
|
||||
|
||||
u, err := bs.ub.BuildBlobUploadURL(bs.name, values...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := bs.client.Post(u, "", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusCreated:
|
||||
desc, err := bs.statter.Stat(ctx, opts.Mount.From.Digest())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, distribution.ErrBlobMounted{From: opts.Mount.From, Descriptor: desc}
|
||||
case http.StatusAccepted:
|
||||
// TODO(dmcgowan): Check for invalid UUID
|
||||
uuid := resp.Header.Get("Docker-Upload-UUID")
|
||||
location, err := sanitizeLocation(resp.Header.Get("Location"), u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &httpBlobUpload{
|
||||
statter: bs.statter,
|
||||
client: bs.client,
|
||||
uuid: uuid,
|
||||
startedAt: time.Now(),
|
||||
location: location,
|
||||
}, nil
|
||||
default:
|
||||
return nil, HandleErrorResponse(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func (bs *blobs) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (bs *blobs) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||
return bs.statter.Clear(ctx, dgst)
|
||||
}
|
||||
|
||||
type blobStatter struct {
|
||||
name reference.Named
|
||||
ub *v2.URLBuilder
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
||||
ref, err := reference.WithDigest(bs.name, dgst)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
u, err := bs.ub.BuildBlobURL(ref)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
|
||||
resp, err := bs.client.Head(u)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
lengthHeader := resp.Header.Get("Content-Length")
|
||||
if lengthHeader == "" {
|
||||
return distribution.Descriptor{}, fmt.Errorf("missing content-length header for request: %s", u)
|
||||
}
|
||||
|
||||
length, err := strconv.ParseInt(lengthHeader, 10, 64)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, fmt.Errorf("error parsing content-length: %v", err)
|
||||
}
|
||||
|
||||
return distribution.Descriptor{
|
||||
MediaType: resp.Header.Get("Content-Type"),
|
||||
Size: length,
|
||||
Digest: dgst,
|
||||
}, nil
|
||||
} else if resp.StatusCode == http.StatusNotFound {
|
||||
return distribution.Descriptor{}, distribution.ErrBlobUnknown
|
||||
}
|
||||
return distribution.Descriptor{}, HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func buildCatalogValues(maxEntries int, last string) url.Values {
|
||||
values := url.Values{}
|
||||
|
||||
if maxEntries > 0 {
|
||||
values.Add("n", strconv.Itoa(maxEntries))
|
||||
}
|
||||
|
||||
if last != "" {
|
||||
values.Add("last", last)
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
|
||||
ref, err := reference.WithDigest(bs.name, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blobURL, err := bs.ub.BuildBlobURL(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", blobURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := bs.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
return nil
|
||||
}
|
||||
return HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func (bs *blobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
|
||||
return nil
|
||||
}
|
251
vendor/github.com/docker/distribution/registry/client/transport/http_reader.go
generated
vendored
Normal file
251
vendor/github.com/docker/distribution/registry/client/transport/http_reader.go
generated
vendored
Normal file
@ -0,0 +1,251 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
contentRangeRegexp = regexp.MustCompile(`bytes ([0-9]+)-([0-9]+)/([0-9]+|\\*)`)
|
||||
|
||||
// ErrWrongCodeForByteRange is returned if the client sends a request
|
||||
// with a Range header but the server returns a 2xx or 3xx code other
|
||||
// than 206 Partial Content.
|
||||
ErrWrongCodeForByteRange = errors.New("expected HTTP 206 from byte range request")
|
||||
)
|
||||
|
||||
// ReadSeekCloser combines io.ReadSeeker with io.Closer.
|
||||
type ReadSeekCloser interface {
|
||||
io.ReadSeeker
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// NewHTTPReadSeeker handles reading from an HTTP endpoint using a GET
|
||||
// request. When seeking and starting a read from a non-zero offset
|
||||
// the a "Range" header will be added which sets the offset.
|
||||
// TODO(dmcgowan): Move this into a separate utility package
|
||||
func NewHTTPReadSeeker(client *http.Client, url string, errorHandler func(*http.Response) error) ReadSeekCloser {
|
||||
return &httpReadSeeker{
|
||||
client: client,
|
||||
url: url,
|
||||
errorHandler: errorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
type httpReadSeeker struct {
|
||||
client *http.Client
|
||||
url string
|
||||
|
||||
// errorHandler creates an error from an unsuccessful HTTP response.
|
||||
// This allows the error to be created with the HTTP response body
|
||||
// without leaking the body through a returned error.
|
||||
errorHandler func(*http.Response) error
|
||||
|
||||
size int64
|
||||
|
||||
// rc is the remote read closer.
|
||||
rc io.ReadCloser
|
||||
// readerOffset tracks the offset as of the last read.
|
||||
readerOffset int64
|
||||
// seekOffset allows Seek to override the offset. Seek changes
|
||||
// seekOffset instead of changing readOffset directly so that
|
||||
// connection resets can be delayed and possibly avoided if the
|
||||
// seek is undone (i.e. seeking to the end and then back to the
|
||||
// beginning).
|
||||
seekOffset int64
|
||||
err error
|
||||
}
|
||||
|
||||
func (hrs *httpReadSeeker) Read(p []byte) (n int, err error) {
|
||||
if hrs.err != nil {
|
||||
return 0, hrs.err
|
||||
}
|
||||
|
||||
// If we sought to a different position, we need to reset the
|
||||
// connection. This logic is here instead of Seek so that if
|
||||
// a seek is undone before the next read, the connection doesn't
|
||||
// need to be closed and reopened. A common example of this is
|
||||
// seeking to the end to determine the length, and then seeking
|
||||
// back to the original position.
|
||||
if hrs.readerOffset != hrs.seekOffset {
|
||||
hrs.reset()
|
||||
}
|
||||
|
||||
hrs.readerOffset = hrs.seekOffset
|
||||
|
||||
rd, err := hrs.reader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n, err = rd.Read(p)
|
||||
hrs.seekOffset += int64(n)
|
||||
hrs.readerOffset += int64(n)
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
||||
if hrs.err != nil {
|
||||
return 0, hrs.err
|
||||
}
|
||||
|
||||
lastReaderOffset := hrs.readerOffset
|
||||
|
||||
if whence == os.SEEK_SET && hrs.rc == nil {
|
||||
// If no request has been made yet, and we are seeking to an
|
||||
// absolute position, set the read offset as well to avoid an
|
||||
// unnecessary request.
|
||||
hrs.readerOffset = offset
|
||||
}
|
||||
|
||||
_, err := hrs.reader()
|
||||
if err != nil {
|
||||
hrs.readerOffset = lastReaderOffset
|
||||
return 0, err
|
||||
}
|
||||
|
||||
newOffset := hrs.seekOffset
|
||||
|
||||
switch whence {
|
||||
case os.SEEK_CUR:
|
||||
newOffset += offset
|
||||
case os.SEEK_END:
|
||||
if hrs.size < 0 {
|
||||
return 0, errors.New("content length not known")
|
||||
}
|
||||
newOffset = hrs.size + offset
|
||||
case os.SEEK_SET:
|
||||
newOffset = offset
|
||||
}
|
||||
|
||||
if newOffset < 0 {
|
||||
err = errors.New("cannot seek to negative position")
|
||||
} else {
|
||||
hrs.seekOffset = newOffset
|
||||
}
|
||||
|
||||
return hrs.seekOffset, err
|
||||
}
|
||||
|
||||
func (hrs *httpReadSeeker) Close() error {
|
||||
if hrs.err != nil {
|
||||
return hrs.err
|
||||
}
|
||||
|
||||
// close and release reader chain
|
||||
if hrs.rc != nil {
|
||||
hrs.rc.Close()
|
||||
}
|
||||
|
||||
hrs.rc = nil
|
||||
|
||||
hrs.err = errors.New("httpLayer: closed")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hrs *httpReadSeeker) reset() {
|
||||
if hrs.err != nil {
|
||||
return
|
||||
}
|
||||
if hrs.rc != nil {
|
||||
hrs.rc.Close()
|
||||
hrs.rc = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
||||
if hrs.err != nil {
|
||||
return nil, hrs.err
|
||||
}
|
||||
|
||||
if hrs.rc != nil {
|
||||
return hrs.rc, nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", hrs.url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if hrs.readerOffset > 0 {
|
||||
// If we are at different offset, issue a range request from there.
|
||||
req.Header.Add("Range", fmt.Sprintf("bytes=%d-", hrs.readerOffset))
|
||||
// TODO: get context in here
|
||||
// context.GetLogger(hrs.context).Infof("Range: %s", req.Header.Get("Range"))
|
||||
}
|
||||
|
||||
req.Header.Add("Accept-Encoding", "identity")
|
||||
resp, err := hrs.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Normally would use client.SuccessStatus, but that would be a cyclic
|
||||
// import
|
||||
if resp.StatusCode >= 200 && resp.StatusCode <= 399 {
|
||||
if hrs.readerOffset > 0 {
|
||||
if resp.StatusCode != http.StatusPartialContent {
|
||||
return nil, ErrWrongCodeForByteRange
|
||||
}
|
||||
|
||||
contentRange := resp.Header.Get("Content-Range")
|
||||
if contentRange == "" {
|
||||
return nil, errors.New("no Content-Range header found in HTTP 206 response")
|
||||
}
|
||||
|
||||
submatches := contentRangeRegexp.FindStringSubmatch(contentRange)
|
||||
if len(submatches) < 4 {
|
||||
return nil, fmt.Errorf("could not parse Content-Range header: %s", contentRange)
|
||||
}
|
||||
|
||||
startByte, err := strconv.ParseUint(submatches[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse start of range in Content-Range header: %s", contentRange)
|
||||
}
|
||||
|
||||
if startByte != uint64(hrs.readerOffset) {
|
||||
return nil, fmt.Errorf("received Content-Range starting at offset %d instead of requested %d", startByte, hrs.readerOffset)
|
||||
}
|
||||
|
||||
endByte, err := strconv.ParseUint(submatches[2], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse end of range in Content-Range header: %s", contentRange)
|
||||
}
|
||||
|
||||
if submatches[3] == "*" {
|
||||
hrs.size = -1
|
||||
} else {
|
||||
size, err := strconv.ParseUint(submatches[3], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse total size in Content-Range header: %s", contentRange)
|
||||
}
|
||||
|
||||
if endByte+1 != size {
|
||||
return nil, fmt.Errorf("range in Content-Range stops before the end of the content: %s", contentRange)
|
||||
}
|
||||
|
||||
hrs.size = int64(size)
|
||||
}
|
||||
} else if resp.StatusCode == http.StatusOK {
|
||||
hrs.size = resp.ContentLength
|
||||
} else {
|
||||
hrs.size = -1
|
||||
}
|
||||
hrs.rc = resp.Body
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
if hrs.errorHandler != nil {
|
||||
return nil, hrs.errorHandler(resp)
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected status resolving reader: %v", resp.Status)
|
||||
}
|
||||
|
||||
return hrs.rc, nil
|
||||
}
|
147
vendor/github.com/docker/distribution/registry/client/transport/transport.go
generated
vendored
Normal file
147
vendor/github.com/docker/distribution/registry/client/transport/transport.go
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// RequestModifier represents an object which will do an inplace
|
||||
// modification of an HTTP request.
|
||||
type RequestModifier interface {
|
||||
ModifyRequest(*http.Request) error
|
||||
}
|
||||
|
||||
type headerModifier http.Header
|
||||
|
||||
// NewHeaderRequestModifier returns a new RequestModifier which will
|
||||
// add the given headers to a request.
|
||||
func NewHeaderRequestModifier(header http.Header) RequestModifier {
|
||||
return headerModifier(header)
|
||||
}
|
||||
|
||||
func (h headerModifier) ModifyRequest(req *http.Request) error {
|
||||
for k, s := range http.Header(h) {
|
||||
req.Header[k] = append(req.Header[k], s...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTransport creates a new transport which will apply modifiers to
|
||||
// the request on a RoundTrip call.
|
||||
func NewTransport(base http.RoundTripper, modifiers ...RequestModifier) http.RoundTripper {
|
||||
return &transport{
|
||||
Modifiers: modifiers,
|
||||
Base: base,
|
||||
}
|
||||
}
|
||||
|
||||
// transport is an http.RoundTripper that makes HTTP requests after
|
||||
// copying and modifying the request
|
||||
type transport struct {
|
||||
Modifiers []RequestModifier
|
||||
Base http.RoundTripper
|
||||
|
||||
mu sync.Mutex // guards modReq
|
||||
modReq map[*http.Request]*http.Request // original -> modified
|
||||
}
|
||||
|
||||
// RoundTrip authorizes and authenticates the request with an
|
||||
// access token. If no token exists or token is expired,
|
||||
// tries to refresh/fetch a new token.
|
||||
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req2 := cloneRequest(req)
|
||||
for _, modifier := range t.Modifiers {
|
||||
if err := modifier.ModifyRequest(req2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
t.setModReq(req, req2)
|
||||
res, err := t.base().RoundTrip(req2)
|
||||
if err != nil {
|
||||
t.setModReq(req, nil)
|
||||
return nil, err
|
||||
}
|
||||
res.Body = &onEOFReader{
|
||||
rc: res.Body,
|
||||
fn: func() { t.setModReq(req, nil) },
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
func (t *transport) CancelRequest(req *http.Request) {
|
||||
type canceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
if cr, ok := t.base().(canceler); ok {
|
||||
t.mu.Lock()
|
||||
modReq := t.modReq[req]
|
||||
delete(t.modReq, req)
|
||||
t.mu.Unlock()
|
||||
cr.CancelRequest(modReq)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *transport) base() http.RoundTripper {
|
||||
if t.Base != nil {
|
||||
return t.Base
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
func (t *transport) setModReq(orig, mod *http.Request) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.modReq == nil {
|
||||
t.modReq = make(map[*http.Request]*http.Request)
|
||||
}
|
||||
if mod == nil {
|
||||
delete(t.modReq, orig)
|
||||
} else {
|
||||
t.modReq[orig] = mod
|
||||
}
|
||||
}
|
||||
|
||||
// cloneRequest returns a clone of the provided *http.Request.
|
||||
// The clone is a shallow copy of the struct and its Header map.
|
||||
func cloneRequest(r *http.Request) *http.Request {
|
||||
// shallow copy of the struct
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
// deep copy of the Header
|
||||
r2.Header = make(http.Header, len(r.Header))
|
||||
for k, s := range r.Header {
|
||||
r2.Header[k] = append([]string(nil), s...)
|
||||
}
|
||||
|
||||
return r2
|
||||
}
|
||||
|
||||
type onEOFReader struct {
|
||||
rc io.ReadCloser
|
||||
fn func()
|
||||
}
|
||||
|
||||
func (r *onEOFReader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.rc.Read(p)
|
||||
if err == io.EOF {
|
||||
r.runFunc()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *onEOFReader) Close() error {
|
||||
err := r.rc.Close()
|
||||
r.runFunc()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *onEOFReader) runFunc() {
|
||||
if fn := r.fn; fn != nil {
|
||||
fn()
|
||||
r.fn = nil
|
||||
}
|
||||
}
|
35
vendor/github.com/docker/distribution/registry/storage/cache/cache.go
generated
vendored
Normal file
35
vendor/github.com/docker/distribution/registry/storage/cache/cache.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// Package cache provides facilities to speed up access to the storage
|
||||
// backend.
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
)
|
||||
|
||||
// BlobDescriptorCacheProvider provides repository scoped
|
||||
// BlobDescriptorService cache instances and a global descriptor cache.
|
||||
type BlobDescriptorCacheProvider interface {
|
||||
distribution.BlobDescriptorService
|
||||
|
||||
RepositoryScoped(repo string) (distribution.BlobDescriptorService, error)
|
||||
}
|
||||
|
||||
// ValidateDescriptor provides a helper function to ensure that caches have
|
||||
// common criteria for admitting descriptors.
|
||||
func ValidateDescriptor(desc distribution.Descriptor) error {
|
||||
if err := desc.Digest.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if desc.Size < 0 {
|
||||
return fmt.Errorf("cache: invalid length in descriptor: %v < 0", desc.Size)
|
||||
}
|
||||
|
||||
if desc.MediaType == "" {
|
||||
return fmt.Errorf("cache: empty mediatype on descriptor: %v", desc)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
101
vendor/github.com/docker/distribution/registry/storage/cache/cachedblobdescriptorstore.go
generated
vendored
Normal file
101
vendor/github.com/docker/distribution/registry/storage/cache/cachedblobdescriptorstore.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/opencontainers/go-digest"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
)
|
||||
|
||||
// Metrics is used to hold metric counters
|
||||
// related to the number of times a cache was
|
||||
// hit or missed.
|
||||
type Metrics struct {
|
||||
Requests uint64
|
||||
Hits uint64
|
||||
Misses uint64
|
||||
}
|
||||
|
||||
// MetricsTracker represents a metric tracker
|
||||
// which simply counts the number of hits and misses.
|
||||
type MetricsTracker interface {
|
||||
Hit()
|
||||
Miss()
|
||||
Metrics() Metrics
|
||||
}
|
||||
|
||||
type cachedBlobStatter struct {
|
||||
cache distribution.BlobDescriptorService
|
||||
backend distribution.BlobDescriptorService
|
||||
tracker MetricsTracker
|
||||
}
|
||||
|
||||
// NewCachedBlobStatter creates a new statter which prefers a cache and
|
||||
// falls back to a backend.
|
||||
func NewCachedBlobStatter(cache distribution.BlobDescriptorService, backend distribution.BlobDescriptorService) distribution.BlobDescriptorService {
|
||||
return &cachedBlobStatter{
|
||||
cache: cache,
|
||||
backend: backend,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCachedBlobStatterWithMetrics creates a new statter which prefers a cache and
|
||||
// falls back to a backend. Hits and misses will send to the tracker.
|
||||
func NewCachedBlobStatterWithMetrics(cache distribution.BlobDescriptorService, backend distribution.BlobDescriptorService, tracker MetricsTracker) distribution.BlobStatter {
|
||||
return &cachedBlobStatter{
|
||||
cache: cache,
|
||||
backend: backend,
|
||||
tracker: tracker,
|
||||
}
|
||||
}
|
||||
|
||||
func (cbds *cachedBlobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
||||
desc, err := cbds.cache.Stat(ctx, dgst)
|
||||
if err != nil {
|
||||
if err != distribution.ErrBlobUnknown {
|
||||
context.GetLogger(ctx).Errorf("error retrieving descriptor from cache: %v", err)
|
||||
}
|
||||
|
||||
goto fallback
|
||||
}
|
||||
|
||||
if cbds.tracker != nil {
|
||||
cbds.tracker.Hit()
|
||||
}
|
||||
return desc, nil
|
||||
fallback:
|
||||
if cbds.tracker != nil {
|
||||
cbds.tracker.Miss()
|
||||
}
|
||||
desc, err = cbds.backend.Stat(ctx, dgst)
|
||||
if err != nil {
|
||||
return desc, err
|
||||
}
|
||||
|
||||
if err := cbds.cache.SetDescriptor(ctx, dgst, desc); err != nil {
|
||||
context.GetLogger(ctx).Errorf("error adding descriptor %v to cache: %v", desc.Digest, err)
|
||||
}
|
||||
|
||||
return desc, err
|
||||
|
||||
}
|
||||
|
||||
func (cbds *cachedBlobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
|
||||
err := cbds.cache.Clear(ctx, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cbds.backend.Clear(ctx, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cbds *cachedBlobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
|
||||
if err := cbds.cache.SetDescriptor(ctx, dgst, desc); err != nil {
|
||||
context.GetLogger(ctx).Errorf("error adding descriptor %v to cache: %v", desc.Digest, err)
|
||||
}
|
||||
return nil
|
||||
}
|
179
vendor/github.com/docker/distribution/registry/storage/cache/memory/memory.go
generated
vendored
Normal file
179
vendor/github.com/docker/distribution/registry/storage/cache/memory/memory.go
generated
vendored
Normal file
@ -0,0 +1,179 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/distribution/registry/storage/cache"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
type inMemoryBlobDescriptorCacheProvider struct {
|
||||
global *mapBlobDescriptorCache
|
||||
repositories map[string]*mapBlobDescriptorCache
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewInMemoryBlobDescriptorCacheProvider returns a new mapped-based cache for
|
||||
// storing blob descriptor data.
|
||||
func NewInMemoryBlobDescriptorCacheProvider() cache.BlobDescriptorCacheProvider {
|
||||
return &inMemoryBlobDescriptorCacheProvider{
|
||||
global: newMapBlobDescriptorCache(),
|
||||
repositories: make(map[string]*mapBlobDescriptorCache),
|
||||
}
|
||||
}
|
||||
|
||||
func (imbdcp *inMemoryBlobDescriptorCacheProvider) RepositoryScoped(repo string) (distribution.BlobDescriptorService, error) {
|
||||
if _, err := reference.ParseNormalizedNamed(repo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imbdcp.mu.RLock()
|
||||
defer imbdcp.mu.RUnlock()
|
||||
|
||||
return &repositoryScopedInMemoryBlobDescriptorCache{
|
||||
repo: repo,
|
||||
parent: imbdcp,
|
||||
repository: imbdcp.repositories[repo],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (imbdcp *inMemoryBlobDescriptorCacheProvider) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
||||
return imbdcp.global.Stat(ctx, dgst)
|
||||
}
|
||||
|
||||
func (imbdcp *inMemoryBlobDescriptorCacheProvider) Clear(ctx context.Context, dgst digest.Digest) error {
|
||||
return imbdcp.global.Clear(ctx, dgst)
|
||||
}
|
||||
|
||||
func (imbdcp *inMemoryBlobDescriptorCacheProvider) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
|
||||
_, err := imbdcp.Stat(ctx, dgst)
|
||||
if err == distribution.ErrBlobUnknown {
|
||||
|
||||
if dgst.Algorithm() != desc.Digest.Algorithm() && dgst != desc.Digest {
|
||||
// if the digests differ, set the other canonical mapping
|
||||
if err := imbdcp.global.SetDescriptor(ctx, desc.Digest, desc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// unknown, just set it
|
||||
return imbdcp.global.SetDescriptor(ctx, dgst, desc)
|
||||
}
|
||||
|
||||
// we already know it, do nothing
|
||||
return err
|
||||
}
|
||||
|
||||
// repositoryScopedInMemoryBlobDescriptorCache provides the request scoped
|
||||
// repository cache. Instances are not thread-safe but the delegated
|
||||
// operations are.
|
||||
type repositoryScopedInMemoryBlobDescriptorCache struct {
|
||||
repo string
|
||||
parent *inMemoryBlobDescriptorCacheProvider // allows lazy allocation of repo's map
|
||||
repository *mapBlobDescriptorCache
|
||||
}
|
||||
|
||||
func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
||||
rsimbdcp.parent.mu.Lock()
|
||||
repo := rsimbdcp.repository
|
||||
rsimbdcp.parent.mu.Unlock()
|
||||
|
||||
if repo == nil {
|
||||
return distribution.Descriptor{}, distribution.ErrBlobUnknown
|
||||
}
|
||||
|
||||
return repo.Stat(ctx, dgst)
|
||||
}
|
||||
|
||||
func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) Clear(ctx context.Context, dgst digest.Digest) error {
|
||||
rsimbdcp.parent.mu.Lock()
|
||||
repo := rsimbdcp.repository
|
||||
rsimbdcp.parent.mu.Unlock()
|
||||
|
||||
if repo == nil {
|
||||
return distribution.ErrBlobUnknown
|
||||
}
|
||||
|
||||
return repo.Clear(ctx, dgst)
|
||||
}
|
||||
|
||||
func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
|
||||
rsimbdcp.parent.mu.Lock()
|
||||
repo := rsimbdcp.repository
|
||||
if repo == nil {
|
||||
// allocate map since we are setting it now.
|
||||
var ok bool
|
||||
// have to read back value since we may have allocated elsewhere.
|
||||
repo, ok = rsimbdcp.parent.repositories[rsimbdcp.repo]
|
||||
if !ok {
|
||||
repo = newMapBlobDescriptorCache()
|
||||
rsimbdcp.parent.repositories[rsimbdcp.repo] = repo
|
||||
}
|
||||
rsimbdcp.repository = repo
|
||||
}
|
||||
rsimbdcp.parent.mu.Unlock()
|
||||
|
||||
if err := repo.SetDescriptor(ctx, dgst, desc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return rsimbdcp.parent.SetDescriptor(ctx, dgst, desc)
|
||||
}
|
||||
|
||||
// mapBlobDescriptorCache provides a simple map-based implementation of the
|
||||
// descriptor cache.
|
||||
type mapBlobDescriptorCache struct {
|
||||
descriptors map[digest.Digest]distribution.Descriptor
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var _ distribution.BlobDescriptorService = &mapBlobDescriptorCache{}
|
||||
|
||||
func newMapBlobDescriptorCache() *mapBlobDescriptorCache {
|
||||
return &mapBlobDescriptorCache{
|
||||
descriptors: make(map[digest.Digest]distribution.Descriptor),
|
||||
}
|
||||
}
|
||||
|
||||
func (mbdc *mapBlobDescriptorCache) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
||||
if err := dgst.Validate(); err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
|
||||
mbdc.mu.RLock()
|
||||
defer mbdc.mu.RUnlock()
|
||||
|
||||
desc, ok := mbdc.descriptors[dgst]
|
||||
if !ok {
|
||||
return distribution.Descriptor{}, distribution.ErrBlobUnknown
|
||||
}
|
||||
|
||||
return desc, nil
|
||||
}
|
||||
|
||||
func (mbdc *mapBlobDescriptorCache) Clear(ctx context.Context, dgst digest.Digest) error {
|
||||
mbdc.mu.Lock()
|
||||
defer mbdc.mu.Unlock()
|
||||
|
||||
delete(mbdc.descriptors, dgst)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mbdc *mapBlobDescriptorCache) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
|
||||
if err := dgst.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cache.ValidateDescriptor(desc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mbdc.mu.Lock()
|
||||
defer mbdc.mu.Unlock()
|
||||
|
||||
mbdc.descriptors[dgst] = desc
|
||||
return nil
|
||||
}
|
27
vendor/github.com/docker/distribution/tags.go
generated
vendored
Normal file
27
vendor/github.com/docker/distribution/tags.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
package distribution
|
||||
|
||||
import (
|
||||
"github.com/docker/distribution/context"
|
||||
)
|
||||
|
||||
// TagService provides access to information about tagged objects.
|
||||
type TagService interface {
|
||||
// Get retrieves the descriptor identified by the tag. Some
|
||||
// implementations may differentiate between "trusted" tags and
|
||||
// "untrusted" tags. If a tag is "untrusted", the mapping will be returned
|
||||
// as an ErrTagUntrusted error, with the target descriptor.
|
||||
Get(ctx context.Context, tag string) (Descriptor, error)
|
||||
|
||||
// Tag associates the tag with the provided descriptor, updating the
|
||||
// current association, if needed.
|
||||
Tag(ctx context.Context, tag string, desc Descriptor) error
|
||||
|
||||
// Untag removes the given tag association
|
||||
Untag(ctx context.Context, tag string) error
|
||||
|
||||
// All returns the set of tags managed by this tag service
|
||||
All(ctx context.Context) ([]string, error)
|
||||
|
||||
// Lookup returns the set of tags referencing the given digest.
|
||||
Lookup(ctx context.Context, digest Descriptor) ([]string, error)
|
||||
}
|
126
vendor/github.com/docker/distribution/uuid/uuid.go
generated
vendored
Normal file
126
vendor/github.com/docker/distribution/uuid/uuid.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
// Package uuid provides simple UUID generation. Only version 4 style UUIDs
|
||||
// can be generated.
|
||||
//
|
||||
// Please see http://tools.ietf.org/html/rfc4122 for details on UUIDs.
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// Bits is the number of bits in a UUID
|
||||
Bits = 128
|
||||
|
||||
// Size is the number of bytes in a UUID
|
||||
Size = Bits / 8
|
||||
|
||||
format = "%08x-%04x-%04x-%04x-%012x"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUUIDInvalid indicates a parsed string is not a valid uuid.
|
||||
ErrUUIDInvalid = fmt.Errorf("invalid uuid")
|
||||
|
||||
// Loggerf can be used to override the default logging destination. Such
|
||||
// log messages in this library should be logged at warning or higher.
|
||||
Loggerf = func(format string, args ...interface{}) {}
|
||||
)
|
||||
|
||||
// UUID represents a UUID value. UUIDs can be compared and set to other values
|
||||
// and accessed by byte.
|
||||
type UUID [Size]byte
|
||||
|
||||
// Generate creates a new, version 4 uuid.
|
||||
func Generate() (u UUID) {
|
||||
const (
|
||||
// ensures we backoff for less than 450ms total. Use the following to
|
||||
// select new value, in units of 10ms:
|
||||
// n*(n+1)/2 = d -> n^2 + n - 2d -> n = (sqrt(8d + 1) - 1)/2
|
||||
maxretries = 9
|
||||
backoff = time.Millisecond * 10
|
||||
)
|
||||
|
||||
var (
|
||||
totalBackoff time.Duration
|
||||
count int
|
||||
retries int
|
||||
)
|
||||
|
||||
for {
|
||||
// This should never block but the read may fail. Because of this,
|
||||
// we just try to read the random number generator until we get
|
||||
// something. This is a very rare condition but may happen.
|
||||
b := time.Duration(retries) * backoff
|
||||
time.Sleep(b)
|
||||
totalBackoff += b
|
||||
|
||||
n, err := io.ReadFull(rand.Reader, u[count:])
|
||||
if err != nil {
|
||||
if retryOnError(err) && retries < maxretries {
|
||||
count += n
|
||||
retries++
|
||||
Loggerf("error generating version 4 uuid, retrying: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Any other errors represent a system problem. What did someone
|
||||
// do to /dev/urandom?
|
||||
panic(fmt.Errorf("error reading random number generator, retried for %v: %v", totalBackoff.String(), err))
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
u[6] = (u[6] & 0x0f) | 0x40 // set version byte
|
||||
u[8] = (u[8] & 0x3f) | 0x80 // set high order byte 0b10{8,9,a,b}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
// Parse attempts to extract a uuid from the string or returns an error.
|
||||
func Parse(s string) (u UUID, err error) {
|
||||
if len(s) != 36 {
|
||||
return UUID{}, ErrUUIDInvalid
|
||||
}
|
||||
|
||||
// create stack addresses for each section of the uuid.
|
||||
p := make([][]byte, 5)
|
||||
|
||||
if _, err := fmt.Sscanf(s, format, &p[0], &p[1], &p[2], &p[3], &p[4]); err != nil {
|
||||
return u, err
|
||||
}
|
||||
|
||||
copy(u[0:4], p[0])
|
||||
copy(u[4:6], p[1])
|
||||
copy(u[6:8], p[2])
|
||||
copy(u[8:10], p[3])
|
||||
copy(u[10:16], p[4])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (u UUID) String() string {
|
||||
return fmt.Sprintf(format, u[:4], u[4:6], u[6:8], u[8:10], u[10:])
|
||||
}
|
||||
|
||||
// retryOnError tries to detect whether or not retrying would be fruitful.
|
||||
func retryOnError(err error) bool {
|
||||
switch err := err.(type) {
|
||||
case *os.PathError:
|
||||
return retryOnError(err.Err) // unpack the target error
|
||||
case syscall.Errno:
|
||||
if err == syscall.EPERM {
|
||||
// EPERM represents an entropy pool exhaustion, a condition under
|
||||
// which we backoff and retry.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
41
vendor/github.com/docker/distribution/vendor.conf
generated
vendored
41
vendor/github.com/docker/distribution/vendor.conf
generated
vendored
@ -1,41 +0,0 @@
|
||||
github.com/Azure/azure-sdk-for-go c6f0533defaaaa26ea4dff3c9774e36033088112
|
||||
github.com/Sirupsen/logrus d26492970760ca5d33129d2d799e34be5c4782eb
|
||||
github.com/aws/aws-sdk-go c6fc52983ea2375810aa38ddb5370e9cdf611716
|
||||
github.com/bshuster-repo/logrus-logstash-hook 5f729f2fb50a301153cae84ff5c58981d51c095a
|
||||
github.com/bugsnag/bugsnag-go b1d153021fcd90ca3f080db36bec96dc690fb274
|
||||
github.com/bugsnag/osext 0dd3f918b21bec95ace9dc86c7e70266cfc5c702
|
||||
github.com/bugsnag/panicwrap e2c28503fcd0675329da73bf48b33404db873782
|
||||
github.com/denverdino/aliyungo afedced274aa9a7fcdd47ac97018f0f8db4e5de2
|
||||
github.com/docker/goamz f0a21f5b2e12f83a505ecf79b633bb2035cf6f85
|
||||
github.com/docker/libtrust fa567046d9b14f6aa788882a950d69651d230b21
|
||||
github.com/garyburd/redigo 535138d7bcd717d6531c701ef5933d98b1866257
|
||||
github.com/go-ini/ini 2ba15ac2dc9cdf88c110ec2dc0ced7fa45f5678c
|
||||
github.com/golang/protobuf/proto 8d92cf5fc15a4382f8964b08e1f42a75c0591aa3
|
||||
github.com/gorilla/context 14f550f51af52180c2eefed15e5fd18d63c0a64a
|
||||
github.com/gorilla/handlers 60c7bfde3e33c201519a200a4507a158cc03a17b
|
||||
github.com/gorilla/mux e444e69cbd2e2e3e0749a2f3c717cec491552bbf
|
||||
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||
github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
|
||||
github.com/miekg/dns 271c58e0c14f552178ea321a545ff9af38930f39
|
||||
github.com/mitchellh/mapstructure 482a9fd5fa83e8c4e7817413b80f3eb8feec03ef
|
||||
github.com/ncw/swift b964f2ca856aac39885e258ad25aec08d5f64ee6
|
||||
github.com/spf13/cobra 312092086bed4968099259622145a0c9ae280064
|
||||
github.com/spf13/pflag 5644820622454e71517561946e3d94b9f9db6842
|
||||
github.com/stevvooe/resumable 51ad44105773cafcbe91927f70ac68e1bf78f8b4
|
||||
github.com/xenolf/lego/acme a9d8cec0e6563575e5868a005359ac97911b5985
|
||||
github.com/yvasiyarov/go-metrics 57bccd1ccd43f94bb17fdd8bf3007059b802f85e
|
||||
github.com/yvasiyarov/gorelic a9bba5b9ab508a086f9a12b8c51fab68478e2128
|
||||
github.com/yvasiyarov/newrelic_platform_go b21fdbd4370f3717f3bbd2bf41c223bc273068e6
|
||||
golang.org/x/crypto c10c31b5e94b6f7a0283272dc2bb27163dcea24b
|
||||
golang.org/x/net 4876518f9e71663000c348837735820161a42df7
|
||||
golang.org/x/oauth2 045497edb6234273d67dbc25da3f2ddbc4c4cacf
|
||||
golang.org/x/time/rate a4bde12657593d5e90d0533a3e4fd95e635124cb
|
||||
google.golang.org/api 9bf6e6e569ff057f75d9604a46c52928f17d2b54
|
||||
google.golang.org/appengine 12d5545dc1cfa6047a286d5e853841b6471f4c19
|
||||
google.golang.org/cloud 975617b05ea8a58727e6c1a06b6161ff4185a9f2
|
||||
google.golang.org/grpc d3ddb4469d5a1b949fc7a7da7c1d6a0d1b6de994
|
||||
gopkg.in/check.v1 64131543e7896d5bcc6bd5a76287eb75ea96c673
|
||||
gopkg.in/square/go-jose.v1 40d457b439244b546f023d056628e5184136899b
|
||||
gopkg.in/yaml.v2 bef53efd0c76e49e6de55ead051f886bea7e9420
|
||||
rsc.io/letsencrypt e770c10b0f1a64775ae91d240407ce00d1a5bdeb https://github.com/dmcgowan/letsencrypt.git
|
||||
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
|
20
vendor/github.com/docker/docker-credential-helpers/LICENSE
generated
vendored
Normal file
20
vendor/github.com/docker/docker-credential-helpers/LICENSE
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2016 David Calavera
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
87
vendor/github.com/docker/docker-credential-helpers/client/client.go
generated
vendored
Normal file
87
vendor/github.com/docker/docker-credential-helpers/client/client.go
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
// Store uses an external program to save credentials.
|
||||
func Store(program ProgramFunc, credentials *credentials.Credentials) error {
|
||||
cmd := program("store")
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(buffer).Encode(credentials); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Input(buffer)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get executes an external program to get the credentials from a native store.
|
||||
func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error) {
|
||||
cmd := program("get")
|
||||
cmd.Input(strings.NewReader(serverURL))
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
|
||||
if credentials.IsErrCredentialsNotFoundMessage(t) {
|
||||
return nil, credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("error getting credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
resp := &credentials.Credentials{
|
||||
ServerURL: serverURL,
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(bytes.NewReader(out)).Decode(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Erase executes a program to remove the server credentials from the native store.
|
||||
func Erase(program ProgramFunc, serverURL string) error {
|
||||
cmd := program("erase")
|
||||
cmd.Input(strings.NewReader(serverURL))
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
return fmt.Errorf("error erasing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List executes a program to list server credentials in the native store.
|
||||
func List(program ProgramFunc) (map[string]string, error) {
|
||||
cmd := program("list")
|
||||
cmd.Input(strings.NewReader("unused"))
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
return nil, fmt.Errorf("error listing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
var resp map[string]string
|
||||
if err = json.NewDecoder(bytes.NewReader(out)).Decode(&resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
37
vendor/github.com/docker/docker-credential-helpers/client/command.go
generated
vendored
Normal file
37
vendor/github.com/docker/docker-credential-helpers/client/command.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Program is an interface to execute external programs.
|
||||
type Program interface {
|
||||
Output() ([]byte, error)
|
||||
Input(in io.Reader)
|
||||
}
|
||||
|
||||
// ProgramFunc is a type of function that initializes programs based on arguments.
|
||||
type ProgramFunc func(args ...string) Program
|
||||
|
||||
// NewShellProgramFunc creates programs that are executed in a Shell.
|
||||
func NewShellProgramFunc(name string) ProgramFunc {
|
||||
return func(args ...string) Program {
|
||||
return &Shell{cmd: exec.Command(name, args...)}
|
||||
}
|
||||
}
|
||||
|
||||
// Shell invokes shell commands to talk with a remote credentials helper.
|
||||
type Shell struct {
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
// Output returns responses from the remote credentials helper.
|
||||
func (s *Shell) Output() ([]byte, error) {
|
||||
return s.cmd.Output()
|
||||
}
|
||||
|
||||
// Input sets the input to send to a remote credentials helper.
|
||||
func (s *Shell) Input(in io.Reader) {
|
||||
s.cmd.Stdin = in
|
||||
}
|
150
vendor/github.com/docker/docker-credential-helpers/credentials/credentials.go
generated
vendored
Normal file
150
vendor/github.com/docker/docker-credential-helpers/credentials/credentials.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Credentials holds the information shared between docker and the credentials store.
|
||||
type Credentials struct {
|
||||
ServerURL string
|
||||
Username string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// Docker credentials should be labeled as such in credentials stores that allow labelling.
|
||||
// That label allows to filter out non-Docker credentials too at lookup/search in macOS keychain,
|
||||
// Windows credentials manager and Linux libsecret. Default value is "Docker Credentials"
|
||||
var CredsLabel = "Docker Credentials"
|
||||
|
||||
func SetCredsLabel(label string) {
|
||||
CredsLabel = label
|
||||
}
|
||||
|
||||
// Serve initializes the credentials helper and parses the action argument.
|
||||
// This function is designed to be called from a command line interface.
|
||||
// It uses os.Args[1] as the key for the action.
|
||||
// It uses os.Stdin as input and os.Stdout as output.
|
||||
// This function terminates the program with os.Exit(1) if there is an error.
|
||||
func Serve(helper Helper) {
|
||||
var err error
|
||||
if len(os.Args) != 2 {
|
||||
err = fmt.Errorf("Usage: %s <store|get|erase|list>", os.Args[0])
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stdout, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleCommand uses a helper and a key to run a credential action.
|
||||
func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error {
|
||||
switch key {
|
||||
case "store":
|
||||
return Store(helper, in)
|
||||
case "get":
|
||||
return Get(helper, in, out)
|
||||
case "erase":
|
||||
return Erase(helper, in)
|
||||
case "list":
|
||||
return List(helper, out)
|
||||
}
|
||||
return fmt.Errorf("Unknown credential action `%s`", key)
|
||||
}
|
||||
|
||||
// Store uses a helper and an input reader to save credentials.
|
||||
// The reader must contain the JSON serialization of a Credentials struct.
|
||||
func Store(helper Helper, reader io.Reader) error {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
for scanner.Scan() {
|
||||
buffer.Write(scanner.Bytes())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
var creds Credentials
|
||||
if err := json.NewDecoder(buffer).Decode(&creds); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return helper.Add(&creds)
|
||||
}
|
||||
|
||||
// Get retrieves the credentials for a given server url.
|
||||
// The reader must contain the server URL to search.
|
||||
// The writer is used to write the JSON serialization of the credentials.
|
||||
func Get(helper Helper, reader io.Reader, writer io.Writer) error {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
for scanner.Scan() {
|
||||
buffer.Write(scanner.Bytes())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
serverURL := strings.TrimSpace(buffer.String())
|
||||
|
||||
username, secret, err := helper.Get(serverURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := Credentials{
|
||||
Username: username,
|
||||
Secret: secret,
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
if err := json.NewEncoder(buffer).Encode(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(writer, buffer.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Erase removes credentials from the store.
|
||||
// The reader must contain the server URL to remove.
|
||||
func Erase(helper Helper, reader io.Reader) error {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
for scanner.Scan() {
|
||||
buffer.Write(scanner.Bytes())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
serverURL := strings.TrimSpace(buffer.String())
|
||||
|
||||
return helper.Delete(serverURL)
|
||||
}
|
||||
|
||||
//List returns all the serverURLs of keys in
|
||||
//the OS store as a list of strings
|
||||
func List(helper Helper, writer io.Writer) error {
|
||||
accts, err := helper.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.NewEncoder(writer).Encode(accts)
|
||||
}
|
37
vendor/github.com/docker/docker-credential-helpers/credentials/error.go
generated
vendored
Normal file
37
vendor/github.com/docker/docker-credential-helpers/credentials/error.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
package credentials
|
||||
|
||||
// ErrCredentialsNotFound standarizes the not found error, so every helper returns
|
||||
// the same message and docker can handle it properly.
|
||||
const errCredentialsNotFoundMessage = "credentials not found in native keychain"
|
||||
|
||||
// errCredentialsNotFound represents an error
|
||||
// raised when credentials are not in the store.
|
||||
type errCredentialsNotFound struct{}
|
||||
|
||||
// Error returns the standard error message
|
||||
// for when the credentials are not in the store.
|
||||
func (errCredentialsNotFound) Error() string {
|
||||
return errCredentialsNotFoundMessage
|
||||
}
|
||||
|
||||
// NewErrCredentialsNotFound creates a new error
|
||||
// for when the credentials are not in the store.
|
||||
func NewErrCredentialsNotFound() error {
|
||||
return errCredentialsNotFound{}
|
||||
}
|
||||
|
||||
// IsErrCredentialsNotFound returns true if the error
|
||||
// was caused by not having a set of credentials in a store.
|
||||
func IsErrCredentialsNotFound(err error) bool {
|
||||
_, ok := err.(errCredentialsNotFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsErrCredentialsNotFoundMessage returns true if the error
|
||||
// was caused by not having a set of credentials in a store.
|
||||
//
|
||||
// This function helps to check messages returned by an
|
||||
// external program via its standard output.
|
||||
func IsErrCredentialsNotFoundMessage(err string) bool {
|
||||
return err == errCredentialsNotFoundMessage
|
||||
}
|
14
vendor/github.com/docker/docker-credential-helpers/credentials/helper.go
generated
vendored
Normal file
14
vendor/github.com/docker/docker-credential-helpers/credentials/helper.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package credentials
|
||||
|
||||
// Helper is the interface a credentials store helper must implement.
|
||||
type Helper interface {
|
||||
// Add appends credentials to the store.
|
||||
Add(*Credentials) error
|
||||
// Delete removes credentials from the store.
|
||||
Delete(serverURL string) error
|
||||
// Get retrieves credentials from the store.
|
||||
// It returns username and secret as strings.
|
||||
Get(serverURL string) (string, string, error)
|
||||
// List returns the stored serverURLs and their associated usernames.
|
||||
List() (map[string]string, error)
|
||||
}
|
228
vendor/github.com/docker/docker-credential-helpers/osxkeychain/osxkeychain_darwin.c
generated
vendored
Normal file
228
vendor/github.com/docker/docker-credential-helpers/osxkeychain/osxkeychain_darwin.c
generated
vendored
Normal file
@ -0,0 +1,228 @@
|
||||
#include "osxkeychain_darwin.h"
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Foundation/NSValue.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
char *get_error(OSStatus status) {
|
||||
char *buf = malloc(128);
|
||||
CFStringRef str = SecCopyErrorMessageString(status, NULL);
|
||||
int success = CFStringGetCString(str, buf, 128, kCFStringEncodingUTF8);
|
||||
if (!success) {
|
||||
strncpy(buf, "Unknown error", 128);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
char *keychain_add(struct Server *server, char *label, char *username, char *secret) {
|
||||
SecKeychainItemRef item;
|
||||
|
||||
OSStatus status = SecKeychainAddInternetPassword(
|
||||
NULL,
|
||||
strlen(server->host), server->host,
|
||||
0, NULL,
|
||||
strlen(username), username,
|
||||
strlen(server->path), server->path,
|
||||
server->port,
|
||||
server->proto,
|
||||
kSecAuthenticationTypeDefault,
|
||||
strlen(secret), secret,
|
||||
&item
|
||||
);
|
||||
|
||||
if (status) {
|
||||
return get_error(status);
|
||||
}
|
||||
|
||||
SecKeychainAttribute attribute;
|
||||
SecKeychainAttributeList attrs;
|
||||
attribute.tag = kSecLabelItemAttr;
|
||||
attribute.data = label;
|
||||
attribute.length = strlen(label);
|
||||
attrs.count = 1;
|
||||
attrs.attr = &attribute;
|
||||
|
||||
status = SecKeychainItemModifyContent(item, &attrs, 0, NULL);
|
||||
|
||||
if (status) {
|
||||
return get_error(status);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret) {
|
||||
char *tmp;
|
||||
SecKeychainItemRef item;
|
||||
|
||||
OSStatus status = SecKeychainFindInternetPassword(
|
||||
NULL,
|
||||
strlen(server->host), server->host,
|
||||
0, NULL,
|
||||
0, NULL,
|
||||
strlen(server->path), server->path,
|
||||
server->port,
|
||||
server->proto,
|
||||
kSecAuthenticationTypeDefault,
|
||||
secret_l, (void **)&tmp,
|
||||
&item);
|
||||
|
||||
if (status) {
|
||||
return get_error(status);
|
||||
}
|
||||
|
||||
*secret = strdup(tmp);
|
||||
SecKeychainItemFreeContent(NULL, tmp);
|
||||
|
||||
SecKeychainAttributeList list;
|
||||
SecKeychainAttribute attr;
|
||||
|
||||
list.count = 1;
|
||||
list.attr = &attr;
|
||||
attr.tag = kSecAccountItemAttr;
|
||||
|
||||
status = SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL);
|
||||
if (status) {
|
||||
return get_error(status);
|
||||
}
|
||||
|
||||
*username = strdup(attr.data);
|
||||
*username_l = attr.length;
|
||||
SecKeychainItemFreeContent(&list, NULL);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *keychain_delete(struct Server *server) {
|
||||
SecKeychainItemRef item;
|
||||
|
||||
OSStatus status = SecKeychainFindInternetPassword(
|
||||
NULL,
|
||||
strlen(server->host), server->host,
|
||||
0, NULL,
|
||||
0, NULL,
|
||||
strlen(server->path), server->path,
|
||||
server->port,
|
||||
server->proto,
|
||||
kSecAuthenticationTypeDefault,
|
||||
0, NULL,
|
||||
&item);
|
||||
|
||||
if (status) {
|
||||
return get_error(status);
|
||||
}
|
||||
|
||||
status = SecKeychainItemDelete(item);
|
||||
if (status) {
|
||||
return get_error(status);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char * CFStringToCharArr(CFStringRef aString) {
|
||||
if (aString == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
CFIndex length = CFStringGetLength(aString);
|
||||
CFIndex maxSize =
|
||||
CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
|
||||
char *buffer = (char *)malloc(maxSize);
|
||||
if (CFStringGetCString(aString, buffer, maxSize,
|
||||
kCFStringEncodingUTF8)) {
|
||||
return buffer;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *keychain_list(char *credsLabel, char *** paths, char *** accts, unsigned int *list_l) {
|
||||
CFStringRef credsLabelCF = CFStringCreateWithCString(NULL, credsLabel, kCFStringEncodingUTF8);
|
||||
CFMutableDictionaryRef query = CFDictionaryCreateMutable (NULL, 1, NULL, NULL);
|
||||
CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
|
||||
CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue);
|
||||
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
|
||||
CFDictionaryAddValue(query, kSecAttrLabel, credsLabelCF);
|
||||
//Use this query dictionary
|
||||
CFTypeRef result= NULL;
|
||||
OSStatus status = SecItemCopyMatching(
|
||||
query,
|
||||
&result);
|
||||
|
||||
CFRelease(credsLabelCF);
|
||||
|
||||
//Ran a search and store the results in result
|
||||
if (status) {
|
||||
return get_error(status);
|
||||
}
|
||||
CFIndex numKeys = CFArrayGetCount(result);
|
||||
*paths = (char **) malloc((int)sizeof(char *)*numKeys);
|
||||
*accts = (char **) malloc((int)sizeof(char *)*numKeys);
|
||||
//result is of type CFArray
|
||||
for(CFIndex i=0; i<numKeys; i++) {
|
||||
CFDictionaryRef currKey = CFArrayGetValueAtIndex(result,i);
|
||||
|
||||
CFStringRef protocolTmp = CFDictionaryGetValue(currKey, CFSTR("ptcl"));
|
||||
if (protocolTmp != NULL) {
|
||||
CFStringRef protocolStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), protocolTmp);
|
||||
if (CFStringCompare(protocolStr, CFSTR("htps"), 0) == kCFCompareEqualTo) {
|
||||
protocolTmp = CFSTR("https://");
|
||||
}
|
||||
else {
|
||||
protocolTmp = CFSTR("http://");
|
||||
}
|
||||
CFRelease(protocolStr);
|
||||
}
|
||||
else {
|
||||
char * path = "0";
|
||||
char * acct = "0";
|
||||
(*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)));
|
||||
memcpy((*paths)[i], path, sizeof(char)*(strlen(path)));
|
||||
(*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)));
|
||||
memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)));
|
||||
continue;
|
||||
}
|
||||
|
||||
CFMutableStringRef str = CFStringCreateMutableCopy(NULL, 0, protocolTmp);
|
||||
CFStringRef serverTmp = CFDictionaryGetValue(currKey, CFSTR("srvr"));
|
||||
if (serverTmp != NULL) {
|
||||
CFStringAppend(str, serverTmp);
|
||||
}
|
||||
|
||||
CFStringRef pathTmp = CFDictionaryGetValue(currKey, CFSTR("path"));
|
||||
if (pathTmp != NULL) {
|
||||
CFStringAppend(str, pathTmp);
|
||||
}
|
||||
|
||||
const NSNumber * portTmp = CFDictionaryGetValue(currKey, CFSTR("port"));
|
||||
if (portTmp != NULL && portTmp.integerValue != 0) {
|
||||
CFStringRef portStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), portTmp);
|
||||
CFStringAppend(str, CFSTR(":"));
|
||||
CFStringAppend(str, portStr);
|
||||
CFRelease(portStr);
|
||||
}
|
||||
|
||||
CFStringRef acctTmp = CFDictionaryGetValue(currKey, CFSTR("acct"));
|
||||
if (acctTmp == NULL) {
|
||||
acctTmp = CFSTR("account not defined");
|
||||
}
|
||||
|
||||
char * path = CFStringToCharArr(str);
|
||||
char * acct = CFStringToCharArr(acctTmp);
|
||||
|
||||
//We now have all we need, username and servername. Now export this to .go
|
||||
(*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)+1));
|
||||
memcpy((*paths)[i], path, sizeof(char)*(strlen(path)+1));
|
||||
(*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)+1));
|
||||
memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)+1));
|
||||
|
||||
CFRelease(str);
|
||||
}
|
||||
*list_l = (int)numKeys;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void freeListData(char *** data, unsigned int length) {
|
||||
for(int i=0; i<length; i++) {
|
||||
free((*data)[i]);
|
||||
}
|
||||
free(*data);
|
||||
}
|
170
vendor/github.com/docker/docker-credential-helpers/osxkeychain/osxkeychain_darwin.go
generated
vendored
Normal file
170
vendor/github.com/docker/docker-credential-helpers/osxkeychain/osxkeychain_darwin.go
generated
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
package osxkeychain
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c -mmacosx-version-min=10.10
|
||||
#cgo LDFLAGS: -framework Security -framework Foundation -mmacosx-version-min=10.10
|
||||
|
||||
#include "osxkeychain_darwin.h"
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
// errCredentialsNotFound is the specific error message returned by OS X
|
||||
// when the credentials are not in the keychain.
|
||||
const errCredentialsNotFound = "The specified item could not be found in the keychain."
|
||||
|
||||
// Osxkeychain handles secrets using the OS X Keychain as store.
|
||||
type Osxkeychain struct{}
|
||||
|
||||
// Add adds new credentials to the keychain.
|
||||
func (h Osxkeychain) Add(creds *credentials.Credentials) error {
|
||||
h.Delete(creds.ServerURL)
|
||||
|
||||
s, err := splitServer(creds.ServerURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer freeServer(s)
|
||||
|
||||
label := C.CString(credentials.CredsLabel)
|
||||
defer C.free(unsafe.Pointer(label))
|
||||
username := C.CString(creds.Username)
|
||||
defer C.free(unsafe.Pointer(username))
|
||||
secret := C.CString(creds.Secret)
|
||||
defer C.free(unsafe.Pointer(secret))
|
||||
|
||||
errMsg := C.keychain_add(s, label, username, secret)
|
||||
if errMsg != nil {
|
||||
defer C.free(unsafe.Pointer(errMsg))
|
||||
return errors.New(C.GoString(errMsg))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes credentials from the keychain.
|
||||
func (h Osxkeychain) Delete(serverURL string) error {
|
||||
s, err := splitServer(serverURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer freeServer(s)
|
||||
|
||||
errMsg := C.keychain_delete(s)
|
||||
if errMsg != nil {
|
||||
defer C.free(unsafe.Pointer(errMsg))
|
||||
return errors.New(C.GoString(errMsg))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the username and secret to use for a given registry server URL.
|
||||
func (h Osxkeychain) Get(serverURL string) (string, string, error) {
|
||||
s, err := splitServer(serverURL)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer freeServer(s)
|
||||
|
||||
var usernameLen C.uint
|
||||
var username *C.char
|
||||
var secretLen C.uint
|
||||
var secret *C.char
|
||||
defer C.free(unsafe.Pointer(username))
|
||||
defer C.free(unsafe.Pointer(secret))
|
||||
|
||||
errMsg := C.keychain_get(s, &usernameLen, &username, &secretLen, &secret)
|
||||
if errMsg != nil {
|
||||
defer C.free(unsafe.Pointer(errMsg))
|
||||
goMsg := C.GoString(errMsg)
|
||||
if goMsg == errCredentialsNotFound {
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
|
||||
return "", "", errors.New(goMsg)
|
||||
}
|
||||
|
||||
user := C.GoStringN(username, C.int(usernameLen))
|
||||
pass := C.GoStringN(secret, C.int(secretLen))
|
||||
return user, pass, nil
|
||||
}
|
||||
|
||||
// List returns the stored URLs and corresponding usernames.
|
||||
func (h Osxkeychain) List() (map[string]string, error) {
|
||||
credsLabelC := C.CString(credentials.CredsLabel)
|
||||
defer C.free(unsafe.Pointer(credsLabelC))
|
||||
|
||||
var pathsC **C.char
|
||||
defer C.free(unsafe.Pointer(pathsC))
|
||||
var acctsC **C.char
|
||||
defer C.free(unsafe.Pointer(acctsC))
|
||||
var listLenC C.uint
|
||||
errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC)
|
||||
if errMsg != nil {
|
||||
defer C.free(unsafe.Pointer(errMsg))
|
||||
goMsg := C.GoString(errMsg)
|
||||
return nil, errors.New(goMsg)
|
||||
}
|
||||
|
||||
defer C.freeListData(&pathsC, listLenC)
|
||||
defer C.freeListData(&acctsC, listLenC)
|
||||
|
||||
var listLen int
|
||||
listLen = int(listLenC)
|
||||
pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
|
||||
acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
|
||||
//taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper
|
||||
resp := make(map[string]string)
|
||||
for i := 0; i < listLen; i++ {
|
||||
if C.GoString(pathTmp[i]) == "0" {
|
||||
continue
|
||||
}
|
||||
resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func splitServer(serverURL string) (*C.struct_Server, error) {
|
||||
u, err := url.Parse(serverURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostAndPort := strings.Split(u.Host, ":")
|
||||
host := hostAndPort[0]
|
||||
var port int
|
||||
if len(hostAndPort) == 2 {
|
||||
p, err := strconv.Atoi(hostAndPort[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port = p
|
||||
}
|
||||
|
||||
proto := C.kSecProtocolTypeHTTPS
|
||||
if u.Scheme != "https" {
|
||||
proto = C.kSecProtocolTypeHTTP
|
||||
}
|
||||
|
||||
return &C.struct_Server{
|
||||
proto: C.SecProtocolType(proto),
|
||||
host: C.CString(host),
|
||||
port: C.uint(port),
|
||||
path: C.CString(u.Path),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func freeServer(s *C.struct_Server) {
|
||||
C.free(unsafe.Pointer(s.host))
|
||||
C.free(unsafe.Pointer(s.path))
|
||||
}
|
14
vendor/github.com/docker/docker-credential-helpers/osxkeychain/osxkeychain_darwin.h
generated
vendored
Normal file
14
vendor/github.com/docker/docker-credential-helpers/osxkeychain/osxkeychain_darwin.h
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
#include <Security/Security.h>
|
||||
|
||||
struct Server {
|
||||
SecProtocolType proto;
|
||||
char *host;
|
||||
char *path;
|
||||
unsigned int port;
|
||||
};
|
||||
|
||||
char *keychain_add(struct Server *server, char *label, char *username, char *secret);
|
||||
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret);
|
||||
char *keychain_delete(struct Server *server);
|
||||
char *keychain_list(char *credsLabel, char *** data, char *** accts, unsigned int *list_l);
|
||||
void freeListData(char *** data, unsigned int length);
|
162
vendor/github.com/docker/docker-credential-helpers/secretservice/secretservice_linux.c
generated
vendored
Normal file
162
vendor/github.com/docker/docker-credential-helpers/secretservice/secretservice_linux.c
generated
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "secretservice_linux.h"
|
||||
|
||||
const SecretSchema *docker_get_schema(void)
|
||||
{
|
||||
static const SecretSchema docker_schema = {
|
||||
"io.docker.Credentials", SECRET_SCHEMA_NONE,
|
||||
{
|
||||
{ "label", SECRET_SCHEMA_ATTRIBUTE_STRING },
|
||||
{ "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
|
||||
{ "username", SECRET_SCHEMA_ATTRIBUTE_STRING },
|
||||
{ "docker_cli", SECRET_SCHEMA_ATTRIBUTE_STRING },
|
||||
{ "NULL", 0 },
|
||||
}
|
||||
};
|
||||
return &docker_schema;
|
||||
}
|
||||
|
||||
GError *add(char *label, char *server, char *username, char *secret) {
|
||||
GError *err = NULL;
|
||||
|
||||
secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT,
|
||||
server, secret, NULL, &err,
|
||||
"label", label,
|
||||
"server", server,
|
||||
"username", username,
|
||||
"docker_cli", "1",
|
||||
NULL);
|
||||
return err;
|
||||
}
|
||||
|
||||
GError *delete(char *server) {
|
||||
GError *err = NULL;
|
||||
|
||||
secret_password_clear_sync(DOCKER_SCHEMA, NULL, &err,
|
||||
"server", server,
|
||||
"docker_cli", "1",
|
||||
NULL);
|
||||
if (err != NULL)
|
||||
return err;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *get_attribute(const char *attribute, SecretItem *item) {
|
||||
GHashTable *attributes;
|
||||
GHashTableIter iter;
|
||||
gchar *value, *key;
|
||||
|
||||
attributes = secret_item_get_attributes(item);
|
||||
g_hash_table_iter_init(&iter, attributes);
|
||||
while (g_hash_table_iter_next(&iter, (void **)&key, (void **)&value)) {
|
||||
if (strncmp(key, attribute, strlen(key)) == 0)
|
||||
return (char *)value;
|
||||
}
|
||||
g_hash_table_unref(attributes);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GError *get(char *server, char **username, char **secret) {
|
||||
GError *err = NULL;
|
||||
GHashTable *attributes;
|
||||
SecretService *service;
|
||||
GList *items, *l;
|
||||
SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK;
|
||||
SecretValue *secretValue;
|
||||
gsize length;
|
||||
gchar *value;
|
||||
|
||||
attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
g_hash_table_insert(attributes, g_strdup("server"), g_strdup(server));
|
||||
g_hash_table_insert(attributes, g_strdup("docker_cli"), g_strdup("1"));
|
||||
|
||||
service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err);
|
||||
if (err == NULL) {
|
||||
items = secret_service_search_sync(service, DOCKER_SCHEMA, attributes, flags, NULL, &err);
|
||||
if (err == NULL) {
|
||||
for (l = items; l != NULL; l = g_list_next(l)) {
|
||||
value = secret_item_get_schema_name(l->data);
|
||||
if (strncmp(value, "io.docker.Credentials", strlen(value)) != 0) {
|
||||
g_free(value);
|
||||
continue;
|
||||
}
|
||||
g_free(value);
|
||||
secretValue = secret_item_get_secret(l->data);
|
||||
if (secret != NULL) {
|
||||
*secret = strdup(secret_value_get(secretValue, &length));
|
||||
secret_value_unref(secretValue);
|
||||
}
|
||||
*username = get_attribute("username", l->data);
|
||||
}
|
||||
g_list_free_full(items, g_object_unref);
|
||||
}
|
||||
g_object_unref(service);
|
||||
}
|
||||
g_hash_table_unref(attributes);
|
||||
if (err != NULL) {
|
||||
return err;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GError *list(char *ref_label, char *** paths, char *** accts, unsigned int *list_l) {
|
||||
GList *items;
|
||||
GError *err = NULL;
|
||||
SecretService *service;
|
||||
SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK;
|
||||
GHashTable *attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
|
||||
// List credentials with the right label only
|
||||
g_hash_table_insert(attributes, g_strdup("label"), g_strdup(ref_label));
|
||||
|
||||
service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err);
|
||||
if (err != NULL) {
|
||||
return err;
|
||||
}
|
||||
|
||||
items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err);
|
||||
int numKeys = g_list_length(items);
|
||||
if (err != NULL) {
|
||||
return err;
|
||||
}
|
||||
|
||||
char **tmp_paths = (char **) calloc(1,(int)sizeof(char *)*numKeys);
|
||||
char **tmp_accts = (char **) calloc(1,(int)sizeof(char *)*numKeys);
|
||||
|
||||
// items now contains our keys from the gnome keyring
|
||||
// we will now put it in our two lists to return it to go
|
||||
GList *current;
|
||||
int listNumber = 0;
|
||||
for(current = items; current!=NULL; current = current->next) {
|
||||
char *pathTmp = secret_item_get_label(current->data);
|
||||
// you cannot have a key without a label in the gnome keyring
|
||||
char *acctTmp = get_attribute("username",current->data);
|
||||
if (acctTmp==NULL) {
|
||||
acctTmp = "account not defined";
|
||||
}
|
||||
|
||||
tmp_paths[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(pathTmp)+1));
|
||||
tmp_accts[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(acctTmp)+1));
|
||||
|
||||
memcpy(tmp_paths[listNumber], pathTmp, sizeof(char)*(strlen(pathTmp)+1));
|
||||
memcpy(tmp_accts[listNumber], acctTmp, sizeof(char)*(strlen(acctTmp)+1));
|
||||
|
||||
listNumber = listNumber + 1;
|
||||
}
|
||||
|
||||
*paths = (char **) realloc(tmp_paths, (int)sizeof(char *)*listNumber);
|
||||
*accts = (char **) realloc(tmp_accts, (int)sizeof(char *)*listNumber);
|
||||
|
||||
*list_l = listNumber;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void freeListData(char *** data, unsigned int length) {
|
||||
int i;
|
||||
for(i=0; i<length; i++) {
|
||||
free((*data)[i]);
|
||||
}
|
||||
free(*data);
|
||||
}
|
115
vendor/github.com/docker/docker-credential-helpers/secretservice/secretservice_linux.go
generated
vendored
Normal file
115
vendor/github.com/docker/docker-credential-helpers/secretservice/secretservice_linux.go
generated
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
package secretservice
|
||||
|
||||
/*
|
||||
#cgo pkg-config: libsecret-1
|
||||
|
||||
#include "secretservice_linux.h"
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
// Secretservice handles secrets using Linux secret-service as a store.
|
||||
type Secretservice struct{}
|
||||
|
||||
// Add adds new credentials to the keychain.
|
||||
func (h Secretservice) Add(creds *credentials.Credentials) error {
|
||||
if creds == nil {
|
||||
return errors.New("missing credentials")
|
||||
}
|
||||
credsLabel := C.CString(credentials.CredsLabel)
|
||||
defer C.free(unsafe.Pointer(credsLabel))
|
||||
server := C.CString(creds.ServerURL)
|
||||
defer C.free(unsafe.Pointer(server))
|
||||
username := C.CString(creds.Username)
|
||||
defer C.free(unsafe.Pointer(username))
|
||||
secret := C.CString(creds.Secret)
|
||||
defer C.free(unsafe.Pointer(secret))
|
||||
|
||||
if err := C.add(credsLabel, server, username, secret); err != nil {
|
||||
defer C.g_error_free(err)
|
||||
errMsg := (*C.char)(unsafe.Pointer(err.message))
|
||||
return errors.New(C.GoString(errMsg))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes credentials from the store.
|
||||
func (h Secretservice) Delete(serverURL string) error {
|
||||
if serverURL == "" {
|
||||
return errors.New("missing server url")
|
||||
}
|
||||
server := C.CString(serverURL)
|
||||
defer C.free(unsafe.Pointer(server))
|
||||
|
||||
if err := C.delete(server); err != nil {
|
||||
defer C.g_error_free(err)
|
||||
errMsg := (*C.char)(unsafe.Pointer(err.message))
|
||||
return errors.New(C.GoString(errMsg))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the username and secret to use for a given registry server URL.
|
||||
func (h Secretservice) Get(serverURL string) (string, string, error) {
|
||||
if serverURL == "" {
|
||||
return "", "", errors.New("missing server url")
|
||||
}
|
||||
var username *C.char
|
||||
defer C.free(unsafe.Pointer(username))
|
||||
var secret *C.char
|
||||
defer C.free(unsafe.Pointer(secret))
|
||||
server := C.CString(serverURL)
|
||||
defer C.free(unsafe.Pointer(server))
|
||||
|
||||
err := C.get(server, &username, &secret)
|
||||
if err != nil {
|
||||
defer C.g_error_free(err)
|
||||
errMsg := (*C.char)(unsafe.Pointer(err.message))
|
||||
return "", "", errors.New(C.GoString(errMsg))
|
||||
}
|
||||
user := C.GoString(username)
|
||||
pass := C.GoString(secret)
|
||||
if pass == "" {
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
return user, pass, nil
|
||||
}
|
||||
|
||||
// List returns the stored URLs and corresponding usernames for a given credentials label
|
||||
func (h Secretservice) List() (map[string]string, error) {
|
||||
credsLabelC := C.CString(credentials.CredsLabel)
|
||||
defer C.free(unsafe.Pointer(credsLabelC))
|
||||
|
||||
var pathsC **C.char
|
||||
defer C.free(unsafe.Pointer(pathsC))
|
||||
var acctsC **C.char
|
||||
defer C.free(unsafe.Pointer(acctsC))
|
||||
var listLenC C.uint
|
||||
err := C.list(credsLabelC, &pathsC, &acctsC, &listLenC)
|
||||
if err != nil {
|
||||
defer C.free(unsafe.Pointer(err))
|
||||
return nil, errors.New("Error from list function in secretservice_linux.c likely due to error in secretservice library")
|
||||
}
|
||||
defer C.freeListData(&pathsC, listLenC)
|
||||
defer C.freeListData(&acctsC, listLenC)
|
||||
|
||||
resp := make(map[string]string)
|
||||
|
||||
listLen := int(listLenC)
|
||||
if listLen == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
|
||||
acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
|
||||
for i := 0; i < listLen; i++ {
|
||||
resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
13
vendor/github.com/docker/docker-credential-helpers/secretservice/secretservice_linux.h
generated
vendored
Normal file
13
vendor/github.com/docker/docker-credential-helpers/secretservice/secretservice_linux.h
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
#define SECRET_WITH_UNSTABLE 1
|
||||
#define SECRET_API_SUBJECT_TO_CHANGE 1
|
||||
#include <libsecret/secret.h>
|
||||
|
||||
const SecretSchema *docker_get_schema(void) G_GNUC_CONST;
|
||||
|
||||
#define DOCKER_SCHEMA docker_get_schema()
|
||||
|
||||
GError *add(char *label, char *server, char *username, char *secret);
|
||||
GError *delete(char *server);
|
||||
GError *get(char *server, char **username, char **secret);
|
||||
GError *list(char *label, char *** paths, char *** accts, unsigned int *list_l);
|
||||
void freeListData(char *** data, unsigned int length);
|
90
vendor/github.com/docker/docker/README.md
generated
vendored
90
vendor/github.com/docker/docker/README.md
generated
vendored
@ -1,90 +0,0 @@
|
||||
### Docker users, see [Moby and Docker](https://mobyproject.org/#moby-and-docker) to clarify the relationship between the projects
|
||||
|
||||
### Docker maintainers and contributors, see [Transitioning to Moby](#transitioning-to-moby) for more details
|
||||
|
||||
The Moby Project
|
||||
================
|
||||
|
||||

|
||||
|
||||
Moby is an open-source project created by Docker to advance the software containerization movement.
|
||||
It provides a “Lego set” of dozens of components, the framework for assembling them into custom container-based systems, and a place for all container enthusiasts to experiment and exchange ideas.
|
||||
|
||||
# Moby
|
||||
|
||||
## Overview
|
||||
|
||||
At the core of Moby is a framework to assemble specialized container systems.
|
||||
It provides:
|
||||
|
||||
- A library of containerized components for all vital aspects of a container system: OS, container runtime, orchestration, infrastructure management, networking, storage, security, build, image distribution, etc.
|
||||
- Tools to assemble the components into runnable artifacts for a variety of platforms and architectures: bare metal (both x86 and Arm); executables for Linux, Mac and Windows; VM images for popular cloud and virtualization providers.
|
||||
- A set of reference assemblies which can be used as-is, modified, or used as inspiration to create your own.
|
||||
|
||||
All Moby components are containers, so creating new components is as easy as building a new OCI-compatible container.
|
||||
|
||||
## Principles
|
||||
|
||||
Moby is an open project guided by strong principles, but modular, flexible and without too strong an opinion on user experience, so it is open to the community to help set its direction.
|
||||
The guiding principles are:
|
||||
|
||||
- Batteries included but swappable: Moby includes enough components to build fully featured container system, but its modular architecture ensures that most of the components can be swapped by different implementations.
|
||||
- Usable security: Moby will provide secure defaults without compromising usability.
|
||||
- Container centric: Moby is built with containers, for running containers.
|
||||
|
||||
With Moby, you should be able to describe all the components of your distributed application, from the high-level configuration files down to the kernel you would like to use and build and deploy it easily.
|
||||
|
||||
Moby uses [containerd](https://github.com/containerd/containerd) as the default container runtime.
|
||||
|
||||
## Audience
|
||||
|
||||
Moby is recommended for anyone who wants to assemble a container-based system. This includes:
|
||||
|
||||
- Hackers who want to customize or patch their Docker build
|
||||
- System engineers or integrators building a container system
|
||||
- Infrastructure providers looking to adapt existing container systems to their environment
|
||||
- Container enthusiasts who want to experiment with the latest container tech
|
||||
- Open-source developers looking to test their project in a variety of different systems
|
||||
- Anyone curious about Docker internals and how it’s built
|
||||
|
||||
Moby is NOT recommended for:
|
||||
|
||||
- Application developers looking for an easy way to run their applications in containers. We recommend Docker CE instead.
|
||||
- Enterprise IT and development teams looking for a ready-to-use, commercially supported container platform. We recommend Docker EE instead.
|
||||
- Anyone curious about containers and looking for an easy way to learn. We recommend the docker.com website instead.
|
||||
|
||||
# Transitioning to Moby
|
||||
|
||||
Docker is transitioning all of its open source collaborations to the Moby project going forward.
|
||||
During the transition, all open source activity should continue as usual.
|
||||
|
||||
We are proposing the following list of changes:
|
||||
|
||||
- splitting up the engine into more open components
|
||||
- removing the docker UI, SDK etc to keep them in the Docker org
|
||||
- clarifying that the project is not limited to the engine, but to the assembly of all the individual components of the Docker platform
|
||||
- open-source new tools & components which we currently use to assemble the Docker product, but could benefit the community
|
||||
- defining an open, community-centric governance inspired by the Fedora project (a very successful example of balancing the needs of the community with the constraints of the primary corporate sponsor)
|
||||
|
||||
-----
|
||||
|
||||
Legal
|
||||
=====
|
||||
|
||||
*Brought to you courtesy of our legal counsel. For more context,
|
||||
please see the [NOTICE](https://github.com/moby/moby/blob/master/NOTICE) document in this repo.*
|
||||
|
||||
Use and transfer of Moby may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
|
||||
For more information, please see https://www.bis.doc.gov
|
||||
|
||||
|
||||
Licensing
|
||||
=========
|
||||
Moby is licensed under the Apache License, Version 2.0. See
|
||||
[LICENSE](https://github.com/moby/moby/blob/master/LICENSE) for the full
|
||||
license text.
|
42
vendor/github.com/docker/docker/api/README.md
generated
vendored
42
vendor/github.com/docker/docker/api/README.md
generated
vendored
@ -1,42 +0,0 @@
|
||||
# Working on the Engine API
|
||||
|
||||
The Engine API is an HTTP API used by the command-line client to communicate with the daemon. It can also be used by third-party software to control the daemon.
|
||||
|
||||
It consists of various components in this repository:
|
||||
|
||||
- `api/swagger.yaml` A Swagger definition of the API.
|
||||
- `api/types/` Types shared by both the client and server, representing various objects, options, responses, etc. Most are written manually, but some are automatically generated from the Swagger definition. See [#27919](https://github.com/docker/docker/issues/27919) for progress on this.
|
||||
- `cli/` The command-line client.
|
||||
- `client/` The Go client used by the command-line client. It can also be used by third-party Go programs.
|
||||
- `daemon/` The daemon, which serves the API.
|
||||
|
||||
## Swagger definition
|
||||
|
||||
The API is defined by the [Swagger](http://swagger.io/specification/) definition in `api/swagger.yaml`. This definition can be used to:
|
||||
|
||||
1. To automatically generate documentation.
|
||||
2. To automatically generate the Go server and client. (A work-in-progress.)
|
||||
3. Provide a machine readable version of the API for introspecting what it can do, automatically generating clients for other languages, etc.
|
||||
|
||||
## Updating the API documentation
|
||||
|
||||
The API documentation is generated entirely from `api/swagger.yaml`. If you make updates to the API, you'll need to edit this file to represent the change in the documentation.
|
||||
|
||||
The file is split into two main sections:
|
||||
|
||||
- `definitions`, which defines re-usable objects used in requests and responses
|
||||
- `paths`, which defines the API endpoints (and some inline objects which don't need to be reusable)
|
||||
|
||||
To make an edit, first look for the endpoint you want to edit under `paths`, then make the required edits. Endpoints may reference reusable objects with `$ref`, which can be found in the `definitions` section.
|
||||
|
||||
There is hopefully enough example material in the file for you to copy a similar pattern from elsewhere in the file (e.g. adding new fields or endpoints), but for the full reference, see the [Swagger specification](https://github.com/docker/docker/issues/27919)
|
||||
|
||||
`swagger.yaml` is validated by `hack/validate/swagger` to ensure it is a valid Swagger definition. This is useful for when you are making edits to ensure you are doing the right thing.
|
||||
|
||||
## Viewing the API documentation
|
||||
|
||||
When you make edits to `swagger.yaml`, you may want to check the generated API documentation to ensure it renders correctly.
|
||||
|
||||
Run `make swagger-docs` and a preview will be running at `http://localhost`. Some of the styling may be incorrect, but you'll be able to ensure that it is generating the correct documentation.
|
||||
|
||||
The production documentation is generated by vendoring `swagger.yaml` into [docker/docker.github.io](https://github.com/docker/docker.github.io).
|
14
vendor/github.com/docker/docker/api/types/versions/README.md
generated
vendored
14
vendor/github.com/docker/docker/api/types/versions/README.md
generated
vendored
@ -1,14 +0,0 @@
|
||||
# Legacy API type versions
|
||||
|
||||
This package includes types for legacy API versions. The stable version of the API types live in `api/types/*.go`.
|
||||
|
||||
Consider moving a type here when you need to keep backwards compatibility in the API. This legacy types are organized by the latest API version they appear in. For instance, types in the `v1p19` package are valid for API versions below or equal `1.19`. Types in the `v1p20` package are valid for the API version `1.20`, since the versions below that will use the legacy types in `v1p19`.
|
||||
|
||||
## Package name conventions
|
||||
|
||||
The package name convention is to use `v` as a prefix for the version number and `p`(patch) as a separator. We use this nomenclature due to a few restrictions in the Go package name convention:
|
||||
|
||||
1. We cannot use `.` because it's interpreted by the language, think of `v1.20.CallFunction`.
|
||||
2. We cannot use `_` because golint complains about it. The code is actually valid, but it looks probably more weird: `v1_20.CallFunction`.
|
||||
|
||||
For instance, if you want to modify a type that was available in the version `1.21` of the API but it will have different fields in the version `1.22`, you want to create a new package under `api/types/versions/v1p21`.
|
310
vendor/github.com/docker/docker/cli/command/cli.go
generated
vendored
Normal file
310
vendor/github.com/docker/docker/cli/command/cli.go
generated
vendored
Normal file
@ -0,0 +1,310 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
cliconfig "github.com/docker/docker/cli/config"
|
||||
"github.com/docker/docker/cli/config/configfile"
|
||||
"github.com/docker/docker/cli/config/credentials"
|
||||
cliflags "github.com/docker/docker/cli/flags"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
dopts "github.com/docker/docker/opts"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Streams is an interface which exposes the standard input and output streams
|
||||
type Streams interface {
|
||||
In() *InStream
|
||||
Out() *OutStream
|
||||
Err() io.Writer
|
||||
}
|
||||
|
||||
// Cli represents the docker command line client.
|
||||
type Cli interface {
|
||||
Client() client.APIClient
|
||||
Out() *OutStream
|
||||
Err() io.Writer
|
||||
In() *InStream
|
||||
SetIn(in *InStream)
|
||||
ConfigFile() *configfile.ConfigFile
|
||||
CredentialsStore(serverAddress string) credentials.Store
|
||||
}
|
||||
|
||||
// DockerCli is an instance the docker command line client.
|
||||
// Instances of the client can be returned from NewDockerCli.
|
||||
type DockerCli struct {
|
||||
configFile *configfile.ConfigFile
|
||||
in *InStream
|
||||
out *OutStream
|
||||
err io.Writer
|
||||
keyFile string
|
||||
client client.APIClient
|
||||
defaultVersion string
|
||||
server ServerInfo
|
||||
}
|
||||
|
||||
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
|
||||
func (cli *DockerCli) DefaultVersion() string {
|
||||
return cli.defaultVersion
|
||||
}
|
||||
|
||||
// Client returns the APIClient
|
||||
func (cli *DockerCli) Client() client.APIClient {
|
||||
return cli.client
|
||||
}
|
||||
|
||||
// Out returns the writer used for stdout
|
||||
func (cli *DockerCli) Out() *OutStream {
|
||||
return cli.out
|
||||
}
|
||||
|
||||
// Err returns the writer used for stderr
|
||||
func (cli *DockerCli) Err() io.Writer {
|
||||
return cli.err
|
||||
}
|
||||
|
||||
// SetIn sets the reader used for stdin
|
||||
func (cli *DockerCli) SetIn(in *InStream) {
|
||||
cli.in = in
|
||||
}
|
||||
|
||||
// In returns the reader used for stdin
|
||||
func (cli *DockerCli) In() *InStream {
|
||||
return cli.in
|
||||
}
|
||||
|
||||
// ShowHelp shows the command help.
|
||||
func (cli *DockerCli) ShowHelp(cmd *cobra.Command, args []string) error {
|
||||
cmd.SetOutput(cli.err)
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigFile returns the ConfigFile
|
||||
func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
|
||||
return cli.configFile
|
||||
}
|
||||
|
||||
// ServerInfo returns the server version details for the host this client is
|
||||
// connected to
|
||||
func (cli *DockerCli) ServerInfo() ServerInfo {
|
||||
return cli.server
|
||||
}
|
||||
|
||||
// GetAllCredentials returns all of the credentials stored in all of the
|
||||
// configured credential stores.
|
||||
func (cli *DockerCli) GetAllCredentials() (map[string]types.AuthConfig, error) {
|
||||
auths := make(map[string]types.AuthConfig)
|
||||
for registry := range cli.configFile.CredentialHelpers {
|
||||
helper := cli.CredentialsStore(registry)
|
||||
newAuths, err := helper.GetAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addAll(auths, newAuths)
|
||||
}
|
||||
defaultStore := cli.CredentialsStore("")
|
||||
newAuths, err := defaultStore.GetAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addAll(auths, newAuths)
|
||||
return auths, nil
|
||||
}
|
||||
|
||||
func addAll(to, from map[string]types.AuthConfig) {
|
||||
for reg, ac := range from {
|
||||
to[reg] = ac
|
||||
}
|
||||
}
|
||||
|
||||
// CredentialsStore returns a new credentials store based
|
||||
// on the settings provided in the configuration file. Empty string returns
|
||||
// the default credential store.
|
||||
func (cli *DockerCli) CredentialsStore(serverAddress string) credentials.Store {
|
||||
if helper := getConfiguredCredentialStore(cli.configFile, serverAddress); helper != "" {
|
||||
return credentials.NewNativeStore(cli.configFile, helper)
|
||||
}
|
||||
return credentials.NewFileStore(cli.configFile)
|
||||
}
|
||||
|
||||
// getConfiguredCredentialStore returns the credential helper configured for the
|
||||
// given registry, the default credsStore, or the empty string if neither are
|
||||
// configured.
|
||||
func getConfiguredCredentialStore(c *configfile.ConfigFile, serverAddress string) string {
|
||||
if c.CredentialHelpers != nil && serverAddress != "" {
|
||||
if helper, exists := c.CredentialHelpers[serverAddress]; exists {
|
||||
return helper
|
||||
}
|
||||
}
|
||||
return c.CredentialsStore
|
||||
}
|
||||
|
||||
// Initialize the dockerCli runs initialization that must happen after command
|
||||
// line flags are parsed.
|
||||
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
||||
cli.configFile = LoadDefaultConfigFile(cli.err)
|
||||
|
||||
var err error
|
||||
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
|
||||
if tlsconfig.IsErrEncryptedKey(err) {
|
||||
var (
|
||||
passwd string
|
||||
giveup bool
|
||||
)
|
||||
passRetriever := passphrase.PromptRetrieverWithInOut(cli.In(), cli.Out(), nil)
|
||||
|
||||
for attempts := 0; tlsconfig.IsErrEncryptedKey(err); attempts++ {
|
||||
// some code and comments borrowed from notary/trustmanager/keystore.go
|
||||
passwd, giveup, err = passRetriever("private", "encrypted TLS private", false, attempts)
|
||||
// Check if the passphrase retriever got an error or if it is telling us to give up
|
||||
if giveup || err != nil {
|
||||
return errors.Wrap(err, "private key is encrypted, but could not get passphrase")
|
||||
}
|
||||
|
||||
opts.Common.TLSOptions.Passphrase = passwd
|
||||
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cli.defaultVersion = cli.client.ClientVersion()
|
||||
|
||||
if opts.Common.TrustKey == "" {
|
||||
cli.keyFile = filepath.Join(cliconfig.Dir(), cliflags.DefaultTrustKeyFile)
|
||||
} else {
|
||||
cli.keyFile = opts.Common.TrustKey
|
||||
}
|
||||
|
||||
if ping, err := cli.client.Ping(context.Background()); err == nil {
|
||||
cli.server = ServerInfo{
|
||||
HasExperimental: ping.Experimental,
|
||||
OSType: ping.OSType,
|
||||
}
|
||||
|
||||
// since the new header was added in 1.25, assume server is 1.24 if header is not present.
|
||||
if ping.APIVersion == "" {
|
||||
ping.APIVersion = "1.24"
|
||||
}
|
||||
|
||||
// if server version is lower than the current cli, downgrade
|
||||
if versions.LessThan(ping.APIVersion, cli.client.ClientVersion()) {
|
||||
cli.client.UpdateClientVersion(ping.APIVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServerInfo stores details about the supported features and platform of the
|
||||
// server
|
||||
type ServerInfo struct {
|
||||
HasExperimental bool
|
||||
OSType string
|
||||
}
|
||||
|
||||
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
|
||||
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}
|
||||
}
|
||||
|
||||
// LoadDefaultConfigFile attempts to load the default config file and returns
|
||||
// an initialized ConfigFile struct if none is found.
|
||||
func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
|
||||
configFile, e := cliconfig.Load(cliconfig.Dir())
|
||||
if e != nil {
|
||||
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
|
||||
}
|
||||
if !configFile.ContainsAuth() {
|
||||
credentials.DetectDefaultStore(configFile)
|
||||
}
|
||||
return configFile
|
||||
}
|
||||
|
||||
// NewAPIClientFromFlags creates a new APIClient from command line flags
|
||||
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
||||
host, err := getServerHost(opts.Hosts, opts.TLSOptions)
|
||||
if err != nil {
|
||||
return &client.Client{}, err
|
||||
}
|
||||
|
||||
customHeaders := configFile.HTTPHeaders
|
||||
if customHeaders == nil {
|
||||
customHeaders = map[string]string{}
|
||||
}
|
||||
customHeaders["User-Agent"] = UserAgent()
|
||||
|
||||
verStr := api.DefaultVersion
|
||||
if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
|
||||
verStr = tmpStr
|
||||
}
|
||||
|
||||
httpClient, err := newHTTPClient(host, opts.TLSOptions)
|
||||
if err != nil {
|
||||
return &client.Client{}, err
|
||||
}
|
||||
|
||||
return client.NewClient(host, verStr, httpClient, customHeaders)
|
||||
}
|
||||
|
||||
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) {
|
||||
switch len(hosts) {
|
||||
case 0:
|
||||
host = os.Getenv("DOCKER_HOST")
|
||||
case 1:
|
||||
host = hosts[0]
|
||||
default:
|
||||
return "", errors.New("Please specify only one -H")
|
||||
}
|
||||
|
||||
host, err = dopts.ParseHost(tlsOptions != nil, host)
|
||||
return
|
||||
}
|
||||
|
||||
func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) {
|
||||
if tlsOptions == nil {
|
||||
// let the api client configure the default transport.
|
||||
return nil, nil
|
||||
}
|
||||
opts := *tlsOptions
|
||||
opts.ExclusiveRootPools = true
|
||||
config, err := tlsconfig.Client(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: config,
|
||||
}
|
||||
proto, addr, _, err := client.ParseHost(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sockets.ConfigureTransport(tr, proto, addr)
|
||||
|
||||
return &http.Client{
|
||||
Transport: tr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UserAgent returns the user agent string used for making API requests
|
||||
func UserAgent() string {
|
||||
return "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
|
||||
}
|
49
vendor/github.com/docker/docker/cli/command/events_utils.go
generated
vendored
Normal file
49
vendor/github.com/docker/docker/cli/command/events_utils.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
eventtypes "github.com/docker/docker/api/types/events"
|
||||
)
|
||||
|
||||
type eventProcessor func(eventtypes.Message, error) error
|
||||
|
||||
// EventHandler is abstract interface for user to customize
|
||||
// own handle functions of each type of events
|
||||
type EventHandler interface {
|
||||
Handle(action string, h func(eventtypes.Message))
|
||||
Watch(c <-chan eventtypes.Message)
|
||||
}
|
||||
|
||||
// InitEventHandler initializes and returns an EventHandler
|
||||
func InitEventHandler() EventHandler {
|
||||
return &eventHandler{handlers: make(map[string]func(eventtypes.Message))}
|
||||
}
|
||||
|
||||
type eventHandler struct {
|
||||
handlers map[string]func(eventtypes.Message)
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (w *eventHandler) Handle(action string, h func(eventtypes.Message)) {
|
||||
w.mu.Lock()
|
||||
w.handlers[action] = h
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
// Watch ranges over the passed in event chan and processes the events based on the
|
||||
// handlers created for a given action.
|
||||
// To stop watching, close the event chan.
|
||||
func (w *eventHandler) Watch(c <-chan eventtypes.Message) {
|
||||
for e := range c {
|
||||
w.mu.Lock()
|
||||
h, exists := w.handlers[e.Action]
|
||||
w.mu.Unlock()
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
logrus.Debugf("event handler: received event: %v", e)
|
||||
go h(e)
|
||||
}
|
||||
}
|
56
vendor/github.com/docker/docker/cli/command/in.go
generated
vendored
Normal file
56
vendor/github.com/docker/docker/cli/command/in.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
)
|
||||
|
||||
// InStream is an input stream used by the DockerCli to read user input
|
||||
type InStream struct {
|
||||
CommonStream
|
||||
in io.ReadCloser
|
||||
}
|
||||
|
||||
func (i *InStream) Read(p []byte) (int, error) {
|
||||
return i.in.Read(p)
|
||||
}
|
||||
|
||||
// Close implements the Closer interface
|
||||
func (i *InStream) Close() error {
|
||||
return i.in.Close()
|
||||
}
|
||||
|
||||
// SetRawTerminal sets raw mode on the input terminal
|
||||
func (i *InStream) SetRawTerminal() (err error) {
|
||||
if os.Getenv("NORAW") != "" || !i.CommonStream.isTerminal {
|
||||
return nil
|
||||
}
|
||||
i.CommonStream.state, err = term.SetRawTerminal(i.CommonStream.fd)
|
||||
return err
|
||||
}
|
||||
|
||||
// CheckTty checks if we are trying to attach to a container tty
|
||||
// from a non-tty client input stream, and if so, returns an error.
|
||||
func (i *InStream) CheckTty(attachStdin, ttyMode bool) error {
|
||||
// In order to attach to a container tty, input stream for the client must
|
||||
// be a tty itself: redirecting or piping the client standard input is
|
||||
// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
|
||||
if ttyMode && attachStdin && !i.isTerminal {
|
||||
eText := "the input device is not a TTY"
|
||||
if runtime.GOOS == "windows" {
|
||||
return errors.New(eText + ". If you are using mintty, try prefixing the command with 'winpty'")
|
||||
}
|
||||
return errors.New(eText)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewInStream returns a new InStream object from a ReadCloser
|
||||
func NewInStream(in io.ReadCloser) *InStream {
|
||||
fd, isTerminal := term.GetFdInfo(in)
|
||||
return &InStream{CommonStream: CommonStream{fd: fd, isTerminal: isTerminal}, in: in}
|
||||
}
|
50
vendor/github.com/docker/docker/cli/command/out.go
generated
vendored
Normal file
50
vendor/github.com/docker/docker/cli/command/out.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
)
|
||||
|
||||
// OutStream is an output stream used by the DockerCli to write normal program
|
||||
// output.
|
||||
type OutStream struct {
|
||||
CommonStream
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
func (o *OutStream) Write(p []byte) (int, error) {
|
||||
return o.out.Write(p)
|
||||
}
|
||||
|
||||
// SetRawTerminal sets raw mode on the input terminal
|
||||
func (o *OutStream) SetRawTerminal() (err error) {
|
||||
if os.Getenv("NORAW") != "" || !o.CommonStream.isTerminal {
|
||||
return nil
|
||||
}
|
||||
o.CommonStream.state, err = term.SetRawTerminalOutput(o.CommonStream.fd)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTtySize returns the height and width in characters of the tty
|
||||
func (o *OutStream) GetTtySize() (uint, uint) {
|
||||
if !o.isTerminal {
|
||||
return 0, 0
|
||||
}
|
||||
ws, err := term.GetWinsize(o.fd)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error getting size: %s", err)
|
||||
if ws == nil {
|
||||
return 0, 0
|
||||
}
|
||||
}
|
||||
return uint(ws.Height), uint(ws.Width)
|
||||
}
|
||||
|
||||
// NewOutStream returns a new OutStream object from a Writer
|
||||
func NewOutStream(out io.Writer) *OutStream {
|
||||
fd, isTerminal := term.GetFdInfo(out)
|
||||
return &OutStream{CommonStream: CommonStream{fd: fd, isTerminal: isTerminal}, out: out}
|
||||
}
|
187
vendor/github.com/docker/docker/cli/command/registry.go
generated
vendored
Normal file
187
vendor/github.com/docker/docker/cli/command/registry.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ElectAuthServer returns the default registry to use (by asking the daemon)
|
||||
func ElectAuthServer(ctx context.Context, cli Cli) string {
|
||||
// The daemon `/info` endpoint informs us of the default registry being
|
||||
// used. This is essential in cross-platforms environment, where for
|
||||
// example a Linux client might be interacting with a Windows daemon, hence
|
||||
// the default registry URL might be Windows specific.
|
||||
serverAddress := registry.IndexServer
|
||||
if info, err := cli.Client().Info(ctx); err != nil {
|
||||
fmt.Fprintf(cli.Out(), "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress)
|
||||
} else {
|
||||
serverAddress = info.IndexServerAddress
|
||||
}
|
||||
return serverAddress
|
||||
}
|
||||
|
||||
// EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload
|
||||
func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
|
||||
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
||||
// for the given command.
|
||||
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
|
||||
return func() (string, error) {
|
||||
fmt.Fprintf(cli.Out(), "\nPlease login prior to %s:\n", cmdName)
|
||||
indexServer := registry.GetAuthConfigKey(index)
|
||||
isDefaultRegistry := indexServer == ElectAuthServer(context.Background(), cli)
|
||||
authConfig, err := ConfigureAuth(cli, "", "", indexServer, isDefaultRegistry)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return EncodeAuthToBase64(authConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
|
||||
// default index, it uses the default index name for the daemon's platform,
|
||||
// not the client's platform.
|
||||
func ResolveAuthConfig(ctx context.Context, cli Cli, index *registrytypes.IndexInfo) types.AuthConfig {
|
||||
configKey := index.Name
|
||||
if index.Official {
|
||||
configKey = ElectAuthServer(ctx, cli)
|
||||
}
|
||||
|
||||
a, _ := cli.CredentialsStore(configKey).Get(configKey)
|
||||
return a
|
||||
}
|
||||
|
||||
// ConfigureAuth returns an AuthConfig from the specified user, password and server.
|
||||
func ConfigureAuth(cli Cli, flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
|
||||
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
|
||||
if runtime.GOOS == "windows" {
|
||||
cli.SetIn(NewInStream(os.Stdin))
|
||||
}
|
||||
|
||||
if !isDefaultRegistry {
|
||||
serverAddress = registry.ConvertToHostname(serverAddress)
|
||||
}
|
||||
|
||||
authconfig, err := cli.CredentialsStore(serverAddress).Get(serverAddress)
|
||||
if err != nil {
|
||||
return authconfig, err
|
||||
}
|
||||
|
||||
// Some links documenting this:
|
||||
// - https://code.google.com/archive/p/mintty/issues/56
|
||||
// - https://github.com/docker/docker/issues/15272
|
||||
// - https://mintty.github.io/ (compatibility)
|
||||
// Linux will hit this if you attempt `cat | docker login`, and Windows
|
||||
// will hit this if you attempt docker login from mintty where stdin
|
||||
// is a pipe, not a character based console.
|
||||
if flPassword == "" && !cli.In().IsTerminal() {
|
||||
return authconfig, errors.Errorf("Error: Cannot perform an interactive login from a non TTY device")
|
||||
}
|
||||
|
||||
authconfig.Username = strings.TrimSpace(authconfig.Username)
|
||||
|
||||
if flUser = strings.TrimSpace(flUser); flUser == "" {
|
||||
if isDefaultRegistry {
|
||||
// if this is a default registry (docker hub), then display the following message.
|
||||
fmt.Fprintln(cli.Out(), "Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.")
|
||||
}
|
||||
promptWithDefault(cli.Out(), "Username", authconfig.Username)
|
||||
flUser = readInput(cli.In(), cli.Out())
|
||||
flUser = strings.TrimSpace(flUser)
|
||||
if flUser == "" {
|
||||
flUser = authconfig.Username
|
||||
}
|
||||
}
|
||||
if flUser == "" {
|
||||
return authconfig, errors.Errorf("Error: Non-null Username Required")
|
||||
}
|
||||
if flPassword == "" {
|
||||
oldState, err := term.SaveState(cli.In().FD())
|
||||
if err != nil {
|
||||
return authconfig, err
|
||||
}
|
||||
fmt.Fprintf(cli.Out(), "Password: ")
|
||||
term.DisableEcho(cli.In().FD(), oldState)
|
||||
|
||||
flPassword = readInput(cli.In(), cli.Out())
|
||||
fmt.Fprint(cli.Out(), "\n")
|
||||
|
||||
term.RestoreTerminal(cli.In().FD(), oldState)
|
||||
if flPassword == "" {
|
||||
return authconfig, errors.Errorf("Error: Password Required")
|
||||
}
|
||||
}
|
||||
|
||||
authconfig.Username = flUser
|
||||
authconfig.Password = flPassword
|
||||
authconfig.ServerAddress = serverAddress
|
||||
authconfig.IdentityToken = ""
|
||||
|
||||
return authconfig, nil
|
||||
}
|
||||
|
||||
func readInput(in io.Reader, out io.Writer) string {
|
||||
reader := bufio.NewReader(in)
|
||||
line, _, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
return string(line)
|
||||
}
|
||||
|
||||
func promptWithDefault(out io.Writer, prompt string, configDefault string) {
|
||||
if configDefault == "" {
|
||||
fmt.Fprintf(out, "%s: ", prompt)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s (%s): ", prompt, configDefault)
|
||||
}
|
||||
}
|
||||
|
||||
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
|
||||
func RetrieveAuthTokenFromImage(ctx context.Context, cli Cli, image string) (string, error) {
|
||||
// Retrieve encoded auth token from the image reference
|
||||
authConfig, err := resolveAuthConfigFromImage(ctx, cli, image)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
encodedAuth, err := EncodeAuthToBase64(authConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return encodedAuth, nil
|
||||
}
|
||||
|
||||
// resolveAuthConfigFromImage retrieves that AuthConfig using the image string
|
||||
func resolveAuthConfigFromImage(ctx context.Context, cli Cli, image string) (types.AuthConfig, error) {
|
||||
registryRef, err := reference.ParseNormalizedNamed(image)
|
||||
if err != nil {
|
||||
return types.AuthConfig{}, err
|
||||
}
|
||||
repoInfo, err := registry.ParseRepositoryInfo(registryRef)
|
||||
if err != nil {
|
||||
return types.AuthConfig{}, err
|
||||
}
|
||||
return ResolveAuthConfig(ctx, cli, repoInfo.Index), nil
|
||||
}
|
34
vendor/github.com/docker/docker/cli/command/stream.go
generated
vendored
Normal file
34
vendor/github.com/docker/docker/cli/command/stream.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/pkg/term"
|
||||
)
|
||||
|
||||
// CommonStream is an input stream used by the DockerCli to read user input
|
||||
type CommonStream struct {
|
||||
fd uintptr
|
||||
isTerminal bool
|
||||
state *term.State
|
||||
}
|
||||
|
||||
// FD returns the file descriptor number for this stream
|
||||
func (s *CommonStream) FD() uintptr {
|
||||
return s.fd
|
||||
}
|
||||
|
||||
// IsTerminal returns true if this stream is connected to a terminal
|
||||
func (s *CommonStream) IsTerminal() bool {
|
||||
return s.isTerminal
|
||||
}
|
||||
|
||||
// RestoreTerminal restores normal mode to the terminal
|
||||
func (s *CommonStream) RestoreTerminal() {
|
||||
if s.state != nil {
|
||||
term.RestoreTerminal(s.fd, s.state)
|
||||
}
|
||||
}
|
||||
|
||||
// SetIsTerminal sets the boolean used for isTerminal
|
||||
func (s *CommonStream) SetIsTerminal(isTerminal bool) {
|
||||
s.isTerminal = isTerminal
|
||||
}
|
43
vendor/github.com/docker/docker/cli/command/trust.go
generated
vendored
Normal file
43
vendor/github.com/docker/docker/cli/command/trust.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO: make this not global
|
||||
untrusted bool
|
||||
)
|
||||
|
||||
// AddTrustVerificationFlags adds content trust flags to the provided flagset
|
||||
func AddTrustVerificationFlags(fs *pflag.FlagSet) {
|
||||
trusted := getDefaultTrustState()
|
||||
fs.BoolVar(&untrusted, "disable-content-trust", !trusted, "Skip image verification")
|
||||
}
|
||||
|
||||
// AddTrustSigningFlags adds "signing" flags to the provided flagset
|
||||
func AddTrustSigningFlags(fs *pflag.FlagSet) {
|
||||
trusted := getDefaultTrustState()
|
||||
fs.BoolVar(&untrusted, "disable-content-trust", !trusted, "Skip image signing")
|
||||
}
|
||||
|
||||
// getDefaultTrustState returns true if content trust is enabled through the $DOCKER_CONTENT_TRUST environment variable.
|
||||
func getDefaultTrustState() bool {
|
||||
var trusted bool
|
||||
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
|
||||
if t, err := strconv.ParseBool(e); t || err != nil {
|
||||
// treat any other value as true
|
||||
trusted = true
|
||||
}
|
||||
}
|
||||
return trusted
|
||||
}
|
||||
|
||||
// IsTrusted returns true if content trust is enabled, either through the $DOCKER_CONTENT_TRUST environment variable,
|
||||
// or through `--disabled-content-trust=false` on a command.
|
||||
func IsTrusted() bool {
|
||||
return !untrusted
|
||||
}
|
119
vendor/github.com/docker/docker/cli/command/utils.go
generated
vendored
Normal file
119
vendor/github.com/docker/docker/cli/command/utils.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
// CopyToFile writes the content of the reader to the specified file
|
||||
func CopyToFile(outfile string, r io.Reader) error {
|
||||
// We use sequential file access here to avoid depleting the standby list
|
||||
// on Windows. On Linux, this is a call directly to ioutil.TempFile
|
||||
tmpFile, err := system.TempFileSequential(filepath.Dir(outfile), ".docker_temp_")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpPath := tmpFile.Name()
|
||||
|
||||
_, err = io.Copy(tmpFile, r)
|
||||
tmpFile.Close()
|
||||
|
||||
if err != nil {
|
||||
os.Remove(tmpPath)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.Rename(tmpPath, outfile); err != nil {
|
||||
os.Remove(tmpPath)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// capitalizeFirst capitalizes the first character of string
|
||||
func capitalizeFirst(s string) string {
|
||||
switch l := len(s); l {
|
||||
case 0:
|
||||
return s
|
||||
case 1:
|
||||
return strings.ToLower(s)
|
||||
default:
|
||||
return strings.ToUpper(string(s[0])) + strings.ToLower(s[1:])
|
||||
}
|
||||
}
|
||||
|
||||
// PrettyPrint outputs arbitrary data for human formatted output by uppercasing the first letter.
|
||||
func PrettyPrint(i interface{}) string {
|
||||
switch t := i.(type) {
|
||||
case nil:
|
||||
return "None"
|
||||
case string:
|
||||
return capitalizeFirst(t)
|
||||
default:
|
||||
return capitalizeFirst(fmt.Sprintf("%s", t))
|
||||
}
|
||||
}
|
||||
|
||||
// PromptForConfirmation requests and checks confirmation from user.
|
||||
// This will display the provided message followed by ' [y/N] '. If
|
||||
// the user input 'y' or 'Y' it returns true other false. If no
|
||||
// message is provided "Are you sure you want to proceed? [y/N] "
|
||||
// will be used instead.
|
||||
func PromptForConfirmation(ins *InStream, outs *OutStream, message string) bool {
|
||||
if message == "" {
|
||||
message = "Are you sure you want to proceed?"
|
||||
}
|
||||
message += " [y/N] "
|
||||
|
||||
fmt.Fprintf(outs, message)
|
||||
|
||||
// On Windows, force the use of the regular OS stdin stream.
|
||||
if runtime.GOOS == "windows" {
|
||||
ins = NewInStream(os.Stdin)
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(ins)
|
||||
answer, _, _ := reader.ReadLine()
|
||||
return strings.ToLower(string(answer)) == "y"
|
||||
}
|
||||
|
||||
// PruneFilters returns consolidated prune filters obtained from config.json and cli
|
||||
func PruneFilters(dockerCli Cli, pruneFilters filters.Args) filters.Args {
|
||||
if dockerCli.ConfigFile() == nil {
|
||||
return pruneFilters
|
||||
}
|
||||
for _, f := range dockerCli.ConfigFile().PruneFilters {
|
||||
parts := strings.SplitN(f, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == "label" {
|
||||
// CLI label filter supersede config.json.
|
||||
// If CLI label filter conflict with config.json,
|
||||
// skip adding label! filter in config.json.
|
||||
if pruneFilters.Include("label!") && pruneFilters.ExactMatch("label!", parts[1]) {
|
||||
continue
|
||||
}
|
||||
} else if parts[0] == "label!" {
|
||||
// CLI label! filter supersede config.json.
|
||||
// If CLI label! filter conflict with config.json,
|
||||
// skip adding label filter in config.json.
|
||||
if pruneFilters.Include("label") && pruneFilters.ExactMatch("label", parts[1]) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
pruneFilters.Add(parts[0], parts[1])
|
||||
}
|
||||
|
||||
return pruneFilters
|
||||
}
|
120
vendor/github.com/docker/docker/cli/config/config.go
generated
vendored
Normal file
120
vendor/github.com/docker/docker/cli/config/config.go
generated
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/cli/config/configfile"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// ConfigFileName is the name of config file
|
||||
ConfigFileName = "config.json"
|
||||
configFileDir = ".docker"
|
||||
oldConfigfile = ".dockercfg"
|
||||
)
|
||||
|
||||
var (
|
||||
configDir = os.Getenv("DOCKER_CONFIG")
|
||||
)
|
||||
|
||||
func init() {
|
||||
if configDir == "" {
|
||||
configDir = filepath.Join(homedir.Get(), configFileDir)
|
||||
}
|
||||
}
|
||||
|
||||
// Dir returns the directory the configuration file is stored in
|
||||
func Dir() string {
|
||||
return configDir
|
||||
}
|
||||
|
||||
// SetDir sets the directory the configuration file is stored in
|
||||
func SetDir(dir string) {
|
||||
configDir = dir
|
||||
}
|
||||
|
||||
// NewConfigFile initializes an empty configuration file for the given filename 'fn'
|
||||
func NewConfigFile(fn string) *configfile.ConfigFile {
|
||||
return &configfile.ConfigFile{
|
||||
AuthConfigs: make(map[string]types.AuthConfig),
|
||||
HTTPHeaders: make(map[string]string),
|
||||
Filename: fn,
|
||||
}
|
||||
}
|
||||
|
||||
// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
|
||||
// a non-nested reader
|
||||
func LegacyLoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
||||
configFile := configfile.ConfigFile{
|
||||
AuthConfigs: make(map[string]types.AuthConfig),
|
||||
}
|
||||
err := configFile.LegacyLoadFromReader(configData)
|
||||
return &configFile, err
|
||||
}
|
||||
|
||||
// LoadFromReader is a convenience function that creates a ConfigFile object from
|
||||
// a reader
|
||||
func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
||||
configFile := configfile.ConfigFile{
|
||||
AuthConfigs: make(map[string]types.AuthConfig),
|
||||
}
|
||||
err := configFile.LoadFromReader(configData)
|
||||
return &configFile, err
|
||||
}
|
||||
|
||||
// Load reads the configuration files in the given directory, and sets up
|
||||
// the auth config information and returns values.
|
||||
// FIXME: use the internal golang config parser
|
||||
func Load(configDir string) (*configfile.ConfigFile, error) {
|
||||
if configDir == "" {
|
||||
configDir = Dir()
|
||||
}
|
||||
|
||||
configFile := configfile.ConfigFile{
|
||||
AuthConfigs: make(map[string]types.AuthConfig),
|
||||
Filename: filepath.Join(configDir, ConfigFileName),
|
||||
}
|
||||
|
||||
// Try happy path first - latest config file
|
||||
if _, err := os.Stat(configFile.Filename); err == nil {
|
||||
file, err := os.Open(configFile.Filename)
|
||||
if err != nil {
|
||||
return &configFile, errors.Errorf("%s - %v", configFile.Filename, err)
|
||||
}
|
||||
defer file.Close()
|
||||
err = configFile.LoadFromReader(file)
|
||||
if err != nil {
|
||||
err = errors.Errorf("%s - %v", configFile.Filename, err)
|
||||
}
|
||||
return &configFile, err
|
||||
} else if !os.IsNotExist(err) {
|
||||
// if file is there but we can't stat it for any reason other
|
||||
// than it doesn't exist then stop
|
||||
return &configFile, errors.Errorf("%s - %v", configFile.Filename, err)
|
||||
}
|
||||
|
||||
// Can't find latest config file so check for the old one
|
||||
confFile := filepath.Join(homedir.Get(), oldConfigfile)
|
||||
if _, err := os.Stat(confFile); err != nil {
|
||||
return &configFile, nil //missing file is not an error
|
||||
}
|
||||
file, err := os.Open(confFile)
|
||||
if err != nil {
|
||||
return &configFile, errors.Errorf("%s - %v", confFile, err)
|
||||
}
|
||||
defer file.Close()
|
||||
err = configFile.LegacyLoadFromReader(file)
|
||||
if err != nil {
|
||||
return &configFile, errors.Errorf("%s - %v", confFile, err)
|
||||
}
|
||||
|
||||
if configFile.HTTPHeaders == nil {
|
||||
configFile.HTTPHeaders = map[string]string{}
|
||||
}
|
||||
return &configFile, nil
|
||||
}
|
189
vendor/github.com/docker/docker/cli/config/configfile/file.go
generated
vendored
Normal file
189
vendor/github.com/docker/docker/cli/config/configfile/file.go
generated
vendored
Normal file
@ -0,0 +1,189 @@
|
||||
package configfile
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// This constant is only used for really old config files when the
|
||||
// URL wasn't saved as part of the config file and it was just
|
||||
// assumed to be this value.
|
||||
defaultIndexserver = "https://index.docker.io/v1/"
|
||||
)
|
||||
|
||||
// ConfigFile ~/.docker/config.json file info
|
||||
type ConfigFile struct {
|
||||
AuthConfigs map[string]types.AuthConfig `json:"auths"`
|
||||
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
|
||||
PsFormat string `json:"psFormat,omitempty"`
|
||||
ImagesFormat string `json:"imagesFormat,omitempty"`
|
||||
NetworksFormat string `json:"networksFormat,omitempty"`
|
||||
PluginsFormat string `json:"pluginsFormat,omitempty"`
|
||||
VolumesFormat string `json:"volumesFormat,omitempty"`
|
||||
StatsFormat string `json:"statsFormat,omitempty"`
|
||||
DetachKeys string `json:"detachKeys,omitempty"`
|
||||
CredentialsStore string `json:"credsStore,omitempty"`
|
||||
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
|
||||
Filename string `json:"-"` // Note: for internal use only
|
||||
ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"`
|
||||
ServicesFormat string `json:"servicesFormat,omitempty"`
|
||||
TasksFormat string `json:"tasksFormat,omitempty"`
|
||||
SecretFormat string `json:"secretFormat,omitempty"`
|
||||
NodesFormat string `json:"nodesFormat,omitempty"`
|
||||
PruneFilters []string `json:"pruneFilters,omitempty"`
|
||||
}
|
||||
|
||||
// LegacyLoadFromReader reads the non-nested configuration data given and sets up the
|
||||
// auth config information with given directory and populates the receiver object
|
||||
func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
|
||||
b, err := ioutil.ReadAll(configData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
|
||||
arr := strings.Split(string(b), "\n")
|
||||
if len(arr) < 2 {
|
||||
return errors.Errorf("The Auth config file is empty")
|
||||
}
|
||||
authConfig := types.AuthConfig{}
|
||||
origAuth := strings.Split(arr[0], " = ")
|
||||
if len(origAuth) != 2 {
|
||||
return errors.Errorf("Invalid Auth config file")
|
||||
}
|
||||
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authConfig.ServerAddress = defaultIndexserver
|
||||
configFile.AuthConfigs[defaultIndexserver] = authConfig
|
||||
} else {
|
||||
for k, authConfig := range configFile.AuthConfigs {
|
||||
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authConfig.Auth = ""
|
||||
authConfig.ServerAddress = k
|
||||
configFile.AuthConfigs[k] = authConfig
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadFromReader reads the configuration data given and sets up the auth config
|
||||
// information with given directory and populates the receiver object
|
||||
func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
|
||||
if err := json.NewDecoder(configData).Decode(&configFile); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
for addr, ac := range configFile.AuthConfigs {
|
||||
ac.Username, ac.Password, err = decodeAuth(ac.Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ac.Auth = ""
|
||||
ac.ServerAddress = addr
|
||||
configFile.AuthConfigs[addr] = ac
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainsAuth returns whether there is authentication configured
|
||||
// in this file or not.
|
||||
func (configFile *ConfigFile) ContainsAuth() bool {
|
||||
return configFile.CredentialsStore != "" ||
|
||||
len(configFile.CredentialHelpers) > 0 ||
|
||||
len(configFile.AuthConfigs) > 0
|
||||
}
|
||||
|
||||
// SaveToWriter encodes and writes out all the authorization information to
|
||||
// the given writer
|
||||
func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
|
||||
// Encode sensitive data into a new/temp struct
|
||||
tmpAuthConfigs := make(map[string]types.AuthConfig, len(configFile.AuthConfigs))
|
||||
for k, authConfig := range configFile.AuthConfigs {
|
||||
authCopy := authConfig
|
||||
// encode and save the authstring, while blanking out the original fields
|
||||
authCopy.Auth = encodeAuth(&authCopy)
|
||||
authCopy.Username = ""
|
||||
authCopy.Password = ""
|
||||
authCopy.ServerAddress = ""
|
||||
tmpAuthConfigs[k] = authCopy
|
||||
}
|
||||
|
||||
saveAuthConfigs := configFile.AuthConfigs
|
||||
configFile.AuthConfigs = tmpAuthConfigs
|
||||
defer func() { configFile.AuthConfigs = saveAuthConfigs }()
|
||||
|
||||
data, err := json.MarshalIndent(configFile, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// Save encodes and writes out all the authorization information
|
||||
func (configFile *ConfigFile) Save() error {
|
||||
if configFile.Filename == "" {
|
||||
return errors.Errorf("Can't save config with empty filename")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(configFile.Filename), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(configFile.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return configFile.SaveToWriter(f)
|
||||
}
|
||||
|
||||
// encodeAuth creates a base64 encoded string to containing authorization information
|
||||
func encodeAuth(authConfig *types.AuthConfig) string {
|
||||
if authConfig.Username == "" && authConfig.Password == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
authStr := authConfig.Username + ":" + authConfig.Password
|
||||
msg := []byte(authStr)
|
||||
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
|
||||
base64.StdEncoding.Encode(encoded, msg)
|
||||
return string(encoded)
|
||||
}
|
||||
|
||||
// decodeAuth decodes a base64 encoded string and returns username and password
|
||||
func decodeAuth(authStr string) (string, string, error) {
|
||||
if authStr == "" {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
decLen := base64.StdEncoding.DecodedLen(len(authStr))
|
||||
decoded := make([]byte, decLen)
|
||||
authByte := []byte(authStr)
|
||||
n, err := base64.StdEncoding.Decode(decoded, authByte)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if n > decLen {
|
||||
return "", "", errors.Errorf("Something went wrong decoding auth config")
|
||||
}
|
||||
arr := strings.SplitN(string(decoded), ":", 2)
|
||||
if len(arr) != 2 {
|
||||
return "", "", errors.Errorf("Invalid auth configuration file")
|
||||
}
|
||||
password := strings.Trim(arr[1], "\x00")
|
||||
return arr[0], password, nil
|
||||
}
|
17
vendor/github.com/docker/docker/cli/config/credentials/credentials.go
generated
vendored
Normal file
17
vendor/github.com/docker/docker/cli/config/credentials/credentials.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
// Store is the interface that any credentials store must implement.
|
||||
type Store interface {
|
||||
// Erase removes credentials from the store for a given server.
|
||||
Erase(serverAddress string) error
|
||||
// Get retrieves credentials from the store for a given server.
|
||||
Get(serverAddress string) (types.AuthConfig, error)
|
||||
// GetAll retrieves all the credentials from the store.
|
||||
GetAll() (map[string]types.AuthConfig, error)
|
||||
// Store saves credentials in the store.
|
||||
Store(authConfig types.AuthConfig) error
|
||||
}
|
22
vendor/github.com/docker/docker/cli/config/credentials/default_store.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/cli/config/credentials/default_store.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/docker/docker/cli/config/configfile"
|
||||
)
|
||||
|
||||
// DetectDefaultStore sets the default credentials store
|
||||
// if the host includes the default store helper program.
|
||||
func DetectDefaultStore(c *configfile.ConfigFile) {
|
||||
if c.CredentialsStore != "" {
|
||||
// user defined
|
||||
return
|
||||
}
|
||||
|
||||
if defaultCredentialsStore != "" {
|
||||
if _, err := exec.LookPath(remoteCredentialsPrefix + defaultCredentialsStore); err == nil {
|
||||
c.CredentialsStore = defaultCredentialsStore
|
||||
}
|
||||
}
|
||||
}
|
3
vendor/github.com/docker/docker/cli/config/credentials/default_store_darwin.go
generated
vendored
Normal file
3
vendor/github.com/docker/docker/cli/config/credentials/default_store_darwin.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
package credentials
|
||||
|
||||
const defaultCredentialsStore = "osxkeychain"
|
3
vendor/github.com/docker/docker/cli/config/credentials/default_store_linux.go
generated
vendored
Normal file
3
vendor/github.com/docker/docker/cli/config/credentials/default_store_linux.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
package credentials
|
||||
|
||||
const defaultCredentialsStore = "secretservice"
|
5
vendor/github.com/docker/docker/cli/config/credentials/default_store_unsupported.go
generated
vendored
Normal file
5
vendor/github.com/docker/docker/cli/config/credentials/default_store_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// +build !windows,!darwin,!linux
|
||||
|
||||
package credentials
|
||||
|
||||
const defaultCredentialsStore = ""
|
3
vendor/github.com/docker/docker/cli/config/credentials/default_store_windows.go
generated
vendored
Normal file
3
vendor/github.com/docker/docker/cli/config/credentials/default_store_windows.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
package credentials
|
||||
|
||||
const defaultCredentialsStore = "wincred"
|
53
vendor/github.com/docker/docker/cli/config/credentials/file_store.go
generated
vendored
Normal file
53
vendor/github.com/docker/docker/cli/config/credentials/file_store.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/cli/config/configfile"
|
||||
"github.com/docker/docker/registry"
|
||||
)
|
||||
|
||||
// fileStore implements a credentials store using
|
||||
// the docker configuration file to keep the credentials in plain text.
|
||||
type fileStore struct {
|
||||
file *configfile.ConfigFile
|
||||
}
|
||||
|
||||
// NewFileStore creates a new file credentials store.
|
||||
func NewFileStore(file *configfile.ConfigFile) Store {
|
||||
return &fileStore{
|
||||
file: file,
|
||||
}
|
||||
}
|
||||
|
||||
// Erase removes the given credentials from the file store.
|
||||
func (c *fileStore) Erase(serverAddress string) error {
|
||||
delete(c.file.AuthConfigs, serverAddress)
|
||||
return c.file.Save()
|
||||
}
|
||||
|
||||
// Get retrieves credentials for a specific server from the file store.
|
||||
func (c *fileStore) Get(serverAddress string) (types.AuthConfig, error) {
|
||||
authConfig, ok := c.file.AuthConfigs[serverAddress]
|
||||
if !ok {
|
||||
// Maybe they have a legacy config file, we will iterate the keys converting
|
||||
// them to the new format and testing
|
||||
for r, ac := range c.file.AuthConfigs {
|
||||
if serverAddress == registry.ConvertToHostname(r) {
|
||||
return ac, nil
|
||||
}
|
||||
}
|
||||
|
||||
authConfig = types.AuthConfig{}
|
||||
}
|
||||
return authConfig, nil
|
||||
}
|
||||
|
||||
func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) {
|
||||
return c.file.AuthConfigs, nil
|
||||
}
|
||||
|
||||
// Store saves the given credentials in the file store.
|
||||
func (c *fileStore) Store(authConfig types.AuthConfig) error {
|
||||
c.file.AuthConfigs[authConfig.ServerAddress] = authConfig
|
||||
return c.file.Save()
|
||||
}
|
144
vendor/github.com/docker/docker/cli/config/credentials/native_store.go
generated
vendored
Normal file
144
vendor/github.com/docker/docker/cli/config/credentials/native_store.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"github.com/docker/docker-credential-helpers/client"
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/cli/config/configfile"
|
||||
)
|
||||
|
||||
const (
|
||||
remoteCredentialsPrefix = "docker-credential-"
|
||||
tokenUsername = "<token>"
|
||||
)
|
||||
|
||||
// nativeStore implements a credentials store
|
||||
// using native keychain to keep credentials secure.
|
||||
// It piggybacks into a file store to keep users' emails.
|
||||
type nativeStore struct {
|
||||
programFunc client.ProgramFunc
|
||||
fileStore Store
|
||||
}
|
||||
|
||||
// NewNativeStore creates a new native store that
|
||||
// uses a remote helper program to manage credentials.
|
||||
func NewNativeStore(file *configfile.ConfigFile, helperSuffix string) Store {
|
||||
name := remoteCredentialsPrefix + helperSuffix
|
||||
return &nativeStore{
|
||||
programFunc: client.NewShellProgramFunc(name),
|
||||
fileStore: NewFileStore(file),
|
||||
}
|
||||
}
|
||||
|
||||
// Erase removes the given credentials from the native store.
|
||||
func (c *nativeStore) Erase(serverAddress string) error {
|
||||
if err := client.Erase(c.programFunc, serverAddress); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fallback to plain text store to remove email
|
||||
return c.fileStore.Erase(serverAddress)
|
||||
}
|
||||
|
||||
// Get retrieves credentials for a specific server from the native store.
|
||||
func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) {
|
||||
// load user email if it exist or an empty auth config.
|
||||
auth, _ := c.fileStore.Get(serverAddress)
|
||||
|
||||
creds, err := c.getCredentialsFromStore(serverAddress)
|
||||
if err != nil {
|
||||
return auth, err
|
||||
}
|
||||
auth.Username = creds.Username
|
||||
auth.IdentityToken = creds.IdentityToken
|
||||
auth.Password = creds.Password
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
// GetAll retrieves all the credentials from the native store.
|
||||
func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) {
|
||||
auths, err := c.listCredentialsInStore()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Emails are only stored in the file store.
|
||||
// This call can be safely eliminated when emails are removed.
|
||||
fileConfigs, _ := c.fileStore.GetAll()
|
||||
|
||||
authConfigs := make(map[string]types.AuthConfig)
|
||||
for registry := range auths {
|
||||
creds, err := c.getCredentialsFromStore(registry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ac, _ := fileConfigs[registry] // might contain Email
|
||||
ac.Username = creds.Username
|
||||
ac.Password = creds.Password
|
||||
ac.IdentityToken = creds.IdentityToken
|
||||
authConfigs[registry] = ac
|
||||
}
|
||||
|
||||
return authConfigs, nil
|
||||
}
|
||||
|
||||
// Store saves the given credentials in the file store.
|
||||
func (c *nativeStore) Store(authConfig types.AuthConfig) error {
|
||||
if err := c.storeCredentialsInStore(authConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
authConfig.Username = ""
|
||||
authConfig.Password = ""
|
||||
authConfig.IdentityToken = ""
|
||||
|
||||
// Fallback to old credential in plain text to save only the email
|
||||
return c.fileStore.Store(authConfig)
|
||||
}
|
||||
|
||||
// storeCredentialsInStore executes the command to store the credentials in the native store.
|
||||
func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error {
|
||||
creds := &credentials.Credentials{
|
||||
ServerURL: config.ServerAddress,
|
||||
Username: config.Username,
|
||||
Secret: config.Password,
|
||||
}
|
||||
|
||||
if config.IdentityToken != "" {
|
||||
creds.Username = tokenUsername
|
||||
creds.Secret = config.IdentityToken
|
||||
}
|
||||
|
||||
return client.Store(c.programFunc, creds)
|
||||
}
|
||||
|
||||
// getCredentialsFromStore executes the command to get the credentials from the native store.
|
||||
func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthConfig, error) {
|
||||
var ret types.AuthConfig
|
||||
|
||||
creds, err := client.Get(c.programFunc, serverAddress)
|
||||
if err != nil {
|
||||
if credentials.IsErrCredentialsNotFound(err) {
|
||||
// do not return an error if the credentials are not
|
||||
// in the keychain. Let docker ask for new credentials.
|
||||
return ret, nil
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
|
||||
if creds.Username == tokenUsername {
|
||||
ret.IdentityToken = creds.Secret
|
||||
} else {
|
||||
ret.Password = creds.Secret
|
||||
ret.Username = creds.Username
|
||||
}
|
||||
|
||||
ret.ServerAddress = serverAddress
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// listCredentialsInStore returns a listing of stored credentials as a map of
|
||||
// URL -> username.
|
||||
func (c *nativeStore) listCredentialsInStore() (map[string]string, error) {
|
||||
return client.List(c.programFunc)
|
||||
}
|
13
vendor/github.com/docker/docker/cli/flags/client.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/cli/flags/client.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
package flags
|
||||
|
||||
// ClientOptions are the options used to configure the client cli
|
||||
type ClientOptions struct {
|
||||
Common *CommonOptions
|
||||
ConfigDir string
|
||||
Version bool
|
||||
}
|
||||
|
||||
// NewClientOptions returns a new ClientOptions
|
||||
func NewClientOptions() *ClientOptions {
|
||||
return &ClientOptions{Common: NewCommonOptions()}
|
||||
}
|
120
vendor/github.com/docker/docker/cli/flags/common.go
generated
vendored
Normal file
120
vendor/github.com/docker/docker/cli/flags/common.go
generated
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
cliconfig "github.com/docker/docker/cli/config"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultTrustKeyFile is the default filename for the trust key
|
||||
DefaultTrustKeyFile = "key.json"
|
||||
// DefaultCaFile is the default filename for the CA pem file
|
||||
DefaultCaFile = "ca.pem"
|
||||
// DefaultKeyFile is the default filename for the key pem file
|
||||
DefaultKeyFile = "key.pem"
|
||||
// DefaultCertFile is the default filename for the cert pem file
|
||||
DefaultCertFile = "cert.pem"
|
||||
// FlagTLSVerify is the flag name for the TLS verification option
|
||||
FlagTLSVerify = "tlsverify"
|
||||
)
|
||||
|
||||
var (
|
||||
dockerCertPath = os.Getenv("DOCKER_CERT_PATH")
|
||||
dockerTLSVerify = os.Getenv("DOCKER_TLS_VERIFY") != ""
|
||||
)
|
||||
|
||||
// CommonOptions are options common to both the client and the daemon.
|
||||
type CommonOptions struct {
|
||||
Debug bool
|
||||
Hosts []string
|
||||
LogLevel string
|
||||
TLS bool
|
||||
TLSVerify bool
|
||||
TLSOptions *tlsconfig.Options
|
||||
TrustKey string
|
||||
}
|
||||
|
||||
// NewCommonOptions returns a new CommonOptions
|
||||
func NewCommonOptions() *CommonOptions {
|
||||
return &CommonOptions{}
|
||||
}
|
||||
|
||||
// InstallFlags adds flags for the common options on the FlagSet
|
||||
func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) {
|
||||
if dockerCertPath == "" {
|
||||
dockerCertPath = cliconfig.Dir()
|
||||
}
|
||||
|
||||
flags.BoolVarP(&commonOpts.Debug, "debug", "D", false, "Enable debug mode")
|
||||
flags.StringVarP(&commonOpts.LogLevel, "log-level", "l", "info", `Set the logging level ("debug"|"info"|"warn"|"error"|"fatal")`)
|
||||
flags.BoolVar(&commonOpts.TLS, "tls", false, "Use TLS; implied by --tlsverify")
|
||||
flags.BoolVar(&commonOpts.TLSVerify, FlagTLSVerify, dockerTLSVerify, "Use TLS and verify the remote")
|
||||
|
||||
// TODO use flag flags.String("identity"}, "i", "", "Path to libtrust key file")
|
||||
|
||||
commonOpts.TLSOptions = &tlsconfig.Options{
|
||||
CAFile: filepath.Join(dockerCertPath, DefaultCaFile),
|
||||
CertFile: filepath.Join(dockerCertPath, DefaultCertFile),
|
||||
KeyFile: filepath.Join(dockerCertPath, DefaultKeyFile),
|
||||
}
|
||||
tlsOptions := commonOpts.TLSOptions
|
||||
flags.Var(opts.NewQuotedString(&tlsOptions.CAFile), "tlscacert", "Trust certs signed only by this CA")
|
||||
flags.Var(opts.NewQuotedString(&tlsOptions.CertFile), "tlscert", "Path to TLS certificate file")
|
||||
flags.Var(opts.NewQuotedString(&tlsOptions.KeyFile), "tlskey", "Path to TLS key file")
|
||||
|
||||
hostOpt := opts.NewNamedListOptsRef("hosts", &commonOpts.Hosts, opts.ValidateHost)
|
||||
flags.VarP(hostOpt, "host", "H", "Daemon socket(s) to connect to")
|
||||
}
|
||||
|
||||
// SetDefaultOptions sets default values for options after flag parsing is
|
||||
// complete
|
||||
func (commonOpts *CommonOptions) SetDefaultOptions(flags *pflag.FlagSet) {
|
||||
// Regardless of whether the user sets it to true or false, if they
|
||||
// specify --tlsverify at all then we need to turn on TLS
|
||||
// TLSVerify can be true even if not set due to DOCKER_TLS_VERIFY env var, so we need
|
||||
// to check that here as well
|
||||
if flags.Changed(FlagTLSVerify) || commonOpts.TLSVerify {
|
||||
commonOpts.TLS = true
|
||||
}
|
||||
|
||||
if !commonOpts.TLS {
|
||||
commonOpts.TLSOptions = nil
|
||||
} else {
|
||||
tlsOptions := commonOpts.TLSOptions
|
||||
tlsOptions.InsecureSkipVerify = !commonOpts.TLSVerify
|
||||
|
||||
// Reset CertFile and KeyFile to empty string if the user did not specify
|
||||
// the respective flags and the respective default files were not found.
|
||||
if !flags.Changed("tlscert") {
|
||||
if _, err := os.Stat(tlsOptions.CertFile); os.IsNotExist(err) {
|
||||
tlsOptions.CertFile = ""
|
||||
}
|
||||
}
|
||||
if !flags.Changed("tlskey") {
|
||||
if _, err := os.Stat(tlsOptions.KeyFile); os.IsNotExist(err) {
|
||||
tlsOptions.KeyFile = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetLogLevel sets the logrus logging level
|
||||
func SetLogLevel(logLevel string) {
|
||||
if logLevel != "" {
|
||||
lvl, err := logrus.ParseLevel(logLevel)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to parse logging level: %s\n", logLevel)
|
||||
os.Exit(1)
|
||||
}
|
||||
logrus.SetLevel(lvl)
|
||||
} else {
|
||||
logrus.SetLevel(logrus.InfoLevel)
|
||||
}
|
||||
}
|
232
vendor/github.com/docker/docker/cli/trust/trust.go
generated
vendored
Normal file
232
vendor/github.com/docker/docker/cli/trust/trust.go
generated
vendored
Normal file
@ -0,0 +1,232 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/registry/client/auth"
|
||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/docker/api/types"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/cli/command"
|
||||
cliconfig "github.com/docker/docker/cli/config"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/client"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/storage"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/trustpinning"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ReleasesRole is the role named "releases"
|
||||
ReleasesRole = path.Join(data.CanonicalTargetsRole, "releases")
|
||||
)
|
||||
|
||||
func trustDirectory() string {
|
||||
return filepath.Join(cliconfig.Dir(), "trust")
|
||||
}
|
||||
|
||||
// certificateDirectory returns the directory containing
|
||||
// TLS certificates for the given server. An error is
|
||||
// returned if there was an error parsing the server string.
|
||||
func certificateDirectory(server string) (string, error) {
|
||||
u, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(cliconfig.Dir(), "tls", u.Host), nil
|
||||
}
|
||||
|
||||
// Server returns the base URL for the trust server.
|
||||
func Server(index *registrytypes.IndexInfo) (string, error) {
|
||||
if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
|
||||
urlObj, err := url.Parse(s)
|
||||
if err != nil || urlObj.Scheme != "https" {
|
||||
return "", errors.Errorf("valid https URL required for trust server, got %s", s)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
if index.Official {
|
||||
return registry.NotaryServer, nil
|
||||
}
|
||||
return "https://" + index.Name, nil
|
||||
}
|
||||
|
||||
type simpleCredentialStore struct {
|
||||
auth types.AuthConfig
|
||||
}
|
||||
|
||||
func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
|
||||
return scs.auth.Username, scs.auth.Password
|
||||
}
|
||||
|
||||
func (scs simpleCredentialStore) RefreshToken(u *url.URL, service string) string {
|
||||
return scs.auth.IdentityToken
|
||||
}
|
||||
|
||||
func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {
|
||||
}
|
||||
|
||||
// GetNotaryRepository returns a NotaryRepository which stores all the
|
||||
// information needed to operate on a notary repository.
|
||||
// It creates an HTTP transport providing authentication support.
|
||||
func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig, actions ...string) (*client.NotaryRepository, error) {
|
||||
server, err := Server(repoInfo.Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cfg = tlsconfig.ClientDefault()
|
||||
cfg.InsecureSkipVerify = !repoInfo.Index.Secure
|
||||
|
||||
// Get certificate base directory
|
||||
certDir, err := certificateDirectory(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Debugf("reading certificate directory: %s", certDir)
|
||||
|
||||
if err := registry.ReadCertsDirectory(cfg, certDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: cfg,
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
|
||||
// Skip configuration headers since request is not going to Docker daemon
|
||||
modifiers := registry.DockerHeaders(command.UserAgent(), http.Header{})
|
||||
authTransport := transport.NewTransport(base, modifiers...)
|
||||
pingClient := &http.Client{
|
||||
Transport: authTransport,
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
endpointStr := server + "/v2/"
|
||||
req, err := http.NewRequest("GET", endpointStr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
challengeManager := challenge.NewSimpleManager()
|
||||
|
||||
resp, err := pingClient.Do(req)
|
||||
if err != nil {
|
||||
// Ignore error on ping to operate in offline mode
|
||||
logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Add response to the challenge manager to parse out
|
||||
// authentication header and register authentication method
|
||||
if err := challengeManager.AddResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
scope := auth.RepositoryScope{
|
||||
Repository: repoInfo.Name.Name(),
|
||||
Actions: actions,
|
||||
Class: repoInfo.Class,
|
||||
}
|
||||
creds := simpleCredentialStore{auth: authConfig}
|
||||
tokenHandlerOptions := auth.TokenHandlerOptions{
|
||||
Transport: authTransport,
|
||||
Credentials: creds,
|
||||
Scopes: []auth.Scope{scope},
|
||||
ClientID: registry.AuthClientID,
|
||||
}
|
||||
tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
|
||||
basicHandler := auth.NewBasicHandler(creds)
|
||||
modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
|
||||
tr := transport.NewTransport(base, modifiers...)
|
||||
|
||||
return client.NewNotaryRepository(
|
||||
trustDirectory(),
|
||||
repoInfo.Name.Name(),
|
||||
server,
|
||||
tr,
|
||||
getPassphraseRetriever(streams),
|
||||
trustpinning.TrustPinConfig{})
|
||||
}
|
||||
|
||||
func getPassphraseRetriever(streams command.Streams) notary.PassRetriever {
|
||||
aliasMap := map[string]string{
|
||||
"root": "root",
|
||||
"snapshot": "repository",
|
||||
"targets": "repository",
|
||||
"default": "repository",
|
||||
}
|
||||
baseRetriever := passphrase.PromptRetrieverWithInOut(streams.In(), streams.Out(), aliasMap)
|
||||
env := map[string]string{
|
||||
"root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
|
||||
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
||||
"targets": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
||||
"default": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
||||
}
|
||||
|
||||
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
|
||||
if v := env[alias]; v != "" {
|
||||
return v, numAttempts > 1, nil
|
||||
}
|
||||
// For non-root roles, we can also try the "default" alias if it is specified
|
||||
if v := env["default"]; v != "" && alias != data.CanonicalRootRole {
|
||||
return v, numAttempts > 1, nil
|
||||
}
|
||||
return baseRetriever(keyName, alias, createNew, numAttempts)
|
||||
}
|
||||
}
|
||||
|
||||
// NotaryError formats an error message received from the notary service
|
||||
func NotaryError(repoName string, err error) error {
|
||||
switch err.(type) {
|
||||
case *json.SyntaxError:
|
||||
logrus.Debugf("Notary syntax error: %s", err)
|
||||
return errors.Errorf("Error: no trust data available for remote repository %s. Try running notary server and setting DOCKER_CONTENT_TRUST_SERVER to its HTTPS address?", repoName)
|
||||
case signed.ErrExpired:
|
||||
return errors.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
|
||||
case trustmanager.ErrKeyNotFound:
|
||||
return errors.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
|
||||
case storage.NetworkError:
|
||||
return errors.Errorf("Error: error contacting notary server: %v", err)
|
||||
case storage.ErrMetaNotFound:
|
||||
return errors.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
|
||||
case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType:
|
||||
return errors.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
|
||||
case signed.ErrNoKeys:
|
||||
return errors.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
|
||||
case signed.ErrLowVersion:
|
||||
return errors.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
|
||||
case signed.ErrRoleThreshold:
|
||||
return errors.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
|
||||
case client.ErrRepositoryNotExist:
|
||||
return errors.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
|
||||
case signed.ErrInsufficientSignatures:
|
||||
return errors.Errorf("Error: could not produce valid signature for %s. If Yubikey was used, was touch input provided?: %v", repoName, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
35
vendor/github.com/docker/docker/client/README.md
generated
vendored
35
vendor/github.com/docker/docker/client/README.md
generated
vendored
@ -1,35 +0,0 @@
|
||||
# Go client for the Docker Engine API
|
||||
|
||||
The `docker` command uses this package to communicate with the daemon. It can also be used by your own Go applications to do anything the command-line interface does – running containers, pulling images, managing swarms, etc.
|
||||
|
||||
For example, to list running containers (the equivalent of `docker ps`):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, container := range containers {
|
||||
fmt.Printf("%s %s\n", container.ID[:10], container.Image)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[Full documentation is available on GoDoc.](https://godoc.org/github.com/docker/docker/client)
|
76
vendor/github.com/docker/docker/dockerversion/useragent.go
generated
vendored
Normal file
76
vendor/github.com/docker/docker/dockerversion/useragent.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
package dockerversion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/pkg/parsers/kernel"
|
||||
"github.com/docker/docker/pkg/useragent"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// UAStringKey is used as key type for user-agent string in net/context struct
|
||||
const UAStringKey = "upstream-user-agent"
|
||||
|
||||
// DockerUserAgent is the User-Agent the Docker client uses to identify itself.
|
||||
// In accordance with RFC 7231 (5.5.3) is of the form:
|
||||
// [docker client's UA] UpstreamClient([upstream client's UA])
|
||||
func DockerUserAgent(ctx context.Context) string {
|
||||
httpVersion := make([]useragent.VersionInfo, 0, 6)
|
||||
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: Version})
|
||||
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "go", Version: runtime.Version()})
|
||||
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "git-commit", Version: GitCommit})
|
||||
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
|
||||
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "kernel", Version: kernelVersion.String()})
|
||||
}
|
||||
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "os", Version: runtime.GOOS})
|
||||
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH})
|
||||
|
||||
dockerUA := useragent.AppendVersions("", httpVersion...)
|
||||
upstreamUA := getUserAgentFromContext(ctx)
|
||||
if len(upstreamUA) > 0 {
|
||||
ret := insertUpstreamUserAgent(upstreamUA, dockerUA)
|
||||
return ret
|
||||
}
|
||||
return dockerUA
|
||||
}
|
||||
|
||||
// getUserAgentFromContext returns the previously saved user-agent context stored in ctx, if one exists
|
||||
func getUserAgentFromContext(ctx context.Context) string {
|
||||
var upstreamUA string
|
||||
if ctx != nil {
|
||||
var ki interface{} = ctx.Value(UAStringKey)
|
||||
if ki != nil {
|
||||
upstreamUA = ctx.Value(UAStringKey).(string)
|
||||
}
|
||||
}
|
||||
return upstreamUA
|
||||
}
|
||||
|
||||
// escapeStr returns s with every rune in charsToEscape escaped by a backslash
|
||||
func escapeStr(s string, charsToEscape string) string {
|
||||
var ret string
|
||||
for _, currRune := range s {
|
||||
appended := false
|
||||
for _, escapeableRune := range charsToEscape {
|
||||
if currRune == escapeableRune {
|
||||
ret += `\` + string(currRune)
|
||||
appended = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !appended {
|
||||
ret += string(currRune)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// insertUpstreamUserAgent adds the upstream client useragent to create a user-agent
|
||||
// string of the form:
|
||||
// $dockerUA UpstreamClient($upstreamUA)
|
||||
func insertUpstreamUserAgent(upstreamUA string, dockerUA string) string {
|
||||
charsToEscape := `();\`
|
||||
upstreamUAEscaped := escapeStr(upstreamUA, charsToEscape)
|
||||
return fmt.Sprintf("%s UpstreamClient(%s)", dockerUA, upstreamUAEscaped)
|
||||
}
|
16
vendor/github.com/docker/docker/dockerversion/version_lib.go
generated
vendored
Normal file
16
vendor/github.com/docker/docker/dockerversion/version_lib.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
// +build !autogen
|
||||
|
||||
// Package dockerversion is auto-generated at build-time
|
||||
package dockerversion
|
||||
|
||||
// Default build-time variable for library-import.
|
||||
// This file is overridden on build with build-time informations.
|
||||
const (
|
||||
GitCommit string = "library-import"
|
||||
Version string = "library-import"
|
||||
BuildTime string = "library-import"
|
||||
IAmStatic string = "library-import"
|
||||
ContainerdCommitID string = "library-import"
|
||||
RuncCommitID string = "library-import"
|
||||
InitCommitID string = "library-import"
|
||||
)
|
67
vendor/github.com/docker/docker/hack/integration-cli-on-swarm/README.md
generated
vendored
67
vendor/github.com/docker/docker/hack/integration-cli-on-swarm/README.md
generated
vendored
@ -1,67 +0,0 @@
|
||||
# Integration Testing on Swarm
|
||||
|
||||
IT on Swarm allows you to execute integration test in parallel across a Docker Swarm cluster
|
||||
|
||||
## Architecture
|
||||
|
||||
### Master service
|
||||
|
||||
- Works as a funker caller
|
||||
- Calls a worker funker (`-worker-service`) with a chunk of `-check.f` filter strings (passed as a file via `-input` flag, typically `/mnt/input`)
|
||||
|
||||
### Worker service
|
||||
|
||||
- Works as a funker callee
|
||||
- Executes an equivalent of `TESTFLAGS=-check.f TestFoo|TestBar|TestBaz ... make test-integration-cli` using the bind-mounted API socket (`docker.sock`)
|
||||
|
||||
### Client
|
||||
|
||||
- Controls master and workers via `docker stack`
|
||||
- No need to have a local daemon
|
||||
|
||||
Typically, the master and workers are supposed to be running on a cloud environment,
|
||||
while the client is supposed to be running on a laptop, e.g. Docker for Mac/Windows.
|
||||
|
||||
## Requirement
|
||||
|
||||
- Docker daemon 1.13 or later
|
||||
- Private registry for distributed execution with multiple nodes
|
||||
|
||||
## Usage
|
||||
|
||||
### Step 1: Prepare images
|
||||
|
||||
$ make build-integration-cli-on-swarm
|
||||
|
||||
Following environment variables are known to work in this step:
|
||||
|
||||
- `BUILDFLAGS`
|
||||
- `DOCKER_INCREMENTAL_BINARY`
|
||||
|
||||
### Step 2: Execute tests
|
||||
|
||||
$ ./hack/integration-cli-on-swarm/integration-cli-on-swarm -replicas 40 -push-worker-image YOUR_REGISTRY.EXAMPLE.COM/integration-cli-worker:latest
|
||||
|
||||
Following environment variables are known to work in this step:
|
||||
|
||||
- `DOCKER_GRAPHDRIVER`
|
||||
- `DOCKER_EXPERIMENTAL`
|
||||
|
||||
#### Flags
|
||||
|
||||
Basic flags:
|
||||
|
||||
- `-replicas N`: the number of worker service replicas. i.e. degree of parallelism.
|
||||
- `-chunks N`: the number of chunks. By default, `chunks` == `replicas`.
|
||||
- `-push-worker-image REGISTRY/IMAGE:TAG`: push the worker image to the registry. Note that if you have only single node and hence you do not need a private registry, you do not need to specify `-push-worker-image`.
|
||||
|
||||
Experimental flags for mitigating makespan nonuniformity:
|
||||
|
||||
- `-shuffle`: Shuffle the test filter strings
|
||||
|
||||
Flags for debugging IT on Swarm itself:
|
||||
|
||||
- `-rand-seed N`: the random seed. This flag is useful for deterministic replaying. By default(0), the timestamp is used.
|
||||
- `-filters-file FILE`: the file contains `-check.f` strings. By default, the file is automatically generated.
|
||||
- `-dry-run`: skip the actual workload
|
||||
- `keep-executor`: do not auto-remove executor containers, which is used for running privileged programs on Swarm
|
2
vendor/github.com/docker/docker/hack/integration-cli-on-swarm/agent/vendor.conf
generated
vendored
2
vendor/github.com/docker/docker/hack/integration-cli-on-swarm/agent/vendor.conf
generated
vendored
@ -1,2 +0,0 @@
|
||||
# dependencies specific to worker (i.e. github.com/docker/docker/...) are not vendored here
|
||||
github.com/bfirsh/funker-go eaa0a2e06f30e72c9a0b7f858951e581e26ef773
|
46
vendor/github.com/docker/docker/opts/env.go
generated
vendored
Normal file
46
vendor/github.com/docker/docker/opts/env.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ValidateEnv validates an environment variable and returns it.
|
||||
// If no value is specified, it returns the current value using os.Getenv.
|
||||
//
|
||||
// As on ParseEnvFile and related to #16585, environment variable names
|
||||
// are not validate what so ever, it's up to application inside docker
|
||||
// to validate them or not.
|
||||
//
|
||||
// The only validation here is to check if name is empty, per #25099
|
||||
func ValidateEnv(val string) (string, error) {
|
||||
arr := strings.Split(val, "=")
|
||||
if arr[0] == "" {
|
||||
return "", fmt.Errorf("invalid environment variable: %s", val)
|
||||
}
|
||||
if len(arr) > 1 {
|
||||
return val, nil
|
||||
}
|
||||
if !doesEnvExist(val) {
|
||||
return val, nil
|
||||
}
|
||||
return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
|
||||
}
|
||||
|
||||
func doesEnvExist(name string) bool {
|
||||
for _, entry := range os.Environ() {
|
||||
parts := strings.SplitN(entry, "=", 2)
|
||||
if runtime.GOOS == "windows" {
|
||||
// Environment variable are case-insensitive on Windows. PaTh, path and PATH are equivalent.
|
||||
if strings.EqualFold(parts[0], name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if parts[0] == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
165
vendor/github.com/docker/docker/opts/hosts.go
generated
vendored
Normal file
165
vendor/github.com/docker/docker/opts/hosts.go
generated
vendored
Normal file
@ -0,0 +1,165 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. dockerd -H tcp://
|
||||
// These are the IANA registered port numbers for use with Docker
|
||||
// see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker
|
||||
DefaultHTTPPort = 2375 // Default HTTP Port
|
||||
// DefaultTLSHTTPPort Default HTTP Port used when TLS enabled
|
||||
DefaultTLSHTTPPort = 2376 // Default TLS encrypted HTTP Port
|
||||
// DefaultUnixSocket Path for the unix socket.
|
||||
// Docker daemon by default always listens on the default unix socket
|
||||
DefaultUnixSocket = "/var/run/docker.sock"
|
||||
// DefaultTCPHost constant defines the default host string used by docker on Windows
|
||||
DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
|
||||
// DefaultTLSHost constant defines the default host string used by docker for TLS sockets
|
||||
DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort)
|
||||
// DefaultNamedPipe defines the default named pipe used by docker on Windows
|
||||
DefaultNamedPipe = `//./pipe/docker_engine`
|
||||
)
|
||||
|
||||
// ValidateHost validates that the specified string is a valid host and returns it.
|
||||
func ValidateHost(val string) (string, error) {
|
||||
host := strings.TrimSpace(val)
|
||||
// The empty string means default and is not handled by parseDockerDaemonHost
|
||||
if host != "" {
|
||||
_, err := parseDockerDaemonHost(host)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
}
|
||||
// Note: unlike most flag validators, we don't return the mutated value here
|
||||
// we need to know what the user entered later (using ParseHost) to adjust for TLS
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// ParseHost and set defaults for a Daemon host string
|
||||
func ParseHost(defaultToTLS bool, val string) (string, error) {
|
||||
host := strings.TrimSpace(val)
|
||||
if host == "" {
|
||||
if defaultToTLS {
|
||||
host = DefaultTLSHost
|
||||
} else {
|
||||
host = DefaultHost
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
host, err = parseDockerDaemonHost(host)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
// parseDockerDaemonHost parses the specified address and returns an address that will be used as the host.
|
||||
// Depending of the address specified, this may return one of the global Default* strings defined in hosts.go.
|
||||
func parseDockerDaemonHost(addr string) (string, error) {
|
||||
addrParts := strings.SplitN(addr, "://", 2)
|
||||
if len(addrParts) == 1 && addrParts[0] != "" {
|
||||
addrParts = []string{"tcp", addrParts[0]}
|
||||
}
|
||||
|
||||
switch addrParts[0] {
|
||||
case "tcp":
|
||||
return ParseTCPAddr(addrParts[1], DefaultTCPHost)
|
||||
case "unix":
|
||||
return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket)
|
||||
case "npipe":
|
||||
return parseSimpleProtoAddr("npipe", addrParts[1], DefaultNamedPipe)
|
||||
case "fd":
|
||||
return addr, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Invalid bind address format: %s", addr)
|
||||
}
|
||||
}
|
||||
|
||||
// parseSimpleProtoAddr parses and validates that the specified address is a valid
|
||||
// socket address for simple protocols like unix and npipe. It returns a formatted
|
||||
// socket address, either using the address parsed from addr, or the contents of
|
||||
// defaultAddr if addr is a blank string.
|
||||
func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) {
|
||||
addr = strings.TrimPrefix(addr, proto+"://")
|
||||
if strings.Contains(addr, "://") {
|
||||
return "", fmt.Errorf("Invalid proto, expected %s: %s", proto, addr)
|
||||
}
|
||||
if addr == "" {
|
||||
addr = defaultAddr
|
||||
}
|
||||
return fmt.Sprintf("%s://%s", proto, addr), nil
|
||||
}
|
||||
|
||||
// ParseTCPAddr parses and validates that the specified address is a valid TCP
|
||||
// address. It returns a formatted TCP address, either using the address parsed
|
||||
// from tryAddr, or the contents of defaultAddr if tryAddr is a blank string.
|
||||
// tryAddr is expected to have already been Trim()'d
|
||||
// defaultAddr must be in the full `tcp://host:port` form
|
||||
func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
|
||||
if tryAddr == "" || tryAddr == "tcp://" {
|
||||
return defaultAddr, nil
|
||||
}
|
||||
addr := strings.TrimPrefix(tryAddr, "tcp://")
|
||||
if strings.Contains(addr, "://") || addr == "" {
|
||||
return "", fmt.Errorf("Invalid proto, expected tcp: %s", tryAddr)
|
||||
}
|
||||
|
||||
defaultAddr = strings.TrimPrefix(defaultAddr, "tcp://")
|
||||
defaultHost, defaultPort, err := net.SplitHostPort(defaultAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// url.Parse fails for trailing colon on IPv6 brackets on Go 1.5, but
|
||||
// not 1.4. See https://github.com/golang/go/issues/12200 and
|
||||
// https://github.com/golang/go/issues/6530.
|
||||
if strings.HasSuffix(addr, "]:") {
|
||||
addr += defaultPort
|
||||
}
|
||||
|
||||
u, err := url.Parse("tcp://" + addr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
// try port addition once
|
||||
host, port, err = net.SplitHostPort(net.JoinHostPort(u.Host, defaultPort))
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
host = defaultHost
|
||||
}
|
||||
if port == "" {
|
||||
port = defaultPort
|
||||
}
|
||||
p, err := strconv.Atoi(port)
|
||||
if err != nil && p == 0 {
|
||||
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil
|
||||
}
|
||||
|
||||
// ValidateExtraHost validates that the specified string is a valid extrahost and returns it.
|
||||
// ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6).
|
||||
func ValidateExtraHost(val string) (string, error) {
|
||||
// allow for IPv6 addresses in extra hosts by only splitting on first ":"
|
||||
arr := strings.SplitN(val, ":", 2)
|
||||
if len(arr) != 2 || len(arr[0]) == 0 {
|
||||
return "", fmt.Errorf("bad format for add-host: %q", val)
|
||||
}
|
||||
if _, err := ValidateIPAddress(arr[1]); err != nil {
|
||||
return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1])
|
||||
}
|
||||
return val, nil
|
||||
}
|
8
vendor/github.com/docker/docker/opts/hosts_unix.go
generated
vendored
Normal file
8
vendor/github.com/docker/docker/opts/hosts_unix.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// +build !windows
|
||||
|
||||
package opts
|
||||
|
||||
import "fmt"
|
||||
|
||||
// DefaultHost constant defines the default host string used by docker on other hosts than Windows
|
||||
var DefaultHost = fmt.Sprintf("unix://%s", DefaultUnixSocket)
|
6
vendor/github.com/docker/docker/opts/hosts_windows.go
generated
vendored
Normal file
6
vendor/github.com/docker/docker/opts/hosts_windows.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
// +build windows
|
||||
|
||||
package opts
|
||||
|
||||
// DefaultHost constant defines the default host string used by docker on Windows
|
||||
var DefaultHost = "npipe://" + DefaultNamedPipe
|
47
vendor/github.com/docker/docker/opts/ip.go
generated
vendored
Normal file
47
vendor/github.com/docker/docker/opts/ip.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// IPOpt holds an IP. It is used to store values from CLI flags.
|
||||
type IPOpt struct {
|
||||
*net.IP
|
||||
}
|
||||
|
||||
// NewIPOpt creates a new IPOpt from a reference net.IP and a
|
||||
// string representation of an IP. If the string is not a valid
|
||||
// IP it will fallback to the specified reference.
|
||||
func NewIPOpt(ref *net.IP, defaultVal string) *IPOpt {
|
||||
o := &IPOpt{
|
||||
IP: ref,
|
||||
}
|
||||
o.Set(defaultVal)
|
||||
return o
|
||||
}
|
||||
|
||||
// Set sets an IPv4 or IPv6 address from a given string. If the given
|
||||
// string is not parseable as an IP address it returns an error.
|
||||
func (o *IPOpt) Set(val string) error {
|
||||
ip := net.ParseIP(val)
|
||||
if ip == nil {
|
||||
return fmt.Errorf("%s is not an ip address", val)
|
||||
}
|
||||
*o.IP = ip
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the IP address stored in the IPOpt. If stored IP is a
|
||||
// nil pointer, it returns an empty string.
|
||||
func (o *IPOpt) String() string {
|
||||
if *o.IP == nil {
|
||||
return ""
|
||||
}
|
||||
return o.IP.String()
|
||||
}
|
||||
|
||||
// Type returns the type of the option
|
||||
func (o *IPOpt) Type() string {
|
||||
return "ip"
|
||||
}
|
173
vendor/github.com/docker/docker/opts/mount.go
generated
vendored
Normal file
173
vendor/github.com/docker/docker/opts/mount.go
generated
vendored
Normal file
@ -0,0 +1,173 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
// MountOpt is a Value type for parsing mounts
|
||||
type MountOpt struct {
|
||||
values []mounttypes.Mount
|
||||
}
|
||||
|
||||
// Set a new mount value
|
||||
func (m *MountOpt) Set(value string) error {
|
||||
csvReader := csv.NewReader(strings.NewReader(value))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mount := mounttypes.Mount{}
|
||||
|
||||
volumeOptions := func() *mounttypes.VolumeOptions {
|
||||
if mount.VolumeOptions == nil {
|
||||
mount.VolumeOptions = &mounttypes.VolumeOptions{
|
||||
Labels: make(map[string]string),
|
||||
}
|
||||
}
|
||||
if mount.VolumeOptions.DriverConfig == nil {
|
||||
mount.VolumeOptions.DriverConfig = &mounttypes.Driver{}
|
||||
}
|
||||
return mount.VolumeOptions
|
||||
}
|
||||
|
||||
bindOptions := func() *mounttypes.BindOptions {
|
||||
if mount.BindOptions == nil {
|
||||
mount.BindOptions = new(mounttypes.BindOptions)
|
||||
}
|
||||
return mount.BindOptions
|
||||
}
|
||||
|
||||
tmpfsOptions := func() *mounttypes.TmpfsOptions {
|
||||
if mount.TmpfsOptions == nil {
|
||||
mount.TmpfsOptions = new(mounttypes.TmpfsOptions)
|
||||
}
|
||||
return mount.TmpfsOptions
|
||||
}
|
||||
|
||||
setValueOnMap := func(target map[string]string, value string) {
|
||||
parts := strings.SplitN(value, "=", 2)
|
||||
if len(parts) == 1 {
|
||||
target[value] = ""
|
||||
} else {
|
||||
target[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
mount.Type = mounttypes.TypeVolume // default to volume mounts
|
||||
// Set writable as the default
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
key := strings.ToLower(parts[0])
|
||||
|
||||
if len(parts) == 1 {
|
||||
switch key {
|
||||
case "readonly", "ro":
|
||||
mount.ReadOnly = true
|
||||
continue
|
||||
case "volume-nocopy":
|
||||
volumeOptions().NoCopy = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
||||
}
|
||||
|
||||
value := parts[1]
|
||||
switch key {
|
||||
case "type":
|
||||
mount.Type = mounttypes.Type(strings.ToLower(value))
|
||||
case "source", "src":
|
||||
mount.Source = value
|
||||
case "target", "dst", "destination":
|
||||
mount.Target = value
|
||||
case "readonly", "ro":
|
||||
mount.ReadOnly, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for %s: %s", key, value)
|
||||
}
|
||||
case "consistency":
|
||||
mount.Consistency = mounttypes.Consistency(strings.ToLower(value))
|
||||
case "bind-propagation":
|
||||
bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value))
|
||||
case "volume-nocopy":
|
||||
volumeOptions().NoCopy, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for volume-nocopy: %s", value)
|
||||
}
|
||||
case "volume-label":
|
||||
setValueOnMap(volumeOptions().Labels, value)
|
||||
case "volume-driver":
|
||||
volumeOptions().DriverConfig.Name = value
|
||||
case "volume-opt":
|
||||
if volumeOptions().DriverConfig.Options == nil {
|
||||
volumeOptions().DriverConfig.Options = make(map[string]string)
|
||||
}
|
||||
setValueOnMap(volumeOptions().DriverConfig.Options, value)
|
||||
case "tmpfs-size":
|
||||
sizeBytes, err := units.RAMInBytes(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for %s: %s", key, value)
|
||||
}
|
||||
tmpfsOptions().SizeBytes = sizeBytes
|
||||
case "tmpfs-mode":
|
||||
ui64, err := strconv.ParseUint(value, 8, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for %s: %s", key, value)
|
||||
}
|
||||
tmpfsOptions().Mode = os.FileMode(ui64)
|
||||
default:
|
||||
return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
|
||||
}
|
||||
}
|
||||
|
||||
if mount.Type == "" {
|
||||
return fmt.Errorf("type is required")
|
||||
}
|
||||
|
||||
if mount.Target == "" {
|
||||
return fmt.Errorf("target is required")
|
||||
}
|
||||
|
||||
if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume {
|
||||
return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type)
|
||||
}
|
||||
if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind {
|
||||
return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type)
|
||||
}
|
||||
if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs {
|
||||
return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type)
|
||||
}
|
||||
|
||||
m.values = append(m.values, mount)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the type of this option
|
||||
func (m *MountOpt) Type() string {
|
||||
return "mount"
|
||||
}
|
||||
|
||||
// String returns a string repr of this option
|
||||
func (m *MountOpt) String() string {
|
||||
mounts := []string{}
|
||||
for _, mount := range m.values {
|
||||
repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
|
||||
mounts = append(mounts, repr)
|
||||
}
|
||||
return strings.Join(mounts, ", ")
|
||||
}
|
||||
|
||||
// Value returns the mounts
|
||||
func (m *MountOpt) Value() []mounttypes.Mount {
|
||||
return m.values
|
||||
}
|
488
vendor/github.com/docker/docker/opts/opts.go
generated
vendored
Normal file
488
vendor/github.com/docker/docker/opts/opts.go
generated
vendored
Normal file
@ -0,0 +1,488 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
var (
|
||||
alphaRegexp = regexp.MustCompile(`[a-zA-Z]`)
|
||||
domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`)
|
||||
)
|
||||
|
||||
// ListOpts holds a list of values and a validation function.
|
||||
type ListOpts struct {
|
||||
values *[]string
|
||||
validator ValidatorFctType
|
||||
}
|
||||
|
||||
// NewListOpts creates a new ListOpts with the specified validator.
|
||||
func NewListOpts(validator ValidatorFctType) ListOpts {
|
||||
var values []string
|
||||
return *NewListOptsRef(&values, validator)
|
||||
}
|
||||
|
||||
// NewListOptsRef creates a new ListOpts with the specified values and validator.
|
||||
func NewListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts {
|
||||
return &ListOpts{
|
||||
values: values,
|
||||
validator: validator,
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *ListOpts) String() string {
|
||||
if len(*opts.values) == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%v", *opts.values)
|
||||
}
|
||||
|
||||
// Set validates if needed the input value and adds it to the
|
||||
// internal slice.
|
||||
func (opts *ListOpts) Set(value string) error {
|
||||
if opts.validator != nil {
|
||||
v, err := opts.validator(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value = v
|
||||
}
|
||||
(*opts.values) = append((*opts.values), value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes the specified element from the slice.
|
||||
func (opts *ListOpts) Delete(key string) {
|
||||
for i, k := range *opts.values {
|
||||
if k == key {
|
||||
(*opts.values) = append((*opts.values)[:i], (*opts.values)[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetMap returns the content of values in a map in order to avoid
|
||||
// duplicates.
|
||||
func (opts *ListOpts) GetMap() map[string]struct{} {
|
||||
ret := make(map[string]struct{})
|
||||
for _, k := range *opts.values {
|
||||
ret[k] = struct{}{}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// GetAll returns the values of slice.
|
||||
func (opts *ListOpts) GetAll() []string {
|
||||
return (*opts.values)
|
||||
}
|
||||
|
||||
// GetAllOrEmpty returns the values of the slice
|
||||
// or an empty slice when there are no values.
|
||||
func (opts *ListOpts) GetAllOrEmpty() []string {
|
||||
v := *opts.values
|
||||
if v == nil {
|
||||
return make([]string, 0)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Get checks the existence of the specified key.
|
||||
func (opts *ListOpts) Get(key string) bool {
|
||||
for _, k := range *opts.values {
|
||||
if k == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the amount of element in the slice.
|
||||
func (opts *ListOpts) Len() int {
|
||||
return len((*opts.values))
|
||||
}
|
||||
|
||||
// Type returns a string name for this Option type
|
||||
func (opts *ListOpts) Type() string {
|
||||
return "list"
|
||||
}
|
||||
|
||||
// WithValidator returns the ListOpts with validator set.
|
||||
func (opts *ListOpts) WithValidator(validator ValidatorFctType) *ListOpts {
|
||||
opts.validator = validator
|
||||
return opts
|
||||
}
|
||||
|
||||
// NamedOption is an interface that list and map options
|
||||
// with names implement.
|
||||
type NamedOption interface {
|
||||
Name() string
|
||||
}
|
||||
|
||||
// NamedListOpts is a ListOpts with a configuration name.
|
||||
// This struct is useful to keep reference to the assigned
|
||||
// field name in the internal configuration struct.
|
||||
type NamedListOpts struct {
|
||||
name string
|
||||
ListOpts
|
||||
}
|
||||
|
||||
var _ NamedOption = &NamedListOpts{}
|
||||
|
||||
// NewNamedListOptsRef creates a reference to a new NamedListOpts struct.
|
||||
func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts {
|
||||
return &NamedListOpts{
|
||||
name: name,
|
||||
ListOpts: *NewListOptsRef(values, validator),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of the NamedListOpts in the configuration.
|
||||
func (o *NamedListOpts) Name() string {
|
||||
return o.name
|
||||
}
|
||||
|
||||
// MapOpts holds a map of values and a validation function.
|
||||
type MapOpts struct {
|
||||
values map[string]string
|
||||
validator ValidatorFctType
|
||||
}
|
||||
|
||||
// Set validates if needed the input value and add it to the
|
||||
// internal map, by splitting on '='.
|
||||
func (opts *MapOpts) Set(value string) error {
|
||||
if opts.validator != nil {
|
||||
v, err := opts.validator(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value = v
|
||||
}
|
||||
vals := strings.SplitN(value, "=", 2)
|
||||
if len(vals) == 1 {
|
||||
(opts.values)[vals[0]] = ""
|
||||
} else {
|
||||
(opts.values)[vals[0]] = vals[1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAll returns the values of MapOpts as a map.
|
||||
func (opts *MapOpts) GetAll() map[string]string {
|
||||
return opts.values
|
||||
}
|
||||
|
||||
func (opts *MapOpts) String() string {
|
||||
return fmt.Sprintf("%v", map[string]string((opts.values)))
|
||||
}
|
||||
|
||||
// Type returns a string name for this Option type
|
||||
func (opts *MapOpts) Type() string {
|
||||
return "map"
|
||||
}
|
||||
|
||||
// NewMapOpts creates a new MapOpts with the specified map of values and a validator.
|
||||
func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts {
|
||||
if values == nil {
|
||||
values = make(map[string]string)
|
||||
}
|
||||
return &MapOpts{
|
||||
values: values,
|
||||
validator: validator,
|
||||
}
|
||||
}
|
||||
|
||||
// NamedMapOpts is a MapOpts struct with a configuration name.
|
||||
// This struct is useful to keep reference to the assigned
|
||||
// field name in the internal configuration struct.
|
||||
type NamedMapOpts struct {
|
||||
name string
|
||||
MapOpts
|
||||
}
|
||||
|
||||
var _ NamedOption = &NamedMapOpts{}
|
||||
|
||||
// NewNamedMapOpts creates a reference to a new NamedMapOpts struct.
|
||||
func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts {
|
||||
return &NamedMapOpts{
|
||||
name: name,
|
||||
MapOpts: *NewMapOpts(values, validator),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of the NamedMapOpts in the configuration.
|
||||
func (o *NamedMapOpts) Name() string {
|
||||
return o.name
|
||||
}
|
||||
|
||||
// ValidatorFctType defines a validator function that returns a validated string and/or an error.
|
||||
type ValidatorFctType func(val string) (string, error)
|
||||
|
||||
// ValidatorFctListType defines a validator function that returns a validated list of string and/or an error
|
||||
type ValidatorFctListType func(val string) ([]string, error)
|
||||
|
||||
// ValidateIPAddress validates an Ip address.
|
||||
func ValidateIPAddress(val string) (string, error) {
|
||||
var ip = net.ParseIP(strings.TrimSpace(val))
|
||||
if ip != nil {
|
||||
return ip.String(), nil
|
||||
}
|
||||
return "", fmt.Errorf("%s is not an ip address", val)
|
||||
}
|
||||
|
||||
// ValidateMACAddress validates a MAC address.
|
||||
func ValidateMACAddress(val string) (string, error) {
|
||||
_, err := net.ParseMAC(strings.TrimSpace(val))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// ValidateDNSSearch validates domain for resolvconf search configuration.
|
||||
// A zero length domain is represented by a dot (.).
|
||||
func ValidateDNSSearch(val string) (string, error) {
|
||||
if val = strings.Trim(val, " "); val == "." {
|
||||
return val, nil
|
||||
}
|
||||
return validateDomain(val)
|
||||
}
|
||||
|
||||
func validateDomain(val string) (string, error) {
|
||||
if alphaRegexp.FindString(val) == "" {
|
||||
return "", fmt.Errorf("%s is not a valid domain", val)
|
||||
}
|
||||
ns := domainRegexp.FindSubmatch([]byte(val))
|
||||
if len(ns) > 0 && len(ns[1]) < 255 {
|
||||
return string(ns[1]), nil
|
||||
}
|
||||
return "", fmt.Errorf("%s is not a valid domain", val)
|
||||
}
|
||||
|
||||
// ValidateLabel validates that the specified string is a valid label, and returns it.
|
||||
// Labels are in the form on key=value.
|
||||
func ValidateLabel(val string) (string, error) {
|
||||
if strings.Count(val, "=") < 1 {
|
||||
return "", fmt.Errorf("bad attribute format: %s", val)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// ValidateSysctl validates a sysctl and returns it.
|
||||
func ValidateSysctl(val string) (string, error) {
|
||||
validSysctlMap := map[string]bool{
|
||||
"kernel.msgmax": true,
|
||||
"kernel.msgmnb": true,
|
||||
"kernel.msgmni": true,
|
||||
"kernel.sem": true,
|
||||
"kernel.shmall": true,
|
||||
"kernel.shmmax": true,
|
||||
"kernel.shmmni": true,
|
||||
"kernel.shm_rmid_forced": true,
|
||||
}
|
||||
validSysctlPrefixes := []string{
|
||||
"net.",
|
||||
"fs.mqueue.",
|
||||
}
|
||||
arr := strings.Split(val, "=")
|
||||
if len(arr) < 2 {
|
||||
return "", fmt.Errorf("sysctl '%s' is not whitelisted", val)
|
||||
}
|
||||
if validSysctlMap[arr[0]] {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
for _, vp := range validSysctlPrefixes {
|
||||
if strings.HasPrefix(arr[0], vp) {
|
||||
return val, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("sysctl '%s' is not whitelisted", val)
|
||||
}
|
||||
|
||||
// FilterOpt is a flag type for validating filters
|
||||
type FilterOpt struct {
|
||||
filter filters.Args
|
||||
}
|
||||
|
||||
// NewFilterOpt returns a new FilterOpt
|
||||
func NewFilterOpt() FilterOpt {
|
||||
return FilterOpt{filter: filters.NewArgs()}
|
||||
}
|
||||
|
||||
func (o *FilterOpt) String() string {
|
||||
repr, err := filters.ToParam(o.filter)
|
||||
if err != nil {
|
||||
return "invalid filters"
|
||||
}
|
||||
return repr
|
||||
}
|
||||
|
||||
// Set sets the value of the opt by parsing the command line value
|
||||
func (o *FilterOpt) Set(value string) error {
|
||||
var err error
|
||||
o.filter, err = filters.ParseFlag(value, o.filter)
|
||||
return err
|
||||
}
|
||||
|
||||
// Type returns the option type
|
||||
func (o *FilterOpt) Type() string {
|
||||
return "filter"
|
||||
}
|
||||
|
||||
// Value returns the value of this option
|
||||
func (o *FilterOpt) Value() filters.Args {
|
||||
return o.filter
|
||||
}
|
||||
|
||||
// NanoCPUs is a type for fixed point fractional number.
|
||||
type NanoCPUs int64
|
||||
|
||||
// String returns the string format of the number
|
||||
func (c *NanoCPUs) String() string {
|
||||
if *c == 0 {
|
||||
return ""
|
||||
}
|
||||
return big.NewRat(c.Value(), 1e9).FloatString(3)
|
||||
}
|
||||
|
||||
// Set sets the value of the NanoCPU by passing a string
|
||||
func (c *NanoCPUs) Set(value string) error {
|
||||
cpus, err := ParseCPUs(value)
|
||||
*c = NanoCPUs(cpus)
|
||||
return err
|
||||
}
|
||||
|
||||
// Type returns the type
|
||||
func (c *NanoCPUs) Type() string {
|
||||
return "decimal"
|
||||
}
|
||||
|
||||
// Value returns the value in int64
|
||||
func (c *NanoCPUs) Value() int64 {
|
||||
return int64(*c)
|
||||
}
|
||||
|
||||
// ParseCPUs takes a string ratio and returns an integer value of nano cpus
|
||||
func ParseCPUs(value string) (int64, error) {
|
||||
cpu, ok := new(big.Rat).SetString(value)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("failed to parse %v as a rational number", value)
|
||||
}
|
||||
nano := cpu.Mul(cpu, big.NewRat(1e9, 1))
|
||||
if !nano.IsInt() {
|
||||
return 0, fmt.Errorf("value is too precise")
|
||||
}
|
||||
return nano.Num().Int64(), nil
|
||||
}
|
||||
|
||||
// ParseLink parses and validates the specified string as a link format (name:alias)
|
||||
func ParseLink(val string) (string, string, error) {
|
||||
if val == "" {
|
||||
return "", "", fmt.Errorf("empty string specified for links")
|
||||
}
|
||||
arr := strings.Split(val, ":")
|
||||
if len(arr) > 2 {
|
||||
return "", "", fmt.Errorf("bad format for links: %s", val)
|
||||
}
|
||||
if len(arr) == 1 {
|
||||
return val, val, nil
|
||||
}
|
||||
// This is kept because we can actually get a HostConfig with links
|
||||
// from an already created container and the format is not `foo:bar`
|
||||
// but `/foo:/c1/bar`
|
||||
if strings.HasPrefix(arr[0], "/") {
|
||||
_, alias := path.Split(arr[1])
|
||||
return arr[0][1:], alias, nil
|
||||
}
|
||||
return arr[0], arr[1], nil
|
||||
}
|
||||
|
||||
// ValidateLink validates that the specified string has a valid link format (containerName:alias).
|
||||
func ValidateLink(val string) (string, error) {
|
||||
_, _, err := ParseLink(val)
|
||||
return val, err
|
||||
}
|
||||
|
||||
// MemBytes is a type for human readable memory bytes (like 128M, 2g, etc)
|
||||
type MemBytes int64
|
||||
|
||||
// String returns the string format of the human readable memory bytes
|
||||
func (m *MemBytes) String() string {
|
||||
// NOTE: In spf13/pflag/flag.go, "0" is considered as "zero value" while "0 B" is not.
|
||||
// We return "0" in case value is 0 here so that the default value is hidden.
|
||||
// (Sometimes "default 0 B" is actually misleading)
|
||||
if m.Value() != 0 {
|
||||
return units.BytesSize(float64(m.Value()))
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
// Set sets the value of the MemBytes by passing a string
|
||||
func (m *MemBytes) Set(value string) error {
|
||||
val, err := units.RAMInBytes(value)
|
||||
*m = MemBytes(val)
|
||||
return err
|
||||
}
|
||||
|
||||
// Type returns the type
|
||||
func (m *MemBytes) Type() string {
|
||||
return "bytes"
|
||||
}
|
||||
|
||||
// Value returns the value in int64
|
||||
func (m *MemBytes) Value() int64 {
|
||||
return int64(*m)
|
||||
}
|
||||
|
||||
// UnmarshalJSON is the customized unmarshaler for MemBytes
|
||||
func (m *MemBytes) UnmarshalJSON(s []byte) error {
|
||||
if len(s) <= 2 || s[0] != '"' || s[len(s)-1] != '"' {
|
||||
return fmt.Errorf("invalid size: %q", s)
|
||||
}
|
||||
val, err := units.RAMInBytes(string(s[1 : len(s)-1]))
|
||||
*m = MemBytes(val)
|
||||
return err
|
||||
}
|
||||
|
||||
// MemSwapBytes is a type for human readable memory bytes (like 128M, 2g, etc).
|
||||
// It differs from MemBytes in that -1 is valid and the default.
|
||||
type MemSwapBytes int64
|
||||
|
||||
// Set sets the value of the MemSwapBytes by passing a string
|
||||
func (m *MemSwapBytes) Set(value string) error {
|
||||
if value == "-1" {
|
||||
*m = MemSwapBytes(-1)
|
||||
return nil
|
||||
}
|
||||
val, err := units.RAMInBytes(value)
|
||||
*m = MemSwapBytes(val)
|
||||
return err
|
||||
}
|
||||
|
||||
// Type returns the type
|
||||
func (m *MemSwapBytes) Type() string {
|
||||
return "bytes"
|
||||
}
|
||||
|
||||
// Value returns the value in int64
|
||||
func (m *MemSwapBytes) Value() int64 {
|
||||
return int64(*m)
|
||||
}
|
||||
|
||||
func (m *MemSwapBytes) String() string {
|
||||
b := MemBytes(*m)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// UnmarshalJSON is the customized unmarshaler for MemSwapBytes
|
||||
func (m *MemSwapBytes) UnmarshalJSON(s []byte) error {
|
||||
b := MemBytes(*m)
|
||||
return b.UnmarshalJSON(s)
|
||||
}
|
6
vendor/github.com/docker/docker/opts/opts_unix.go
generated
vendored
Normal file
6
vendor/github.com/docker/docker/opts/opts_unix.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
// +build !windows
|
||||
|
||||
package opts
|
||||
|
||||
// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. dockerd -H tcp://:8080
|
||||
const DefaultHTTPHost = "localhost"
|
56
vendor/github.com/docker/docker/opts/opts_windows.go
generated
vendored
Normal file
56
vendor/github.com/docker/docker/opts/opts_windows.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
package opts
|
||||
|
||||
// TODO Windows. Identify bug in GOLang 1.5.1+ and/or Windows Server 2016 TP5.
|
||||
// @jhowardmsft, @swernli.
|
||||
//
|
||||
// On Windows, this mitigates a problem with the default options of running
|
||||
// a docker client against a local docker daemon on TP5.
|
||||
//
|
||||
// What was found that if the default host is "localhost", even if the client
|
||||
// (and daemon as this is local) is not physically on a network, and the DNS
|
||||
// cache is flushed (ipconfig /flushdns), then the client will pause for
|
||||
// exactly one second when connecting to the daemon for calls. For example
|
||||
// using docker run windowsservercore cmd, the CLI will send a create followed
|
||||
// by an attach. You see the delay between the attach finishing and the attach
|
||||
// being seen by the daemon.
|
||||
//
|
||||
// Here's some daemon debug logs with additional debug spew put in. The
|
||||
// AfterWriteJSON log is the very last thing the daemon does as part of the
|
||||
// create call. The POST /attach is the second CLI call. Notice the second
|
||||
// time gap.
|
||||
//
|
||||
// time="2015-11-06T13:38:37.259627400-08:00" level=debug msg="After createRootfs"
|
||||
// time="2015-11-06T13:38:37.263626300-08:00" level=debug msg="After setHostConfig"
|
||||
// time="2015-11-06T13:38:37.267631200-08:00" level=debug msg="before createContainerPl...."
|
||||
// time="2015-11-06T13:38:37.271629500-08:00" level=debug msg=ToDiskLocking....
|
||||
// time="2015-11-06T13:38:37.275643200-08:00" level=debug msg="loggin event...."
|
||||
// time="2015-11-06T13:38:37.277627600-08:00" level=debug msg="logged event...."
|
||||
// time="2015-11-06T13:38:37.279631800-08:00" level=debug msg="In defer func"
|
||||
// time="2015-11-06T13:38:37.282628100-08:00" level=debug msg="After daemon.create"
|
||||
// time="2015-11-06T13:38:37.286651700-08:00" level=debug msg="return 2"
|
||||
// time="2015-11-06T13:38:37.289629500-08:00" level=debug msg="Returned from daemon.ContainerCreate"
|
||||
// time="2015-11-06T13:38:37.311629100-08:00" level=debug msg="After WriteJSON"
|
||||
// ... 1 second gap here....
|
||||
// time="2015-11-06T13:38:38.317866200-08:00" level=debug msg="Calling POST /v1.22/containers/984758282b842f779e805664b2c95d563adc9a979c8a3973e68c807843ee4757/attach"
|
||||
// time="2015-11-06T13:38:38.326882500-08:00" level=info msg="POST /v1.22/containers/984758282b842f779e805664b2c95d563adc9a979c8a3973e68c807843ee4757/attach?stderr=1&stdin=1&stdout=1&stream=1"
|
||||
//
|
||||
// We suspect this is either a bug introduced in GOLang 1.5.1, or that a change
|
||||
// in GOLang 1.5.1 (from 1.4.3) is exposing a bug in Windows. In theory,
|
||||
// the Windows networking stack is supposed to resolve "localhost" internally,
|
||||
// without hitting DNS, or even reading the hosts file (which is why localhost
|
||||
// is commented out in the hosts file on Windows).
|
||||
//
|
||||
// We have validated that working around this using the actual IPv4 localhost
|
||||
// address does not cause the delay.
|
||||
//
|
||||
// This does not occur with the docker client built with 1.4.3 on the same
|
||||
// Windows build, regardless of whether the daemon is built using 1.5.1
|
||||
// or 1.4.3. It does not occur on Linux. We also verified we see the same thing
|
||||
// on a cross-compiled Windows binary (from Linux).
|
||||
//
|
||||
// Final note: This is a mitigation, not a 'real' fix. It is still susceptible
|
||||
// to the delay if a user were to do 'docker run -H=tcp://localhost:2375...'
|
||||
// explicitly.
|
||||
|
||||
// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. dockerd -H tcp://:8080
|
||||
const DefaultHTTPHost = "127.0.0.1"
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user