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 }