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  }