github.com/google/osv-scalibr@v0.4.1/extractor/standalone/containers/containerd/fakeclient/fake_containerd_client.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 fakeclient contains a fake implementation of the containerd client for testing purposes. 18 package fakeclient 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 25 containerd "github.com/containerd/containerd" 26 tasks "github.com/containerd/containerd/api/services/tasks/v1" 27 task "github.com/containerd/containerd/api/types/task" 28 "github.com/containerd/containerd/cio" 29 "github.com/containerd/containerd/containers" 30 "github.com/containerd/containerd/namespaces" 31 "github.com/containerd/containerd/oci" 32 plugin "github.com/google/osv-scalibr/extractor/standalone/containers/containerd" 33 "github.com/opencontainers/go-digest" 34 imagespecs "github.com/opencontainers/image-spec/specs-go/v1" 35 runtimespecs "github.com/opencontainers/runtime-spec/specs-go" 36 "google.golang.org/grpc" 37 ) 38 39 // CtrdClient is a fake implementation of CtrdClient for testing purposes. 40 type CtrdClient struct { 41 plugin.CtrdClient 42 43 tasksService tasks.TasksClient 44 nsService namespaces.Store 45 // A map of task IDs to containerd.Container objects. 46 ctrTasksIDs map[string]containerd.Container 47 // A map of namespace name to task IDs that are running in that namespace. 48 nssTaskIDs map[string][]string 49 // List of all active tasks that will be returned by the FakeTaskService. 50 tsks []*task.Process 51 // A map of container ID to containerd.Container object. 52 ctrs []containerd.Container 53 } 54 55 // NewFakeCtrdClient creates a new fake containerd client. 56 func NewFakeCtrdClient(ctx context.Context, nssTaskIDs map[string][]string, tsks []*task.Process, ctrs []containerd.Container) (CtrdClient, error) { 57 ctrTasks, err := initContainerTasks(tsks, ctrs) 58 if err != nil { 59 return CtrdClient{}, err 60 } 61 62 nss := make([]string, 0, len(nssTaskIDs)) 63 for ns := range nssTaskIDs { 64 nss = append(nss, ns) 65 } 66 67 return CtrdClient{ 68 tasksService: NewFakeTasksService(tsks, nssTaskIDs), 69 nsService: NewFakeNamespacesService(nss), 70 ctrTasksIDs: ctrTasks, 71 nssTaskIDs: nssTaskIDs, 72 ctrs: ctrs, 73 tsks: tsks, 74 }, nil 75 } 76 77 // LoadContainer returns the containerd.Container object for the given task ID from ctrTasksIDs. 78 func (c *CtrdClient) LoadContainer(ctx context.Context, id string) (containerd.Container, error) { 79 if ctr, ok := c.ctrTasksIDs[id]; ok { 80 return ctr, nil 81 } 82 return nil, fmt.Errorf("no container found with task id %v", id) 83 } 84 85 // NamespaceService returns the fake namespaces service for testing purposes. 86 func (c *CtrdClient) NamespaceService() namespaces.Store { 87 return c.nsService 88 } 89 90 // TaskService returns the fake task service for testing purposes. 91 func (c *CtrdClient) TaskService() tasks.TasksClient { 92 return c.tasksService 93 } 94 95 // Close is a no-op for the fake containerd client. 96 func (c *CtrdClient) Close() error { 97 return nil 98 } 99 100 // initContainerTasks initializes the ctrTasksIDs map. 101 func initContainerTasks(tsks []*task.Process, ctrs []containerd.Container) (map[string]containerd.Container, error) { 102 ctrTasksIDs := make(map[string]containerd.Container) 103 104 for _, ctr := range ctrs { 105 for _, task := range tsks { 106 if task.ID == ctr.ID() { 107 ctrTasksIDs[ctr.ID()] = ctr 108 break 109 } 110 } 111 // All containers are expected to have a task. 112 if ctrTasksIDs[ctr.ID()] == nil { 113 return nil, fmt.Errorf("no task found for container %v", ctr.ID()) 114 } 115 } 116 return ctrTasksIDs, nil 117 } 118 119 // TasksService is a fake implementation of the containerd tasks service. 120 type TasksService struct { 121 tasks.TasksClient 122 123 tasks []*task.Process 124 nssTaskIDs map[string][]string 125 } 126 127 // NewFakeTasksService creates a new fake tasks service. 128 func NewFakeTasksService(tasks []*task.Process, nssTaskIDs map[string][]string) *TasksService { 129 return &TasksService{ 130 tasks: tasks, 131 nssTaskIDs: nssTaskIDs, 132 } 133 } 134 135 // List returns a list of tasks for a namespace that is obtained from the context. 136 func (s *TasksService) List(ctx context.Context, in *tasks.ListTasksRequest, opts ...grpc.CallOption) (*tasks.ListTasksResponse, error) { 137 var tsks []*task.Process 138 139 ns, ok := namespaces.Namespace(ctx) 140 if !ok { 141 return &tasks.ListTasksResponse{Tasks: []*task.Process{}}, errors.New("no namespace found in context") 142 } 143 144 ids := s.nssTaskIDs[ns] 145 if ids == nil { 146 return &tasks.ListTasksResponse{Tasks: []*task.Process{}}, nil 147 } 148 149 for _, id := range ids { 150 for _, t := range s.tasks { 151 if id == t.ID { 152 tsks = append(tsks, t) 153 break 154 } 155 } 156 } 157 158 return &tasks.ListTasksResponse{Tasks: tsks}, nil 159 } 160 161 // NamespacesService is a fake implementation of the containerd namespaces service. 162 type NamespacesService struct { 163 namespaces.Store 164 165 namespaces []string 166 } 167 168 // NewFakeNamespacesService creates a new fake namespaces service. 169 func NewFakeNamespacesService(namespaces []string) *NamespacesService { 170 return &NamespacesService{ 171 namespaces: namespaces, 172 } 173 } 174 175 // List returns a list of all namespaces that are stored in the fake namespaces service. 176 func (s *NamespacesService) List(ctx context.Context) ([]string, error) { 177 return s.namespaces, nil 178 } 179 180 // Container is a fake implementation of the containerd container object. 181 type Container struct { 182 containerd.Container 183 184 id string 185 image string 186 digest string 187 rootfs string 188 } 189 190 // NewFakeContainer creates a new fake instance of containerd container. 191 func NewFakeContainer(id, image, digest, rootfs string) *Container { 192 return &Container{ 193 id: id, 194 image: image, 195 digest: digest, 196 rootfs: rootfs, 197 } 198 } 199 200 // ID returns the container's unique id. 201 func (c *Container) ID() string { 202 return c.id 203 } 204 205 // Info returns the underlying container record type. 206 func (c *Container) Info(ctx context.Context, opts ...containerd.InfoOpts) (containers.Container, error) { 207 return containers.Container{ 208 ID: c.id, 209 Image: c.image, 210 Labels: map[string]string{ 211 "image": c.image, 212 }, 213 Runtime: containers.RuntimeInfo{Name: "fake_runc"}, 214 }, nil 215 } 216 217 // Task mocks a containerd.Task with a fake task. 218 func (c *Container) Task(context.Context, cio.Attach) (containerd.Task, error) { 219 return NewFakeTask(c.rootfs), nil 220 } 221 222 // Image returns the underlying container Image object with a given digest. 223 func (c *Container) Image(ctx context.Context) (containerd.Image, error) { 224 return NewFakeImage(c.digest), nil 225 } 226 227 // Image is a fake implementation of the containerd image object. 228 type Image struct { 229 containerd.Image 230 231 digest string 232 } 233 234 // NewFakeImage creates a new fake instance of containerd image. 235 func NewFakeImage(digest string) *Image { 236 return &Image{ 237 digest: digest, 238 } 239 } 240 241 // Target returns the image's target descriptor with the image's digest only. 242 func (i *Image) Target() imagespecs.Descriptor { 243 return imagespecs.Descriptor{ 244 Digest: digest.Digest(i.digest), 245 MediaType: "fake_media_type", 246 } 247 } 248 249 // Task is a fake implementation of the containerd task object. 250 type Task struct { 251 containerd.Task 252 253 rootfs string 254 } 255 256 // NewFakeTask creates a new fake instance of containerd image. 257 func NewFakeTask(rootfs string) *Task { 258 return &Task{ 259 rootfs: rootfs, 260 } 261 } 262 263 // Spec returns the task runtime spec with the rootfs path only. 264 func (t *Task) Spec(ctx context.Context) (*oci.Spec, error) { 265 return &oci.Spec{Root: &runtimespecs.Root{Path: t.rootfs}}, nil 266 }