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  }