99 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			99 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # Client options
 | |
| 
 | |
| The containerd client was built to be easily extended by consumers.
 | |
| The goal is that the execution flow of the calls remain the same across implementations while `Opts` are written to extend functionality.
 | |
| To accomplish this we depend on the `Opts` pattern in Go.
 | |
| 
 | |
| ## Method Calls
 | |
| 
 | |
| For many functions and methods within the client package you will generally see variadic args as the last parameter.
 | |
| 
 | |
| If we look at the `NewContainer` method on the client we can see that it has a required argument of `id` and then additional `NewContainerOpts`.
 | |
| 
 | |
| There are a few built in options that allow the container to be created with an existing spec, `WithSpec`, and snapshot opts for creating or using an existing snapshot.
 | |
| 
 | |
| ```go
 | |
| func (c *Client) NewContainer(ctx context.Context, id string, opts ...NewContainerOpts) (Container, error) {
 | |
| }
 | |
| ```
 | |
| 
 | |
| ## Extending the Client
 | |
| 
 | |
| As a consumer of the containerd client you need to be able to add your domain specific functionality.
 | |
| There are a few ways of doing this, changing the client code, submitting a PR to the containerd client, or forking the client.
 | |
| These ways of extending the client should only be considered after every other method has been tried.
 | |
| 
 | |
| The proper and supported way of extending the client is to build a package of `Opts` that define your application specific logic.
 | |
| 
 | |
| As an example, if Docker is integrating containerd support and needs to add concepts such as Volumes, they would create a `docker` package with options.
 | |
| 
 | |
| #### Bad Extension Example
 | |
| 
 | |
| ```go
 | |
| // example code
 | |
| container, err := client.NewContainer(ctx, id)
 | |
| 
 | |
| // add volumes with their config and bind mounts
 | |
| container.Labels["volumes"] = VolumeConfig{}
 | |
| container.Spec.Binds  = append({"/var/lib/docker/volumes..."})
 | |
| ```
 | |
| 
 | |
| #### Good Extension Example
 | |
| 
 | |
| ```go
 | |
| // example code
 | |
| import "github.com/docker/docker"
 | |
| import "github.com/docker/libnetwork"
 | |
| 
 | |
| container, err := client.NewContainer(ctx, id,
 | |
| 	docker.WithVolume("volume-name"),
 | |
| 	libnetwork.WithOverlayNetwork("cluster-network"),
 | |
| )
 | |
| ```
 | |
| 
 | |
| There are a few advantages using this model.
 | |
| 
 | |
| 1. Your application code is not scattered in the execution flow of the containerd client.
 | |
| 2. Your code can be unit tested without mocking the containerd client.
 | |
| 3. Contributors can better follow your containerd implementation and understand when and where your application logic is added to standard containerd client calls.
 | |
| 
 | |
| ## Example SpecOpt
 | |
| 
 | |
| If we want to make a `SpecOpt` to setup a container to monitor the host system with `htop` it can be easily done without ever touching a line of code in the containerd repository.
 | |
| 
 | |
| ```go
 | |
| package monitor
 | |
| 
 | |
| import (
 | |
| 	"github.com/containerd/containerd/oci"
 | |
| 	specs "github.com/opencontainers/runtime-spec/specs-go"
 | |
| )
 | |
| 
 | |
| // WithHtop configures a container to monitor the host system via `htop`
 | |
| func WithHtop(s *specs.Spec) error {
 | |
| 	// make sure we are in the host pid namespace
 | |
| 	if err := oci.WithHostNamespace(specs.PIDNamespace)(s); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	// make sure we set htop as our arg
 | |
| 	s.Process.Args = []string{"htop"}
 | |
| 	// make sure we have a tty set for htop
 | |
| 	if err := oci.WithTTY(s); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| ```
 | |
| 
 | |
| Adding your new option to spec generation is as easy as importing your new package and adding the option when creating a spec.
 | |
| 
 | |
| ```go
 | |
| import "github.com/crosbymichael/monitor"
 | |
| 
 | |
| container, err := client.NewContainer(ctx, id,
 | |
| 	containerd.WithNewSpec(oci.WithImageConfig(image), monitor.WithHtop),
 | |
| )
 | |
| ```
 | |
| 
 | |
| You can see the full code and run the monitor container [here](https://github.com/crosbymichael/monitor).
 | 
