mirror of
https://github.com/containers/skopeo.git
synced 2025-04-28 03:10:18 +00:00
v1 fallback and fixes
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
parent
2431a49897
commit
b59409255a
92
inspect.go
92
inspect.go
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@ -9,6 +10,8 @@ import (
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/opts"
|
||||
versionPkg "github.com/docker/docker/pkg/version"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/docker/docker/registry"
|
||||
types "github.com/docker/engine-api/types"
|
||||
@ -59,16 +62,16 @@ func inspect(c *cli.Context) (*imageInspect, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authConfig, err := getAuthConfig(c, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
ii *imageInspect
|
||||
)
|
||||
|
||||
// TODO(runcom): remove docker.io case cause unqualified images
|
||||
// can be from additional registry below
|
||||
// tweak the ParseNamed above so I can know if its unqualified
|
||||
if ref.Hostname() != "" {
|
||||
ii, err = getData(ref, authConfig)
|
||||
if err != nil {
|
||||
@ -77,7 +80,6 @@ func inspect(c *cli.Context) (*imageInspect, error) {
|
||||
return ii, nil
|
||||
}
|
||||
|
||||
_ = authConfig
|
||||
// TODO(runcom): ...
|
||||
// both authConfig and unqualified images
|
||||
|
||||
@ -93,16 +95,24 @@ func getData(ref reference.Named, authConfig types.AuthConfig) (*imageInspect, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
registryService := registry.NewService(nil)
|
||||
|
||||
// FATA[0000] open /etc/docker/certs.d/myreg.com:4000: permission denied
|
||||
// need to be run as root, really? :(
|
||||
// just pass tlsconfig via cli?!?!?!
|
||||
//
|
||||
// this happens only with private registry, docker.io works out of the box
|
||||
// EDIT: this happens with v1 registries?! no
|
||||
//
|
||||
// TODO(runcom): do not assume docker is installed on the system!
|
||||
// just fallback as for getAuthConfig
|
||||
options := ®istry.Options{}
|
||||
options.InsecureRegistries = opts.NewListOpts(nil)
|
||||
options.Mirrors = opts.NewListOpts(nil)
|
||||
options.InsecureRegistries.Set("0.0.0.0/0")
|
||||
registryService := registry.NewService(options)
|
||||
for _, ic := range registryService.Config.IndexConfigs {
|
||||
ic.Secure = false
|
||||
}
|
||||
|
||||
endpoints, err := registryService.LookupPullEndpoints(repoInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -117,6 +127,21 @@ func getData(ref reference.Named, authConfig types.AuthConfig) (*imageInspect, e
|
||||
)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
// TODO(runcom):
|
||||
//
|
||||
// always try to login first so the registry is pinged and we can return timeout
|
||||
// instead of trying every v version (like push,pull and others do in docker)
|
||||
//
|
||||
//./skopeo --debug --username runcom --password 20121990cia0@! myreg.com:4000/rhel7
|
||||
//DEBU[0000] hostDir: /etc/docker/certs.d/https:/index.docker.io/v1
|
||||
//FATA[0000] open /etc/docker/certs.d/https:/index.docker.io/v1: permission denied
|
||||
//
|
||||
//status, err := registryService.Auth(&authConfig)
|
||||
//if err != nil {
|
||||
//return nil, err
|
||||
//}
|
||||
//logrus.Debug(status)
|
||||
|
||||
if confirmedV2 && endpoint.Version == registry.APIVersion1 {
|
||||
logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
|
||||
continue
|
||||
@ -178,12 +203,13 @@ func newManifestFetcher(endpoint registry.APIEndpoint, repoInfo *registry.Reposi
|
||||
service: registryService,
|
||||
repoInfo: repoInfo,
|
||||
}, nil
|
||||
//case registry.APIVersion1:
|
||||
//return &v1ManifestFetcher{
|
||||
//endpoint: endpoint,
|
||||
////config: config,
|
||||
//repoInfo: repoInfo,
|
||||
//}, nil
|
||||
case registry.APIVersion1:
|
||||
return &v1ManifestFetcher{
|
||||
endpoint: endpoint,
|
||||
authConfig: authConfig,
|
||||
service: registryService,
|
||||
repoInfo: repoInfo,
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
|
||||
}
|
||||
@ -257,3 +283,45 @@ func makeImageInspect(repoInfo *registry.RepositoryInfo, img *image.Image, tag s
|
||||
Registry: repoInfo.Index.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func makeRawConfigFromV1Config(imageJSON []byte, rootfs *image.RootFS, history []image.History) (map[string]*json.RawMessage, error) {
|
||||
var dver struct {
|
||||
DockerVersion string `json:"docker_version"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(imageJSON, &dver); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
useFallback := versionPkg.Version(dver.DockerVersion).LessThan("1.8.3")
|
||||
|
||||
if useFallback {
|
||||
var v1Image image.V1Image
|
||||
err := json.Unmarshal(imageJSON, &v1Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageJSON, err = json.Marshal(v1Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var c map[string]*json.RawMessage
|
||||
if err := json.Unmarshal(imageJSON, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c["rootfs"] = rawJSON(rootfs)
|
||||
c["history"] = rawJSON(history)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func rawJSON(value interface{}) *json.RawMessage {
|
||||
jsonval, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return (*json.RawMessage)(&jsonval)
|
||||
}
|
||||
|
170
inspect_v1.go
170
inspect_v1.go
@ -1 +1,171 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/image/v1"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type v1ManifestFetcher struct {
|
||||
endpoint registry.APIEndpoint
|
||||
repoInfo *registry.RepositoryInfo
|
||||
repo distribution.Repository
|
||||
confirmedV2 bool
|
||||
// wrap in a config?
|
||||
authConfig types.AuthConfig
|
||||
service *registry.Service
|
||||
session *registry.Session
|
||||
}
|
||||
|
||||
func (mf *v1ManifestFetcher) Fetch(ctx context.Context, ref reference.Named) (*imageInspect, error) {
|
||||
var (
|
||||
imgInspect *imageInspect
|
||||
)
|
||||
if _, isCanonical := ref.(reference.Canonical); isCanonical {
|
||||
// Allowing fallback, because HTTPS v1 is before HTTP v2
|
||||
return nil, fallbackError{err: registry.ErrNoSupport{errors.New("Cannot pull by digest with v1 registry")}}
|
||||
}
|
||||
tag := ""
|
||||
if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
tlsConfig, err := mf.service.TLSConfig(mf.repoInfo.Index.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
|
||||
tr := transport.NewTransport(
|
||||
registry.NewTransport(tlsConfig),
|
||||
//registry.DockerHeaders(mf.config.MetaHeaders)...,
|
||||
registry.DockerHeaders(nil)...,
|
||||
)
|
||||
client := registry.HTTPClient(tr)
|
||||
//v1Endpoint, err := mf.endpoint.ToV1Endpoint(mf.config.MetaHeaders)
|
||||
v1Endpoint, err := mf.endpoint.ToV1Endpoint(nil)
|
||||
if err != nil {
|
||||
logrus.Debugf("Could not get v1 endpoint: %v", err)
|
||||
return nil, fallbackError{err: err}
|
||||
}
|
||||
mf.session, err = registry.NewSession(client, &mf.authConfig, v1Endpoint)
|
||||
if err != nil {
|
||||
logrus.Debugf("Fallback from error: %s", err)
|
||||
return nil, fallbackError{err: err}
|
||||
}
|
||||
imgInspect, err = mf.fetchWithSession(ctx, tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return imgInspect, nil
|
||||
}
|
||||
|
||||
func (mf *v1ManifestFetcher) fetchWithSession(ctx context.Context, askedTag string) (*imageInspect, error) {
|
||||
repoData, err := mf.session.GetRepositoryData(mf.repoInfo)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "HTTP code: 404") {
|
||||
return nil, fmt.Errorf("Error: image %s not found", mf.repoInfo.RemoteName)
|
||||
}
|
||||
// Unexpected HTTP error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Debugf("Retrieving the tag list from V1 endpoints")
|
||||
tagsList, err := mf.session.GetRemoteTags(repoData.Endpoints, mf.repoInfo)
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to get remote tags: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
if len(tagsList) < 1 {
|
||||
return nil, fmt.Errorf("No tags available for remote repository %s", mf.repoInfo.FullName())
|
||||
}
|
||||
|
||||
for tag, id := range tagsList {
|
||||
repoData.ImgList[id] = ®istry.ImgData{
|
||||
ID: id,
|
||||
Tag: tag,
|
||||
Checksum: "",
|
||||
}
|
||||
}
|
||||
|
||||
// If no tag has been specified, choose `latest` if it exists
|
||||
if askedTag == "" {
|
||||
if _, exists := tagsList[reference.DefaultTag]; exists {
|
||||
askedTag = reference.DefaultTag
|
||||
}
|
||||
}
|
||||
if askedTag == "" {
|
||||
// fallback to any tag in the repository
|
||||
for tag := range tagsList {
|
||||
askedTag = tag
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
id, exists := tagsList[askedTag]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("Tag %s not found in repository %s", askedTag, mf.repoInfo.FullName())
|
||||
}
|
||||
img := repoData.ImgList[id]
|
||||
|
||||
var pulledImg *image.Image
|
||||
for _, ep := range mf.repoInfo.Index.Mirrors {
|
||||
if pulledImg, err = mf.pullImageJSON(img.ID, ep, repoData.Tokens); err != nil {
|
||||
// Don't report errors when pulling from mirrors.
|
||||
logrus.Debugf("Error pulling image json of %s:%s, mirror: %s, %s", mf.repoInfo.FullName(), img.Tag, ep, err)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if pulledImg == nil {
|
||||
for _, ep := range repoData.Endpoints {
|
||||
if pulledImg, err = mf.pullImageJSON(img.ID, ep, repoData.Tokens); err != nil {
|
||||
// It's not ideal that only the last error is returned, it would be better to concatenate the errors.
|
||||
logrus.Infof("Error pulling image json of %s:%s, endpoint: %s, %v", mf.repoInfo.FullName(), img.Tag, ep, err)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, mf.repoInfo.FullName(), err)
|
||||
}
|
||||
if pulledImg == nil {
|
||||
return nil, fmt.Errorf("No such image %s:%s", mf.repoInfo.FullName(), askedTag)
|
||||
}
|
||||
|
||||
return makeImageInspect(mf.repoInfo, pulledImg, askedTag, ""), nil
|
||||
}
|
||||
|
||||
func (mf *v1ManifestFetcher) pullImageJSON(imgID, endpoint string, token []string) (*image.Image, error) {
|
||||
imgJSON, _, err := mf.session.GetRemoteImageJSON(imgID, endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, err := v1.HistoryFromConfig(imgJSON, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configRaw, err := makeRawConfigFromV1Config(imgJSON, image.NewRootFS(), []image.History{h})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config, err := json.Marshal(configRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img, err := image.NewFromJSON(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import (
|
||||
dockerdistribution "github.com/docker/docker/distribution"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/image/v1"
|
||||
versionPkg "github.com/docker/docker/pkg/version"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/engine-api/types"
|
||||
@ -28,8 +27,9 @@ type v2ManifestFetcher struct {
|
||||
repoInfo *registry.RepositoryInfo
|
||||
repo distribution.Repository
|
||||
confirmedV2 bool
|
||||
authConfig types.AuthConfig
|
||||
service *registry.Service
|
||||
// wrap in a config?
|
||||
authConfig types.AuthConfig
|
||||
service *registry.Service
|
||||
}
|
||||
|
||||
func (mf *v2ManifestFetcher) Fetch(ctx context.Context, ref reference.Named) (*imageInspect, error) {
|
||||
@ -45,21 +45,12 @@ func (mf *v2ManifestFetcher) Fetch(ctx context.Context, ref reference.Named) (*i
|
||||
return nil, fallbackError{err: err, confirmedV2: mf.confirmedV2}
|
||||
}
|
||||
|
||||
_, err = mf.service.Auth(&mf.authConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imgInspect, err = mf.fetchWithRepository(ctx, ref)
|
||||
if err != nil {
|
||||
switch t := err.(type) {
|
||||
case errcode.Errors:
|
||||
if len(t) == 1 {
|
||||
err = t[0]
|
||||
}
|
||||
if _, ok := err.(fallbackError); ok {
|
||||
return nil, err
|
||||
}
|
||||
if registry.ContinueOnError(err) {
|
||||
logrus.Debugf("Error trying v2 registry: %v", err)
|
||||
err = fallbackError{err: err, confirmedV2: mf.confirmedV2}
|
||||
}
|
||||
}
|
||||
@ -92,8 +83,14 @@ func (mf *v2ManifestFetcher) fetchWithRepository(ctx context.Context, ref refere
|
||||
} else {
|
||||
tagList, err := mf.repo.Tags(ctx).All(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, allowV1Fallback(err)
|
||||
}
|
||||
|
||||
// The v2 registry knows about this repository, so we will not
|
||||
// allow fallback to the v1 protocol even if we encounter an
|
||||
// error later on.
|
||||
mf.confirmedV2 = true
|
||||
|
||||
for _, t := range tagList {
|
||||
if t == reference.DefaultTag {
|
||||
tag = t
|
||||
@ -113,16 +110,16 @@ func (mf *v2ManifestFetcher) fetchWithRepository(ctx context.Context, ref refere
|
||||
if err != nil {
|
||||
return nil, allowV1Fallback(err)
|
||||
}
|
||||
|
||||
// If manSvc.Get succeeded, we can be confident that the registry on
|
||||
// the other side speaks the v2 protocol.
|
||||
mf.confirmedV2 = true
|
||||
}
|
||||
|
||||
if manifest == nil {
|
||||
return nil, fmt.Errorf("image manifest does not exist for tag or digest %q", tagOrDigest)
|
||||
}
|
||||
|
||||
// If manSvc.Get succeeded, we can be confident that the registry on
|
||||
// the other side speaks the v2 protocol.
|
||||
mf.confirmedV2 = true
|
||||
|
||||
var (
|
||||
image *image.Image
|
||||
manifestDigest digest.Digest
|
||||
@ -202,48 +199,6 @@ func (mf *v2ManifestFetcher) pullSchema1(ctx context.Context, ref reference.Name
|
||||
return img, manifestDigest, nil
|
||||
}
|
||||
|
||||
func makeRawConfigFromV1Config(imageJSON []byte, rootfs *image.RootFS, history []image.History) (map[string]*json.RawMessage, error) {
|
||||
var dver struct {
|
||||
DockerVersion string `json:"docker_version"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(imageJSON, &dver); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
useFallback := versionPkg.Version(dver.DockerVersion).LessThan("1.8.3")
|
||||
|
||||
if useFallback {
|
||||
var v1Image image.V1Image
|
||||
err := json.Unmarshal(imageJSON, &v1Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageJSON, err = json.Marshal(v1Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var c map[string]*json.RawMessage
|
||||
if err := json.Unmarshal(imageJSON, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c["rootfs"] = rawJSON(rootfs)
|
||||
c["history"] = rawJSON(history)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func rawJSON(value interface{}) *json.RawMessage {
|
||||
jsonval, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return (*json.RawMessage)(&jsonval)
|
||||
}
|
||||
|
||||
func verifySchema1Manifest(signedManifest *schema1.SignedManifest, ref reference.Named) (m *schema1.Manifest, err error) {
|
||||
// If pull by digest, then verify the manifest digest. NOTE: It is
|
||||
// important to do this first, before any other content validation. If the
|
||||
@ -431,7 +386,6 @@ func allowV1Fallback(err error) error {
|
||||
return fallbackError{err: err, confirmedV2: false}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user