github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/containers/k8simage/k8simage_test.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package k8simage_test 16 17 import ( 18 "context" 19 "testing" 20 21 "github.com/google/go-cmp/cmp" 22 "github.com/google/go-cmp/cmp/cmpopts" 23 "github.com/google/osv-scalibr/extractor" 24 "github.com/google/osv-scalibr/extractor/filesystem/containers/k8simage" 25 "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" 26 "github.com/google/osv-scalibr/inventory" 27 "github.com/google/osv-scalibr/purl" 28 "github.com/google/osv-scalibr/testing/extracttest" 29 ) 30 31 func TestExtract(t *testing.T) { 32 tests := []struct { 33 name string 34 path string 35 cfg k8simage.Config 36 wantPackages []*extractor.Package 37 }{ 38 { 39 name: "comprehensive_multi-resource_test_file", 40 path: "testdata/comprehensive-test.yaml", 41 cfg: k8simage.DefaultConfig(), 42 wantPackages: []*extractor.Package{ 43 { 44 Name: "tag1", 45 Version: "sha256:b692a91e4e531d2a9cd8e8c3b1e6d5c7f9d2e5a3b1c8d7f4e6a9b2c5d8f1e4a7", 46 Locations: []string{"testdata/comprehensive-test.yaml"}, 47 PURLType: purl.TypeK8s, 48 }, 49 { 50 Name: "tag2", 51 Version: "2.0.0", 52 Locations: []string{"testdata/comprehensive-test.yaml"}, 53 PURLType: purl.TypeK8s, 54 }, 55 { 56 Name: "tag3", 57 Version: "3.0.0", 58 Locations: []string{"testdata/comprehensive-test.yaml"}, 59 PURLType: purl.TypeK8s, 60 }, 61 { 62 Name: "tag4.io/prometheus/node-exporter", 63 Version: "v4.0.0", 64 Locations: []string{"testdata/comprehensive-test.yaml"}, 65 PURLType: purl.TypeK8s, 66 }, 67 { 68 Name: "tag5", 69 Version: "5.0.0", 70 Locations: []string{"testdata/comprehensive-test.yaml"}, 71 PURLType: purl.TypeK8s, 72 }, 73 { 74 Name: "tag6", 75 Version: "6.0.0", 76 Locations: []string{"testdata/comprehensive-test.yaml"}, 77 PURLType: purl.TypeK8s, 78 }, 79 { 80 Name: "tag7:5000/my-app", 81 Version: "dev", 82 Locations: []string{"testdata/comprehensive-test.yaml"}, 83 PURLType: purl.TypeK8s, 84 }, 85 { 86 Name: "centos", 87 Version: "latest", 88 Locations: []string{"testdata/comprehensive-test.yaml"}, 89 PURLType: purl.TypeK8s, 90 }, 91 }, 92 }, 93 { 94 name: "larger than size limit", 95 path: "testdata/comprehensive-test.yaml", 96 cfg: k8simage.Config{MaxFileSizeBytes: 1}, 97 wantPackages: nil, 98 }, 99 } 100 101 for _, tc := range tests { 102 t.Run(tc.name, func(t *testing.T) { 103 extr := k8simage.New(tc.cfg) 104 105 input := extracttest.GenerateScanInputMock(t, extracttest.ScanInputMockConfig{ 106 Path: tc.path, 107 }) 108 defer extracttest.CloseTestScanInput(t, input) 109 110 got, err := extr.Extract(context.Background(), &input) 111 if err != nil { 112 t.Fatalf("%s.Extract(%q) failed: %v", extr.Name(), tc.path, err) 113 } 114 115 wantInv := inventory.Inventory{Packages: tc.wantPackages} 116 if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" { 117 t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tc.path, diff) 118 } 119 }) 120 } 121 } 122 123 func TestFileRequired(t *testing.T) { 124 extr := k8simage.New(k8simage.DefaultConfig()) 125 126 tests := []struct { 127 name string 128 path string 129 want bool 130 }{ 131 { 132 name: "yaml_extension", 133 path: "deployment.yaml", 134 want: true, 135 }, 136 { 137 name: "yml_extension", 138 path: "service.yml", 139 want: true, 140 }, 141 { 142 name: "other_extension", 143 path: "config.json", 144 want: false, 145 }, 146 } 147 148 for _, tc := range tests { 149 t.Run(tc.name, func(t *testing.T) { 150 isRequired := extr.FileRequired(simplefileapi.New(tc.path, nil)) 151 if isRequired != tc.want { 152 t.Fatalf("FileRequired(%s): got %v, want %v", tc.path, isRequired, tc.want) 153 } 154 }) 155 } 156 } 157 158 func TestExtract_empty_results(t *testing.T) { 159 tests := []struct { 160 name string 161 path string 162 cfg k8simage.Config 163 }{ 164 { 165 name: "empty_image_fields_should_result_in_no_packages", 166 path: "testdata/comprehensive-test-failures.yaml", 167 cfg: k8simage.DefaultConfig(), 168 }, 169 } 170 171 for _, tc := range tests { 172 t.Run(tc.name, func(t *testing.T) { 173 extr := k8simage.New(tc.cfg) 174 175 input := extracttest.GenerateScanInputMock(t, extracttest.ScanInputMockConfig{ 176 Path: tc.path, 177 }) 178 defer extracttest.CloseTestScanInput(t, input) 179 180 got, err := extr.Extract(context.Background(), &input) 181 if err != nil { 182 t.Fatalf("%s.Extract(%q) failed: %v", extr.Name(), tc.path, err) 183 } 184 185 // Should have no packages since all image fields are empty or with errors 186 if len(got.Packages) != 0 { 187 t.Errorf("%s.Extract(%q): got %d packages, want 0. Packages: %+v", 188 extr.Name(), tc.path, len(got.Packages), got.Packages) 189 } 190 }) 191 } 192 } 193 194 func TestExtract_skip_non_k8s_files(t *testing.T) { 195 tests := []struct { 196 name string 197 path string 198 }{ 199 { 200 name: "invalid_YAML_syntax", 201 path: "testdata/yaml-parsing-error.yaml", 202 }, 203 } 204 205 for _, tc := range tests { 206 t.Run(tc.name, func(t *testing.T) { 207 extr := k8simage.New(k8simage.DefaultConfig()) 208 209 input := extracttest.GenerateScanInputMock(t, extracttest.ScanInputMockConfig{ 210 Path: tc.path, 211 }) 212 defer extracttest.CloseTestScanInput(t, input) 213 214 inventory, err := extr.Extract(context.Background(), &input) 215 if err != nil { 216 t.Fatalf("Extract(): %v", err) 217 } 218 if len(inventory.Packages) != 0 { 219 t.Errorf("Extract(): got %v, want empty package", inventory.Packages) 220 } 221 }) 222 } 223 }