forked from TrueCloudLab/distribution
Merge pull request #3987 from milosgajdos/remove-schema1-handlers
Remove references to schema1 pacakge from handlers
This commit is contained in:
commit
ac7a334069
3 changed files with 99 additions and 598 deletions
|
@ -23,7 +23,6 @@ import (
|
||||||
"github.com/distribution/distribution/v3/configuration"
|
"github.com/distribution/distribution/v3/configuration"
|
||||||
"github.com/distribution/distribution/v3/manifest"
|
"github.com/distribution/distribution/v3/manifest"
|
||||||
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
||||||
"github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
"github.com/distribution/distribution/v3/manifest/schema2"
|
"github.com/distribution/distribution/v3/manifest/schema2"
|
||||||
"github.com/distribution/distribution/v3/reference"
|
"github.com/distribution/distribution/v3/reference"
|
||||||
"github.com/distribution/distribution/v3/registry/api/errcode"
|
"github.com/distribution/distribution/v3/registry/api/errcode"
|
||||||
|
@ -1245,21 +1244,18 @@ type manifestArgs struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifestAPI(t *testing.T) {
|
func TestManifestAPI(t *testing.T) {
|
||||||
schema1Repo, _ := reference.WithName("foo/schema1")
|
|
||||||
schema2Repo, _ := reference.WithName("foo/schema2")
|
schema2Repo, _ := reference.WithName("foo/schema2")
|
||||||
|
|
||||||
deleteEnabled := false
|
deleteEnabled := false
|
||||||
env1 := newTestEnv(t, deleteEnabled)
|
env1 := newTestEnv(t, deleteEnabled)
|
||||||
defer env1.Shutdown()
|
defer env1.Shutdown()
|
||||||
testManifestAPISchema1(t, env1, schema1Repo)
|
schema2Args := testManifestAPISchema2(t, env1, schema2Repo, "schema2tag")
|
||||||
schema2Args := testManifestAPISchema2(t, env1, schema2Repo)
|
|
||||||
testManifestAPIManifestList(t, env1, schema2Args)
|
testManifestAPIManifestList(t, env1, schema2Args)
|
||||||
|
|
||||||
deleteEnabled = true
|
deleteEnabled = true
|
||||||
env2 := newTestEnv(t, deleteEnabled)
|
env2 := newTestEnv(t, deleteEnabled)
|
||||||
defer env2.Shutdown()
|
defer env2.Shutdown()
|
||||||
testManifestAPISchema1(t, env2, schema1Repo)
|
schema2Args = testManifestAPISchema2(t, env2, schema2Repo, "schema2tag")
|
||||||
schema2Args = testManifestAPISchema2(t, env2, schema2Repo)
|
|
||||||
testManifestAPIManifestList(t, env2, schema2Args)
|
testManifestAPIManifestList(t, env2, schema2Args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1438,24 +1434,21 @@ func TestGetManifestWithStorageError(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifestDelete(t *testing.T) {
|
func TestManifestDelete(t *testing.T) {
|
||||||
schema1Repo, _ := reference.WithName("foo/schema1")
|
|
||||||
schema2Repo, _ := reference.WithName("foo/schema2")
|
schema2Repo, _ := reference.WithName("foo/schema2")
|
||||||
|
|
||||||
deleteEnabled := true
|
deleteEnabled := true
|
||||||
env := newTestEnv(t, deleteEnabled)
|
env := newTestEnv(t, deleteEnabled)
|
||||||
defer env.Shutdown()
|
defer env.Shutdown()
|
||||||
schema1Args := testManifestAPISchema1(t, env, schema1Repo)
|
schema2Args := testManifestAPISchema2(t, env, schema2Repo, "schema2tag")
|
||||||
testManifestDelete(t, env, schema1Args)
|
|
||||||
schema2Args := testManifestAPISchema2(t, env, schema2Repo)
|
|
||||||
testManifestDelete(t, env, schema2Args)
|
testManifestDelete(t, env, schema2Args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifestDeleteDisabled(t *testing.T) {
|
func TestManifestDeleteDisabled(t *testing.T) {
|
||||||
schema1Repo, _ := reference.WithName("foo/schema1")
|
schema2Repo, _ := reference.WithName("foo/schema2")
|
||||||
deleteEnabled := false
|
deleteEnabled := false
|
||||||
env := newTestEnv(t, deleteEnabled)
|
env := newTestEnv(t, deleteEnabled)
|
||||||
defer env.Shutdown()
|
defer env.Shutdown()
|
||||||
testManifestDeleteDisabled(t, env, schema1Repo)
|
testManifestDeleteDisabled(t, env, schema2Repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testManifestDeleteDisabled(t *testing.T, env *testEnv, imageName reference.Named) {
|
func testManifestDeleteDisabled(t *testing.T, env *testEnv, imageName reference.Named) {
|
||||||
|
@ -1493,324 +1486,7 @@ func testManifestWithStorageError(t *testing.T, env *testEnv, imageName referenc
|
||||||
checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, expectedErrorCode)
|
checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, expectedErrorCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testManifestAPISchema1(t *testing.T, env *testEnv, imageName reference.Named) manifestArgs {
|
func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Named, tag string) manifestArgs {
|
||||||
tag := "thetag"
|
|
||||||
args := manifestArgs{imageName: imageName}
|
|
||||||
|
|
||||||
tagRef, _ := reference.WithTag(imageName, tag)
|
|
||||||
manifestURL, err := env.builder.BuildManifestURL(tagRef)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error getting manifest url: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------
|
|
||||||
// Attempt to fetch the manifest
|
|
||||||
resp, err := http.Get(manifestURL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error getting manifest: %v", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
checkResponse(t, "getting non-existent manifest", resp, http.StatusNotFound)
|
|
||||||
checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, v2.ErrorCodeManifestUnknown)
|
|
||||||
|
|
||||||
tagsURL, err := env.builder.BuildTagsURL(imageName)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error building tags url: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err = http.Get(tagsURL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error getting unknown tags: %v", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// Check that we get an unknown repository error when asking for tags
|
|
||||||
checkResponse(t, "getting unknown manifest tags", resp, http.StatusNotFound)
|
|
||||||
checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeNameUnknown)
|
|
||||||
|
|
||||||
// --------------------------------
|
|
||||||
// Attempt to push unsigned manifest with missing layers
|
|
||||||
unsignedManifest := &schema1.Manifest{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
Versioned: manifest.Versioned{
|
|
||||||
SchemaVersion: 1,
|
|
||||||
},
|
|
||||||
Name: imageName.Name(),
|
|
||||||
Tag: tag,
|
|
||||||
FSLayers: []schema1.FSLayer{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
{
|
|
||||||
BlobSum: "asdf",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BlobSum: "qwer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
History: []schema1.History{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
{
|
|
||||||
V1Compatibility: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
V1Compatibility: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = putManifest(t, "putting unsigned manifest", manifestURL, "", unsignedManifest)
|
|
||||||
defer resp.Body.Close()
|
|
||||||
checkResponse(t, "putting unsigned manifest", resp, http.StatusBadRequest)
|
|
||||||
_, p, counts := checkBodyHasErrorCodes(t, "putting unsigned manifest", resp, v2.ErrorCodeManifestInvalid)
|
|
||||||
|
|
||||||
expectedCounts := map[errcode.ErrorCode]int{
|
|
||||||
v2.ErrorCodeManifestInvalid: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(counts, expectedCounts) {
|
|
||||||
t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
// sign the manifest and still get some interesting errors.
|
|
||||||
sm, err := schema1.Sign(unsignedManifest, env.pk) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error signing manifest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = putManifest(t, "putting signed manifest with errors", manifestURL, "", sm)
|
|
||||||
defer resp.Body.Close()
|
|
||||||
checkResponse(t, "putting signed manifest with errors", resp, http.StatusBadRequest)
|
|
||||||
_, p, counts = checkBodyHasErrorCodes(t, "putting signed manifest with errors", resp,
|
|
||||||
v2.ErrorCodeManifestBlobUnknown, v2.ErrorCodeDigestInvalid)
|
|
||||||
|
|
||||||
expectedCounts = map[errcode.ErrorCode]int{
|
|
||||||
v2.ErrorCodeManifestBlobUnknown: 2,
|
|
||||||
v2.ErrorCodeDigestInvalid: 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(counts, expectedCounts) {
|
|
||||||
t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(stevvooe): Add a test case where we take a mostly valid registry,
|
|
||||||
// tamper with the content and ensure that we get an unverified manifest
|
|
||||||
// error.
|
|
||||||
|
|
||||||
// Push 2 random layers
|
|
||||||
expectedLayers := make(map[digest.Digest]io.ReadSeeker)
|
|
||||||
|
|
||||||
for i := range unsignedManifest.FSLayers {
|
|
||||||
rs, dgst, err := testutil.CreateRandomTarFile()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating random layer %d: %v", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedLayers[dgst] = rs
|
|
||||||
unsignedManifest.FSLayers[i].BlobSum = dgst
|
|
||||||
|
|
||||||
uploadURLBase, _ := startPushLayer(t, env, imageName)
|
|
||||||
pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------
|
|
||||||
// Push the signed manifest with all layers pushed.
|
|
||||||
signedManifest, err := schema1.Sign(unsignedManifest, env.pk) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error signing manifest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dgst := digest.FromBytes(signedManifest.Canonical)
|
|
||||||
args.manifest = signedManifest
|
|
||||||
args.dgst = dgst
|
|
||||||
|
|
||||||
digestRef, _ := reference.WithDigest(imageName, dgst)
|
|
||||||
manifestDigestURL, err := env.builder.BuildManifestURL(digestRef)
|
|
||||||
checkErr(t, err, "building manifest url")
|
|
||||||
|
|
||||||
resp = putManifest(t, "putting signed manifest no error", manifestURL, "", signedManifest)
|
|
||||||
checkResponse(t, "putting signed manifest no error", resp, http.StatusCreated)
|
|
||||||
checkHeaders(t, resp, http.Header{
|
|
||||||
"Location": []string{manifestDigestURL},
|
|
||||||
"Docker-Content-Digest": []string{dgst.String()},
|
|
||||||
})
|
|
||||||
|
|
||||||
// --------------------
|
|
||||||
// Push by digest -- should get same result
|
|
||||||
resp = putManifest(t, "putting signed manifest", manifestDigestURL, "", signedManifest)
|
|
||||||
checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
|
|
||||||
checkHeaders(t, resp, http.Header{
|
|
||||||
"Location": []string{manifestDigestURL},
|
|
||||||
"Docker-Content-Digest": []string{dgst.String()},
|
|
||||||
})
|
|
||||||
|
|
||||||
// ------------------
|
|
||||||
// Fetch by tag name
|
|
||||||
resp, err = http.Get(manifestURL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error fetching manifest: %v", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
|
|
||||||
checkHeaders(t, resp, http.Header{
|
|
||||||
"Docker-Content-Digest": []string{dgst.String()},
|
|
||||||
"ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
|
|
||||||
})
|
|
||||||
|
|
||||||
var fetchedManifest schema1.SignedManifest //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
dec := json.NewDecoder(resp.Body)
|
|
||||||
|
|
||||||
if err := dec.Decode(&fetchedManifest); err != nil {
|
|
||||||
t.Fatalf("error decoding fetched manifest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(fetchedManifest.Canonical, signedManifest.Canonical) {
|
|
||||||
t.Fatalf("manifests do not match")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------
|
|
||||||
// Fetch by digest
|
|
||||||
resp, err = http.Get(manifestDigestURL)
|
|
||||||
checkErr(t, err, "fetching manifest by digest")
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
|
|
||||||
checkHeaders(t, resp, http.Header{
|
|
||||||
"Docker-Content-Digest": []string{dgst.String()},
|
|
||||||
"ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
|
|
||||||
})
|
|
||||||
|
|
||||||
var fetchedManifestByDigest schema1.SignedManifest //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
dec = json.NewDecoder(resp.Body)
|
|
||||||
if err := dec.Decode(&fetchedManifestByDigest); err != nil {
|
|
||||||
t.Fatalf("error decoding fetched manifest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(fetchedManifestByDigest.Canonical, signedManifest.Canonical) {
|
|
||||||
t.Fatalf("manifests do not match")
|
|
||||||
}
|
|
||||||
|
|
||||||
// check signature was roundtripped
|
|
||||||
signatures, err := fetchedManifestByDigest.Signatures() //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(signatures) != 1 {
|
|
||||||
t.Fatalf("expected 1 signature from manifest, got: %d", len(signatures))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-sign, push and pull the same digest
|
|
||||||
sm2, err := schema1.Sign(&fetchedManifestByDigest.Manifest, env.pk) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-push with a few different Content-Types. The official schema1
|
|
||||||
// content type should work, as should application/json with/without a
|
|
||||||
// charset.
|
|
||||||
resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, schema1.MediaTypeSignedManifest, sm2) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)
|
|
||||||
resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, "application/json", sm2)
|
|
||||||
checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)
|
|
||||||
resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, "application/json", sm2)
|
|
||||||
checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)
|
|
||||||
|
|
||||||
resp, err = http.Get(manifestDigestURL)
|
|
||||||
checkErr(t, err, "re-fetching manifest by digest")
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
checkResponse(t, "re-fetching uploaded manifest", resp, http.StatusOK)
|
|
||||||
checkHeaders(t, resp, http.Header{
|
|
||||||
"Docker-Content-Digest": []string{dgst.String()},
|
|
||||||
"ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
|
|
||||||
})
|
|
||||||
|
|
||||||
dec = json.NewDecoder(resp.Body)
|
|
||||||
if err := dec.Decode(&fetchedManifestByDigest); err != nil {
|
|
||||||
t.Fatalf("error decoding fetched manifest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check only 1 signature is returned
|
|
||||||
signatures, err = fetchedManifestByDigest.Signatures() //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(signatures) != 1 {
|
|
||||||
t.Fatalf("expected 2 signature from manifest, got: %d", len(signatures))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get by name with etag, gives 304
|
|
||||||
etag := resp.Header.Get("Etag")
|
|
||||||
req, err := http.NewRequest(http.MethodGet, manifestURL, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error constructing request: %s", err)
|
|
||||||
}
|
|
||||||
req.Header.Set("If-None-Match", etag)
|
|
||||||
resp, err = http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error constructing request: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified)
|
|
||||||
|
|
||||||
// Get by digest with etag, gives 304
|
|
||||||
req, err = http.NewRequest(http.MethodGet, manifestDigestURL, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error constructing request: %s", err)
|
|
||||||
}
|
|
||||||
req.Header.Set("If-None-Match", etag)
|
|
||||||
resp, err = http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error constructing request: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
|
|
||||||
|
|
||||||
// Ensure that the tag is listed.
|
|
||||||
resp, err = http.Get(tagsURL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error getting unknown tags: %v", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
checkResponse(t, "getting tags", resp, http.StatusOK)
|
|
||||||
dec = json.NewDecoder(resp.Body)
|
|
||||||
|
|
||||||
var tagsResponse tagsAPIResponse
|
|
||||||
|
|
||||||
if err := dec.Decode(&tagsResponse); err != nil {
|
|
||||||
t.Fatalf("unexpected error decoding error response: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagsResponse.Name != imageName.Name() {
|
|
||||||
t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tagsResponse.Tags) != 1 {
|
|
||||||
t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagsResponse.Tags[0] != tag {
|
|
||||||
t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to put a manifest with mismatching FSLayer and History array cardinalities
|
|
||||||
|
|
||||||
unsignedManifest.History = append(unsignedManifest.History, schema1.History{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
V1Compatibility: "",
|
|
||||||
})
|
|
||||||
invalidSigned, err := schema1.Sign(unsignedManifest, env.pk) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error signing manifest")
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = putManifest(t, "putting invalid signed manifest", manifestDigestURL, "", invalidSigned)
|
|
||||||
checkResponse(t, "putting invalid signed manifest", resp, http.StatusBadRequest)
|
|
||||||
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Named) manifestArgs {
|
|
||||||
tag := "schema2tag"
|
|
||||||
args := manifestArgs{
|
args := manifestArgs{
|
||||||
imageName: imageName,
|
imageName: imageName,
|
||||||
mediaType: schema2.MediaTypeManifest,
|
mediaType: schema2.MediaTypeManifest,
|
||||||
|
@ -2101,63 +1777,6 @@ func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Name
|
||||||
t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
|
t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
|
||||||
// Fetch as a schema1 manifest
|
|
||||||
resp, err = http.Get(manifestURL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error fetching manifest as schema1: %v", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
manifestBytes, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error reading response body: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
checkResponse(t, "fetching uploaded manifest as schema1", resp, http.StatusOK)
|
|
||||||
|
|
||||||
m, desc, err := distribution.UnmarshalManifest(schema1.MediaTypeManifest, manifestBytes) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error unmarshalling manifest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchedSchema1Manifest, ok := m.(*schema1.SignedManifest) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("expecting schema1 manifest")
|
|
||||||
}
|
|
||||||
|
|
||||||
checkHeaders(t, resp, http.Header{
|
|
||||||
"Docker-Content-Digest": []string{desc.Digest.String()},
|
|
||||||
"ETag": []string{fmt.Sprintf(`"%s"`, desc.Digest)},
|
|
||||||
})
|
|
||||||
|
|
||||||
if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 {
|
|
||||||
t.Fatal("wrong schema version")
|
|
||||||
}
|
|
||||||
if fetchedSchema1Manifest.Architecture != "amd64" {
|
|
||||||
t.Fatal("wrong architecture")
|
|
||||||
}
|
|
||||||
if fetchedSchema1Manifest.Name != imageName.Name() {
|
|
||||||
t.Fatal("wrong image name")
|
|
||||||
}
|
|
||||||
if fetchedSchema1Manifest.Tag != tag {
|
|
||||||
t.Fatal("wrong tag")
|
|
||||||
}
|
|
||||||
if len(fetchedSchema1Manifest.FSLayers) != 2 {
|
|
||||||
t.Fatal("wrong number of FSLayers")
|
|
||||||
}
|
|
||||||
for i := range manifest.Layers {
|
|
||||||
if fetchedSchema1Manifest.FSLayers[i].BlobSum != manifest.Layers[len(manifest.Layers)-i-1].Digest {
|
|
||||||
t.Fatalf("blob digest mismatch in schema1 manifest for layer %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(fetchedSchema1Manifest.History) != 2 {
|
|
||||||
t.Fatal("wrong number of History entries")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't check V1Compatibility fields because we're using randomly-generated
|
|
||||||
// layers.
|
|
||||||
|
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2246,7 +1865,7 @@ func testManifestAPIManifestList(t *testing.T, env *testEnv, args manifestArgs)
|
||||||
t.Fatalf("Error constructing request: %s", err)
|
t.Fatalf("Error constructing request: %s", err)
|
||||||
}
|
}
|
||||||
// multiple headers in mixed list format to ensure we parse correctly server-side
|
// multiple headers in mixed list format to ensure we parse correctly server-side
|
||||||
req.Header.Set("Accept", fmt.Sprintf(` %s ; q=0.8 , %s ; q=0.5 `, manifestlist.MediaTypeManifestList, schema1.MediaTypeSignedManifest)) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
req.Header.Set("Accept", fmt.Sprintf(` %s ; q=0.8 `, manifestlist.MediaTypeManifestList))
|
||||||
req.Header.Add("Accept", schema2.MediaTypeManifest)
|
req.Header.Add("Accept", schema2.MediaTypeManifest)
|
||||||
resp, err = http.DefaultClient.Do(req)
|
resp, err = http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2334,64 +1953,6 @@ func testManifestAPIManifestList(t *testing.T, env *testEnv, args manifestArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
|
checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
|
||||||
|
|
||||||
// ------------------
|
|
||||||
// Fetch as a schema1 manifest
|
|
||||||
resp, err = http.Get(manifestURL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error fetching manifest list as schema1: %v", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
manifestBytes, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error reading response body: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
checkResponse(t, "fetching uploaded manifest list as schema1", resp, http.StatusOK)
|
|
||||||
|
|
||||||
m, desc, err := distribution.UnmarshalManifest(schema1.MediaTypeManifest, manifestBytes) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error unmarshalling manifest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchedSchema1Manifest, ok := m.(*schema1.SignedManifest) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("expecting schema1 manifest")
|
|
||||||
}
|
|
||||||
|
|
||||||
checkHeaders(t, resp, http.Header{
|
|
||||||
"Docker-Content-Digest": []string{desc.Digest.String()},
|
|
||||||
"ETag": []string{fmt.Sprintf(`"%s"`, desc.Digest)},
|
|
||||||
})
|
|
||||||
|
|
||||||
if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 {
|
|
||||||
t.Fatal("wrong schema version")
|
|
||||||
}
|
|
||||||
if fetchedSchema1Manifest.Architecture != "amd64" {
|
|
||||||
t.Fatal("wrong architecture")
|
|
||||||
}
|
|
||||||
if fetchedSchema1Manifest.Name != imageName.Name() {
|
|
||||||
t.Fatal("wrong image name")
|
|
||||||
}
|
|
||||||
if fetchedSchema1Manifest.Tag != tag {
|
|
||||||
t.Fatal("wrong tag")
|
|
||||||
}
|
|
||||||
if len(fetchedSchema1Manifest.FSLayers) != 2 {
|
|
||||||
t.Fatal("wrong number of FSLayers")
|
|
||||||
}
|
|
||||||
layers := args.manifest.(*schema2.DeserializedManifest).Layers
|
|
||||||
for i := range layers {
|
|
||||||
if fetchedSchema1Manifest.FSLayers[i].BlobSum != layers[len(layers)-i-1].Digest {
|
|
||||||
t.Fatalf("blob digest mismatch in schema1 manifest for layer %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(fetchedSchema1Manifest.History) != 2 {
|
|
||||||
t.Fatal("wrong number of History entries")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't check V1Compatibility fields because we're using randomly-generated
|
|
||||||
// layers.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) {
|
func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) {
|
||||||
|
@ -2556,7 +2117,6 @@ func newTestEnvMirror(t *testing.T, deleteEnabled bool) *testEnv {
|
||||||
MaxEntries: 5,
|
MaxEntries: 5,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
config.Compatibility.Schema1.Enabled = true //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
|
|
||||||
return newTestEnvWithConfig(t, &config)
|
return newTestEnvWithConfig(t, &config)
|
||||||
}
|
}
|
||||||
|
@ -2575,7 +2135,6 @@ func newTestEnv(t *testing.T, deleteEnabled bool) *testEnv {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Compatibility.Schema1.Enabled = true //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
config.HTTP.Headers = headerConfig
|
config.HTTP.Headers = headerConfig
|
||||||
|
|
||||||
return newTestEnvWithConfig(t, &config)
|
return newTestEnvWithConfig(t, &config)
|
||||||
|
@ -2615,12 +2174,6 @@ func putManifest(t *testing.T, msg, url, contentType string, v interface{}) *htt
|
||||||
var body []byte
|
var body []byte
|
||||||
|
|
||||||
switch m := v.(type) {
|
switch m := v.(type) {
|
||||||
case *schema1.SignedManifest: //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
_, pl, err := m.Payload() //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting payload: %v", err)
|
|
||||||
}
|
|
||||||
body = pl
|
|
||||||
case *manifestlist.DeserializedManifestList:
|
case *manifestlist.DeserializedManifestList:
|
||||||
_, pl, err := m.Payload()
|
_, pl, err := m.Payload()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2968,61 +2521,89 @@ func createRepository(env *testEnv, t *testing.T, imageName string, tag string)
|
||||||
t.Fatalf("unable to parse reference: %v", err)
|
t.Fatalf("unable to parse reference: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedManifest := &schema1.Manifest{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
tagRef, _ := reference.WithTag(imageNameRef, tag)
|
||||||
|
manifestURL, err := env.builder.BuildManifestURL(tagRef)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting manifest url: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest := &schema2.Manifest{
|
||||||
Versioned: manifest.Versioned{
|
Versioned: manifest.Versioned{
|
||||||
SchemaVersion: 1,
|
SchemaVersion: 2,
|
||||||
|
MediaType: schema2.MediaTypeManifest,
|
||||||
},
|
},
|
||||||
Name: imageName,
|
Config: distribution.Descriptor{
|
||||||
Tag: tag,
|
Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
|
||||||
FSLayers: []schema1.FSLayer{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
Size: 3253,
|
||||||
{
|
MediaType: schema2.MediaTypeImageConfig,
|
||||||
BlobSum: "asdf",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
History: []schema1.History{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
Layers: []distribution.Descriptor{
|
||||||
{
|
{
|
||||||
V1Compatibility: "",
|
Digest: "sha256:463434349086340864309863409683460843608348608934092322395278926a",
|
||||||
|
Size: 6323,
|
||||||
|
MediaType: schema2.MediaTypeLayer,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push 2 random layers
|
// Push a config, and reference it in the manifest
|
||||||
expectedLayers := make(map[digest.Digest]io.ReadSeeker)
|
sampleConfig := []byte(`{
|
||||||
|
"architecture": "amd64",
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"created": "2015-10-31T22:22:54.690851953Z",
|
||||||
|
"created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"rootfs": {
|
||||||
|
"diff_ids": [
|
||||||
|
"sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
|
||||||
|
],
|
||||||
|
"type": "layers"
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
sampleConfigDigest := digest.FromBytes(sampleConfig)
|
||||||
|
|
||||||
for i := range unsignedManifest.FSLayers {
|
uploadURLBase, _ := startPushLayer(t, env, imageNameRef)
|
||||||
|
pushLayer(t, env.builder, imageNameRef, sampleConfigDigest, uploadURLBase, bytes.NewReader(sampleConfig))
|
||||||
|
manifest.Config.Digest = sampleConfigDigest
|
||||||
|
manifest.Config.Size = int64(len(sampleConfig))
|
||||||
|
|
||||||
|
// Push random layers
|
||||||
|
|
||||||
|
for i := range manifest.Layers {
|
||||||
rs, dgst, err := testutil.CreateRandomTarFile()
|
rs, dgst, err := testutil.CreateRandomTarFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error creating random layer %d: %v", i, err)
|
t.Fatalf("error creating random layer %d: %v", i, err)
|
||||||
}
|
}
|
||||||
|
manifest.Layers[i].Digest = dgst
|
||||||
|
|
||||||
expectedLayers[dgst] = rs
|
|
||||||
unsignedManifest.FSLayers[i].BlobSum = dgst
|
|
||||||
uploadURLBase, _ := startPushLayer(t, env, imageNameRef)
|
uploadURLBase, _ := startPushLayer(t, env, imageNameRef)
|
||||||
pushLayer(t, env.builder, imageNameRef, dgst, uploadURLBase, rs)
|
pushLayer(t, env.builder, imageNameRef, dgst, uploadURLBase, rs)
|
||||||
}
|
}
|
||||||
|
|
||||||
signedManifest, err := schema1.Sign(unsignedManifest, env.pk) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
// -------------------
|
||||||
|
// Push the manifest with all layers pushed.
|
||||||
|
deserializedManifest, err := schema2.FromStruct(*manifest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error signing manifest: %v", err)
|
t.Fatalf("could not create DeserializedManifest: %v", err)
|
||||||
}
|
}
|
||||||
|
_, canonical, err := deserializedManifest.Payload()
|
||||||
dgst := digest.FromBytes(signedManifest.Canonical)
|
if err != nil {
|
||||||
|
t.Fatalf("could not get manifest payload: %v", err)
|
||||||
// Create this repository by tag to ensure the tag mapping is made in the registry
|
}
|
||||||
tagRef, _ := reference.WithTag(imageNameRef, tag)
|
dgst := digest.FromBytes(canonical)
|
||||||
manifestDigestURL, err := env.builder.BuildManifestURL(tagRef)
|
digestRef, _ := reference.WithDigest(imageNameRef, dgst)
|
||||||
|
manifestDigestURL, err := env.builder.BuildManifestURL(digestRef)
|
||||||
checkErr(t, err, "building manifest url")
|
checkErr(t, err, "building manifest url")
|
||||||
|
|
||||||
digestRef, _ := reference.WithDigest(imageNameRef, dgst)
|
resp := putManifest(t, "putting manifest no error", manifestURL, schema2.MediaTypeManifest, manifest)
|
||||||
location, err := env.builder.BuildManifestURL(digestRef)
|
checkResponse(t, "putting manifest no error", resp, http.StatusCreated)
|
||||||
checkErr(t, err, "building location URL")
|
|
||||||
|
|
||||||
resp := putManifest(t, "putting signed manifest", manifestDigestURL, "", signedManifest)
|
|
||||||
checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
|
|
||||||
checkHeaders(t, resp, http.Header{
|
checkHeaders(t, resp, http.Header{
|
||||||
"Location": []string{location},
|
"Location": []string{manifestDigestURL},
|
||||||
"Docker-Content-Digest": []string{dgst.String()},
|
"Docker-Content-Digest": []string{dgst.String()},
|
||||||
})
|
})
|
||||||
|
|
||||||
return dgst
|
return dgst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3042,27 +2623,36 @@ func TestRegistryAsCacheMutationAPIs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manifest upload
|
// Manifest upload
|
||||||
m := &schema1.Manifest{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
manifest := &schema2.Manifest{
|
||||||
Versioned: manifest.Versioned{
|
Versioned: manifest.Versioned{
|
||||||
SchemaVersion: 1,
|
SchemaVersion: 2,
|
||||||
|
MediaType: schema2.MediaTypeManifest,
|
||||||
|
},
|
||||||
|
Config: distribution.Descriptor{
|
||||||
|
Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
|
||||||
|
Size: 3253,
|
||||||
|
MediaType: schema2.MediaTypeImageConfig,
|
||||||
|
},
|
||||||
|
Layers: []distribution.Descriptor{
|
||||||
|
{
|
||||||
|
Digest: "sha256:463434349086340864309863409683460843608348608934092322395278926a",
|
||||||
|
Size: 6323,
|
||||||
|
MediaType: schema2.MediaTypeLayer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Digest: "sha256:630923423623623423352523525237238023652897356239852383652aaaaaaa",
|
||||||
|
Size: 6863,
|
||||||
|
MediaType: schema2.MediaTypeLayer,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Name: imageName.Name(),
|
|
||||||
Tag: tag,
|
|
||||||
FSLayers: []schema1.FSLayer{}, //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
History: []schema1.History{}, //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sm, err := schema1.Sign(m, env.pk) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
resp := putManifest(t, "putting missing config manifest", manifestURL, schema2.MediaTypeManifest, manifest)
|
||||||
if err != nil {
|
checkResponse(t, "putting missing config manifest", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
|
||||||
t.Fatalf("error signing manifest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := putManifest(t, "putting unsigned manifest", manifestURL, "", sm)
|
|
||||||
checkResponse(t, "putting signed manifest to cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
|
|
||||||
|
|
||||||
// Manifest Delete
|
// Manifest Delete
|
||||||
resp, _ = httpDelete(manifestURL)
|
resp, _ = httpDelete(manifestURL)
|
||||||
checkResponse(t, "deleting signed manifest from cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
|
checkResponse(t, "deleting config manifest from cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
|
||||||
|
|
||||||
// Blob upload initialization
|
// Blob upload initialization
|
||||||
layerUploadURL, err := env.builder.BuildBlobUploadURL(imageName)
|
layerUploadURL, err := env.builder.BuildBlobUploadURL(imageName)
|
||||||
|
@ -3094,7 +2684,6 @@ func TestProxyManifestGetByTag(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
truthConfig.Compatibility.Schema1.Enabled = true //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
truthConfig.HTTP.Headers = headerConfig
|
truthConfig.HTTP.Headers = headerConfig
|
||||||
|
|
||||||
imageName, _ := reference.WithName("foo/bar")
|
imageName, _ := reference.WithName("foo/bar")
|
||||||
|
@ -3113,7 +2702,6 @@ func TestProxyManifestGetByTag(t *testing.T) {
|
||||||
RemoteURL: truthEnv.server.URL,
|
RemoteURL: truthEnv.server.URL,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
proxyConfig.Compatibility.Schema1.Enabled = true //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
proxyConfig.HTTP.Headers = headerConfig
|
proxyConfig.HTTP.Headers = headerConfig
|
||||||
|
|
||||||
proxyEnv := newTestEnvWithConfig(t, &proxyConfig)
|
proxyEnv := newTestEnvWithConfig(t, &proxyConfig)
|
||||||
|
|
|
@ -40,7 +40,6 @@ import (
|
||||||
"github.com/distribution/distribution/v3/version"
|
"github.com/distribution/distribution/v3/version"
|
||||||
events "github.com/docker/go-events"
|
events "github.com/docker/go-events"
|
||||||
"github.com/docker/go-metrics"
|
"github.com/docker/go-metrics"
|
||||||
"github.com/docker/libtrust"
|
|
||||||
"github.com/gomodule/redigo/redis"
|
"github.com/gomodule/redigo/redis"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -79,11 +78,6 @@ type App struct {
|
||||||
|
|
||||||
redis *redis.Pool
|
redis *redis.Pool
|
||||||
|
|
||||||
// trustKey is a deprecated key used to sign manifests converted to
|
|
||||||
// schema1 for backward compatibility. It should not be used for any
|
|
||||||
// other purposes.
|
|
||||||
trustKey libtrust.PrivateKey
|
|
||||||
|
|
||||||
// isCache is true if this registry is configured as a pull through cache
|
// isCache is true if this registry is configured as a pull through cache
|
||||||
isCache bool
|
isCache bool
|
||||||
|
|
||||||
|
@ -164,25 +158,6 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
|
||||||
app.configureLogHook(config)
|
app.configureLogHook(config)
|
||||||
|
|
||||||
options := registrymiddleware.GetRegistryOptions()
|
options := registrymiddleware.GetRegistryOptions()
|
||||||
if config.Compatibility.Schema1.TrustKey != "" {
|
|
||||||
app.trustKey, err = libtrust.LoadKeyFile(config.Compatibility.Schema1.TrustKey)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf(`could not load schema1 "signingkey" parameter: %v`, err))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Generate an ephemeral key to be used for signing converted manifests
|
|
||||||
// for clients that don't support schema2.
|
|
||||||
app.trustKey, err = libtrust.GenerateECP256PrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options = append(options, storage.Schema1SigningKey(app.trustKey))
|
|
||||||
|
|
||||||
if config.Compatibility.Schema1.Enabled {
|
|
||||||
options = append(options, storage.EnableSchema1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.HTTP.Host != "" {
|
if config.HTTP.Host != "" {
|
||||||
u, err := url.Parse(config.HTTP.Host)
|
u, err := url.Parse(config.HTTP.Host)
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
dcontext "github.com/distribution/distribution/v3/context"
|
dcontext "github.com/distribution/distribution/v3/context"
|
||||||
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
"github.com/distribution/distribution/v3/manifest/manifestlist"
|
||||||
"github.com/distribution/distribution/v3/manifest/ocischema"
|
"github.com/distribution/distribution/v3/manifest/ocischema"
|
||||||
"github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
"github.com/distribution/distribution/v3/manifest/schema2"
|
"github.com/distribution/distribution/v3/manifest/schema2"
|
||||||
"github.com/distribution/distribution/v3/reference"
|
"github.com/distribution/distribution/v3/reference"
|
||||||
"github.com/distribution/distribution/v3/registry/api/errcode"
|
"github.com/distribution/distribution/v3/registry/api/errcode"
|
||||||
|
@ -23,8 +22,6 @@ import (
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These constants determine which architecture and OS to choose from a
|
|
||||||
// manifest list when downconverting it to a schema1 manifest.
|
|
||||||
const (
|
const (
|
||||||
defaultArch = "amd64"
|
defaultArch = "amd64"
|
||||||
defaultOS = "linux"
|
defaultOS = "linux"
|
||||||
|
@ -35,12 +32,11 @@ const (
|
||||||
type storageType int
|
type storageType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
manifestSchema1 storageType = iota // 0
|
manifestSchema2 storageType = iota // 0
|
||||||
manifestSchema2 // 1
|
manifestlistSchema // 1
|
||||||
manifestlistSchema // 2
|
ociSchema // 2
|
||||||
ociSchema // 3
|
ociImageIndexSchema // 3
|
||||||
ociImageIndexSchema // 4
|
numStorageTypes // 4
|
||||||
numStorageTypes // 5
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// manifestDispatcher takes the request context and builds the
|
// manifestDispatcher takes the request context and builds the
|
||||||
|
@ -151,12 +147,9 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// determine the type of the returned manifest
|
// determine the type of the returned manifest
|
||||||
manifestType := manifestSchema1
|
manifestType := manifestSchema2
|
||||||
schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest)
|
|
||||||
manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList)
|
manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList)
|
||||||
if isSchema2 {
|
if _, isOCImanifest := manifest.(*ocischema.DeserializedManifest); isOCImanifest {
|
||||||
manifestType = manifestSchema2
|
|
||||||
} else if _, isOCImanifest := manifest.(*ocischema.DeserializedManifest); isOCImanifest {
|
|
||||||
manifestType = ociSchema
|
manifestType = ociSchema
|
||||||
} else if isManifestList {
|
} else if isManifestList {
|
||||||
if manifestList.MediaType == manifestlist.MediaTypeManifestList {
|
if manifestList.MediaType == manifestlist.MediaTypeManifestList {
|
||||||
|
@ -174,18 +167,8 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithMessage("OCI index found, but accept header does not support OCI indexes"))
|
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithMessage("OCI index found, but accept header does not support OCI indexes"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Only rewrite schema2 manifests when they are being fetched by tag.
|
|
||||||
// If they are being fetched by digest, we can't return something not
|
|
||||||
// matching the digest.
|
|
||||||
if imh.Tag != "" && manifestType == manifestSchema2 && !supports[manifestSchema2] {
|
|
||||||
// Rewrite manifest in schema1 format
|
|
||||||
dcontext.GetLogger(imh).Infof("rewriting manifest %s in schema1 format to support old client", imh.Digest.String())
|
|
||||||
|
|
||||||
manifest, err = imh.convertSchema2Manifest(schema2Manifest)
|
if imh.Tag != "" && manifestType == manifestlistSchema && !supports[manifestlistSchema] {
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if imh.Tag != "" && manifestType == manifestlistSchema && !supports[manifestlistSchema] {
|
|
||||||
// Rewrite manifest in schema1 format
|
// Rewrite manifest in schema1 format
|
||||||
dcontext.GetLogger(imh).Infof("rewriting manifest list %s in schema1 format to support old client", imh.Digest.String())
|
dcontext.GetLogger(imh).Infof("rewriting manifest list %s in schema1 format to support old client", imh.Digest.String())
|
||||||
|
|
||||||
|
@ -214,12 +197,9 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If necessary, convert the image manifest
|
if _, isSchema2 := manifest.(*schema2.DeserializedManifest); isSchema2 && !supports[manifestSchema2] {
|
||||||
if schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest); isSchema2 && !supports[manifestSchema2] {
|
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithMessage("Schema 2 manifest not supported by client"))
|
||||||
manifest, err = imh.convertSchema2Manifest(schema2Manifest)
|
return
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
imh.Digest = manifestDigest
|
imh.Digest = manifestDigest
|
||||||
}
|
}
|
||||||
|
@ -237,46 +217,6 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
|
||||||
w.Write(p)
|
w.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imh *manifestHandler) convertSchema2Manifest(schema2Manifest *schema2.DeserializedManifest) (distribution.Manifest, error) {
|
|
||||||
targetDescriptor := schema2Manifest.Target()
|
|
||||||
blobs := imh.Repository.Blobs(imh)
|
|
||||||
configJSON, err := blobs.Get(imh, targetDescriptor.Digest)
|
|
||||||
if err != nil {
|
|
||||||
if err == distribution.ErrBlobUnknown {
|
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
|
|
||||||
} else {
|
|
||||||
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ref := imh.Repository.Named()
|
|
||||||
|
|
||||||
if imh.Tag != "" {
|
|
||||||
ref, err = reference.WithTag(ref, imh.Tag)
|
|
||||||
if err != nil {
|
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeTagInvalid.WithDetail(err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder := schema1.NewConfigManifestBuilder(imh.Repository.Blobs(imh), imh.Context.App.trustKey, ref, configJSON) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
for _, d := range schema2Manifest.Layers {
|
|
||||||
if err := builder.AppendReference(d); err != nil {
|
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
manifest, err := builder.Build(imh)
|
|
||||||
if err != nil {
|
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
imh.Digest = digest.FromBytes(manifest.(*schema1.SignedManifest).Canonical) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
|
|
||||||
return manifest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func etagMatch(r *http.Request, etag string) bool {
|
func etagMatch(r *http.Request, etag string) bool {
|
||||||
for _, headerVal := range r.Header["If-None-Match"] {
|
for _, headerVal := range r.Header["If-None-Match"] {
|
||||||
if headerVal == etag || headerVal == fmt.Sprintf(`"%s"`, etag) { // allow quoted or unquoted
|
if headerVal == etag || headerVal == fmt.Sprintf(`"%s"`, etag) { // allow quoted or unquoted
|
||||||
|
@ -421,8 +361,6 @@ func (imh *manifestHandler) applyResourcePolicy(manifest distribution.Manifest)
|
||||||
|
|
||||||
var class string
|
var class string
|
||||||
switch m := manifest.(type) {
|
switch m := manifest.(type) {
|
||||||
case *schema1.SignedManifest: //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
|
|
||||||
class = imageClass
|
|
||||||
case *schema2.DeserializedManifest:
|
case *schema2.DeserializedManifest:
|
||||||
switch m.Config.MediaType {
|
switch m.Config.MediaType {
|
||||||
case schema2.MediaTypeImageConfig:
|
case schema2.MediaTypeImageConfig:
|
||||||
|
|
Loading…
Reference in a new issue