mirror of
https://github.com/containers/skopeo.git
synced 2025-08-30 21:25:11 +00:00
168 lines
4.9 KiB
Go
168 lines
4.9 KiB
Go
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")}}
|
|
}
|
|
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, ref)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return imgInspect, nil
|
|
}
|
|
|
|
func (mf *v1ManifestFetcher) fetchWithSession(ctx context.Context, ref reference.Named) (*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
|
|
}
|
|
|
|
var tagsList map[string]string
|
|
tagsList, err = mf.session.GetRemoteTags(repoData.Endpoints, mf.repoInfo)
|
|
if err != nil {
|
|
logrus.Errorf("unable to get remote tags: %s", err)
|
|
return nil, err
|
|
}
|
|
|
|
logrus.Debugf("Retrieving the tag list")
|
|
tagged, isTagged := ref.(reference.NamedTagged)
|
|
var tagID, tag string
|
|
if isTagged {
|
|
tag = tagged.Tag()
|
|
tagsList[tagged.Tag()] = tagID
|
|
} else {
|
|
ref, err = reference.WithTag(ref, reference.DefaultTag)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tagged, _ := ref.(reference.NamedTagged)
|
|
tag = tagged.Tag()
|
|
tagsList[tagged.Tag()] = tagID
|
|
}
|
|
tagID, err = mf.session.GetRemoteTag(repoData.Endpoints, mf.repoInfo, tag)
|
|
if err == registry.ErrRepoNotFound {
|
|
return nil, fmt.Errorf("Tag %s not found in repository %s", tag, mf.repoInfo.FullName())
|
|
}
|
|
if err != nil {
|
|
logrus.Errorf("unable to get remote tags: %s", err)
|
|
return nil, err
|
|
}
|
|
|
|
tagList := []string{}
|
|
for tag := range tagsList {
|
|
tagList = append(tagList, tag)
|
|
}
|
|
|
|
img := repoData.ImgList[tagID]
|
|
|
|
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(), tag)
|
|
}
|
|
|
|
return makeImageInspect(pulledImg, tag, "", tagList), 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
|
|
}
|