2015-04-17 20:32:51 +00:00
package client
import (
"bytes"
"crypto/rand"
2016-06-28 21:44:51 +00:00
"encoding/json"
2015-04-17 20:32:51 +00:00
"fmt"
2015-07-17 18:42:47 +00:00
"io"
2015-04-17 20:32:51 +00:00
"log"
"net/http"
"net/http/httptest"
2017-09-20 01:25:37 +00:00
"reflect"
"sort"
2015-07-13 20:08:13 +00:00
"strconv"
2015-05-09 00:40:30 +00:00
"strings"
2015-04-17 20:32:51 +00:00
"testing"
"time"
2020-08-24 11:18:39 +00:00
"github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/context"
"github.com/distribution/distribution/v3/manifest"
2023-05-09 11:18:47 +00:00
"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.
2020-08-24 11:18:39 +00:00
"github.com/distribution/distribution/v3/reference"
"github.com/distribution/distribution/v3/registry/api/errcode"
v2 "github.com/distribution/distribution/v3/registry/api/v2"
"github.com/distribution/distribution/v3/testutil"
"github.com/distribution/distribution/v3/uuid"
2015-09-15 04:12:33 +00:00
"github.com/docker/libtrust"
2016-12-17 00:28:34 +00:00
"github.com/opencontainers/go-digest"
2015-04-17 20:32:51 +00:00
)
2015-05-08 23:29:23 +00:00
func testServer ( rrm testutil . RequestResponseMap ) ( string , func ( ) ) {
2015-04-17 20:32:51 +00:00
h := testutil . NewHandler ( rrm )
s := httptest . NewServer ( h )
2015-05-08 23:29:23 +00:00
return s . URL , s . Close
2015-04-17 20:32:51 +00:00
}
func newRandomBlob ( size int ) ( digest . Digest , [ ] byte ) {
b := make ( [ ] byte , size )
if n , err := rand . Read ( b ) ; err != nil {
panic ( err )
} else if n != size {
panic ( "unable to read enough bytes" )
}
2015-12-14 22:30:51 +00:00
return digest . FromBytes ( b ) , b
2015-04-17 20:32:51 +00:00
}
func addTestFetch ( repo string , dgst digest . Digest , content [ ] byte , m * testutil . RequestResponseMap ) {
* m = append ( * m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodGet ,
2015-04-17 20:32:51 +00:00
Route : "/v2/" + repo + "/blobs/" + dgst . String ( ) ,
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Body : content ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( len ( content ) ) } ,
2019-05-10 10:07:50 +00:00
"Content-Type" : { "application/octet-stream" } ,
2015-04-17 20:32:51 +00:00
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
} ) ,
} ,
} )
2015-07-14 23:25:37 +00:00
2015-04-17 20:32:51 +00:00
* m = append ( * m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodHead ,
2015-04-17 20:32:51 +00:00
Route : "/v2/" + repo + "/blobs/" + dgst . String ( ) ,
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( len ( content ) ) } ,
2019-05-10 10:07:50 +00:00
"Content-Type" : { "application/octet-stream" } ,
2015-04-17 20:32:51 +00:00
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
} ) ,
} ,
} )
}
2015-07-17 18:42:47 +00:00
func addTestCatalog ( route string , content [ ] byte , link string , m * testutil . RequestResponseMap ) {
headers := map [ string ] [ ] string {
"Content-Length" : { strconv . Itoa ( len ( content ) ) } ,
2019-01-14 08:53:03 +00:00
"Content-Type" : { "application/json" } ,
2015-07-17 18:42:47 +00:00
}
if link != "" {
headers [ "Link" ] = append ( headers [ "Link" ] , link )
}
2015-07-13 20:08:13 +00:00
* m = append ( * m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodGet ,
2015-07-17 18:42:47 +00:00
Route : route ,
2015-07-13 20:08:13 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Body : content ,
2015-07-17 18:42:47 +00:00
Headers : http . Header ( headers ) ,
2015-07-13 20:08:13 +00:00
} ,
} )
}
2019-05-10 10:07:50 +00:00
func TestBlobServeBlob ( t * testing . T ) {
dgst , blob := newRandomBlob ( 1024 )
var m testutil . RequestResponseMap
addTestFetch ( "test.example.com/repo1" , dgst , blob , & m )
e , c := testServer ( m )
defer c ( )
ctx := context . Background ( )
repo , _ := reference . WithName ( "test.example.com/repo1" )
r , err := NewRepository ( repo , e , nil )
if err != nil {
t . Fatal ( err )
}
l := r . Blobs ( ctx )
resp := httptest . NewRecorder ( )
2022-11-02 22:31:23 +00:00
req := httptest . NewRequest ( http . MethodGet , "/" , nil )
2019-05-10 10:07:50 +00:00
err = l . ServeBlob ( ctx , resp , req , dgst )
if err != nil {
t . Errorf ( "Error serving blob: %s" , err . Error ( ) )
}
2022-11-02 21:55:22 +00:00
body , err := io . ReadAll ( resp . Body )
2019-05-10 10:07:50 +00:00
if err != nil {
t . Errorf ( "Error reading response body: %s" , err . Error ( ) )
}
if string ( body ) != string ( blob ) {
t . Errorf ( "Unexpected response body. Got %q, expected %q" , string ( body ) , string ( blob ) )
}
expectedHeaders := [ ] struct {
Name string
Value string
} {
{ Name : "Content-Length" , Value : "1024" } ,
{ Name : "Content-Type" , Value : "application/octet-stream" } ,
{ Name : "Docker-Content-Digest" , Value : dgst . String ( ) } ,
{ Name : "Etag" , Value : dgst . String ( ) } ,
}
for _ , h := range expectedHeaders {
if resp . Header ( ) . Get ( h . Name ) != h . Value {
t . Errorf ( "Unexpected %s. Got %s, expected %s" , h . Name , resp . Header ( ) . Get ( h . Name ) , h . Value )
}
}
2019-05-10 12:08:31 +00:00
}
func TestBlobServeBlobHEAD ( t * testing . T ) {
dgst , blob := newRandomBlob ( 1024 )
var m testutil . RequestResponseMap
addTestFetch ( "test.example.com/repo1" , dgst , blob , & m )
e , c := testServer ( m )
defer c ( )
ctx := context . Background ( )
repo , _ := reference . WithName ( "test.example.com/repo1" )
r , err := NewRepository ( repo , e , nil )
if err != nil {
t . Fatal ( err )
}
l := r . Blobs ( ctx )
resp := httptest . NewRecorder ( )
2022-11-02 22:31:23 +00:00
req := httptest . NewRequest ( http . MethodHead , "/" , nil )
2019-05-10 12:08:31 +00:00
err = l . ServeBlob ( ctx , resp , req , dgst )
if err != nil {
t . Errorf ( "Error serving blob: %s" , err . Error ( ) )
}
2022-11-02 21:55:22 +00:00
body , err := io . ReadAll ( resp . Body )
2019-05-10 12:08:31 +00:00
if err != nil {
t . Errorf ( "Error reading response body: %s" , err . Error ( ) )
}
if string ( body ) != "" {
t . Errorf ( "Unexpected response body. Got %q, expected %q" , string ( body ) , "" )
}
expectedHeaders := [ ] struct {
Name string
Value string
} {
{ Name : "Content-Length" , Value : "1024" } ,
{ Name : "Content-Type" , Value : "application/octet-stream" } ,
{ Name : "Docker-Content-Digest" , Value : dgst . String ( ) } ,
{ Name : "Etag" , Value : dgst . String ( ) } ,
}
2019-05-10 10:07:50 +00:00
2019-05-10 12:08:31 +00:00
for _ , h := range expectedHeaders {
if resp . Header ( ) . Get ( h . Name ) != h . Value {
t . Errorf ( "Unexpected %s. Got %s, expected %s" , h . Name , resp . Header ( ) . Get ( h . Name ) , h . Value )
}
}
2019-05-10 10:07:50 +00:00
}
2019-05-07 08:33:45 +00:00
func TestBlobResume ( t * testing . T ) {
dgst , b1 := newRandomBlob ( 1024 )
id := uuid . Generate ( ) . String ( )
var m testutil . RequestResponseMap
repo , _ := reference . WithName ( "test.example.com/repo1" )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPatch ,
2019-05-07 08:33:45 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" + id ,
Body : b1 ,
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : http . Header ( map [ string ] [ ] string {
"Docker-Content-Digest" : { dgst . String ( ) } ,
"Range" : { fmt . Sprintf ( "0-%d" , len ( b1 ) - 1 ) } ,
} ) ,
} ,
} )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPut ,
2019-05-07 08:33:45 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" + id ,
QueryParams : map [ string ] [ ] string {
"digest" : { dgst . String ( ) } ,
} ,
} ,
Response : testutil . Response {
StatusCode : http . StatusCreated ,
Headers : http . Header ( map [ string ] [ ] string {
"Docker-Content-Digest" : { dgst . String ( ) } ,
"Content-Range" : { fmt . Sprintf ( "0-%d" , len ( b1 ) - 1 ) } ,
} ) ,
} ,
} )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodHead ,
2019-05-07 08:33:45 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/" + dgst . String ( ) ,
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( len ( b1 ) ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
} ) ,
} ,
} )
e , c := testServer ( m )
defer c ( )
ctx := context . Background ( )
r , err := NewRepository ( repo , e , nil )
if err != nil {
t . Fatal ( err )
}
l := r . Blobs ( ctx )
upload , err := l . Resume ( ctx , id )
if err != nil {
t . Errorf ( "Error resuming blob: %s" , err . Error ( ) )
}
if upload . ID ( ) != id {
t . Errorf ( "Unexpected UUID %s; expected %s" , upload . ID ( ) , id )
}
n , err := upload . ReadFrom ( bytes . NewReader ( b1 ) )
if err != nil {
t . Fatal ( err )
}
if n != int64 ( len ( b1 ) ) {
t . Fatalf ( "Unexpected ReadFrom length: %d; expected: %d" , n , len ( b1 ) )
}
blob , err := upload . Commit ( ctx , distribution . Descriptor {
Digest : dgst ,
Size : int64 ( len ( b1 ) ) ,
} )
if err != nil {
t . Fatal ( err )
}
if blob . Size != int64 ( len ( b1 ) ) {
t . Fatalf ( "Unexpected blob size: %d; expected: %d" , blob . Size , len ( b1 ) )
}
}
2015-05-27 17:52:22 +00:00
func TestBlobDelete ( t * testing . T ) {
dgst , _ := newRandomBlob ( 1024 )
var m testutil . RequestResponseMap
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/repo1" )
2015-05-27 17:52:22 +00:00
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodDelete ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/" + dgst . String ( ) ,
2015-05-27 17:52:22 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
} ) ,
} ,
} )
e , c := testServer ( m )
defer c ( )
ctx := context . Background ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-05-27 17:52:22 +00:00
if err != nil {
t . Fatal ( err )
}
l := r . Blobs ( ctx )
err = l . Delete ( ctx , dgst )
if err != nil {
t . Errorf ( "Error deleting blob: %s" , err . Error ( ) )
}
}
2015-05-15 20:29:44 +00:00
func TestBlobFetch ( t * testing . T ) {
2015-04-17 20:32:51 +00:00
d1 , b1 := newRandomBlob ( 1024 )
var m testutil . RequestResponseMap
addTestFetch ( "test.example.com/repo1" , d1 , b1 , & m )
e , c := testServer ( m )
defer c ( )
2015-05-15 20:29:44 +00:00
ctx := context . Background ( )
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/repo1" )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-05-15 20:29:44 +00:00
l := r . Blobs ( ctx )
2015-04-17 20:32:51 +00:00
2015-05-15 20:29:44 +00:00
b , err := l . Get ( ctx , d1 )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2019-02-05 00:01:04 +00:00
if ! bytes . Equal ( b , b1 ) {
2015-04-17 20:32:51 +00:00
t . Fatalf ( "Wrong bytes values fetched: [%d]byte != [%d]byte" , len ( b ) , len ( b1 ) )
}
2015-05-20 17:05:44 +00:00
// TODO(dmcgowan): Test for unknown blob case
2015-04-17 20:32:51 +00:00
}
2015-12-07 18:17:49 +00:00
func TestBlobExistsNoContentLength ( t * testing . T ) {
var m testutil . RequestResponseMap
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "biff" )
2015-12-07 18:17:49 +00:00
dgst , content := newRandomBlob ( 1024 )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodGet ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/" + dgst . String ( ) ,
2015-12-07 18:17:49 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Body : content ,
Headers : http . Header ( map [ string ] [ ] string {
// "Content-Length": {fmt.Sprint(len(content))},
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
} ) ,
} ,
} )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodHead ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/" + dgst . String ( ) ,
2015-12-07 18:17:49 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Headers : http . Header ( map [ string ] [ ] string {
// "Content-Length": {fmt.Sprint(len(content))},
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
} ) ,
} ,
} )
e , c := testServer ( m )
defer c ( )
ctx := context . Background ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-12-07 18:17:49 +00:00
if err != nil {
t . Fatal ( err )
}
l := r . Blobs ( ctx )
_ , err = l . Stat ( ctx , dgst )
if err == nil {
t . Fatal ( err )
}
if ! strings . Contains ( err . Error ( ) , "missing content-length heade" ) {
t . Fatalf ( "Expected missing content-length error message" )
}
}
2015-05-15 20:29:44 +00:00
func TestBlobExists ( t * testing . T ) {
2015-04-17 20:32:51 +00:00
d1 , b1 := newRandomBlob ( 1024 )
var m testutil . RequestResponseMap
addTestFetch ( "test.example.com/repo1" , d1 , b1 , & m )
e , c := testServer ( m )
defer c ( )
2015-05-15 20:29:44 +00:00
ctx := context . Background ( )
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/repo1" )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-05-15 20:29:44 +00:00
l := r . Blobs ( ctx )
2015-04-17 20:32:51 +00:00
2015-05-15 20:29:44 +00:00
stat , err := l . Stat ( ctx , d1 )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-05-15 20:29:44 +00:00
if stat . Digest != d1 {
t . Fatalf ( "Unexpected digest: %s, expected %s" , stat . Digest , d1 )
2015-04-17 20:32:51 +00:00
}
2015-07-18 00:07:11 +00:00
if stat . Size != int64 ( len ( b1 ) ) {
t . Fatalf ( "Unexpected length: %d, expected %d" , stat . Size , len ( b1 ) )
2015-05-15 20:29:44 +00:00
}
// TODO(dmcgowan): Test error cases and ErrBlobUnknown case
2015-04-17 20:32:51 +00:00
}
2015-05-15 20:29:44 +00:00
func TestBlobUploadChunked ( t * testing . T ) {
2015-04-17 20:32:51 +00:00
dgst , b1 := newRandomBlob ( 1024 )
var m testutil . RequestResponseMap
chunks := [ ] [ ] byte {
b1 [ 0 : 256 ] ,
b1 [ 256 : 512 ] ,
b1 [ 512 : 513 ] ,
b1 [ 513 : 1024 ] ,
}
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/uploadrepo" )
2015-05-22 22:55:54 +00:00
uuids := [ ] string { uuid . Generate ( ) . String ( ) }
2015-04-17 20:32:51 +00:00
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPost ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" ,
2015-04-17 20:32:51 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
2015-12-15 22:35:23 +00:00
"Location" : { "/v2/" + repo . Name ( ) + "/blobs/uploads/" + uuids [ 0 ] } ,
2015-04-17 20:32:51 +00:00
"Docker-Upload-UUID" : { uuids [ 0 ] } ,
"Range" : { "0-0" } ,
} ) ,
} ,
} )
offset := 0
for i , chunk := range chunks {
2015-05-22 22:55:54 +00:00
uuids = append ( uuids , uuid . Generate ( ) . String ( ) )
2015-04-17 20:32:51 +00:00
newOffset := offset + len ( chunk )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPatch ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" + uuids [ i ] ,
2015-04-17 20:32:51 +00:00
Body : chunk ,
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
2015-12-15 22:35:23 +00:00
"Location" : { "/v2/" + repo . Name ( ) + "/blobs/uploads/" + uuids [ i + 1 ] } ,
2015-04-17 20:32:51 +00:00
"Docker-Upload-UUID" : { uuids [ i + 1 ] } ,
"Range" : { fmt . Sprintf ( "%d-%d" , offset , newOffset - 1 ) } ,
} ) ,
} ,
} )
offset = newOffset
}
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPut ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" + uuids [ len ( uuids ) - 1 ] ,
2015-04-17 20:32:51 +00:00
QueryParams : map [ string ] [ ] string {
"digest" : { dgst . String ( ) } ,
} ,
} ,
Response : testutil . Response {
StatusCode : http . StatusCreated ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
"Docker-Content-Digest" : { dgst . String ( ) } ,
"Content-Range" : { fmt . Sprintf ( "0-%d" , offset - 1 ) } ,
} ) ,
} ,
} )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodHead ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/" + dgst . String ( ) ,
2015-04-17 20:32:51 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( offset ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
} ) ,
} ,
} )
e , c := testServer ( m )
defer c ( )
2015-05-15 20:29:44 +00:00
ctx := context . Background ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-05-15 20:29:44 +00:00
l := r . Blobs ( ctx )
2015-04-17 20:32:51 +00:00
2015-05-15 23:25:00 +00:00
upload , err := l . Create ( ctx )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-05-15 20:29:44 +00:00
if upload . ID ( ) != uuids [ 0 ] {
log . Fatalf ( "Unexpected UUID %s; expected %s" , upload . ID ( ) , uuids [ 0 ] )
2015-04-17 20:32:51 +00:00
}
for _ , chunk := range chunks {
n , err := upload . Write ( chunk )
if err != nil {
t . Fatal ( err )
}
if n != len ( chunk ) {
t . Fatalf ( "Unexpected length returned from write: %d; expected: %d" , n , len ( chunk ) )
}
}
2015-05-15 20:29:44 +00:00
blob , err := upload . Commit ( ctx , distribution . Descriptor {
Digest : dgst ,
2015-07-18 00:07:11 +00:00
Size : int64 ( len ( b1 ) ) ,
2015-05-15 20:29:44 +00:00
} )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-07-18 00:07:11 +00:00
if blob . Size != int64 ( len ( b1 ) ) {
t . Fatalf ( "Unexpected blob size: %d; expected: %d" , blob . Size , len ( b1 ) )
2015-04-17 20:32:51 +00:00
}
}
2015-05-15 20:29:44 +00:00
func TestBlobUploadMonolithic ( t * testing . T ) {
2015-04-17 20:32:51 +00:00
dgst , b1 := newRandomBlob ( 1024 )
var m testutil . RequestResponseMap
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/uploadrepo" )
2015-05-22 22:55:54 +00:00
uploadID := uuid . Generate ( ) . String ( )
2015-04-17 20:32:51 +00:00
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPost ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" ,
2015-04-17 20:32:51 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
2015-12-15 22:35:23 +00:00
"Location" : { "/v2/" + repo . Name ( ) + "/blobs/uploads/" + uploadID } ,
2015-04-17 20:32:51 +00:00
"Docker-Upload-UUID" : { uploadID } ,
"Range" : { "0-0" } ,
} ) ,
} ,
} )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPatch ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" + uploadID ,
2015-04-17 20:32:51 +00:00
Body : b1 ,
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : http . Header ( map [ string ] [ ] string {
2015-12-15 22:35:23 +00:00
"Location" : { "/v2/" + repo . Name ( ) + "/blobs/uploads/" + uploadID } ,
2015-04-17 20:32:51 +00:00
"Docker-Upload-UUID" : { uploadID } ,
"Content-Length" : { "0" } ,
"Docker-Content-Digest" : { dgst . String ( ) } ,
"Range" : { fmt . Sprintf ( "0-%d" , len ( b1 ) - 1 ) } ,
} ) ,
} ,
} )
2019-05-21 08:14:59 +00:00
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPut ,
2019-05-21 08:14:59 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" + uploadID ,
QueryParams : map [ string ] [ ] string {
"digest" : { dgst . String ( ) } ,
} ,
} ,
Response : testutil . Response {
StatusCode : http . StatusCreated ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
"Docker-Content-Digest" : { dgst . String ( ) } ,
"Content-Range" : { fmt . Sprintf ( "0-%d" , len ( b1 ) - 1 ) } ,
} ) ,
} ,
} )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodHead ,
2019-05-21 08:14:59 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/" + dgst . String ( ) ,
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( len ( b1 ) ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
} ) ,
} ,
} )
e , c := testServer ( m )
defer c ( )
ctx := context . Background ( )
r , err := NewRepository ( repo , e , nil )
if err != nil {
t . Fatal ( err )
}
l := r . Blobs ( ctx )
upload , err := l . Create ( ctx )
if err != nil {
t . Fatal ( err )
}
if upload . ID ( ) != uploadID {
log . Fatalf ( "Unexpected UUID %s; expected %s" , upload . ID ( ) , uploadID )
}
n , err := upload . ReadFrom ( bytes . NewReader ( b1 ) )
if err != nil {
t . Fatal ( err )
}
if n != int64 ( len ( b1 ) ) {
t . Fatalf ( "Unexpected ReadFrom length: %d; expected: %d" , n , len ( b1 ) )
}
blob , err := upload . Commit ( ctx , distribution . Descriptor {
Digest : dgst ,
Size : int64 ( len ( b1 ) ) ,
} )
if err != nil {
t . Fatal ( err )
}
if blob . Size != int64 ( len ( b1 ) ) {
t . Fatalf ( "Unexpected blob size: %d; expected: %d" , blob . Size , len ( b1 ) )
}
}
2019-05-21 08:22:29 +00:00
func TestBlobUploadMonolithicDockerUploadUUIDFromURL ( t * testing . T ) {
2019-05-21 08:14:59 +00:00
dgst , b1 := newRandomBlob ( 1024 )
var m testutil . RequestResponseMap
repo , _ := reference . WithName ( "test.example.com/uploadrepo" )
uploadID := uuid . Generate ( ) . String ( )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPost ,
2019-05-21 08:14:59 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" ,
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
"Location" : { "/v2/" + repo . Name ( ) + "/blobs/uploads/" + uploadID } ,
"Range" : { "0-0" } ,
} ) ,
} ,
} )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPatch ,
2019-05-21 08:14:59 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" + uploadID ,
Body : b1 ,
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : http . Header ( map [ string ] [ ] string {
"Location" : { "/v2/" + repo . Name ( ) + "/blobs/uploads/" + uploadID } ,
"Content-Length" : { "0" } ,
"Docker-Content-Digest" : { dgst . String ( ) } ,
"Range" : { fmt . Sprintf ( "0-%d" , len ( b1 ) - 1 ) } ,
} ) ,
} ,
} )
2015-04-17 20:32:51 +00:00
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPut ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" + uploadID ,
2015-04-17 20:32:51 +00:00
QueryParams : map [ string ] [ ] string {
"digest" : { dgst . String ( ) } ,
} ,
} ,
Response : testutil . Response {
StatusCode : http . StatusCreated ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
"Docker-Content-Digest" : { dgst . String ( ) } ,
"Content-Range" : { fmt . Sprintf ( "0-%d" , len ( b1 ) - 1 ) } ,
} ) ,
} ,
} )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodHead ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/" + dgst . String ( ) ,
2015-04-17 20:32:51 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( len ( b1 ) ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
} ) ,
} ,
} )
e , c := testServer ( m )
defer c ( )
2015-05-15 20:29:44 +00:00
ctx := context . Background ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-05-15 20:29:44 +00:00
l := r . Blobs ( ctx )
2015-04-17 20:32:51 +00:00
2015-05-15 23:25:00 +00:00
upload , err := l . Create ( ctx )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-05-15 20:29:44 +00:00
if upload . ID ( ) != uploadID {
log . Fatalf ( "Unexpected UUID %s; expected %s" , upload . ID ( ) , uploadID )
2015-04-17 20:32:51 +00:00
}
n , err := upload . ReadFrom ( bytes . NewReader ( b1 ) )
if err != nil {
t . Fatal ( err )
}
if n != int64 ( len ( b1 ) ) {
t . Fatalf ( "Unexpected ReadFrom length: %d; expected: %d" , n , len ( b1 ) )
}
2015-05-15 20:29:44 +00:00
blob , err := upload . Commit ( ctx , distribution . Descriptor {
Digest : dgst ,
2015-07-18 00:07:11 +00:00
Size : int64 ( len ( b1 ) ) ,
2015-05-15 20:29:44 +00:00
} )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-07-18 00:07:11 +00:00
if blob . Size != int64 ( len ( b1 ) ) {
t . Fatalf ( "Unexpected blob size: %d; expected: %d" , blob . Size , len ( b1 ) )
2015-04-17 20:32:51 +00:00
}
}
2019-05-21 08:22:29 +00:00
func TestBlobUploadMonolithicNoDockerUploadUUID ( t * testing . T ) {
dgst , b1 := newRandomBlob ( 1024 )
var m testutil . RequestResponseMap
repo , _ := reference . WithName ( "test.example.com/uploadrepo" )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPost ,
2019-05-21 08:22:29 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" ,
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
"Location" : { "/v2/" + repo . Name ( ) + "/blobs/uploads/" } ,
"Range" : { "0-0" } ,
} ) ,
} ,
} )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPatch ,
2019-05-21 08:22:29 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" ,
Body : b1 ,
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : http . Header ( map [ string ] [ ] string {
"Location" : { "/v2/" + repo . Name ( ) + "/blobs/uploads/" } ,
"Content-Length" : { "0" } ,
"Docker-Content-Digest" : { dgst . String ( ) } ,
"Range" : { fmt . Sprintf ( "0-%d" , len ( b1 ) - 1 ) } ,
} ) ,
} ,
} )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPut ,
2019-05-21 08:22:29 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" ,
QueryParams : map [ string ] [ ] string {
"digest" : { dgst . String ( ) } ,
} ,
} ,
Response : testutil . Response {
StatusCode : http . StatusCreated ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
"Docker-Content-Digest" : { dgst . String ( ) } ,
"Content-Range" : { fmt . Sprintf ( "0-%d" , len ( b1 ) - 1 ) } ,
} ) ,
} ,
} )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodHead ,
2019-05-21 08:22:29 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/" + dgst . String ( ) ,
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( len ( b1 ) ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
} ) ,
} ,
} )
e , c := testServer ( m )
defer c ( )
ctx := context . Background ( )
r , err := NewRepository ( repo , e , nil )
if err != nil {
t . Fatal ( err )
}
l := r . Blobs ( ctx )
upload , err := l . Create ( ctx )
if err . Error ( ) != "cannot retrieve docker upload UUID" {
log . Fatalf ( "expected rejection to retrieve docker upload UUID error. Got %q" , err )
}
if upload != nil {
log . Fatal ( "Expected upload to be nil" )
}
}
2015-12-15 02:34:18 +00:00
func TestBlobMount ( t * testing . T ) {
dgst , content := newRandomBlob ( 1024 )
var m testutil . RequestResponseMap
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/uploadrepo" )
2016-01-13 19:44:42 +00:00
2017-01-14 01:06:03 +00:00
sourceRepo , _ := reference . WithName ( "test.example.com/sourcerepo" )
2015-12-15 22:35:23 +00:00
canonicalRef , _ := reference . WithDigest ( sourceRepo , dgst )
2016-01-13 19:44:42 +00:00
2015-12-15 02:34:18 +00:00
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPost ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/uploads/" ,
QueryParams : map [ string ] [ ] string { "from" : { sourceRepo . Name ( ) } , "mount" : { dgst . String ( ) } } ,
2015-12-15 02:34:18 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusCreated ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
2015-12-15 22:35:23 +00:00
"Location" : { "/v2/" + repo . Name ( ) + "/blobs/" + dgst . String ( ) } ,
2015-12-15 02:34:18 +00:00
"Docker-Content-Digest" : { dgst . String ( ) } ,
} ) ,
} ,
} )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodHead ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/blobs/" + dgst . String ( ) ,
2015-12-15 02:34:18 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( len ( content ) ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
} ) ,
} ,
} )
e , c := testServer ( m )
defer c ( )
ctx := context . Background ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-12-15 02:34:18 +00:00
if err != nil {
t . Fatal ( err )
}
l := r . Blobs ( ctx )
2016-01-14 18:08:52 +00:00
bw , err := l . Create ( ctx , WithMountFrom ( canonicalRef ) )
2016-01-13 19:44:42 +00:00
if bw != nil {
t . Fatalf ( "Expected blob writer to be nil, was %v" , bw )
2015-12-15 02:34:18 +00:00
}
2016-01-13 19:44:42 +00:00
if ebm , ok := err . ( distribution . ErrBlobMounted ) ; ok {
if ebm . From . Digest ( ) != dgst {
t . Fatalf ( "Unexpected digest: %s, expected %s" , ebm . From . Digest ( ) , dgst )
}
2015-12-15 22:35:23 +00:00
if ebm . From . Name ( ) != sourceRepo . Name ( ) {
2016-01-13 19:44:42 +00:00
t . Fatalf ( "Unexpected from: %s, expected %s" , ebm . From . Name ( ) , sourceRepo )
}
} else {
t . Fatalf ( "Unexpected error: %v, expected an ErrBlobMounted" , err )
2015-12-15 02:34:18 +00:00
}
}
2023-05-09 11:18:47 +00:00
func newRandomSchemaV1Manifest ( name reference . Named , tag string , blobCount int ) ( * schema1 . SignedManifest , digest . Digest , [ ] byte ) { //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
blobs := make ( [ ] schema1 . FSLayer , blobCount ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
history := make ( [ ] schema1 . History , blobCount ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-04-17 20:32:51 +00:00
for i := 0 ; i < blobCount ; i ++ {
dgst , blob := newRandomBlob ( ( i % 5 ) * 16 )
2023-05-09 11:18:47 +00:00
blobs [ i ] = schema1 . FSLayer { BlobSum : dgst } //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
history [ i ] = schema1 . History { V1Compatibility : fmt . Sprintf ( "{\"Hex\": \"%x\"}" , blob ) } //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-04-17 20:32:51 +00:00
}
2023-05-09 11:18:47 +00:00
m := schema1 . Manifest { //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-12-15 22:35:23 +00:00
Name : name . String ( ) ,
2015-09-15 04:12:33 +00:00
Tag : tag ,
Architecture : "x86" ,
FSLayers : blobs ,
History : history ,
Versioned : manifest . Versioned {
SchemaVersion : 1 ,
2015-04-17 20:32:51 +00:00
} ,
}
2015-09-15 04:12:33 +00:00
pk , err := libtrust . GenerateECP256PrivateKey ( )
2015-04-17 20:32:51 +00:00
if err != nil {
panic ( err )
}
2015-09-15 04:12:33 +00:00
2023-05-09 11:18:47 +00:00
sm , err := schema1 . Sign ( & m , pk ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-04-17 20:32:51 +00:00
if err != nil {
panic ( err )
}
2015-08-21 04:50:15 +00:00
return sm , digest . FromBytes ( sm . Canonical ) , sm . Canonical
2015-04-17 20:32:51 +00:00
}
2015-12-15 22:35:23 +00:00
func addTestManifestWithEtag ( repo reference . Named , reference string , content [ ] byte , m * testutil . RequestResponseMap , dgst string ) {
2015-12-14 22:30:51 +00:00
actualDigest := digest . FromBytes ( content )
2015-07-14 23:25:37 +00:00
getReqWithEtag := testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodGet ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/manifests/" + reference ,
2015-07-14 23:25:37 +00:00
Headers : http . Header ( map [ string ] [ ] string {
2015-07-29 18:12:01 +00:00
"If-None-Match" : { fmt . Sprintf ( ` "%s" ` , dgst ) } ,
2015-07-14 23:25:37 +00:00
} ) ,
}
var getRespWithEtag testutil . Response
if actualDigest . String ( ) == dgst {
getRespWithEtag = testutil . Response {
StatusCode : http . StatusNotModified ,
Body : [ ] byte { } ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
2023-05-09 11:18:47 +00:00
"Content-Type" : { schema1 . MediaTypeSignedManifest } , //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-07-14 23:25:37 +00:00
} ) ,
}
} else {
getRespWithEtag = testutil . Response {
StatusCode : http . StatusOK ,
Body : content ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( len ( content ) ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
2023-05-09 11:18:47 +00:00
"Content-Type" : { schema1 . MediaTypeSignedManifest } , //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-07-14 23:25:37 +00:00
} ) ,
}
}
* m = append ( * m , testutil . RequestResponseMapping { Request : getReqWithEtag , Response : getRespWithEtag } )
}
2016-06-09 00:02:29 +00:00
func contentDigestString ( mediatype string , content [ ] byte ) string {
2023-05-09 11:18:47 +00:00
if mediatype == schema1 . MediaTypeSignedManifest { //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2016-06-09 00:02:29 +00:00
m , _ , _ := distribution . UnmarshalManifest ( mediatype , content )
2023-05-09 11:18:47 +00:00
content = m . ( * schema1 . SignedManifest ) . Canonical //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2016-06-09 00:02:29 +00:00
}
return digest . Canonical . FromBytes ( content ) . String ( )
}
2015-12-15 22:35:23 +00:00
func addTestManifest ( repo reference . Named , reference string , mediatype string , content [ ] byte , m * testutil . RequestResponseMap ) {
2015-04-17 20:32:51 +00:00
* m = append ( * m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodGet ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/manifests/" + reference ,
2015-04-17 20:32:51 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Body : content ,
Headers : http . Header ( map [ string ] [ ] string {
2016-06-09 00:02:29 +00:00
"Content-Length" : { fmt . Sprint ( len ( content ) ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
"Content-Type" : { mediatype } ,
"Docker-Content-Digest" : { contentDigestString ( mediatype , content ) } ,
2015-04-17 20:32:51 +00:00
} ) ,
} ,
} )
* m = append ( * m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodHead ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/manifests/" + reference ,
2015-04-17 20:32:51 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Headers : http . Header ( map [ string ] [ ] string {
2016-06-09 00:02:29 +00:00
"Content-Length" : { fmt . Sprint ( len ( content ) ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
"Content-Type" : { mediatype } ,
"Docker-Content-Digest" : { digest . Canonical . FromBytes ( content ) . String ( ) } ,
2015-04-17 20:32:51 +00:00
} ) ,
} ,
} )
2017-08-25 21:15:10 +00:00
}
2015-04-17 20:32:51 +00:00
2017-08-25 21:15:10 +00:00
func addTestManifestWithoutDigestHeader ( repo reference . Named , reference string , mediatype string , content [ ] byte , m * testutil . RequestResponseMap ) {
* m = append ( * m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodGet ,
2017-08-25 21:15:10 +00:00
Route : "/v2/" + repo . Name ( ) + "/manifests/" + reference ,
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Body : content ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( len ( content ) ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
"Content-Type" : { mediatype } ,
} ) ,
} ,
} )
* m = append ( * m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodHead ,
2017-08-25 21:15:10 +00:00
Route : "/v2/" + repo . Name ( ) + "/manifests/" + reference ,
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( len ( content ) ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
"Content-Type" : { mediatype } ,
} ) ,
} ,
} )
2015-04-17 20:32:51 +00:00
}
2023-05-09 11:18:47 +00:00
func checkEqualManifest ( m1 , m2 * schema1 . SignedManifest ) error { //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-04-17 20:32:51 +00:00
if m1 . Name != m2 . Name {
return fmt . Errorf ( "name does not match %q != %q" , m1 . Name , m2 . Name )
}
if m1 . Tag != m2 . Tag {
return fmt . Errorf ( "tag does not match %q != %q" , m1 . Tag , m2 . Tag )
}
if len ( m1 . FSLayers ) != len ( m2 . FSLayers ) {
2015-05-15 20:29:44 +00:00
return fmt . Errorf ( "fs blob length does not match %d != %d" , len ( m1 . FSLayers ) , len ( m2 . FSLayers ) )
2015-04-17 20:32:51 +00:00
}
for i := range m1 . FSLayers {
if m1 . FSLayers [ i ] . BlobSum != m2 . FSLayers [ i ] . BlobSum {
return fmt . Errorf ( "blobsum does not match %q != %q" , m1 . FSLayers [ i ] . BlobSum , m2 . FSLayers [ i ] . BlobSum )
}
}
if len ( m1 . History ) != len ( m2 . History ) {
return fmt . Errorf ( "history length does not match %d != %d" , len ( m1 . History ) , len ( m2 . History ) )
}
for i := range m1 . History {
if m1 . History [ i ] . V1Compatibility != m2 . History [ i ] . V1Compatibility {
return fmt . Errorf ( "blobsum does not match %q != %q" , m1 . History [ i ] . V1Compatibility , m2 . History [ i ] . V1Compatibility )
}
}
return nil
}
2015-08-21 04:50:15 +00:00
func TestV1ManifestFetch ( t * testing . T ) {
2015-06-15 17:39:34 +00:00
ctx := context . Background ( )
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/repo" )
2015-09-15 04:12:33 +00:00
m1 , dgst , _ := newRandomSchemaV1Manifest ( repo , "latest" , 6 )
2015-04-17 20:32:51 +00:00
var m testutil . RequestResponseMap
2023-05-09 11:18:47 +00:00
_ , pl , err := m1 . Payload ( ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-08-21 04:50:15 +00:00
if err != nil {
t . Fatal ( err )
}
2023-05-09 11:18:47 +00:00
addTestManifest ( repo , dgst . String ( ) , schema1 . MediaTypeSignedManifest , pl , & m ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
addTestManifest ( repo , "latest" , schema1 . MediaTypeSignedManifest , pl , & m ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2016-01-21 17:34:06 +00:00
addTestManifest ( repo , "badcontenttype" , "text/html" , pl , & m )
2015-04-17 20:32:51 +00:00
e , c := testServer ( m )
defer c ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-06-15 17:39:34 +00:00
ms , err := r . Manifests ( ctx )
if err != nil {
t . Fatal ( err )
}
2015-04-17 20:32:51 +00:00
2015-08-21 04:50:15 +00:00
ok , err := ms . Exists ( ctx , dgst )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
if ! ok {
t . Fatal ( "Manifest does not exist" )
}
2015-08-21 04:50:15 +00:00
manifest , err := ms . Get ( ctx , dgst )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2023-05-09 11:18:47 +00:00
v1manifest , ok := manifest . ( * schema1 . SignedManifest ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-08-21 04:50:15 +00:00
if ! ok {
t . Fatalf ( "Unexpected manifest type from Get: %T" , manifest )
}
if err := checkEqualManifest ( v1manifest , m1 ) ; err != nil {
t . Fatal ( err )
}
2016-06-09 00:02:29 +00:00
var contentDigest digest . Digest
manifest , err = ms . Get ( ctx , dgst , distribution . WithTag ( "latest" ) , ReturnContentDigest ( & contentDigest ) )
2015-08-21 04:50:15 +00:00
if err != nil {
t . Fatal ( err )
}
2023-05-09 11:18:47 +00:00
v1manifest , ok = manifest . ( * schema1 . SignedManifest ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-08-21 04:50:15 +00:00
if ! ok {
t . Fatalf ( "Unexpected manifest type from Get: %T" , manifest )
}
if err = checkEqualManifest ( v1manifest , m1 ) ; err != nil {
2015-04-17 20:32:51 +00:00
t . Fatal ( err )
}
2016-01-21 17:34:06 +00:00
2016-06-09 00:02:29 +00:00
if contentDigest != dgst {
t . Fatalf ( "Unexpected returned content digest %v, expected %v" , contentDigest , dgst )
}
2016-03-18 22:30:47 +00:00
manifest , err = ms . Get ( ctx , dgst , distribution . WithTag ( "badcontenttype" ) )
2016-01-21 17:34:06 +00:00
if err != nil {
t . Fatal ( err )
}
2023-05-09 11:18:47 +00:00
v1manifest , ok = manifest . ( * schema1 . SignedManifest ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2016-01-21 17:34:06 +00:00
if ! ok {
t . Fatalf ( "Unexpected manifest type from Get: %T" , manifest )
}
if err = checkEqualManifest ( v1manifest , m1 ) ; err != nil {
t . Fatal ( err )
}
2015-04-17 20:32:51 +00:00
}
2015-07-14 23:25:37 +00:00
func TestManifestFetchWithEtag ( t * testing . T ) {
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/repo/by/tag" )
2015-09-15 04:12:33 +00:00
_ , d1 , p1 := newRandomSchemaV1Manifest ( repo , "latest" , 6 )
2015-04-17 20:32:51 +00:00
var m testutil . RequestResponseMap
2015-09-15 04:12:33 +00:00
addTestManifestWithEtag ( repo , "latest" , p1 , & m , d1 . String ( ) )
2015-04-17 20:32:51 +00:00
e , c := testServer ( m )
defer c ( )
2015-08-21 04:50:15 +00:00
ctx := context . Background ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-08-21 04:50:15 +00:00
2015-06-15 17:39:34 +00:00
ms , err := r . Manifests ( ctx )
if err != nil {
t . Fatal ( err )
}
2015-04-17 20:32:51 +00:00
2015-08-21 04:50:15 +00:00
clientManifestService , ok := ms . ( * manifests )
if ! ok {
panic ( "wrong type for client manifest service" )
}
2016-03-18 22:30:47 +00:00
_ , err = clientManifestService . Get ( ctx , d1 , distribution . WithTag ( "latest" ) , AddEtagToTag ( "latest" , d1 . String ( ) ) )
2015-09-18 18:00:44 +00:00
if err != distribution . ErrManifestNotModified {
2015-04-17 20:32:51 +00:00
t . Fatal ( err )
}
}
2017-09-20 01:25:37 +00:00
func TestManifestFetchWithAccept ( t * testing . T ) {
ctx := context . Background ( )
repo , _ := reference . WithName ( "test.example.com/repo" )
_ , dgst , _ := newRandomSchemaV1Manifest ( repo , "latest" , 6 )
headers := make ( chan [ ] string , 1 )
s := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
headers <- req . Header [ "Accept" ]
} ) )
defer close ( headers )
defer s . Close ( )
r , err := NewRepository ( repo , s . URL , nil )
if err != nil {
t . Fatal ( err )
}
ms , err := r . Manifests ( ctx )
if err != nil {
t . Fatal ( err )
}
testCases := [ ] struct {
// the media types we send
mediaTypes [ ] string
// the expected Accept headers the server should receive
expect [ ] string
// whether to sort the request and response values for comparison
sort bool
} {
{
mediaTypes : [ ] string { } ,
expect : distribution . ManifestMediaTypes ( ) ,
sort : true ,
} ,
{
mediaTypes : [ ] string { "test1" , "test2" } ,
expect : [ ] string { "test1" , "test2" } ,
} ,
{
mediaTypes : [ ] string { "test1" } ,
expect : [ ] string { "test1" } ,
} ,
{
mediaTypes : [ ] string { "" } ,
expect : [ ] string { "" } ,
} ,
}
for _ , testCase := range testCases {
ms . Get ( ctx , dgst , distribution . WithManifestMediaTypes ( testCase . mediaTypes ) )
actual := <- headers
if testCase . sort {
sort . Strings ( actual )
sort . Strings ( testCase . expect )
}
if ! reflect . DeepEqual ( actual , testCase . expect ) {
t . Fatalf ( "unexpected Accept header values: %v" , actual )
}
}
}
2015-04-17 20:32:51 +00:00
func TestManifestDelete ( t * testing . T ) {
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/repo/delete" )
2015-09-15 04:12:33 +00:00
_ , dgst1 , _ := newRandomSchemaV1Manifest ( repo , "latest" , 6 )
_ , dgst2 , _ := newRandomSchemaV1Manifest ( repo , "latest" , 6 )
2015-04-17 20:32:51 +00:00
var m testutil . RequestResponseMap
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodDelete ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/manifests/" + dgst1 . String ( ) ,
2015-04-17 20:32:51 +00:00
} ,
Response : testutil . Response {
2015-05-27 17:52:22 +00:00
StatusCode : http . StatusAccepted ,
2015-04-17 20:32:51 +00:00
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
} ) ,
} ,
} )
e , c := testServer ( m )
defer c ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-06-15 17:39:34 +00:00
ctx := context . Background ( )
ms , err := r . Manifests ( ctx )
if err != nil {
t . Fatal ( err )
}
2015-04-17 20:32:51 +00:00
2015-08-21 04:50:15 +00:00
if err := ms . Delete ( ctx , dgst1 ) ; err != nil {
2015-04-17 20:32:51 +00:00
t . Fatal ( err )
}
2015-08-21 04:50:15 +00:00
if err := ms . Delete ( ctx , dgst2 ) ; err == nil {
2015-04-17 20:32:51 +00:00
t . Fatal ( "Expected error deleting unknown manifest" )
}
// TODO(dmcgowan): Check for specific unknown error
}
func TestManifestPut ( t * testing . T ) {
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/repo/delete" )
2015-09-15 04:12:33 +00:00
m1 , dgst , _ := newRandomSchemaV1Manifest ( repo , "other" , 6 )
2015-08-21 04:50:15 +00:00
2023-05-09 11:18:47 +00:00
_ , payload , err := m1 . Payload ( ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-08-21 04:50:15 +00:00
if err != nil {
t . Fatal ( err )
}
2016-01-26 22:20:23 +00:00
2015-04-17 20:32:51 +00:00
var m testutil . RequestResponseMap
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPut ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/manifests/other" ,
2015-08-21 04:50:15 +00:00
Body : payload ,
2015-04-17 20:32:51 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
"Docker-Content-Digest" : { dgst . String ( ) } ,
} ) ,
} ,
} )
2016-01-26 22:20:23 +00:00
putDgst := digest . FromBytes ( m1 . Canonical )
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodPut ,
2016-01-26 22:20:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/manifests/" + putDgst . String ( ) ,
Body : payload ,
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { "0" } ,
"Docker-Content-Digest" : { putDgst . String ( ) } ,
} ) ,
} ,
} )
2015-04-17 20:32:51 +00:00
e , c := testServer ( m )
defer c ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-04-17 20:32:51 +00:00
if err != nil {
t . Fatal ( err )
}
2015-06-15 17:39:34 +00:00
ctx := context . Background ( )
ms , err := r . Manifests ( ctx )
if err != nil {
t . Fatal ( err )
}
2015-04-17 20:32:51 +00:00
2016-03-18 22:30:47 +00:00
if _ , err := ms . Put ( ctx , m1 , distribution . WithTag ( m1 . Tag ) ) ; err != nil {
2015-04-17 20:32:51 +00:00
t . Fatal ( err )
}
2016-01-26 22:20:23 +00:00
if _ , err := ms . Put ( ctx , m1 ) ; err != nil {
t . Fatal ( err )
}
2015-05-20 17:05:44 +00:00
// TODO(dmcgowan): Check for invalid input error
2015-04-17 20:32:51 +00:00
}
2015-05-09 00:40:30 +00:00
func TestManifestTags ( t * testing . T ) {
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/repo/tags/list" )
2015-05-09 00:40:30 +00:00
tagsList := [ ] byte ( strings . TrimSpace ( `
{
"name" : "test.example.com/repo/tags/list" ,
"tags" : [
"tag1" ,
"tag2" ,
"funtag"
]
}
` ) )
var m testutil . RequestResponseMap
2015-08-21 04:50:15 +00:00
for i := 0 ; i < 3 ; i ++ {
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodGet ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/tags/list" ,
2015-08-21 04:50:15 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Body : tagsList ,
Headers : http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( len ( tagsList ) ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
} ) ,
} ,
} )
}
2015-05-09 00:40:30 +00:00
e , c := testServer ( m )
defer c ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-05-09 00:40:30 +00:00
if err != nil {
t . Fatal ( err )
}
2015-08-21 04:50:15 +00:00
2015-06-15 17:39:34 +00:00
ctx := context . Background ( )
2015-08-21 04:50:15 +00:00
tagService := r . Tags ( ctx )
2015-05-09 00:40:30 +00:00
2015-08-21 04:50:15 +00:00
tags , err := tagService . All ( ctx )
2015-05-09 00:40:30 +00:00
if err != nil {
t . Fatal ( err )
}
if len ( tags ) != 3 {
t . Fatalf ( "Wrong number of tags returned: %d, expected 3" , len ( tags ) )
}
2015-08-21 04:50:15 +00:00
expected := map [ string ] struct { } {
"tag1" : { } ,
"tag2" : { } ,
"funtag" : { } ,
}
for _ , t := range tags {
delete ( expected , t )
}
if len ( expected ) != 0 {
t . Fatalf ( "unexpected tags returned: %v" , expected )
}
2015-05-09 00:40:30 +00:00
// TODO(dmcgowan): Check for error cases
}
2015-05-20 17:05:44 +00:00
2021-05-27 22:13:59 +00:00
func TestTagDelete ( t * testing . T ) {
tag := "latest"
repo , _ := reference . WithName ( "test.example.com/repo/delete" )
newRandomSchemaV1Manifest ( repo , tag , 1 )
var m testutil . RequestResponseMap
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodDelete ,
2021-05-27 22:13:59 +00:00
Route : "/v2/" + repo . Name ( ) + "/manifests/" + tag ,
} ,
Response : testutil . Response {
StatusCode : http . StatusAccepted ,
Headers : map [ string ] [ ] string {
"Content-Length" : { "0" } ,
} ,
} ,
} )
e , c := testServer ( m )
defer c ( )
r , err := NewRepository ( repo , e , nil )
if err != nil {
t . Fatal ( err )
}
ctx := context . Background ( )
ts := r . Tags ( ctx )
if err := ts . Untag ( ctx , tag ) ; err != nil {
t . Fatal ( err )
}
if err := ts . Untag ( ctx , tag ) ; err == nil {
t . Fatal ( "expected error deleting unknown tag" )
}
}
2016-08-26 21:35:04 +00:00
func TestObtainsErrorForMissingTag ( t * testing . T ) {
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/repo" )
2016-08-26 21:35:04 +00:00
var m testutil . RequestResponseMap
var errors errcode . Errors
errors = append ( errors , v2 . ErrorCodeManifestUnknown . WithDetail ( "unknown manifest" ) )
errBytes , err := json . Marshal ( errors )
if err != nil {
t . Fatal ( err )
}
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodGet ,
2016-08-26 21:35:04 +00:00
Route : "/v2/" + repo . Name ( ) + "/manifests/1.0.0" ,
} ,
Response : testutil . Response {
StatusCode : http . StatusNotFound ,
Body : errBytes ,
Headers : http . Header ( map [ string ] [ ] string {
2019-01-14 08:53:03 +00:00
"Content-Type" : { "application/json" } ,
2016-08-26 21:35:04 +00:00
} ) ,
} ,
} )
e , c := testServer ( m )
defer c ( )
ctx := context . Background ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2016-08-26 21:35:04 +00:00
if err != nil {
t . Fatal ( err )
}
tagService := r . Tags ( ctx )
_ , err = tagService . Get ( ctx , "1.0.0" )
if err == nil {
t . Fatalf ( "Expected an error" )
}
if ! strings . Contains ( err . Error ( ) , "manifest unknown" ) {
t . Fatalf ( "Expected unknown manifest error message" )
}
}
2017-08-25 21:15:10 +00:00
func TestObtainsManifestForTagWithoutHeaders ( t * testing . T ) {
repo , _ := reference . WithName ( "test.example.com/repo" )
var m testutil . RequestResponseMap
m1 , dgst , _ := newRandomSchemaV1Manifest ( repo , "latest" , 6 )
2023-05-09 11:18:47 +00:00
_ , pl , err := m1 . Payload ( ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2017-08-25 21:15:10 +00:00
if err != nil {
t . Fatal ( err )
}
2023-05-09 11:18:47 +00:00
addTestManifestWithoutDigestHeader ( repo , "1.0.0" , schema1 . MediaTypeSignedManifest , pl , & m ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2017-08-25 21:15:10 +00:00
e , c := testServer ( m )
defer c ( )
ctx := context . Background ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2017-08-25 21:15:10 +00:00
if err != nil {
t . Fatal ( err )
}
tagService := r . Tags ( ctx )
desc , err := tagService . Get ( ctx , "1.0.0" )
if err != nil {
t . Fatalf ( "Expected no error" )
}
if desc . Digest != dgst {
t . Fatalf ( "Unexpected digest" )
}
}
2022-11-02 21:05:45 +00:00
2016-06-28 21:44:51 +00:00
func TestManifestTagsPaginated ( t * testing . T ) {
s := httptest . NewServer ( http . NotFoundHandler ( ) )
defer s . Close ( )
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/repo/tags/list" )
2016-06-28 21:44:51 +00:00
tagsList := [ ] string { "tag1" , "tag2" , "funtag" }
var m testutil . RequestResponseMap
for i := 0 ; i < 3 ; i ++ {
body , err := json . Marshal ( map [ string ] interface { } {
"name" : "test.example.com/repo/tags/list" ,
"tags" : [ ] string { tagsList [ i ] } ,
} )
if err != nil {
t . Fatal ( err )
}
queryParams := make ( map [ string ] [ ] string )
if i > 0 {
queryParams [ "n" ] = [ ] string { "1" }
queryParams [ "last" ] = [ ] string { tagsList [ i - 1 ] }
}
2017-11-19 05:50:22 +00:00
// Test both relative and absolute links.
relativeLink := "/v2/" + repo . Name ( ) + "/tags/list?n=1&last=" + tagsList [ i ]
var link string
switch i {
case 0 :
link = relativeLink
case len ( tagsList ) - 1 :
link = ""
default :
link = s . URL + relativeLink
}
2016-06-28 21:44:51 +00:00
headers := http . Header ( map [ string ] [ ] string {
"Content-Length" : { fmt . Sprint ( len ( body ) ) } ,
"Last-Modified" : { time . Now ( ) . Add ( - 1 * time . Second ) . Format ( time . ANSIC ) } ,
} )
2017-11-19 05:50:22 +00:00
if link != "" {
headers . Set ( "Link" , fmt . Sprintf ( ` <%s>; rel="next" ` , link ) )
2016-06-28 21:44:51 +00:00
}
2017-11-19 05:50:22 +00:00
2016-06-28 21:44:51 +00:00
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodGet ,
2016-06-28 21:44:51 +00:00
Route : "/v2/" + repo . Name ( ) + "/tags/list" ,
QueryParams : queryParams ,
} ,
Response : testutil . Response {
StatusCode : http . StatusOK ,
Body : body ,
Headers : headers ,
} ,
} )
}
s . Config . Handler = testutil . NewHandler ( m )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , s . URL , nil )
2016-06-28 21:44:51 +00:00
if err != nil {
t . Fatal ( err )
}
ctx := context . Background ( )
tagService := r . Tags ( ctx )
tags , err := tagService . All ( ctx )
if err != nil {
t . Fatal ( tags , err )
}
if len ( tags ) != 3 {
t . Fatalf ( "Wrong number of tags returned: %d, expected 3" , len ( tags ) )
}
expected := map [ string ] struct { } {
"tag1" : { } ,
"tag2" : { } ,
"funtag" : { } ,
}
for _ , t := range tags {
delete ( expected , t )
}
if len ( expected ) != 0 {
t . Fatalf ( "unexpected tags returned: %v" , expected )
}
}
2015-05-20 17:05:44 +00:00
func TestManifestUnauthorized ( t * testing . T ) {
2017-01-14 01:06:03 +00:00
repo , _ := reference . WithName ( "test.example.com/repo" )
2015-09-15 04:12:33 +00:00
_ , dgst , _ := newRandomSchemaV1Manifest ( repo , "latest" , 6 )
2015-05-20 17:05:44 +00:00
var m testutil . RequestResponseMap
m = append ( m , testutil . RequestResponseMapping {
Request : testutil . Request {
2022-11-02 22:31:23 +00:00
Method : http . MethodGet ,
2015-12-15 22:35:23 +00:00
Route : "/v2/" + repo . Name ( ) + "/manifests/" + dgst . String ( ) ,
2015-05-20 17:05:44 +00:00
} ,
Response : testutil . Response {
StatusCode : http . StatusUnauthorized ,
Body : [ ] byte ( "<html>garbage</html>" ) ,
} ,
} )
e , c := testServer ( m )
defer c ( )
2017-09-02 15:37:34 +00:00
r , err := NewRepository ( repo , e , nil )
2015-05-20 17:05:44 +00:00
if err != nil {
t . Fatal ( err )
}
2015-06-15 17:39:34 +00:00
ctx := context . Background ( )
ms , err := r . Manifests ( ctx )
if err != nil {
t . Fatal ( err )
}
2015-05-20 17:05:44 +00:00
2015-08-21 04:50:15 +00:00
_ , err = ms . Get ( ctx , dgst )
2015-05-20 17:05:44 +00:00
if err == nil {
t . Fatal ( "Expected error fetching manifest" )
}
2015-06-18 00:39:27 +00:00
v2Err , ok := err . ( errcode . Error )
2015-05-20 17:05:44 +00:00
if ! ok {
t . Fatalf ( "Unexpected error type: %#v" , err )
}
2015-08-06 23:25:08 +00:00
if v2Err . Code != errcode . ErrorCodeUnauthorized {
2015-05-20 17:05:44 +00:00
t . Fatalf ( "Unexpected error code: %s" , v2Err . Code . String ( ) )
}
2015-08-06 23:25:08 +00:00
if expected := errcode . ErrorCodeUnauthorized . Message ( ) ; v2Err . Message != expected {
2015-06-18 00:39:27 +00:00
t . Fatalf ( "Unexpected message value: %q, expected %q" , v2Err . Message , expected )
2015-05-20 17:05:44 +00:00
}
}
2015-07-13 20:08:13 +00:00
func TestCatalog ( t * testing . T ) {
var m testutil . RequestResponseMap
2015-07-17 18:42:47 +00:00
addTestCatalog (
"/v2/_catalog?n=5" ,
[ ] byte ( "{\"repositories\":[\"foo\", \"bar\", \"baz\"]}" ) , "" , & m )
2015-07-13 20:08:13 +00:00
e , c := testServer ( m )
defer c ( )
2015-07-17 18:42:47 +00:00
entries := make ( [ ] string , 5 )
2017-09-02 15:37:34 +00:00
r , err := NewRegistry ( e , nil )
2015-07-22 22:18:03 +00:00
if err != nil {
t . Fatal ( err )
}
2015-07-13 20:08:13 +00:00
ctx := context . Background ( )
2015-07-22 22:18:03 +00:00
numFilled , err := r . Repositories ( ctx , entries , "" )
2015-07-17 18:42:47 +00:00
if err != io . EOF {
2015-07-13 20:08:13 +00:00
t . Fatal ( err )
}
2015-07-17 18:42:47 +00:00
if numFilled != 3 {
t . Fatalf ( "Got wrong number of repos" )
}
}
func TestCatalogInParts ( t * testing . T ) {
var m testutil . RequestResponseMap
addTestCatalog (
"/v2/_catalog?n=2" ,
[ ] byte ( "{\"repositories\":[\"bar\", \"baz\"]}" ) ,
"</v2/_catalog?last=baz&n=2>" , & m )
addTestCatalog (
"/v2/_catalog?last=baz&n=2" ,
[ ] byte ( "{\"repositories\":[\"foo\"]}" ) ,
"" , & m )
e , c := testServer ( m )
defer c ( )
entries := make ( [ ] string , 2 )
2017-09-02 15:37:34 +00:00
r , err := NewRegistry ( e , nil )
2015-07-22 22:18:03 +00:00
if err != nil {
t . Fatal ( err )
}
2015-07-17 18:42:47 +00:00
ctx := context . Background ( )
2015-07-22 22:18:03 +00:00
numFilled , err := r . Repositories ( ctx , entries , "" )
2015-07-13 20:08:13 +00:00
if err != nil {
t . Fatal ( err )
}
2015-07-17 18:42:47 +00:00
if numFilled != 2 {
t . Fatalf ( "Got wrong number of repos" )
}
2015-07-22 22:18:03 +00:00
numFilled , err = r . Repositories ( ctx , entries , "baz" )
2015-07-17 18:42:47 +00:00
if err != io . EOF {
t . Fatal ( err )
}
if numFilled != 1 {
2015-07-13 20:08:13 +00:00
t . Fatalf ( "Got wrong number of repos" )
}
}
2015-08-27 02:00:28 +00:00
func TestSanitizeLocation ( t * testing . T ) {
for _ , testcase := range [ ] struct {
description string
location string
source string
expected string
err error
} {
{
description : "ensure relative location correctly resolved" ,
location : "/v2/foo/baasdf" ,
source : "http://blahalaja.com/v1" ,
expected : "http://blahalaja.com/v2/foo/baasdf" ,
} ,
{
description : "ensure parameters are preserved" ,
location : "/v2/foo/baasdf?_state=asdfasfdasdfasdf&digest=foo" ,
source : "http://blahalaja.com/v1" ,
expected : "http://blahalaja.com/v2/foo/baasdf?_state=asdfasfdasdfasdf&digest=foo" ,
} ,
{
2018-09-27 12:27:09 +00:00
description : "ensure new hostname overridden" ,
2015-08-27 02:00:28 +00:00
location : "https://mwhahaha.com/v2/foo/baasdf?_state=asdfasfdasdfasdf" ,
source : "http://blahalaja.com/v1" ,
expected : "https://mwhahaha.com/v2/foo/baasdf?_state=asdfasfdasdfasdf" ,
} ,
} {
fatalf := func ( format string , args ... interface { } ) {
t . Fatalf ( testcase . description + ": " + format , args ... )
}
s , err := sanitizeLocation ( testcase . location , testcase . source )
if err != testcase . err {
if testcase . err != nil {
fatalf ( "expected error: %v != %v" , err , testcase )
} else {
fatalf ( "unexpected error sanitizing: %v" , err )
}
}
if s != testcase . expected {
fatalf ( "bad sanitize: %q != %q" , s , testcase . expected )
}
}
}