update vendor

Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-09-25 12:27:46 -04:00
parent 19a32db84d
commit 94d1cfbfbf
No known key found for this signature in database
GPG key ID: 18F3685C0022BFF3
10501 changed files with 2307943 additions and 29279 deletions

View file

@ -1,6 +1,7 @@
package registry
import (
"errors"
"fmt"
"net/http"
"net/url"
@ -12,6 +13,9 @@ var (
bearerRegex = regexp.MustCompile(
`^\s*Bearer\s+(.*)$`)
basicRegex = regexp.MustCompile(`^\s*Basic\s+.*$`)
// ErrBasicAuth indicates that the repository requires basic rather than token authentication.
ErrBasicAuth = errors.New("basic auth required")
)
func parseAuthHeader(header http.Header) (*authService, error) {
@ -25,14 +29,14 @@ func parseAuthHeader(header http.Header) (*authService, error) {
func parseChallenge(challengeHeader string) (*authService, error) {
if basicRegex.MatchString(challengeHeader) {
return nil, nil
return nil, ErrBasicAuth
}
match := bearerRegex.FindAllStringSubmatch(challengeHeader, -1)
if d := len(match); d != 1 {
return nil, fmt.Errorf("malformed auth challenge header: '%s', %d", challengeHeader, d)
}
parts := strings.Split(strings.TrimSpace(match[0][1]), ",")
parts := strings.SplitN(strings.TrimSpace(match[0][1]), ",", 3)
var realm, service string
var scope []string

View file

@ -1,7 +1,6 @@
package registry
import (
"reflect"
"strings"
"testing"
)
@ -22,13 +21,13 @@ func (asm authServiceMock) equalTo(v *authService) bool {
if asm.service != v.Service {
return false
}
if reflect.DeepEqual(asm.scope, v.Scope) {
return false
for i, v := range v.Scope {
if v != asm.scope[i] {
return false
}
}
if asm.realm != v.Realm.String() {
return false
}
return true
return asm.realm == v.Realm.String()
}
func TestParseChallenge(t *testing.T) {
@ -40,8 +39,47 @@ func TestParseChallenge(t *testing.T) {
realm: "https://foobar.com/api/v1/token",
},
},
{
header: `Bearer realm="https://r.j3ss.co/auth",service="Docker registry",scope="repository:chrome:pull"`,
value: authServiceMock{
service: "Docker registry",
realm: "https://r.j3ss.co/auth",
scope: []string{"repository:chrome:pull"},
},
},
{
header: `Basic realm="https://r.j3ss.co/auth",service="Docker registry"`,
errorString: "basic auth required",
},
{
header: `Basic realm="Registry Realm",service="Docker registry"`,
errorString: "basic auth required",
},
}
for _, tc := range challengeHeaderCases {
val, err := parseChallenge(tc.header)
if err != nil && !strings.Contains(err.Error(), tc.errorString) {
t.Fatalf("expected error to contain %v, got %s", tc.errorString, err)
}
if err == nil && !tc.value.equalTo(val) {
t.Fatalf("got %v, expected %v", val, tc.value)
}
}
}
func TestParseChallengePush(t *testing.T) {
challengeHeaderCases := []challengeTestCase{
{
header: `Bearer realm="https://foo.com/v2/token",service="foo.com",scope="repository:pdr/tls:pull,push"`,
value: authServiceMock{
realm: "https://foo.com/v2/token",
service: "foo.com",
scope: []string{"repository:pdr/tls:pull,push"},
},
},
}
for _, tc := range challengeHeaderCases {
val, err := parseChallenge(tc.header)
if err != nil && !strings.Contains(err.Error(), tc.errorString) {

View file

@ -15,7 +15,7 @@ type BasicTransport struct {
// RoundTrip defines the round tripper for basic auth transport.
func (t *BasicTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if strings.HasPrefix(req.URL.String(), t.URL) {
if strings.HasPrefix(req.URL.String(), t.URL) && req.Header.Get("Authorization") == "" {
if t.Username != "" || t.Password != "" {
req.SetBasicAuth(t.Username, t.Password)
}

View file

@ -0,0 +1,24 @@
package registry
import (
"net/http"
)
// CustomTransport defines the data structure for custom http.Request options.
type CustomTransport struct {
Transport http.RoundTripper
Headers map[string]string
}
// RoundTrip defines the round tripper for the error transport.
func (t *CustomTransport) RoundTrip(request *http.Request) (*http.Response, error) {
if len(t.Headers) != 0 {
for header, value := range t.Headers {
request.Header.Add(header, value)
}
}
resp, err := t.Transport.RoundTrip(request)
return resp, err
}

View file

@ -5,24 +5,12 @@ import (
"net/http"
"github.com/docker/distribution/manifest/schema2"
ocd "github.com/opencontainers/go-digest"
digest "github.com/opencontainers/go-digest"
)
// Delete removes a repository digest or reference from the registry.
// Delete removes a repository digest from the registry.
// https://docs.docker.com/registry/spec/api/#deleting-an-image
func (r *Registry) Delete(repository, digest string) error {
// If digest is not valid try resolving it as a reference
if _, err := ocd.Parse(digest); err != nil {
digest, err = r.Digest(repository, digest)
if err != nil {
return err
}
if digest == "" {
return nil
}
}
// Delete the image.
func (r *Registry) Delete(repository string, digest digest.Digest) (err error) {
url := r.url("/v2/%s/manifests/%s", repository, digest)
r.Logf("registry.manifests.delete url=%s repository=%s digest=%s",
url, repository, digest)
@ -32,7 +20,7 @@ func (r *Registry) Delete(repository, digest string) error {
return err
}
req.Header.Set("Accept", schema2.MediaTypeManifest)
req.Header.Add("Accept", fmt.Sprintf("%s;q=0.9", schema2.MediaTypeManifest))
resp, err := r.Client.Do(req)
if err != nil {
return err

View file

@ -5,20 +5,26 @@ import (
"net/http"
"github.com/docker/distribution/manifest/schema2"
digest "github.com/opencontainers/go-digest"
)
// Digest returns the digest for a repository and reference.
func (r *Registry) Digest(repository, ref string) (string, error) {
url := r.url("/v2/%s/manifests/%s", repository, ref)
// Digest returns the digest for an image.
func (r *Registry) Digest(image Image) (digest.Digest, error) {
if len(image.Digest) > 1 {
// return early if we already have an image digest.
return image.Digest, nil
}
url := r.url("/v2/%s/manifests/%s", image.Path, image.Tag)
r.Logf("registry.manifests.get url=%s repository=%s ref=%s",
url, repository, ref)
url, image.Path, image.Tag)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("Accept", schema2.MediaTypeManifest)
req.Header.Add("Accept", schema2.MediaTypeManifest)
resp, err := r.Client.Do(req)
if err != nil {
return "", err
@ -29,6 +35,5 @@ func (r *Registry) Digest(repository, ref string) (string, error) {
return "", fmt.Errorf("Got status code: %d", resp.StatusCode)
}
digest := resp.Header.Get("Docker-Content-Digest")
return digest, nil
return digest.Parse(resp.Header.Get("Docker-Content-Digest"))
}

View file

@ -0,0 +1,49 @@
package registry
import (
"testing"
"github.com/genuinetools/reg/repoutils"
)
func TestDigestFromDockerHub(t *testing.T) {
auth, err := repoutils.GetAuthConfig("", "", "docker.io")
if err != nil {
t.Fatalf("Could not get auth config: %s", err)
}
r, err := New(auth, Opt{})
if err != nil {
t.Fatalf("Could not create registry instance: %s", err)
}
d, err := r.Digest(Image{Domain: "docker.io", Path: "library/alpine", Tag: "latest"})
if err != nil {
t.Fatalf("Could not get digest: %s", err)
}
if d == "" {
t.Error("Empty digest received")
}
}
func TestDigestFromGCR(t *testing.T) {
auth, err := repoutils.GetAuthConfig("", "", "gcr.io")
if err != nil {
t.Fatalf("Could not get auth config: %s", err)
}
r, err := New(auth, Opt{})
if err != nil {
t.Fatalf("Could not create registry instance: %s", err)
}
d, err := r.Digest(Image{Domain: "gcr.io", Path: "google_containers/hyperkube", Tag: "v1.9.9"})
if err != nil {
t.Fatalf("Could not get digest: %s", err)
}
if d == "" {
t.Error("Empty digest received")
}
}

67
vendor/github.com/genuinetools/reg/registry/image.go generated vendored Normal file
View file

@ -0,0 +1,67 @@
package registry
import (
"fmt"
"github.com/docker/distribution/reference"
digest "github.com/opencontainers/go-digest"
)
// Image holds information about an image.
type Image struct {
Domain string
Path string
Tag string
Digest digest.Digest
named reference.Named
}
// String returns the string representation of an image.
func (i Image) String() string {
return i.named.String()
}
// Reference returns either the digest if it is non-empty or the tag for the image.
func (i Image) Reference() string {
if len(i.Digest.String()) > 1 {
return i.Digest.String()
}
return i.Tag
}
// WithDigest sets the digest for an image.
func (i *Image) WithDigest(digest digest.Digest) (err error) {
i.Digest = digest
i.named, err = reference.WithDigest(i.named, digest)
return err
}
// ParseImage returns an Image struct with all the values filled in for a given image.
func ParseImage(image string) (Image, error) {
// Parse the image name and tag.
named, err := reference.ParseNormalizedNamed(image)
if err != nil {
return Image{}, fmt.Errorf("parsing image %q failed: %v", image, err)
}
// Add the latest lag if they did not provide one.
named = reference.TagNameOnly(named)
i := Image{
named: named,
Domain: reference.Domain(named),
Path: reference.Path(named),
}
// Add the tag if there was one.
if tagged, ok := named.(reference.Tagged); ok {
i.Tag = tagged.Tag()
}
// Add the digest if there was one.
if canonical, ok := named.(reference.Canonical); ok {
i.Digest = canonical.Digest()
}
return i, nil
}

View file

@ -5,6 +5,7 @@ import (
"net/http"
"net/url"
"fmt"
"github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest"
)
@ -24,7 +25,7 @@ func (r *Registry) DownloadLayer(repository string, digest digest.Digest) (io.Re
// UploadLayer uploads a specific layer by digest for a repository.
func (r *Registry) UploadLayer(repository string, digest reference.Reference, content io.Reader) error {
uploadURL, err := r.initiateUpload(repository)
uploadURL, token, err := r.initiateUpload(repository)
if err != nil {
return err
}
@ -39,6 +40,7 @@ func (r *Registry) UploadLayer(repository string, digest reference.Reference, co
return err
}
upload.Header.Set("Content-Type", "application/octet-stream")
upload.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
_, err = r.Client.Do(upload)
return err
@ -70,20 +72,21 @@ func (r *Registry) HasLayer(repository string, digest digest.Digest) (bool, erro
return false, err
}
func (r *Registry) initiateUpload(repository string) (*url.URL, error) {
func (r *Registry) initiateUpload(repository string) (*url.URL, string, error) {
initiateURL := r.url("/v2/%s/blobs/uploads/", repository)
r.Logf("registry.layer.initiate-upload url=%s repository=%s", initiateURL, repository)
resp, err := r.Client.Post(initiateURL, "application/octet-stream", nil)
if err != nil {
return nil, err
return nil, "", err
}
token := resp.Header.Get("Request-Token")
defer resp.Body.Close()
location := resp.Header.Get("Location")
locationURL, err := url.Parse(location)
if err != nil {
return nil, err
return nil, token, err
}
return locationURL, nil
return locationURL, token, nil
}

View file

@ -1,6 +1,9 @@
package registry
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
@ -20,8 +23,7 @@ func (r *Registry) Manifest(repository, ref string) (distribution.Manifest, erro
return nil, err
}
req.Header.Add("Accept", schema2.MediaTypeManifest)
req.Header.Add("Accept", manifestlist.MediaTypeManifestList)
req.Header.Add("Accept", fmt.Sprintf("%s;q=0.9", schema2.MediaTypeManifest))
resp, err := r.Client.Do(req)
if err != nil {
@ -84,3 +86,26 @@ func (r *Registry) ManifestV1(repository, ref string) (schema1.SignedManifest, e
return m, nil
}
// PutManifest calls a PUT for the specific manifest for an image.
func (r *Registry) PutManifest(repository, ref string, manifest distribution.Manifest) error {
url := r.url("/v2/%s/manifests/%s", repository, ref)
r.Logf("registry.manifest.put url=%s repository=%s reference=%s", url, repository, ref)
b, err := json.Marshal(manifest)
if err != nil {
return err
}
req, err := http.NewRequest("PUT", url, bytes.NewBuffer(b))
if err != nil {
return err
}
req.Header.Set("Content-Type", schema2.MediaTypeManifest)
resp, err := r.Client.Do(req)
if resp != nil {
defer resp.Body.Close()
}
return err
}

View file

@ -8,6 +8,7 @@ import (
"net/http"
"regexp"
"strings"
"time"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema2"
@ -22,6 +23,7 @@ type Registry struct {
Password string
Client *http.Client
Logf LogfCallback
Opt Opt
}
var reProtocol = regexp.MustCompile("^https?://")
@ -37,26 +39,31 @@ func Log(format string, args ...interface{}) {
log.Printf(format, args...)
}
// Opt holds the options for a new registry.
type Opt struct {
Insecure bool
Debug bool
SkipPing bool
Timeout time.Duration
Headers map[string]string
}
// New creates a new Registry struct with the given URL and credentials.
func New(auth types.AuthConfig, debug bool) (*Registry, error) {
func New(auth types.AuthConfig, opt Opt) (*Registry, error) {
transport := http.DefaultTransport
return newFromTransport(auth, transport, debug)
}
// NewInsecure creates a new Registry struct with the given URL and credentials,
// using a http.Transport that will not verify an SSL certificate.
func NewInsecure(auth types.AuthConfig, debug bool) (*Registry, error) {
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
if opt.Insecure {
transport = &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
}
return newFromTransport(auth, transport, debug)
return newFromTransport(auth, transport, opt)
}
func newFromTransport(auth types.AuthConfig, transport http.RoundTripper, debug bool) (*Registry, error) {
func newFromTransport(auth types.AuthConfig, transport http.RoundTripper, opt Opt) (*Registry, error) {
url := strings.TrimSuffix(auth.ServerAddress, "/")
if !reProtocol.MatchString(url) {
@ -77,10 +84,14 @@ func newFromTransport(auth types.AuthConfig, transport http.RoundTripper, debug
errorTransport := &ErrorTransport{
Transport: basicAuthTransport,
}
customTransport := &CustomTransport{
Transport: errorTransport,
Headers: opt.Headers,
}
// set the logging
logf := Quiet
if debug {
if opt.Debug {
logf = Log
}
@ -88,15 +99,19 @@ func newFromTransport(auth types.AuthConfig, transport http.RoundTripper, debug
URL: url,
Domain: reProtocol.ReplaceAllString(url, ""),
Client: &http.Client{
Transport: errorTransport,
Timeout: opt.Timeout,
Transport: customTransport,
},
Username: auth.Username,
Password: auth.Password,
Logf: logf,
Opt: opt,
}
if err := registry.Ping(); err != nil {
return nil, err
if !opt.SkipPing {
if err := registry.Ping(); err != nil {
return nil, err
}
}
return registry, nil
@ -115,8 +130,7 @@ func (r *Registry) getJSON(url string, response interface{}, addV2Header bool) (
return nil, err
}
if addV2Header {
req.Header.Add("Accept", schema2.MediaTypeManifest)
req.Header.Add("Accept", manifestlist.MediaTypeManifestList)
req.Header.Add("Accept", fmt.Sprintf("%s,%s;q=0.9", schema2.MediaTypeManifest, manifestlist.MediaTypeManifestList))
}
resp, err := r.Client.Do(req)
if err != nil {

View file

@ -1,6 +1,8 @@
package registry
import (
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
@ -44,7 +46,11 @@ func (t *TokenTransport) authAndRetry(authService *authService, req *http.Reques
return authResp, err
}
return t.retry(req, token)
response, err := t.retry(req, token)
if response != nil {
response.Header.Set("request-token", token)
}
return response, err
}
func (t *TokenTransport) auth(authService *authService) (string, *http.Response, error) {
@ -114,7 +120,8 @@ func isTokenDemand(resp *http.Response) (*authService, error) {
return parseAuthHeader(resp.Header)
}
// Token returns the required token for the specific resource url.
// Token returns the required token for the specific resource url. If the registry requires basic authentication, this
// function returns ErrBasicAuth.
func (r *Registry) Token(url string) (string, error) {
r.Logf("registry.token url=%s", url)
@ -123,7 +130,19 @@ func (r *Registry) Token(url string) (string, error) {
return "", err
}
resp, err := r.Client.Do(req)
client := http.DefaultClient
if r.Opt.Insecure {
client = &http.Client{
Timeout: r.Opt.Timeout,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
@ -143,7 +162,7 @@ func (r *Registry) Token(url string) (string, error) {
if err != nil {
return "", err
}
resp, err = r.Client.Do(authReq)
resp, err = http.DefaultClient.Do(authReq)
if err != nil {
return "", err
}
@ -164,3 +183,26 @@ func (r *Registry) Token(url string) (string, error) {
return authToken.Token, nil
}
// Headers returns the authorization headers for a specific uri.
func (r *Registry) Headers(uri string) (map[string]string, error) {
// Get the token.
token, err := r.Token(uri)
if err != nil {
if err == ErrBasicAuth {
// If we couldn't get a token because the server requires basic auth, just return basic auth headers.
return map[string]string{
"Authorization": fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(r.Username+":"+r.Password))),
}, nil
}
}
if len(token) < 1 {
r.Logf("got empty token for %s", uri)
return map[string]string{}, nil
}
return map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", token),
}, nil
}

View file

@ -0,0 +1,38 @@
package registry
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/docker/docker/api/types"
)
func TestErrBasicAuth(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
w.Header().Set("www-authenticate", `Basic realm="Registry Realm",service="Docker registry"`)
w.WriteHeader(http.StatusUnauthorized)
} else {
w.WriteHeader(http.StatusOK)
}
}))
defer ts.Close()
authConfig := types.AuthConfig{
Username: "j3ss",
Password: "ss3j",
ServerAddress: ts.URL,
}
r, err := New(authConfig, Opt{Insecure: true, Debug: true})
if err != nil {
t.Fatalf("expected no error creating client, got %v", err)
}
token, err := r.Token(ts.URL)
if err != ErrBasicAuth {
t.Fatalf("expected ErrBasicAuth getting token, got %v", err)
}
if token != "" {
t.Fatalf("expected empty token, got %v", err)
}
}