github.com/castai/kvisor@v1.7.1-0.20240516114728-b3572a2607b5/cmd/imagescan/collector/collector_test.go (about) 1 package collector 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "net/http/httptest" 10 "os" 11 "path" 12 "runtime" 13 "runtime/pprof" 14 "testing" 15 "time" 16 17 "github.com/aquasecurity/trivy/pkg/fanal/types" 18 "github.com/google/go-containerregistry/pkg/name" 19 v1 "github.com/google/go-containerregistry/pkg/v1" 20 "github.com/sirupsen/logrus" 21 "github.com/stretchr/testify/require" 22 "google.golang.org/grpc" 23 "google.golang.org/protobuf/encoding/protojson" 24 25 "github.com/castai/image-analyzer/image" 26 "github.com/castai/image-analyzer/image/hostfs" 27 castaipb "github.com/castai/kvisor/api/v1/runtime" 28 "github.com/castai/kvisor/cmd/imagescan/config" 29 mockblobcache "github.com/castai/kvisor/pkg/blobscache/mock" 30 ) 31 32 func TestCollector(t *testing.T) { 33 t.Run("collect and sends metadata", func(t *testing.T) { 34 imgName := "notused" 35 imgID := "gke.gcr.io/phpmyadmin@sha256:b0d9c54760b35edd1854e5710c1a62a28ad2d2b070c801da3e30a3e59c19e7e3" //nolint:gosec 36 37 r := require.New(t) 38 ctx := context.Background() 39 log := logrus.New() 40 log.SetLevel(logrus.DebugLevel) 41 42 mockCache := mockblobcache.MockClient{} 43 44 cwd, _ := os.Getwd() 45 p := path.Join(cwd, "testdata/amd64-linux/io.containerd.content.v1.content") 46 47 ingestClient := &mockIngestClient{} 48 49 c := New( 50 log, 51 config.Config{ 52 ImageID: imgID, 53 ImageName: imgName, 54 Timeout: 5 * time.Minute, 55 Mode: config.ModeHostFS, 56 Runtime: config.RuntimeContainerd, 57 ImageArchitecture: "amd64", 58 ImageOS: "linux", 59 Parallel: 1, 60 }, 61 ingestClient, 62 mockCache, 63 &hostfs.ContainerdHostFSConfig{ 64 Platform: v1.Platform{ 65 Architecture: "amd64", 66 OS: "linux", 67 }, 68 ContentDir: p, 69 }, 70 ) 71 72 r.NoError(c.Collect(ctx)) 73 74 // Read expect metadata. 75 var expected castaipb.ImageMetadata 76 b, err := os.ReadFile("./testdata/expected_image_scan_meta1.json") 77 r.NoError(err) 78 r.NoError(protojson.Unmarshal(b, &expected)) 79 80 receivedMetadataJson, err := protojson.Marshal(ingestClient.receivedMeta) 81 r.NoError(err) 82 var actual castaipb.ImageMetadata 83 r.NoError(protojson.Unmarshal(receivedMetadataJson, &actual)) 84 85 var expectedPackages []types.BlobInfo 86 var actualPackages []types.BlobInfo 87 r.NoError(json.Unmarshal(expected.Packages, &expectedPackages)) 88 r.NoError(json.Unmarshal(actual.Packages, &actualPackages)) 89 expected.Packages = nil 90 actual.Packages = nil 91 92 r.Equal(&expected, &actual) 93 r.ElementsMatch(expectedPackages, actualPackages) 94 }) 95 } 96 97 func TestCollectorLargeImageDocker(t *testing.T) { 98 // Skip this test by default. Uncomment to run locally. 99 if os.Getenv("LOCAL_IMAGE") == "" { 100 t.Skip() 101 } 102 103 // You will spend a lot of time on macOS to fetch image into temp file from daemon. 104 // Instead, export image once to local tar file. 105 // docker save ghcr.io/castai/egressd:am1 -o egressd.tar 106 imgName := "kvisor:local" 107 imgID := imgName 108 109 r := require.New(t) 110 ctx := context.Background() 111 log := logrus.New() 112 log.SetLevel(logrus.DebugLevel) 113 //debug.SetGCPercent(-1) 114 mockCache := mockblobcache.MockClient{} 115 116 var receivedMetaBytes []byte 117 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 118 data, err := io.ReadAll(req.Body) 119 r.NoError(err) 120 receivedMetaBytes = data 121 })) 122 defer srv.Close() 123 124 ingestClient := &mockIngestClient{} 125 126 c := New(log, config.Config{ 127 CastaiAPIGrpcAddr: srv.URL, 128 ImageID: imgID, 129 ImageName: imgName, 130 Timeout: 5 * time.Minute, 131 Mode: config.ModeTarArchive, 132 Runtime: config.RuntimeDocker, 133 Parallel: 1, 134 ImageLocalTarPath: "egressd.tar", 135 }, ingestClient, mockCache, nil) 136 137 go func() { 138 for { 139 printMemStats() 140 time.Sleep(500 * time.Millisecond) 141 } 142 }() 143 144 r.NoError(c.Collect(ctx)) 145 writeMemProfile("heap.prof") 146 r.NoError(os.WriteFile("metadata.json", receivedMetaBytes, 0600)) 147 } 148 149 func printMemStats() { 150 runtime.GC() // Get up-to-date statistics. 151 var stats runtime.MemStats 152 runtime.ReadMemStats(&stats) 153 fmt.Printf("allocs=%d MB, total_allocs=%d MB, sys=%d MB\n", stats.Alloc/1024/1024, stats.TotalAlloc/1024/1024, stats.Sys/1024/1024) 154 } 155 156 func writeMemProfile(name string) { 157 f, err := os.Create(name) 158 if err != nil { 159 logrus.Fatalf("could not create memory profile: %v", err) 160 } 161 defer f.Close() // error handling omitted for example 162 if err := pprof.WriteHeapProfile(f); err != nil { 163 logrus.Fatalf("could not write memory profile: %v", err) 164 } 165 } 166 167 func TestFindRegistryAuth(t *testing.T) { 168 registryAuth := image.RegistryAuth{Username: "u", Password: "p", Token: "t"} 169 170 tests := []struct { 171 name string 172 cfg image.DockerConfig 173 imageRef name.Reference 174 175 expectedFound bool 176 expectedKey string 177 expectedAuth image.RegistryAuth 178 }{ 179 { 180 name: "find auth for image", 181 cfg: image.DockerConfig{ 182 Auths: map[string]image.RegistryAuth{ 183 "a": registryAuth, 184 "gitlab.com": registryAuth, 185 "us-east4-docker.pkg.dev": registryAuth, 186 "us-east4-docker.pkg.dev/project": registryAuth, 187 "x": {}, 188 }, 189 }, 190 imageRef: name.MustParseReference("us-east4-docker.pkg.dev/project/repo/name:tag"), 191 expectedFound: true, 192 expectedKey: "us-east4-docker.pkg.dev", 193 expectedAuth: registryAuth, 194 }, 195 { 196 name: "find auth scoped by repository", 197 cfg: image.DockerConfig{ 198 Auths: map[string]image.RegistryAuth{ 199 "a": registryAuth, 200 "us-east4-docker.pkg.dev/project/repo": registryAuth, 201 "x": {}, 202 }, 203 }, 204 imageRef: name.MustParseReference("us-east4-docker.pkg.dev/project/repo/name:tag"), 205 expectedFound: true, 206 expectedKey: "us-east4-docker.pkg.dev/project/repo", 207 expectedAuth: registryAuth, 208 }, 209 { 210 name: "find auth for http or https prefixed auths", 211 cfg: image.DockerConfig{ 212 Auths: map[string]image.RegistryAuth{ 213 "a": registryAuth, 214 "https://us-east4-docker.pkg.dev/project/repo": registryAuth, 215 "x": {}, 216 }, 217 }, 218 imageRef: name.MustParseReference("us-east4-docker.pkg.dev/project/repo/name:tag"), 219 expectedFound: true, 220 expectedKey: "https://us-east4-docker.pkg.dev/project/repo", 221 expectedAuth: registryAuth, 222 }, 223 { 224 name: "no auth for unmatched auths", 225 cfg: image.DockerConfig{ 226 Auths: map[string]image.RegistryAuth{ 227 "a": registryAuth, 228 "https://us-east4-docker.pkg.dev/project/repo": registryAuth, 229 "x": {}, 230 }, 231 }, 232 imageRef: name.MustParseReference("nginx:latest"), 233 expectedFound: false, 234 expectedKey: "", 235 expectedAuth: image.RegistryAuth{}, 236 }, 237 } 238 239 for _, test := range tests { 240 t.Run(test.name, func(t *testing.T) { 241 r := require.New(t) 242 actualKey, actualAuth, found := findRegistryAuth(test.cfg, test.imageRef) 243 r.Equal(test.expectedFound, found) 244 r.Equal(test.expectedKey, actualKey) 245 r.Equal(test.expectedAuth, actualAuth) 246 }) 247 } 248 } 249 250 type mockIngestClient struct { 251 receivedMeta *castaipb.ImageMetadata 252 } 253 254 func (m *mockIngestClient) ImageMetadataIngest(ctx context.Context, in *castaipb.ImageMetadata, opts ...grpc.CallOption) (*castaipb.ImageMetadataIngestResponse, error) { 255 m.receivedMeta = in 256 return &castaipb.ImageMetadataIngestResponse{}, nil 257 }