github.com/google/osv-scalibr@v0.4.1/extractor/standalone/containers/containerd/containerd_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 //go:build linux 16 17 // Package containerd_test contains unit tests for containerd extractor. 18 package containerd_test 19 20 import ( 21 "fmt" 22 "runtime" 23 "slices" 24 "testing" 25 26 containerd "github.com/containerd/containerd" 27 "github.com/containerd/containerd/api/types/task" 28 "github.com/google/go-cmp/cmp" 29 "github.com/google/go-cmp/cmp/cmpopts" 30 "github.com/google/osv-scalibr/extractor" 31 "github.com/google/osv-scalibr/extractor/standalone" 32 plugin "github.com/google/osv-scalibr/extractor/standalone/containers/containerd" 33 md "github.com/google/osv-scalibr/extractor/standalone/containers/containerd/containerdmetadata" 34 "github.com/google/osv-scalibr/extractor/standalone/containers/containerd/fakeclient" 35 "github.com/google/osv-scalibr/inventory" 36 ) 37 38 func TestExtract(t *testing.T) { 39 tests := []struct { 40 name string 41 onGoos []string 42 nssTaskIDs map[string][]string 43 tsks []*task.Process 44 ctrs []containerd.Container 45 wantPackages []*extractor.Package 46 wantErr error 47 }{ 48 { 49 name: "valid with no tasks", 50 onGoos: []string{"linux"}, 51 nssTaskIDs: map[string][]string{"default": {}, "k8s.io": {}}, 52 tsks: []*task.Process{}, 53 ctrs: []containerd.Container{}, 54 wantPackages: []*extractor.Package{}, 55 }, 56 { 57 name: "valid with tasks and rootfs", 58 onGoos: []string{"linux"}, 59 nssTaskIDs: map[string][]string{"default": {"123456789"}, "k8s.io": {"567890123"}}, 60 tsks: []*task.Process{{ID: "123456789", ContainerID: "", Pid: 12345}, {ID: "567890123", ContainerID: "", Pid: 5678}}, 61 ctrs: []containerd.Container{fakeclient.NewFakeContainer("123456789", "image1", "digest1", "/run/containerd/io.containerd.runtime.v2.task/default/123456789/rootfs"), fakeclient.NewFakeContainer("567890123", "image2", "digest2", "/run/containerd/io.containerd.runtime.v2.task/k8s.io/567890123/rootfs")}, 62 wantPackages: []*extractor.Package{ 63 { 64 Name: "image1", 65 Version: "digest1", 66 Locations: []string{"/run/containerd/io.containerd.runtime.v2.task/default/123456789/rootfs"}, 67 Metadata: &md.Metadata{ 68 Namespace: "default", 69 ImageName: "image1", 70 ImageDigest: "digest1", 71 Runtime: "fake_runc", 72 ID: "123456789", 73 PID: 12345, 74 RootFS: "/run/containerd/io.containerd.runtime.v2.task/default/123456789/rootfs", 75 }, 76 }, 77 { 78 Name: "image2", 79 Version: "digest2", 80 Locations: []string{"/run/containerd/io.containerd.runtime.v2.task/k8s.io/567890123/rootfs"}, 81 Metadata: &md.Metadata{ 82 Namespace: "k8s.io", 83 ImageName: "image2", 84 ImageDigest: "digest2", 85 ID: "567890123", 86 Runtime: "fake_runc", 87 PID: 5678, 88 RootFS: "/run/containerd/io.containerd.runtime.v2.task/k8s.io/567890123/rootfs", 89 }, 90 }, 91 }, 92 }, 93 { 94 name: "valid with tasks and no rootfs", 95 onGoos: []string{"linux"}, 96 nssTaskIDs: map[string][]string{"default": {"123456789"}, "k8s.io": {"567890123"}}, 97 tsks: []*task.Process{{ID: "123456789", ContainerID: "", Pid: 12345}, {ID: "567890123", ContainerID: "", Pid: 5678}}, 98 ctrs: []containerd.Container{fakeclient.NewFakeContainer("123456789", "image1", "digest1", ""), fakeclient.NewFakeContainer("567890123", "image2", "digest2", "")}, 99 wantPackages: []*extractor.Package{ 100 { 101 Name: "image1", 102 Version: "digest1", 103 Locations: []string{"/run/containerd/io.containerd.runtime.v2.task/default/123456789/rootfs"}, 104 Metadata: &md.Metadata{ 105 Namespace: "default", 106 ImageName: "image1", 107 ImageDigest: "digest1", 108 Runtime: "fake_runc", 109 ID: "123456789", 110 PID: 12345, 111 RootFS: "/run/containerd/io.containerd.runtime.v2.task/default/123456789/rootfs", 112 }, 113 }, 114 { 115 Name: "image2", 116 Version: "digest2", 117 Locations: []string{"/run/containerd/io.containerd.runtime.v2.task/k8s.io/567890123/rootfs"}, 118 Metadata: &md.Metadata{ 119 Namespace: "k8s.io", 120 ImageName: "image2", 121 ImageDigest: "digest2", 122 ID: "567890123", 123 Runtime: "fake_runc", 124 PID: 5678, 125 RootFS: "/run/containerd/io.containerd.runtime.v2.task/k8s.io/567890123/rootfs", 126 }, 127 }, 128 }, 129 }, 130 { 131 name: "valid with tasks and relative-path-only rootfs", 132 onGoos: []string{"linux"}, 133 nssTaskIDs: map[string][]string{"default": {"123456788"}, "k8s.io": {"567890122"}}, 134 tsks: []*task.Process{{ID: "123456788", ContainerID: "", Pid: 12346}, {ID: "567890122", ContainerID: "", Pid: 5677}}, 135 ctrs: []containerd.Container{fakeclient.NewFakeContainer("123456788", "image1", "digest1", "test/rootfs"), fakeclient.NewFakeContainer("567890122", "image2", "digest2", "test2/rootfs")}, 136 wantPackages: []*extractor.Package{ 137 { 138 Name: "image1", 139 Version: "digest1", 140 Locations: []string{"/run/containerd/io.containerd.runtime.v2.task/default/123456788/test/rootfs"}, 141 Metadata: &md.Metadata{ 142 Namespace: "default", 143 ImageName: "image1", 144 ImageDigest: "digest1", 145 Runtime: "fake_runc", 146 ID: "123456788", 147 PID: 12346, 148 RootFS: "/run/containerd/io.containerd.runtime.v2.task/default/123456788/test/rootfs", 149 }, 150 }, 151 { 152 Name: "image2", 153 Version: "digest2", 154 Locations: []string{"/run/containerd/io.containerd.runtime.v2.task/k8s.io/567890122/test2/rootfs"}, 155 Metadata: &md.Metadata{ 156 Namespace: "k8s.io", 157 ImageName: "image2", 158 ImageDigest: "digest2", 159 ID: "567890122", 160 Runtime: "fake_runc", 161 PID: 5677, 162 RootFS: "/run/containerd/io.containerd.runtime.v2.task/k8s.io/567890122/test2/rootfs", 163 }, 164 }, 165 }, 166 }, 167 } 168 169 for _, tt := range tests { 170 t.Run(tt.name, func(t *testing.T) { 171 if len(tt.onGoos) > 0 && !slices.Contains(tt.onGoos, runtime.GOOS) { 172 t.Skipf("Skipping test on %s", runtime.GOOS) 173 } 174 175 var input *standalone.ScanInput 176 cli, err := fakeclient.NewFakeCtrdClient(t.Context(), tt.nssTaskIDs, tt.tsks, tt.ctrs) 177 if err != nil { 178 t.Fatalf("NewFakeCtrdClient() error: %v", err) 179 } 180 e := plugin.NewWithClient(&cli, "test") 181 got, err := e.Extract(t.Context(), input) 182 if !cmp.Equal(err, tt.wantErr, cmpopts.EquateErrors()) { 183 t.Fatalf("Extract(%+v) error: got %v, want %v\n", tt.name, err, tt.wantErr) 184 } 185 186 ignoreOrder := cmpopts.SortSlices(func(a, b any) bool { 187 return fmt.Sprintf("%+v", a) < fmt.Sprintf("%+v", b) 188 }) 189 wantInv := inventory.Inventory{Packages: tt.wantPackages} 190 if diff := cmp.Diff(wantInv, got, ignoreOrder); diff != "" { 191 t.Errorf("Extract(%s) (-want +got):\n%s", tt.name, diff) 192 } 193 }) 194 } 195 }