add better generate

Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-03-20 01:33:56 -04:00
parent 3fc6abf56b
commit cdd93563f5
5655 changed files with 1187011 additions and 392 deletions

View file

@ -0,0 +1,69 @@
package registry
import (
"fmt"
"net/http"
"net/url"
"regexp"
"strings"
)
var (
bearerRegex = regexp.MustCompile(
`^\s*Bearer\s+(.*)$`)
basicRegex = regexp.MustCompile(`^\s*Basic\s+.*$`)
)
func parseAuthHeader(header http.Header) (*authService, error) {
ch, err := parseChallenge(header.Get("www-authenticate"))
if err != nil {
return nil, err
}
return ch, nil
}
func parseChallenge(challengeHeader string) (*authService, error) {
if basicRegex.MatchString(challengeHeader) {
return nil, nil
}
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]), ",")
var realm, service string
var scope []string
for _, s := range parts {
p := strings.SplitN(s, "=", 2)
if len(p) != 2 {
return nil, fmt.Errorf("malformed auth challenge header: '%s'", challengeHeader)
}
key := p[0]
value := strings.TrimSuffix(strings.TrimPrefix(p[1], `"`), `"`)
switch key {
case "realm":
realm = value
case "service":
service = value
case "scope":
scope = strings.Fields(value)
default:
return nil, fmt.Errorf("unknown field in challege header %s: %v", key, challengeHeader)
}
}
parsedRealm, err := url.Parse(realm)
if err != nil {
return nil, err
}
a := &authService{
Realm: parsedRealm,
Service: service,
Scope: scope,
}
return a, nil
}

View file

@ -0,0 +1,55 @@
package registry
import (
"reflect"
"strings"
"testing"
)
type authServiceMock struct {
service string
realm string
scope []string
}
type challengeTestCase struct {
header string
errorString string
value authServiceMock
}
func (asm authServiceMock) equalTo(v *authService) bool {
if asm.service != v.Service {
return false
}
if reflect.DeepEqual(asm.scope, v.Scope) {
return false
}
if asm.realm != v.Realm.String() {
return false
}
return true
}
func TestParseChallenge(t *testing.T) {
challengeHeaderCases := []challengeTestCase{
{
header: `Bearer realm="https://foobar.com/api/v1/token",service=foobar.com,scope=""`,
value: authServiceMock{
service: "foobar.com",
realm: "https://foobar.com/api/v1/token",
},
},
}
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 !tc.value.equalTo(val) {
t.Fatalf("got %v, expected %v", val, tc.value)
}
}
}

View file

@ -0,0 +1,25 @@
package registry
import (
"net/http"
"strings"
)
// BasicTransport defines the data structure for authentication via basic auth.
type BasicTransport struct {
Transport http.RoundTripper
URL string
Username string
Password string
}
// 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 t.Username != "" || t.Password != "" {
req.SetBasicAuth(t.Username, t.Password)
}
}
resp, err := t.Transport.RoundTrip(req)
return resp, err
}

39
vendor/github.com/genuinetools/reg/registry/catalog.go generated vendored Normal file
View file

@ -0,0 +1,39 @@
package registry
import (
"net/url"
"github.com/peterhellberg/link"
)
type catalogResponse struct {
Repositories []string `json:"repositories"`
}
// Catalog returns the repositories in a registry.
func (r *Registry) Catalog(u string) ([]string, error) {
if u == "" {
u = "/v2/_catalog"
}
uri := r.url(u)
r.Logf("registry.catalog url=%s", uri)
var response catalogResponse
h, err := r.getJSON(uri, &response, false)
if err != nil {
return nil, err
}
for _, l := range link.ParseHeader(h) {
if l.Rel == "next" {
unescaped, _ := url.QueryUnescape(l.URI)
repos, err := r.Catalog(unescaped)
if err != nil {
return nil, err
}
response.Repositories = append(response.Repositories, repos...)
}
}
return response.Repositories, nil
}

47
vendor/github.com/genuinetools/reg/registry/delete.go generated vendored Normal file
View file

@ -0,0 +1,47 @@
package registry
import (
"fmt"
"net/http"
"github.com/docker/distribution/manifest/schema2"
ocd "github.com/opencontainers/go-digest"
)
// Delete removes a repository digest or reference 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.
url := r.url("/v2/%s/manifests/%s", repository, digest)
r.Logf("registry.manifests.delete url=%s repository=%s digest=%s",
url, repository, digest)
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return err
}
req.Header.Set("Accept", schema2.MediaTypeManifest)
resp, err := r.Client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusNotFound {
return nil
}
return fmt.Errorf("Got status code: %d", resp.StatusCode)
}

