Add initial containerd client package#904
Conversation
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
client.go
Outdated
| conn *grpc.ClientConn | ||
|
|
||
| Runtime string | ||
| Namespace string |
There was a problem hiding this comment.
We should consider not having these exported. The With* pattern allows setting them and they should not be mutated by callers. Creating a new client to change these values seems reasonable.
Codecov Report
@@ Coverage Diff @@
## master #904 +/- ##
=======================================
Coverage 59.55% 59.55%
=======================================
Files 5 5
Lines 759 759
=======================================
Hits 452 452
Misses 199 199
Partials 108 108Continue to review full report at Codecov.
|
client.go
Outdated
| type NewContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error | ||
|
|
||
| // WithContainerLables adds the provided labels to the container | ||
| func WithContainerLables(labels map[string]string) NewContainerOpts { |
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
| type PullOpts func(*Client, *PullContext) error | ||
|
|
||
| type PullContext struct { | ||
| Resolver remotes.Resolver |
|
FYI: Not all of the functionality is implemented yet. This is opened to get early feedback on the overall design for the client and then to have it merged in where we all can work on it together. I also have the integration tests turned off at the moment while we figure out how to seed the content and run the daemon inside travis. I'll try to get integration tests running on travis by the end of this week. |
| return nil, err | ||
| } | ||
| is := c.images() | ||
| if err := is.Put(ctx, name, desc); err != nil { |
There was a problem hiding this comment.
@stevvooe Should we Put image metadata before or after pulling the image or doesn't matter? I'm a bit confused now.
| } | ||
| if err := images.Dispatch(ctx, images.Handlers(handlers...), desc); err != nil { | ||
| return nil, err | ||
| } |
There was a problem hiding this comment.
Add TODO to wait for image pulling?
There was a problem hiding this comment.
Yep, we will also be adding ways to hook into progress on the pull/push
There was a problem hiding this comment.
Dispatch waits until all handlers are executed, so this effectively waits until the image is pulled.
Does CRI report status?
There was a problem hiding this comment.
Alright, let me know what error is returned.
There was a problem hiding this comment.
This error https://github.com/containerd/containerd/blob/8ae905b92b3246b27580b:19affb070ea165a59bf/content/locks.go#L31.
And previous discussion:
#885 (comment)
There was a problem hiding this comment.
Ok, we should probably have an inprogress error and then detect that in the handler.
| return nil | ||
| } | ||
|
|
||
| type Unpacker interface { |
There was a problem hiding this comment.
Why only Unpacker is an interface?
It would help us write unit test if we could make all clients an interface. :)
There was a problem hiding this comment.
You can always depend on a Client interface in your code. I think we talked about this some and @stevvooe may have some more context on this subject
There was a problem hiding this comment.
@crosbymichael The problem is that, we expect Image and Container also to be an interface. It will be harder to wrap them all on our side, e.g. https://play.golang.org/p/JvlZhLONen will fail.
| parts := strings.Split(config.User, ":") | ||
| switch len(parts) { | ||
| case 1: | ||
| v, err := strconv.ParseUint(parts[0], 0, 10) |
There was a problem hiding this comment.
How do we handle user name and group name? We don't have a rootfs before creating the container.
There was a problem hiding this comment.
I'll have to add some helpers for this because its complex when you have to parse a username
| client *Client | ||
|
|
||
| i images.Image | ||
| } |
There was a problem hiding this comment.
It would be very helpful, if we could add some frequently used function here, GetManifest, GetConfig, GetSize etc.
There was a problem hiding this comment.
Yep, will be adding those soon.
There was a problem hiding this comment.
No getters in Go. Manifest, Config, Size.
Although, we should be very careful what we consider size. This will be the packed size.
There was a problem hiding this comment.
@stevvooe Based on the discussion, would it help to introduce a Ready function? Or else Config or Manifest may return weird error during the image is being downloaded or unpacked.
| /bin/ | ||
| **/coverage.txt | ||
| **/coverage.out | ||
| containerd.test |
There was a problem hiding this comment.
This output from compiling a test executable. go test -c, I believe.
client_test.go
Outdated
|
|
||
| func TestNewClient(t *testing.T) { | ||
| if testing.Short() { | ||
| return |
| ) | ||
|
|
||
| const defaultAddress = "/run/containerd/containerd.sock" | ||
|
|
There was a problem hiding this comment.
Does tester need to launch the daemon manually?
|
I'd expect |
task.go
Outdated
| func (t *Task) Wait(ctx context.Context) (uint32, error) { | ||
| events, err := t.client.tasks().Events(ctx, &execution.EventsRequest{}) | ||
| if err != nil { | ||
| return 255, err |
There was a problem hiding this comment.
I understand why this is 255, but it still looks like a magic number. Can you put this in a constant so it has a name? Something like failureExitStatus?
There was a problem hiding this comment.
I'm more for UnknownExitStatus, but yes, probably better to have it defined (and exported)
There was a problem hiding this comment.
Just put a comment:
// this number is magic
return 255, err
mlaventure
left a comment
There was a problem hiding this comment.
I really like the principles, but I had a few comments obviously 😉
client_windows.go
Outdated
| ) | ||
|
|
||
| func dialer(address string, timeout time.Duration) (net.Conn, error) { | ||
| return winio.DialPipe(bindAddress, &timeout) |
| ) | ||
|
|
||
| func dialer(address string, timeout time.Duration) (net.Conn, error) { | ||
| address = strings.TrimPrefix(address, "unix://") |
There was a problem hiding this comment.
Validate that the prefix is actually present?
There was a problem hiding this comment.
Why validate first? Just trim it. same reason you don't stat a file before opening, just open it
|
|
||
| // Client is the client to interact with containerd and its various services | ||
| // using a uniform interface | ||
| type Client struct { |
There was a problem hiding this comment.
I think I would prefer for Client to be an interface and have client internally as the type. Since the struct doesn't have any exported data, I don't think it would be an issue and it will definitely help when writing unit tests.
|
|
||
| // New returns a new containerd client that is connected to the containerd | ||
| // instance provided by address | ||
| func New(address string, opts ...NewClientOpts) (*Client, error) { |
There was a problem hiding this comment.
Take a context here and use grc.DialContext()
| func New(address string, opts ...NewClientOpts) (*Client, error) { | ||
| gopts := []grpc.DialOption{ | ||
| grpc.WithInsecure(), | ||
| grpc.WithTimeout(100 * time.Second), |
There was a problem hiding this comment.
Add a WithTimeout NewClientOpts
container.go
Outdated
| Stderr: i.Stderr, | ||
| } | ||
| // get the rootfs from the snapshotter and add it to the request | ||
| mounts, err := c.client.snapshotter().Mounts(ctx, c.c.RootFS) |
There was a problem hiding this comment.
Use of the snapshotter should be optional,. If c.c.RootFS is not set, this should just be skipped
spec.go
Outdated
| } | ||
| } | ||
|
|
||
| func WithArgs(args ...string) SpecOpts { |
task.go
Outdated
| return nil | ||
| } | ||
|
|
||
| type Task struct { |
There was a problem hiding this comment.
idem, make it an interface.
There was a problem hiding this comment.
Easier to mock it in test that way
task.go
Outdated
| func (t *Task) Wait(ctx context.Context) (uint32, error) { | ||
| events, err := t.client.tasks().Events(ctx, &execution.EventsRequest{}) | ||
| if err != nil { | ||
| return 255, err |
There was a problem hiding this comment.
I'm more for UnknownExitStatus, but yes, probably better to have it defined (and exported)
| @@ -0,0 +1,252 @@ | |||
| package containerd | |||
There was a problem hiding this comment.
cough, cough
windows? 😇
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
client_test.go
Outdated
| "testing" | ||
| ) | ||
|
|
||
| const defaultAddress = "/run/containerd/containerd.sock" |
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
|
This should be good for another round of review. We can get it merged in and continue filling out the missing functionality and getting integration tests running in a separate PR for travis. |
|
|
||
| // NewContainer will create a new container in container with the provided id | ||
| // the id must be unique within the namespace | ||
| func (c *Client) NewContainer(ctx context.Context, id string, spec *specs.Spec, opts ...NewContainerOpts) (Container, error) { |
There was a problem hiding this comment.
To fully support runtimes that need more than the OCI spec (e.g. windows atm), we're either going to have to introduce an interface for the spec, or have the spec specified as an option (i.e. WithSpec)
There was a problem hiding this comment.
@mlaventure We could wrap it in an interface and then type assert?
There was a problem hiding this comment.
I actually think the WithSpec would do just fine and nicely fit into the current definition, wdyt?
There was a problem hiding this comment.
We really need a way to test windows stuff because its just guessing for most of us
client_test.go
Outdated
| func TestNewClient(t *testing.T) { | ||
| if testing.Short() { | ||
| t.Skip() | ||
| return |
There was a problem hiding this comment.
No need to return, t.Skip calls os.Exit
| _, err = client.Pull(context.Background(), ref) | ||
| if err != nil { | ||
| t.Error(err) | ||
| return |
There was a problem hiding this comment.
No, I want the defers to run
| @@ -0,0 +1,252 @@ | |||
| package containerd | |||
| Delete(context.Context) error | ||
| NewTask(context.Context, IOCreation) (Task, error) | ||
| Spec() (*specs.Spec, error) | ||
| Task() Task |
There was a problem hiding this comment.
How can we add labels to this container?
There was a problem hiding this comment.
NewContainer(WithLabels)
There was a problem hiding this comment.
What if I want to change the labels on an existing container?
There was a problem hiding this comment.
I don't have an update method yet cuz this is the initial client PR
task.go
Outdated
| Resume(context.Context) error | ||
| Pid() uint32 | ||
| Start(context.Context) error | ||
| Status(context.Context) (string, error) |
There was a problem hiding this comment.
Would it be possible to have a type for status? This is hard to discover in docker today because it is just a string field.
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
|
LGTM |
1 similar comment
|
LGTM |
This adds the initial containerd client package that consumers will use when working with containerd. It also adds a start to the integration tests for the daemon based on the client.
You can see a full, working example of the client here: