github.com/quay/claircore@v1.5.28/test/layer.go (about)

     1  package test
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"context"
     7  	"crypto/rand"
     8  	"crypto/sha256"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"net/http/httptest"
    13  	"net/url"
    14  	"path"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/quay/claircore"
    22  	"github.com/quay/claircore/test/fetch"
    23  )
    24  
    25  // AnyDescription is pre-made [LayerDescription] for cases where the actual
    26  // contents of the description *shouldn't* matter.
    27  var AnyDescription = claircore.LayerDescription{
    28  	Digest:    `sha256:` + strings.Repeat(`deadbeef`, 8),
    29  	URI:       `example:test.AnyDescription`,
    30  	MediaType: MediaType,
    31  	Headers:   make(map[string][]string),
    32  }
    33  
    34  type layerserver struct {
    35  	now   time.Time
    36  	blobs []*bytes.Reader
    37  }
    38  
    39  func (l *layerserver) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    40  	ns := path.Base(r.URL.Path)
    41  	n, err := strconv.Atoi(ns)
    42  	if err != nil {
    43  		w.WriteHeader(http.StatusInternalServerError)
    44  		return
    45  	}
    46  	if n < 0 || n >= len(l.blobs) {
    47  		w.WriteHeader(http.StatusNotFound)
    48  		return
    49  	}
    50  	w.Header().Set("content-type", "application/vnd.oci.image.layer.v1.tar")
    51  	http.ServeContent(w, r, "layer.tar", l.now, l.blobs[n])
    52  }
    53  
    54  // ServeLayers constructs "n" random layers, arranges to serve them, and returns
    55  // corresponding LayerDescriptions.
    56  func ServeLayers(t *testing.T, n int) (*http.Client, []claircore.LayerDescription) {
    57  	const filesize = 32
    58  	lsrv := &layerserver{
    59  		now:   time.Now(),
    60  		blobs: make([]*bytes.Reader, n),
    61  	}
    62  	descs := make([]claircore.LayerDescription, n)
    63  	srv := httptest.NewServer(lsrv)
    64  	t.Cleanup(func() {
    65  		srv.CloseClientConnections()
    66  		srv.Close()
    67  	})
    68  	u, err := url.Parse(srv.URL)
    69  	if err != nil {
    70  		t.Fatal(err)
    71  	}
    72  
    73  	for i := 0; i < n; i++ {
    74  		buf := &bytes.Buffer{}
    75  		h := sha256.New()
    76  		w := tar.NewWriter(io.MultiWriter(buf, h))
    77  		u, err := u.Parse(strconv.Itoa(i))
    78  		if err != nil {
    79  			t.Fatal(err)
    80  		}
    81  
    82  		if err := w.WriteHeader(&tar.Header{
    83  			Typeflag: tar.TypeReg,
    84  			Name:     "./randomfile",
    85  			Size:     filesize,
    86  			Mode:     0755,
    87  			Uid:      1000,
    88  			Gid:      1000,
    89  			ModTime:  lsrv.now,
    90  		}); err != nil {
    91  			t.Fatal(err)
    92  		}
    93  		if _, err := io.Copy(w, io.LimitReader(rand.Reader, filesize)); err != nil {
    94  			t.Fatal(err)
    95  		}
    96  		if err := w.Close(); err != nil {
    97  			t.Fatal(err)
    98  		}
    99  
   100  		lsrv.blobs[i] = bytes.NewReader(buf.Bytes())
   101  		d := &descs[i]
   102  		d.MediaType = "application/vnd.oci.image.layer.v1.tar"
   103  		d.Headers = make(http.Header)
   104  		d.URI = u.String()
   105  		d.Digest = fmt.Sprintf("sha256:%x", h.Sum(nil))
   106  	}
   107  
   108  	return srv.Client(), descs
   109  }
   110  
   111  // RealizeLayers uses fetch.Layer to populate a directory and returns a slice of Layers describing them.
   112  //
   113  // Any needed cleanup is handled via the passed [testing.T].
   114  func RealizeLayers(ctx context.Context, t *testing.T, refs ...LayerRef) []claircore.Layer {
   115  	ret := make([]claircore.Layer, len(refs))
   116  	fetchCh := make(chan int)
   117  	var wg sync.WaitGroup
   118  	for i := 0; i < 3; i++ {
   119  		wg.Add(1)
   120  		go func() {
   121  			defer wg.Done()
   122  			for n := range fetchCh {
   123  				id, err := claircore.ParseDigest(refs[n].Digest)
   124  				if err != nil {
   125  					t.Error(err)
   126  					continue
   127  				}
   128  				f, err := fetch.Layer(ctx, t, refs[n].Registry, refs[n].Name, id)
   129  				if err != nil {
   130  					t.Error(err)
   131  					continue
   132  				}
   133  				t.Cleanup(func() {
   134  					if err := f.Close(); err != nil {
   135  						t.Errorf("closing %q: %v", f.Name(), err)
   136  					}
   137  				})
   138  				desc := claircore.LayerDescription{
   139  					URI:    "file:///dev/null",
   140  					Digest: id.String(),
   141  					// Bit of bad coupling seeping in here: all tar-based layers
   142  					// are handled the same, so this doesn't matter as long as
   143  					// it's a tar.
   144  					MediaType: MediaType,
   145  				}
   146  				if err := ret[n].Init(ctx, &desc, f); err != nil {
   147  					t.Error(err)
   148  				}
   149  				t.Cleanup(func() {
   150  					l := &ret[n]
   151  					if err := l.Close(); err != nil {
   152  						t.Errorf("closing %q: %v", l.Hash, err)
   153  					}
   154  				})
   155  			}
   156  		}()
   157  	}
   158  	for n := 0; n < len(refs); n++ {
   159  		fetchCh <- n
   160  	}
   161  	close(fetchCh)
   162  	wg.Wait()
   163  	return ret
   164  }
   165  
   166  // RealizeLayer is a helper around [RealizeLayers] for a single layer.
   167  //
   168  // This is useful for testing a Scanner implementation.
   169  func RealizeLayer(ctx context.Context, t *testing.T, ref LayerRef) *claircore.Layer {
   170  	t.Helper()
   171  	ls := RealizeLayers(ctx, t, ref)
   172  	return &ls[0]
   173  }