34
vendor/github.com/genuinetools/reg/registry/digest.go generated vendored Normal file
View file

@ -0,0 +1,34 @@
package registry
import (
"fmt"
"net/http"
"github.com/docker/distribution/manifest/schema2"
)
// 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)
r.Logf("registry.manifests.get url=%s repository=%s ref=%s",
url, repository, ref)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("Accept", schema2.MediaTypeManifest)
resp, err := r.Client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotFound {
return "", fmt.Errorf("Got status code: %d", resp.StatusCode)
}
digest := resp.Header.Get("Docker-Content-Digest")
return digest, nil
}

View file

@ -0,0 +1,46 @@
package registry
import (
"fmt"
"io/ioutil"
"net/http"
)
type httpStatusError struct {
Response *http.Response
Body []byte // Copied from `Response.Body` to avoid problems with unclosed bodies later. Nobody calls `err.Response.Body.Close()`, ever.
}
func (err *httpStatusError) Error() string {
return fmt.Sprintf("http: non-successful response (status=%v body=%q)", err.Response.StatusCode, err.Body)
}
var _ error = &httpStatusError{}
// ErrorTransport defines the data structure for returning errors from the round tripper.
type ErrorTransport struct {
Transport http.RoundTripper
}
// RoundTrip defines the round tripper for the error transport.
func (t *ErrorTransport) RoundTrip(request *http.Request) (*http.Response, error) {
resp, err := t.Transport.RoundTrip(request)
if err != nil {
return resp, err
}
if resp.StatusCode >= 500 || resp.StatusCode == http.StatusUnauthorized {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("http: failed to read response body (status=%v, err=%q)", resp.StatusCode, err)
}
return nil, &httpStatusError{
Response: resp,
Body: body,
}
}
return resp, err
}

89
vendor/github.com/genuinetools/reg/registry/layer.go generated vendored Normal file
View file

@ -0,0 +1,89 @@
package registry
import (
"io"
"net/http"
"net/url"
"github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest"
)
// DownloadLayer downloads a specific layer by digest for a repository.
func (r *Registry) DownloadLayer(repository string, digest digest.Digest) (io.ReadCloser, error) {
url := r.url("/v2/%s/blobs/%s", repository, digest)
r.Logf("registry.layer.download url=%s repository=%s digest=%s", url, repository, digest)
resp, err := r.Client.Get(url)
if err != nil {
return nil, err
}
return resp.Body, nil
}
// 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)
if err != nil {
return err
}
q := uploadURL.Query()
q.Set("digest", digest.String())
uploadURL.RawQuery = q.Encode()
r.Logf("registry.layer.upload url=%s repository=%s digest=%s", uploadURL, repository, digest)
upload, err := http.NewRequest("PUT", uploadURL.String(), content)
if err != nil {
return err
}
upload.Header.Set("Content-Type", "application/octet-stream")
_, err = r.Client.Do(upload)
return err
}
// HasLayer returns if the registry contains the specific digest for a repository.
func (r *Registry) HasLayer(repository string, digest digest.Digest) (bool, error) {
checkURL := r.url("/v2/%s/blobs/%s", repository, digest)
r.Logf("registry.layer.check url=%s repository=%s digest=%s", checkURL, repository, digest)
resp, err := r.Client.Head(checkURL)
if err == nil {
defer resp.Body.Close()
return resp.StatusCode == http.StatusOK, nil
}
urlErr, ok := err.(*url.Error)
if !ok {
return false, err
}
httpErr, ok := urlErr.Err.(*httpStatusError)
if !ok {
return false, err
}
if httpErr.Response.StatusCode == http.StatusNotFound {
return false, nil
}
return false, err
}
func (r *Registry) initiateUpload(repository string) (*url.URL, 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
}
defer resp.Body.Close()
location := resp.Header.Get("Location")
locationURL, err := url.Parse(location)
if err != nil {
return nil, err
}
return locationURL, nil
}

View file

