mirror of
https://github.com/openshift/image-registry.git
synced 2026-02-05 09:45:55 +01:00
when we bump library-go the function diff.ObjectGoPrintDiff vanished. we need to replace it with diff.ObjectGoPrintSideBySide on all test files.
335 lines
10 KiB
Go
335 lines
10 KiB
Go
package testutil
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/distribution/distribution/v3"
|
|
"github.com/distribution/distribution/v3/manifest"
|
|
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
|
"github.com/distribution/distribution/v3/manifest/ocischema"
|
|
"github.com/distribution/distribution/v3/manifest/schema1"
|
|
"github.com/distribution/distribution/v3/manifest/schema2"
|
|
"github.com/distribution/distribution/v3/registry/client/auth"
|
|
"github.com/docker/libtrust"
|
|
"github.com/opencontainers/go-digest"
|
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/diff"
|
|
|
|
imageapiv1 "github.com/openshift/api/image/v1"
|
|
imageapi "github.com/openshift/image-registry/pkg/origin-common/image/apis/image"
|
|
"github.com/openshift/image-registry/pkg/origin-common/util"
|
|
"github.com/openshift/library-go/pkg/image/dockerv1client"
|
|
)
|
|
|
|
type ManifestSchemaVersion string
|
|
|
|
type ConfigPayload []byte
|
|
|
|
const (
|
|
ManifestSchema1 ManifestSchemaVersion = "v1"
|
|
ManifestSchema2 ManifestSchemaVersion = "v2"
|
|
ManifestSchemaOCI ManifestSchemaVersion = "oci"
|
|
)
|
|
|
|
// MakeSchema1Manifest constructs a schema 1 manifest from a given list of digests and returns
|
|
// the digest of the manifest
|
|
func MakeSchema1Manifest(name, tag string, layers []distribution.Descriptor) (distribution.Manifest, error) {
|
|
m := schema1.Manifest{
|
|
Versioned: manifest.Versioned{
|
|
SchemaVersion: 1,
|
|
},
|
|
FSLayers: make([]schema1.FSLayer, 0, len(layers)),
|
|
History: make([]schema1.History, 0, len(layers)),
|
|
Name: name,
|
|
Tag: tag,
|
|
}
|
|
|
|
for _, layer := range layers {
|
|
m.FSLayers = append(m.FSLayers, schema1.FSLayer{BlobSum: layer.Digest})
|
|
m.History = append(m.History, schema1.History{V1Compatibility: "{}"})
|
|
}
|
|
|
|
pk, err := libtrust.GenerateECP256PrivateKey()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unexpected error generating private key: %v", err)
|
|
}
|
|
|
|
signedManifest, err := schema1.Sign(&m, pk)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error signing manifest: %v", err)
|
|
}
|
|
|
|
return signedManifest, nil
|
|
}
|
|
|
|
// MakeSchema2Manifest constructs a schema 2 manifest from a given list of digests and returns
|
|
// the digest of the manifest
|
|
func MakeSchema2Manifest(config distribution.Descriptor, layers []distribution.Descriptor) (distribution.Manifest, error) {
|
|
m := schema2.Manifest{
|
|
Versioned: schema2.SchemaVersion,
|
|
Config: config,
|
|
Layers: make([]distribution.Descriptor, 0, len(layers)),
|
|
}
|
|
m.Config.MediaType = schema2.MediaTypeImageConfig
|
|
|
|
for _, layer := range layers {
|
|
layer.MediaType = schema2.MediaTypeLayer
|
|
m.Layers = append(m.Layers, layer)
|
|
}
|
|
|
|
manifest, err := schema2.FromStruct(m)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return manifest, nil
|
|
}
|
|
|
|
// MakeOCISchemaManifest constructs an OCI schema manifest from a given list of digests and returns
|
|
// the digest of the manifest
|
|
func MakeOCISchemaManifest(config distribution.Descriptor, layers []distribution.Descriptor) (distribution.Manifest, error) {
|
|
m := ocischema.Manifest{
|
|
Versioned: ocischema.SchemaVersion,
|
|
Config: config,
|
|
Layers: make([]distribution.Descriptor, 0, len(layers)),
|
|
}
|
|
m.Config.MediaType = v1.MediaTypeImageConfig
|
|
|
|
for _, layer := range layers {
|
|
layer.MediaType = v1.MediaTypeImageLayer
|
|
m.Layers = append(m.Layers, layer)
|
|
}
|
|
|
|
return ocischema.FromStruct(m)
|
|
}
|
|
|
|
// CanonicalManifest returns m in its canonical representation.
|
|
func CanonicalManifest(m distribution.Manifest) ([]byte, error) {
|
|
switch m := m.(type) {
|
|
case *schema1.SignedManifest:
|
|
return m.Canonical, nil
|
|
case *schema2.DeserializedManifest, *ocischema.DeserializedManifest, *manifestlist.DeserializedManifestList:
|
|
_, payload, err := m.Payload()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return payload, nil
|
|
}
|
|
return nil, fmt.Errorf("no canonical representation of %T: %#+v", m, m)
|
|
}
|
|
|
|
func MakeRandomLayer() ([]byte, distribution.Descriptor, error) {
|
|
content, err := CreateRandomTarFile()
|
|
if err != nil {
|
|
return nil, distribution.Descriptor{}, fmt.Errorf("failed to generate data for a random layer: %v", err)
|
|
}
|
|
|
|
return content, distribution.Descriptor{
|
|
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
|
Size: int64(len(content)),
|
|
Digest: digest.FromBytes(content),
|
|
}, nil
|
|
}
|
|
|
|
func MakeManifestConfig() (ConfigPayload, distribution.Descriptor, error) {
|
|
cfg := dockerv1client.DockerImageConfig{}
|
|
cfgDesc := distribution.Descriptor{}
|
|
|
|
jsonBytes, err := json.Marshal(&cfg)
|
|
if err != nil {
|
|
return nil, cfgDesc, err
|
|
}
|
|
|
|
cfgDesc.Digest = digest.FromBytes(jsonBytes)
|
|
cfgDesc.Size = int64(len(jsonBytes))
|
|
|
|
return jsonBytes, cfgDesc, nil
|
|
}
|
|
|
|
// CreateAndUploadTestManifest generates a random manifest blob and uploads it to the given repository. For this
|
|
// purpose, a given number of layers will be created and uploaded.
|
|
func CreateAndUploadTestManifest(
|
|
ctx context.Context,
|
|
schemaVersion ManifestSchemaVersion,
|
|
layerCount int,
|
|
serverURL *url.URL,
|
|
creds auth.CredentialStore,
|
|
repoName, tag string,
|
|
) (dgst digest.Digest, canonical, manifestConfig string, manifest distribution.Manifest, err error) {
|
|
layerDescriptors := make([]distribution.Descriptor, 0, layerCount)
|
|
|
|
for i := 0; i < layerCount; i++ {
|
|
ds, _, err := UploadRandomTestBlob(ctx, serverURL.String(), creds, repoName)
|
|
if err != nil {
|
|
return "", "", "", nil, fmt.Errorf("unexpected error generating test blob layer: %v", err)
|
|
}
|
|
layerDescriptors = append(layerDescriptors, ds)
|
|
}
|
|
|
|
rt, err := NewTransport(serverURL.String(), repoName, creds)
|
|
if err != nil {
|
|
return "", "", "", nil, err
|
|
}
|
|
|
|
repo, err := NewRepository(repoName, serverURL.String(), rt)
|
|
if err != nil {
|
|
return "", "", "", nil, err
|
|
}
|
|
|
|
switch schemaVersion {
|
|
case ManifestSchema1:
|
|
manifest, err = MakeSchema1Manifest(repoName, tag, layerDescriptors)
|
|
if err != nil {
|
|
return "", "", "", nil, fmt.Errorf("failed to make manifest of schema 1: %v", err)
|
|
}
|
|
case ManifestSchema2:
|
|
cfgPayload, cfgDesc, err := MakeManifestConfig()
|
|
if err != nil {
|
|
return "", "", "", nil, err
|
|
}
|
|
err = UploadBlob(ctx, repo, cfgDesc, cfgPayload)
|
|
if err != nil {
|
|
return "", "", "", nil, fmt.Errorf("failed to upload manifest config of schema 2: %v", err)
|
|
}
|
|
manifest, err = MakeSchema2Manifest(cfgDesc, layerDescriptors)
|
|
if err != nil {
|
|
return "", "", "", nil, fmt.Errorf("failed to make manifest schema 2: %v", err)
|
|
}
|
|
manifestConfig = string(cfgPayload)
|
|
case ManifestSchemaOCI:
|
|
cfgPayload, cfgDesc, err := MakeManifestConfig()
|
|
if err != nil {
|
|
return "", "", "", nil, err
|
|
}
|
|
err = UploadBlob(ctx, repo, cfgDesc, cfgPayload)
|
|
if err != nil {
|
|
return "", "", "", nil, fmt.Errorf("failed to upload manifest config of schema 2: %v", err)
|
|
}
|
|
manifest, err = MakeOCISchemaManifest(cfgDesc, layerDescriptors)
|
|
if err != nil {
|
|
return "", "", "", nil, fmt.Errorf("failed to make manifest schema 2: %v", err)
|
|
}
|
|
manifestConfig = string(cfgPayload)
|
|
default:
|
|
return "", "", "", nil, fmt.Errorf("unsupported manifest version %s", schemaVersion)
|
|
}
|
|
|
|
canonicalBytes, err := CanonicalManifest(manifest)
|
|
if err != nil {
|
|
return "", "", "", nil, err
|
|
}
|
|
|
|
dgst = digest.FromBytes(canonicalBytes)
|
|
|
|
if err := UploadManifest(ctx, repo, tag, manifest); err != nil {
|
|
return "", "", "", nil, err
|
|
}
|
|
|
|
return dgst, string(canonicalBytes), manifestConfig, manifest, nil
|
|
}
|
|
|
|
// AssertManifestsEqual compares two manifests and returns if they are equal. Signatures of manifest schema 1
|
|
// are not taken into account.
|
|
func AssertManifestsEqual(t *testing.T, description string, ma distribution.Manifest, mb distribution.Manifest) {
|
|
if ma == mb {
|
|
return
|
|
}
|
|
|
|
if (ma == nil) != (mb == nil) {
|
|
t.Fatalf("[%s] only one of the manifests is nil", description)
|
|
}
|
|
|
|
_, pa, err := ma.Payload()
|
|
if err != nil {
|
|
t.Fatalf("[%s] failed to get payload for first manifest: %v", description, err)
|
|
}
|
|
_, pb, err := mb.Payload()
|
|
if err != nil {
|
|
t.Fatalf("[%s] failed to get payload for second manifest: %v", description, err)
|
|
}
|
|
|
|
var va, vb manifest.Versioned
|
|
if err := json.Unmarshal(pa, &va); err != nil {
|
|
t.Fatalf("[%s] failed to unmarshal payload of the first manifest: %v", description, err)
|
|
}
|
|
if err := json.Unmarshal(pb, &vb); err != nil {
|
|
t.Fatalf("[%s] failed to unmarshal payload of the second manifest: %v", description, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(va, vb) {
|
|
t.Fatalf("[%s] manifests are of different version: %s", description, diff.ObjectGoPrintSideBySide(va, vb))
|
|
}
|
|
|
|
switch va.SchemaVersion {
|
|
case 1:
|
|
ms1a, ok := ma.(*schema1.SignedManifest)
|
|
if !ok {
|
|
t.Fatalf("[%s] failed to convert first manifest (%T) to schema1.SignedManifest", description, ma)
|
|
}
|
|
ms1b, ok := mb.(*schema1.SignedManifest)
|
|
if !ok {
|
|
t.Fatalf("[%s] failed to convert first manifest (%T) to schema1.SignedManifest", description, mb)
|
|
}
|
|
if !reflect.DeepEqual(ms1a.Manifest, ms1b.Manifest) {
|
|
t.Fatalf("[%s] manifests don't match: %s", description, diff.ObjectGoPrintSideBySide(ms1a.Manifest, ms1b.Manifest))
|
|
}
|
|
|
|
case 2:
|
|
if !reflect.DeepEqual(ma, mb) {
|
|
t.Fatalf("[%s] manifests don't match: %s", description, diff.ObjectGoPrintSideBySide(ma, mb))
|
|
}
|
|
|
|
default:
|
|
t.Fatalf("[%s] unrecognized manifest schema version: %d", description, va.SchemaVersion)
|
|
}
|
|
}
|
|
|
|
// NewImageForManifest creates a new Image object for the given manifest string. Note that the manifest must
|
|
// contain signatures if it is of schema 1.
|
|
func NewImageForManifest(repoName string, rawManifest string, manifestConfig string, managedByOpenShift bool) (*imageapiv1.Image, error) {
|
|
var versioned manifest.Versioned
|
|
if err := json.Unmarshal([]byte(rawManifest), &versioned); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, desc, err := distribution.UnmarshalManifest(versioned.MediaType, []byte(rawManifest))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
annotations := make(map[string]string)
|
|
if managedByOpenShift {
|
|
annotations[imageapiv1.ManagedByOpenShiftAnnotation] = "true"
|
|
}
|
|
|
|
image := &imageapi.Image{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: desc.Digest.String(),
|
|
Annotations: annotations,
|
|
},
|
|
DockerImageReference: fmt.Sprintf("localhost:5000/%s@%s", repoName, desc.Digest.String()),
|
|
DockerImageManifest: rawManifest,
|
|
DockerImageConfig: manifestConfig,
|
|
}
|
|
if err := util.InternalImageWithMetadata(image); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
newImage, err := ConvertImage(image)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert image from internal to external type: %v", err)
|
|
}
|
|
if err := util.ImageWithMetadata(newImage); err != nil {
|
|
return nil, fmt.Errorf("failed to fill image with metadata: %v", err)
|
|
}
|
|
|
|
return newImage, nil
|
|
}
|