Add NewContainer and Delete container support
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
d0e5732f0b
commit
31a3bda41b
102
client.go
102
client.go
@ -2,11 +2,23 @@ package containerd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/containerd/api/services/containers"
|
"github.com/containerd/containerd/api/services/containers"
|
||||||
|
contentapi "github.com/containerd/containerd/api/services/content"
|
||||||
|
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
|
||||||
|
"github.com/containerd/containerd/content"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
|
contentservice "github.com/containerd/containerd/services/content"
|
||||||
|
snapshotservice "github.com/containerd/containerd/services/snapshot"
|
||||||
|
"github.com/containerd/containerd/snapshot"
|
||||||
|
protobuf "github.com/gogo/protobuf/types"
|
||||||
|
"github.com/opencontainers/image-spec/identity"
|
||||||
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/grpclog"
|
"google.golang.org/grpc/grpclog"
|
||||||
@ -51,6 +63,88 @@ func (c *Client) Containers(ctx context.Context) ([]*Container, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NewContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error
|
||||||
|
|
||||||
|
// NewContainerWithLables adds the provided labels to the container
|
||||||
|
func NewContainerWithLables(labels map[string]string) NewContainerOpts {
|
||||||
|
return func(_ context.Context, _ *Client, c *containers.Container) error {
|
||||||
|
c.Labels = labels
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContainerWithExistingRootFS uses an existing root filesystem for the container
|
||||||
|
func NewContainerWithExistingRootFS(id string) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
// check that the snapshot exists, if not, fail on creation
|
||||||
|
if _, err := client.snapshotter().Mounts(ctx, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.RootFS = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContainerWithNewRootFS allocates a new snapshot to be used by the container as the
|
||||||
|
// root filesystem in read-write mode
|
||||||
|
func NewContainerWithNewRootFS(id string, image v1.Descriptor) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
diffIDs, err := images.RootFS(ctx, client.content(), image)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := client.snapshotter().Prepare(ctx, id, identity.ChainID(diffIDs).String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.RootFS = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContainerWithNewReadonlyRootFS allocates a new snapshot to be used by the container as the
|
||||||
|
// root filesystem in read-only mode
|
||||||
|
func NewContainerWithNewReadonlyRootFS(id string, image v1.Descriptor) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
diffIDs, err := images.RootFS(ctx, client.content(), image)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := client.snapshotter().View(ctx, id, identity.ChainID(diffIDs).String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.RootFS = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
data, err := json.Marshal(spec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
container := containers.Container{
|
||||||
|
ID: id,
|
||||||
|
Spec: &protobuf.Any{
|
||||||
|
TypeUrl: specs.Version,
|
||||||
|
Value: data,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
if err := o(ctx, c, &container); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r, err := c.containers().Create(ctx, &containers.CreateContainerRequest{
|
||||||
|
Container: container,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return containerFromProto(c, r.Container), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes the clients connection to containerd
|
// Close closes the clients connection to containerd
|
||||||
func (c *Client) Close() error {
|
func (c *Client) Close() error {
|
||||||
return c.conn.Close()
|
return c.conn.Close()
|
||||||
@ -59,3 +153,11 @@ func (c *Client) Close() error {
|
|||||||
func (c *Client) containers() containers.ContainersClient {
|
func (c *Client) containers() containers.ContainersClient {
|
||||||
return containers.NewContainersClient(c.conn)
|
return containers.NewContainersClient(c.conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) content() content.Store {
|
||||||
|
return contentservice.NewStoreFromClient(contentapi.NewContentClient(c.conn))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) snapshotter() snapshot.Snapshotter {
|
||||||
|
return snapshotservice.NewSnapshotterFromClient(snapshotapi.NewSnapshotClient(c.conn))
|
||||||
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
const defaultAddress = "/run/containerd/containerd.sock"
|
const defaultAddress = "/run/containerd/containerd.sock"
|
||||||
|
|
||||||
@ -16,3 +19,38 @@ func TestNewClient(t *testing.T) {
|
|||||||
t.Errorf("client closed returned errror %v", err)
|
t.Errorf("client closed returned errror %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewContainer(t *testing.T) {
|
||||||
|
client, err := New(defaultAddress)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
id := "test"
|
||||||
|
spec, err := GenerateSpec(WithHostname(id))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
container, err := client.NewContainer(context.Background(), id, spec)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if container.ID() != id {
|
||||||
|
t.Errorf("expected container id %q but received %q", id, container.ID())
|
||||||
|
}
|
||||||
|
if spec, err = container.Spec(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if spec.Hostname != id {
|
||||||
|
t.Errorf("expected spec hostname id %q but received %q", id, container.ID())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := container.Delete(context.Background()); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
34
container.go
34
container.go
@ -1,21 +1,47 @@
|
|||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import "github.com/containerd/containerd/api/services/containers"
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/api/services/containers"
|
||||||
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
func containerFromProto(client *Client, c containers.Container) *Container {
|
func containerFromProto(client *Client, c containers.Container) *Container {
|
||||||
return &Container{
|
return &Container{
|
||||||
client: client,
|
client: client,
|
||||||
id: c.ID,
|
c: c,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
client *Client
|
client *Client
|
||||||
|
|
||||||
id string
|
c containers.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID returns the container's unique id
|
// ID returns the container's unique id
|
||||||
func (c *Container) ID() string {
|
func (c *Container) ID() string {
|
||||||
return c.id
|
return c.c.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spec returns the current OCI specification for the container
|
||||||
|
func (c *Container) Spec() (*specs.Spec, error) {
|
||||||
|
var s specs.Spec
|
||||||
|
if err := json.Unmarshal(c.c.Spec.Value, &s); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes an existing container
|
||||||
|
// an error is returned if the container has running tasks
|
||||||
|
func (c *Container) Delete(ctx context.Context) error {
|
||||||
|
// TODO: should the client be the one removing resources attached
|
||||||
|
// to the container at the moment before we have GC?
|
||||||
|
_, err := c.client.containers().Delete(ctx, &containers.DeleteContainerRequest{
|
||||||
|
ID: c.c.ID,
|
||||||
|
})
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user