@ -0,0 +1,86 @@
package registry
import (
"io/ioutil"
"net/http"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
)
// Manifest returns the manifest for a specific repository:tag.
func (r *Registry) Manifest(repository, ref string) (distribution.Manifest, error) {
uri := r.url("/v2/%s/manifests/%s", repository, ref)
r.Logf("registry.manifests uri=%s repository=%s ref=%s", uri, repository, ref)
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, err
}
req.Header.Add("Accept", schema2.MediaTypeManifest)
req.Header.Add("Accept", manifestlist.MediaTypeManifestList)
resp, err := r.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
r.Logf("registry.manifests resp.Status=%s, body=%s", resp.Status, body)
m, _, err := distribution.UnmarshalManifest(resp.Header.Get("Content-Type"), body)
if err != nil {
return nil, err
}
return m, nil
}
// ManifestList gets the registry v2 manifest list.
func (r *Registry) ManifestList(repository, ref string) (manifestlist.ManifestList, error) {
uri := r.url("/v2/%s/manifests/%s", repository, ref)
r.Logf("registry.manifests uri=%s repository=%s ref=%s", uri, repository, ref)
var m manifestlist.ManifestList
if _, err := r.getJSON(uri, &m, true); err != nil {
r.Logf("registry.manifests response=%v", m)
return m, err
}
return m, nil
}
// ManifestV2 gets the registry v2 manifest.
func (r *Registry) ManifestV2(repository, ref string) (schema2.Manifest, error) {
uri := r.url("/v2/%s/manifests/%s", repository, ref)
r.Logf("registry.manifests uri=%s repository=%s ref=%s", uri, repository, ref)
var m schema2.Manifest
if _, err := r.getJSON(uri, &m, true); err != nil {
r.Logf("registry.manifests response=%v", m)
return m, err
}
return m, nil
}
// ManifestV1 gets the registry v1 manifest.
func (r *Registry) ManifestV1(repository, ref string) (schema1.SignedManifest, error) {
uri := r.url("/v2/%s/manifests/%s", repository, ref)
r.Logf("registry.manifests uri=%s repository=%s ref=%s", uri, repository, ref)
var m schema1.SignedManifest
if _, err := r.getJSON(uri, &m, false); err != nil {
r.Logf("registry.manifests response=%v", m)
return m, err
}
return m, nil
}

12
vendor/github.com/genuinetools/reg/registry/ping.go generated vendored Normal file
View file

@ -0,0 +1,12 @@
package registry
// Ping tries to contact a registry URL to make sure it is up and accessible.
func (r *Registry) Ping() error {
url := r.url("/v2/")
r.Logf("registry.ping url=%s", url)
resp, err := r.Client.Get(url)
if resp != nil {
defer resp.Body.Close()
}
return err
}

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

@ -0,0 +1,133 @@
package registry
import (
"crypto/tls"
"encoding/json"
"fmt"
"log"
"net/http"
"regexp"
"strings"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema2"
"github.com/docker/docker/api/types"
)
// Registry defines the client for retriving information from the registry API.
type Registry struct {
URL string
Domain string
Username string
Password string
Client *http.Client
Logf LogfCallback
}
var reProtocol = regexp.MustCompile("^https?://")
// LogfCallback is the callback for formatting logs.
type LogfCallback func(format string, args ...interface{})
// Quiet discards logs silently.
func Quiet(format string, args ...interface{}) {}
// Log passes log messages to the logging package.
func Log(format string, args ...interface{}) {
log.Printf(format, args...)
}
// New creates a new Registry struct with the given URL and credentials.
func New(auth types.AuthConfig, debug bool) (*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,
},
}
return newFromTransport(auth, transport, debug)
}
func newFromTransport(auth types.AuthConfig, transport http.RoundTripper, debug bool) (*Registry, error) {
url := strings.TrimSuffix(auth.ServerAddress, "/")
if !reProtocol.MatchString(url) {
url = "https://" + url
}
tokenTransport := &TokenTransport{
Transport: transport,
Username: auth.Username,
Password: auth.Password,
}
basicAuthTransport := &BasicTransport{
Transport: tokenTransport,
URL: url,
Username: auth.Username,
Password: auth.Password,
}
errorTransport := &ErrorTransport{
Transport: basicAuthTransport,
}
// set the logging
logf := Quiet
if debug {
logf = Log
}
registry := &Registry{
URL: url,
Domain: reProtocol.ReplaceAllString(url, ""),
Client: &http.Client{
Transport: errorTransport,
},
Username: auth.Username,
Password: auth.Password,
Logf: logf,
}
if err := registry.Ping(); err != nil {
return nil, err
}
return registry, nil
}
// url returns a registry URL with the passed arguements concatenated.
func (r *Registry) url(pathTemplate string, args ...interface{}) string {
pathSuffix := fmt.Sprintf(pathTemplate, args...)
url := fmt.Sprintf("%s%s", r.URL, pathSuffix)
return url
}
func (r *Registry) getJSON(url string, response interface{}, addV2Header bool) (http.Header, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
if addV2Header {
req.Header.Add("Accept", schema2.MediaTypeManifest)
req.Header.Add("Accept", manifestlist.MediaTypeManifestList)
}
resp, err := r.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
r.Logf("registry.registry resp.Status=%s", resp.Status)
if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
return nil, err
}
return resp.Header, nil
}

18
vendor/github.com/genuinetools/reg/registry/tags.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
package registry
type tagsResponse struct {
Tags []string `json:"tags"`
}
// Tags returns the tags for a specific repository.
func (r *Registry) Tags(repository string) ([]string, error) {
url := r.url("/v2/%s/tags/list", repository)
r.Logf("registry.tags url=%s repository=%s", url, repository)
var response tagsResponse
if _, err := r.getJSON(url, &response, false); err != nil {
return nil, err
}
return response.Tags, nil
}

View file

@ -0,0 +1,166 @@
package registry
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
)
// TokenTransport defines the data structure for authentication via tokens.
type TokenTransport struct {
Transport http.RoundTripper
Username string
Password string
}
// RoundTrip defines the round tripper for token transport.
func (t *TokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := t.Transport.RoundTrip(req)
if err != nil {
return resp, err
}
authService, err := isTokenDemand(resp)
if err != nil {
return nil, err
}
if authService == nil {
return resp, nil
}
return t.authAndRetry(authService, req)
}
type authToken struct {
Token string `json:"token"`
}
func (t *TokenTransport) authAndRetry(authService *authService, req *http.Request) (*http.Response, error) {
token, authResp, err := t.auth(authService)
if err != nil {
return authResp, err
}
return t.retry(req, token)
}
func (t *TokenTransport) auth(authService *authService) (string, *http.Response, error) {
authReq, err := authService.Request(t.Username, t.Password)
if err != nil {
return "", nil, err
}
c := http.Client{
Transport: t.Transport,
}
resp, err := c.Do(authReq)
if err != nil {
return "", nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", resp, err
}
var authToken authToken
if err := json.NewDecoder(resp.Body).Decode(&authToken); err != nil {
return "", nil, err
}
return authToken.Token, nil, nil
}
func (t *TokenTransport) retry(req *http.Request, token string) (*http.Response, error) {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
return t.Transport.RoundTrip(req)
}
type authService struct {
Realm *url.URL
Service string
Scope []string
}
func (a *authService) Request(username, password string) (*http.Request, error) {
q := a.Realm.Query()
q.Set("service", a.Service)
for _, s := range a.Scope {
q.Set("scope", s)
}
// q.Set("scope", "repository:r.j3ss.co/htop:push,pull")
a.Realm.RawQuery = q.Encode()
req, err := http.NewRequest("GET", a.Realm.String(), nil)
if username != "" || password != "" {
req.SetBasicAuth(username, password)
}
return req, err
}
func isTokenDemand(resp *http.Response) (*authService, error) {
if resp == nil {
return nil, nil
}
if resp.StatusCode != http.StatusUnauthorized {
return nil, nil
}
return parseAuthHeader(resp.Header)
}
// Token returns the required token for the specific resource url.
func (r *Registry) Token(url string) (string, error) {
r.Logf("registry.token url=%s", url)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
resp, err := r.Client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
a, err := isTokenDemand(resp)
if err != nil {
return "", err
}
if a == nil {
r.Logf("registry.token authService=nil")
return "", nil
}
authReq, err := a.Request(r.Username, r.Password)
if err != nil {
return "", err
}
resp, err = r.Client.Do(authReq)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("Getting token failed with StatusCode != StatusOK but %d", resp.StatusCode)
}
var authToken authToken
if err := json.NewDecoder(resp.Body).Decode(&authToken); err != nil {
return "", err
}
if authToken.Token == "" {
return "", errors.New("Auth token cannot be empty")
}
return authToken.Token, nil
}