github.com/antevens/oras@v0.8.1/pkg/oras/oras_test.go (about)

     1  package oras
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  	"time"
    11  
    12  	orascontent "github.com/deislabs/oras/pkg/content"
    13  
    14  	"github.com/containerd/containerd/images"
    15  	"github.com/containerd/containerd/remotes"
    16  	"github.com/containerd/containerd/remotes/docker"
    17  	"github.com/docker/distribution/configuration"
    18  	"github.com/docker/distribution/registry"
    19  	_ "github.com/docker/distribution/registry/storage/driver/inmemory"
    20  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    21  	"github.com/phayes/freeport"
    22  	"github.com/stretchr/testify/suite"
    23  )
    24  
    25  var (
    26  	testTarball  = "../../testdata/charts/chartmuseum-1.8.2.tgz"
    27  	testDir      = "../../testdata/charts/chartmuseum"
    28  	testDirFiles = []string{
    29  		"Chart.yaml",
    30  		"values.yaml",
    31  		"README.md",
    32  		"templates/_helpers.tpl",
    33  		"templates/NOTES.txt",
    34  		"templates/service.yaml",
    35  		".helmignore",
    36  	}
    37  )
    38  
    39  type ORASTestSuite struct {
    40  	suite.Suite
    41  	DockerRegistryHost string
    42  }
    43  
    44  func newContext() context.Context {
    45  	return context.Background()
    46  }
    47  
    48  func newResolver() remotes.Resolver {
    49  	return docker.NewResolver(docker.ResolverOptions{})
    50  }
    51  
    52  // Start Docker registry
    53  func (suite *ORASTestSuite) SetupSuite() {
    54  	config := &configuration.Configuration{}
    55  	port, err := freeport.GetFreePort()
    56  	if err != nil {
    57  		suite.Nil(err, "no error finding free port for test registry")
    58  	}
    59  	suite.DockerRegistryHost = fmt.Sprintf("localhost:%d", port)
    60  	config.HTTP.Addr = fmt.Sprintf(":%d", port)
    61  	config.HTTP.DrainTimeout = time.Duration(10) * time.Second
    62  	config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
    63  	dockerRegistry, err := registry.NewRegistry(context.Background(), config)
    64  	suite.Nil(err, "no error finding free port for test registry")
    65  
    66  	go dockerRegistry.ListenAndServe()
    67  }
    68  
    69  // Push files to docker registry
    70  func (suite *ORASTestSuite) Test_0_Push() {
    71  	var (
    72  		err         error
    73  		ref         string
    74  		desc        ocispec.Descriptor
    75  		descriptors []ocispec.Descriptor
    76  		store       *orascontent.FileStore
    77  	)
    78  
    79  	_, err = Push(newContext(), nil, ref, nil, descriptors)
    80  	suite.NotNil(err, "error pushing with empty resolver")
    81  
    82  	_, err = Push(newContext(), newResolver(), ref, nil, descriptors)
    83  	suite.NotNil(err, "error pushing when context missing hostname")
    84  
    85  	ref = fmt.Sprintf("%s/empty:test", suite.DockerRegistryHost)
    86  	_, err = Push(newContext(), newResolver(), ref, nil, descriptors)
    87  	suite.NotNil(ErrEmptyDescriptors, err, "error pushing with empty descriptors")
    88  
    89  	// Load descriptors with test chart tgz (as single layer)
    90  	store = orascontent.NewFileStore("")
    91  	basename := filepath.Base(testTarball)
    92  	desc, err = store.Add(basename, "", testTarball)
    93  	suite.Nil(err, "no error loading test chart")
    94  	descriptors = []ocispec.Descriptor{desc}
    95  
    96  	ref = fmt.Sprintf("%s/chart-tgz:test", suite.DockerRegistryHost)
    97  	_, err = Push(newContext(), newResolver(), ref, store, descriptors)
    98  	suite.Nil(err, "no error pushing test chart tgz (as single layer)")
    99  
   100  	// Load descriptors with test chart dir (each file as layer)
   101  	testDirAbs, err := filepath.Abs(testDir)
   102  	suite.Nil(err, "no error parsing test directory")
   103  	store = orascontent.NewFileStore(testDirAbs)
   104  	descriptors = []ocispec.Descriptor{}
   105  	var ff = func(pathX string, infoX os.FileInfo, errX error) error {
   106  		if !infoX.IsDir() {
   107  			filename := filepath.Join(filepath.Dir(pathX), infoX.Name())
   108  			name := filepath.ToSlash(filename)
   109  			desc, err = store.Add(name, "", filename)
   110  			if err != nil {
   111  				return err
   112  			}
   113  			descriptors = append(descriptors, desc)
   114  		}
   115  		return nil
   116  	}
   117  
   118  	cwd, _ := os.Getwd()
   119  	os.Chdir(testDir)
   120  	filepath.Walk(".", ff)
   121  	os.Chdir(cwd)
   122  
   123  	ref = fmt.Sprintf("%s/chart-dir:test", suite.DockerRegistryHost)
   124  	_, err = Push(newContext(), newResolver(), ref, store, descriptors)
   125  	suite.Nil(err, "no error pushing test chart dir (each file as layer)")
   126  }
   127  
   128  // Pull files and verify descriptors
   129  func (suite *ORASTestSuite) Test_1_Pull() {
   130  	var (
   131  		err         error
   132  		ref         string
   133  		descriptors []ocispec.Descriptor
   134  		store       *orascontent.Memorystore
   135  	)
   136  
   137  	_, descriptors, err = Pull(newContext(), nil, ref, nil)
   138  	suite.NotNil(err, "error pulling with empty resolver")
   139  	suite.Nil(descriptors, "descriptors nil pulling with empty resolver")
   140  
   141  	// Pull non-existant
   142  	store = orascontent.NewMemoryStore()
   143  	ref = fmt.Sprintf("%s/nonexistant:test", suite.DockerRegistryHost)
   144  	_, descriptors, err = Pull(newContext(), newResolver(), ref, store)
   145  	suite.NotNil(err, "error pulling non-existant ref")
   146  	suite.Nil(descriptors, "descriptors empty with error")
   147  
   148  	// Pull chart-tgz
   149  	store = orascontent.NewMemoryStore()
   150  	ref = fmt.Sprintf("%s/chart-tgz:test", suite.DockerRegistryHost)
   151  	_, descriptors, err = Pull(newContext(), newResolver(), ref, store)
   152  	suite.Nil(err, "no error pulling chart-tgz ref")
   153  
   154  	// Verify the descriptors, single layer/file
   155  	content, err := ioutil.ReadFile(testTarball)
   156  	suite.Nil(err, "no error loading test chart")
   157  	name := filepath.Base(testTarball)
   158  	_, actualContent, ok := store.GetByName(name)
   159  	suite.True(ok, "find in memory")
   160  	suite.Equal(content, actualContent, ".tgz content matches on pull")
   161  
   162  	// Pull chart-dir
   163  	store = orascontent.NewMemoryStore()
   164  	ref = fmt.Sprintf("%s/chart-dir:test", suite.DockerRegistryHost)
   165  	_, descriptors, err = Pull(newContext(), newResolver(), ref, store)
   166  	suite.Nil(err, "no error pulling chart-dir ref")
   167  
   168  	// Verify the descriptors, multiple layers/files
   169  	cwd, _ := os.Getwd()
   170  	os.Chdir(testDir)
   171  	for _, filename := range testDirFiles {
   172  		content, err = ioutil.ReadFile(filename)
   173  		suite.Nil(err, fmt.Sprintf("no error loading %s", filename))
   174  		_, actualContent, ok := store.GetByName(filename)
   175  		suite.True(ok, "find in memory")
   176  		suite.Equal(content, actualContent, fmt.Sprintf("%s content matches on pull", filename))
   177  	}
   178  	os.Chdir(cwd)
   179  }
   180  
   181  // Push and pull with customized media types
   182  func (suite *ORASTestSuite) Test_2_MediaType() {
   183  	var (
   184  		testData = [][]string{
   185  			{"hi.txt", "application/vnd.me.hi", "hi"},
   186  			{"bye.txt", "application/vnd.me.bye", "bye"},
   187  		}
   188  		err         error
   189  		ref         string
   190  		descriptors []ocispec.Descriptor
   191  		store       *orascontent.Memorystore
   192  	)
   193  
   194  	// Push content with customized media types
   195  	store = orascontent.NewMemoryStore()
   196  	descriptors = nil
   197  	for _, data := range testData {
   198  		desc := store.Add(data[0], data[1], []byte(data[2]))
   199  		descriptors = append(descriptors, desc)
   200  	}
   201  	ref = fmt.Sprintf("%s/media-type:test", suite.DockerRegistryHost)
   202  	_, err = Push(newContext(), newResolver(), ref, store, descriptors)
   203  	suite.Nil(err, "no error pushing test data with customized media type")
   204  
   205  	// Pull with all media types
   206  	store = orascontent.NewMemoryStore()
   207  	ref = fmt.Sprintf("%s/media-type:test", suite.DockerRegistryHost)
   208  	_, descriptors, err = Pull(newContext(), newResolver(), ref, store)
   209  	suite.Nil(err, "no error pulling media-type ref")
   210  	suite.Equal(2, len(descriptors), "number of contents matches on pull")
   211  	for _, data := range testData {
   212  		_, actualContent, ok := store.GetByName(data[0])
   213  		suite.True(ok, "find in memory")
   214  		content := []byte(data[2])
   215  		suite.Equal(content, actualContent, "test content matches on pull")
   216  	}
   217  
   218  	// Pull with specified media type
   219  	store = orascontent.NewMemoryStore()
   220  	ref = fmt.Sprintf("%s/media-type:test", suite.DockerRegistryHost)
   221  	_, descriptors, err = Pull(newContext(), newResolver(), ref, store, WithAllowedMediaType(testData[0][1]))
   222  	suite.Nil(err, "no error pulling media-type ref")
   223  	suite.Equal(1, len(descriptors), "number of contents matches on pull")
   224  	for _, data := range testData[:1] {
   225  		_, actualContent, ok := store.GetByName(data[0])
   226  		suite.True(ok, "find in memory")
   227  		content := []byte(data[2])
   228  		suite.Equal(content, actualContent, "test content matches on pull")
   229  	}
   230  
   231  	// Pull with non-existing media type
   232  	store = orascontent.NewMemoryStore()
   233  	ref = fmt.Sprintf("%s/media-type:test", suite.DockerRegistryHost)
   234  	_, descriptors, err = Pull(newContext(), newResolver(), ref, store, WithAllowedMediaType("non.existing.media.type"))
   235  	suite.Nil(err, "no error pulling media-type ref")
   236  	suite.Equal(0, len(descriptors), "number of contents matches on pull")
   237  }
   238  
   239  // Pull with condition
   240  func (suite *ORASTestSuite) Test_3_Conditional_Pull() {
   241  	var (
   242  		testData = [][]string{
   243  			{"version.txt", "edge"},
   244  			{"content.txt", "hello world"},
   245  		}
   246  		err         error
   247  		ref         string
   248  		descriptors []ocispec.Descriptor
   249  		store       *orascontent.Memorystore
   250  		stop        bool
   251  	)
   252  
   253  	// Push test content
   254  	store = orascontent.NewMemoryStore()
   255  	descriptors = nil
   256  	for _, data := range testData {
   257  		desc := store.Add(data[0], "", []byte(data[1]))
   258  		descriptors = append(descriptors, desc)
   259  	}
   260  	ref = fmt.Sprintf("%s/conditional-pull:test", suite.DockerRegistryHost)
   261  	_, err = Push(newContext(), newResolver(), ref, store, descriptors)
   262  	suite.Nil(err, "no error pushing test data")
   263  
   264  	// Pull all contents in sequence
   265  	store = orascontent.NewMemoryStore()
   266  	ref = fmt.Sprintf("%s/conditional-pull:test", suite.DockerRegistryHost)
   267  	_, descriptors, err = Pull(newContext(), newResolver(), ref, store, WithPullByBFS)
   268  	suite.Nil(err, "no error pulling ref")
   269  	suite.Equal(2, len(descriptors), "number of contents matches on pull")
   270  	for i, data := range testData {
   271  		_, actualContent, ok := store.GetByName(data[0])
   272  		suite.True(ok, "find in memory")
   273  		content := []byte(data[1])
   274  		suite.Equal(content, actualContent, "test content matches on pull")
   275  		name, _ := orascontent.ResolveName(descriptors[i])
   276  		suite.Equal(data[0], name, "content sequence matches on pull")
   277  	}
   278  
   279  	// Selective pull contents: stop at the very beginning
   280  	store = orascontent.NewMemoryStore()
   281  	ref = fmt.Sprintf("%s/conditional-pull:test", suite.DockerRegistryHost)
   282  	_, descriptors, err = Pull(newContext(), newResolver(), ref, store, WithPullByBFS,
   283  		WithPullBaseHandler(images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   284  			if name, ok := orascontent.ResolveName(desc); ok && name == testData[0][0] {
   285  				return nil, ErrStopProcessing
   286  			}
   287  			return nil, nil
   288  		})))
   289  	suite.Nil(err, "no error pulling ref")
   290  	suite.Equal(0, len(descriptors), "number of contents matches on pull")
   291  
   292  	// Selective pull contents: stop in the middle
   293  	store = orascontent.NewMemoryStore()
   294  	ref = fmt.Sprintf("%s/conditional-pull:test", suite.DockerRegistryHost)
   295  	stop = false
   296  	_, descriptors, err = Pull(newContext(), newResolver(), ref, store, WithPullByBFS,
   297  		WithPullBaseHandler(images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   298  			if stop {
   299  				return nil, ErrStopProcessing
   300  			}
   301  			if name, ok := orascontent.ResolveName(desc); ok && name == testData[0][0] {
   302  				stop = true
   303  			}
   304  			return nil, nil
   305  		})))
   306  	suite.Nil(err, "no error pulling ref")
   307  	suite.Equal(1, len(descriptors), "number of contents matches on pull")
   308  	for _, data := range testData[:1] {
   309  		_, actualContent, ok := store.GetByName(data[0])
   310  		suite.True(ok, "find in memory")
   311  		content := []byte(data[1])
   312  		suite.Equal(content, actualContent, "test content matches on pull")
   313  	}
   314  }
   315  
   316  func TestORASTestSuite(t *testing.T) {
   317  	suite.Run(t, new(ORASTestSuite))
   318  }