github.com/quay/claircore@v1.5.28/rhel/repositoryscanner_test.go (about) 1 package rhel 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "io" 9 "net/http" 10 "net/http/httptest" 11 "os" 12 "path" 13 "sort" 14 "strings" 15 "testing" 16 17 "github.com/quay/claircore/pkg/tmp" 18 19 "github.com/google/go-cmp/cmp" 20 "github.com/quay/zlog" 21 22 "github.com/quay/claircore" 23 "github.com/quay/claircore/test" 24 "github.com/quay/claircore/toolkit/types/cpe" 25 ) 26 27 func TestRepositoryScanner(t *testing.T) { 28 t.Parallel() 29 ctx := zlog.Test(context.Background(), t) 30 31 // Set up a response map and test server to mock the Container API. 32 apiData := map[string]*strings.Reader{ 33 "rh-pkg-1-1": strings.NewReader(`{"data":[{"cpe_ids":["cpe:/o:redhat:enterprise_linux:8::computenode","cpe:/o:redhat:enterprise_linux:8::baseos"],"parsed_data":{"architecture":"x86_64","labels":[{"name":"architecture","value":"x86_64"}]}}]}`), 34 } 35 mappingData := strings.NewReader(`{"data":{"content-set-1":{"cpes":["cpe:/o:redhat:enterprise_linux:6::server","cpe:/o:redhat:enterprise_linux:7::server"]},"content-set-2":{"cpes":["cpe:/o:redhat:enterprise_linux:7::server","cpe:/o:redhat:enterprise_linux:8::server"]}}}`) 36 var mappingDataBytes bytes.Buffer 37 if _, err := io.Copy(&mappingDataBytes, mappingData); err != nil { 38 t.Fatal(err) 39 } 40 41 mux := http.NewServeMux() 42 mux.HandleFunc("/repository-2-cpe.json", func(w http.ResponseWriter, r *http.Request) { 43 w.Header().Set("last-modified", "Mon, 02 Jan 2006 15:04:05 MST") 44 if _, err := mappingData.Seek(0, io.SeekStart); err != nil { 45 t.Fatal(err) 46 } 47 if _, err := io.Copy(w, mappingData); err != nil { 48 t.Fatal(err) 49 } 50 }) 51 mux.HandleFunc("/v1/images/nvr/", func(w http.ResponseWriter, r *http.Request) { 52 path := path.Base(r.URL.Path) 53 d := apiData[path] 54 if _, err := d.Seek(0, io.SeekStart); err != nil { 55 t.Fatal(err) 56 } 57 if _, err := io.Copy(w, d); err != nil { 58 t.Fatal(err) 59 } 60 }) 61 srv := httptest.NewServer(mux) 62 defer srv.Close() 63 64 esrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 65 t.Fatal("external http request invoked when none was expected") 66 })) 67 68 td := t.TempDir() 69 f, err := tmp.NewFile(td, "repository-2-cpe.json") 70 if err != nil { 71 t.Fatal("trying to create repository-2-cpe.json for FromMappingFile test", err) 72 } 73 defer f.Close() 74 75 if _, err := f.Write(mappingDataBytes.Bytes()); err != nil { 76 t.Fatalf("trying to write %s for FromMappingFile test: %v", f.Name(), err) 77 } 78 79 table := []struct { 80 cfg *RepositoryScannerConfig 81 name string 82 layerPath string 83 want []*claircore.Repository 84 }{ 85 { 86 name: "FromAPI", 87 want: []*claircore.Repository{ 88 { 89 Name: "cpe:/o:redhat:enterprise_linux:8::baseos", 90 Key: repositoryKey, 91 CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:8::baseos"), 92 }, 93 { 94 Name: "cpe:/o:redhat:enterprise_linux:8::computenode", 95 Key: repositoryKey, 96 CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:8::computenode"), 97 }, 98 }, 99 cfg: &RepositoryScannerConfig{API: srv.URL, Repo2CPEMappingURL: srv.URL + "/repository-2-cpe.json"}, 100 layerPath: "testdata/layer-with-cpe.tar", 101 }, 102 { 103 name: "FromMappingUrl", 104 want: []*claircore.Repository{ 105 { 106 Name: "cpe:/o:redhat:enterprise_linux:6::server", 107 Key: repositoryKey, 108 CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:6::server"), 109 }, 110 { 111 Name: "cpe:/o:redhat:enterprise_linux:7::server", 112 Key: repositoryKey, 113 CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:7::server"), 114 }, 115 { 116 Name: "cpe:/o:redhat:enterprise_linux:8::server", 117 Key: repositoryKey, 118 CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:8::server"), 119 }, 120 }, 121 cfg: &RepositoryScannerConfig{API: srv.URL, Repo2CPEMappingURL: srv.URL + "/repository-2-cpe.json"}, 122 layerPath: "testdata/layer-with-embedded-cs.tar", 123 }, 124 { 125 name: "FromMappingFile", 126 want: []*claircore.Repository{ 127 { 128 Name: "cpe:/o:redhat:enterprise_linux:6::server", 129 Key: repositoryKey, 130 CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:6::server"), 131 }, 132 { 133 Name: "cpe:/o:redhat:enterprise_linux:7::server", 134 Key: repositoryKey, 135 CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:7::server"), 136 }, 137 { 138 Name: "cpe:/o:redhat:enterprise_linux:8::server", 139 Key: repositoryKey, 140 CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:8::server"), 141 }, 142 }, 143 cfg: &RepositoryScannerConfig{Repo2CPEMappingFile: f.Name()}, 144 layerPath: "testdata/layer-with-embedded-cs.tar", 145 }, 146 { 147 name: "FromMappingFileAirGap", 148 want: []*claircore.Repository{ 149 { 150 Name: "cpe:/o:redhat:enterprise_linux:6::server", 151 Key: repositoryKey, 152 CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:6::server"), 153 }, 154 { 155 Name: "cpe:/o:redhat:enterprise_linux:7::server", 156 Key: repositoryKey, 157 CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:7::server"), 158 }, 159 { 160 Name: "cpe:/o:redhat:enterprise_linux:8::server", 161 Key: repositoryKey, 162 CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:8::server"), 163 }, 164 }, 165 cfg: &RepositoryScannerConfig{DisableAPI: true, API: esrv.URL, Repo2CPEMappingURL: "/", Repo2CPEMappingFile: f.Name()}, 166 layerPath: "testdata/layer-with-embedded-cs.tar", 167 }, 168 { 169 name: "NoCPE", 170 want: nil, 171 cfg: &RepositoryScannerConfig{}, 172 layerPath: "testdata/layer-with-no-cpe-info.tar", 173 }, 174 { 175 name: "NoCPEWithAirGap", 176 want: nil, 177 cfg: &RepositoryScannerConfig{DisableAPI: true}, 178 layerPath: "testdata/layer-with-embedded-cs.tar", 179 }, { 180 name: "BadContentManifestsFile", 181 want: nil, 182 cfg: &RepositoryScannerConfig{API: srv.URL, Repo2CPEMappingURL: srv.URL + "/repository-2-cpe.json"}, 183 layerPath: "testdata/layer-with-invalid-content-manifests-json.tar", 184 }, 185 } 186 187 for _, tt := range table { 188 t.Run(tt.name, func(t *testing.T) { 189 ctx := zlog.Test(ctx, t) 190 f, err := os.Open(tt.layerPath) 191 if err != nil { 192 t.Fatal(err) 193 } 194 defer func() { 195 if err := f.Close(); err != nil { 196 t.Error(err) 197 } 198 }() 199 scanner := new(RepositoryScanner) 200 var l claircore.Layer 201 desc := claircore.LayerDescription{ 202 Digest: `sha256:` + strings.Repeat(`beef`, 16), 203 URI: `file:///dev/null`, 204 MediaType: test.MediaType, 205 Headers: make(map[string][]string), 206 } 207 if err := l.Init(ctx, &desc, f); err != nil { 208 t.Fatal(err) 209 } 210 t.Cleanup(func() { 211 if err := l.Close(); err != nil { 212 t.Error(err) 213 } 214 }) 215 216 if tt.cfg != nil { 217 var buf bytes.Buffer 218 if err := json.NewEncoder(&buf).Encode(&tt.cfg); err != nil { 219 t.Error(err) 220 } 221 if err := scanner.Configure(ctx, json.NewDecoder(&buf).Decode, srv.Client()); err != nil { 222 t.Error(err) 223 } 224 } 225 226 got, err := scanner.Scan(ctx, &l) 227 if err != nil { 228 t.Error(err) 229 } 230 sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name }) 231 if !cmp.Equal(got, tt.want) { 232 t.Error(cmp.Diff(got, tt.want)) 233 } 234 }) 235 } 236 } 237 238 func TestLabelError(t *testing.T) { 239 err := missingLabel("test") 240 t.Log(err) 241 if got, want := err, errBadDockerfile; !errors.Is(got, want) { 242 t.Errorf("%v != %v", got, want) 243 } 244 if got, want := err, missingLabel("test"); !errors.Is(got, want) { 245 t.Errorf("%v != %v", got, want) 246 } 247 }