Move services to plugins/services
Signed-off-by: Derek McGowan <derek@mcg.dev>
This commit is contained in:
85
plugins/services/containers/helpers.go
Normal file
85
plugins/services/containers/helpers.go
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package containers
|
||||
|
||||
import (
|
||||
api "github.com/containerd/containerd/v2/api/services/containers/v1"
|
||||
"github.com/containerd/containerd/v2/core/containers"
|
||||
"github.com/containerd/containerd/v2/protobuf"
|
||||
"github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/typeurl/v2"
|
||||
)
|
||||
|
||||
func containersToProto(containers []containers.Container) []*api.Container {
|
||||
var containerspb []*api.Container
|
||||
|
||||
for _, image := range containers {
|
||||
image := image
|
||||
containerspb = append(containerspb, containerToProto(&image))
|
||||
}
|
||||
|
||||
return containerspb
|
||||
}
|
||||
|
||||
func containerToProto(container *containers.Container) *api.Container {
|
||||
extensions := make(map[string]*types.Any)
|
||||
for k, v := range container.Extensions {
|
||||
extensions[k] = protobuf.FromAny(v)
|
||||
}
|
||||
return &api.Container{
|
||||
ID: container.ID,
|
||||
Labels: container.Labels,
|
||||
Image: container.Image,
|
||||
Runtime: &api.Container_Runtime{
|
||||
Name: container.Runtime.Name,
|
||||
Options: protobuf.FromAny(container.Runtime.Options),
|
||||
},
|
||||
Spec: protobuf.FromAny(container.Spec),
|
||||
Snapshotter: container.Snapshotter,
|
||||
SnapshotKey: container.SnapshotKey,
|
||||
CreatedAt: protobuf.ToTimestamp(container.CreatedAt),
|
||||
UpdatedAt: protobuf.ToTimestamp(container.UpdatedAt),
|
||||
Extensions: extensions,
|
||||
Sandbox: container.SandboxID,
|
||||
}
|
||||
}
|
||||
|
||||
func containerFromProto(containerpb *api.Container) containers.Container {
|
||||
var runtime containers.RuntimeInfo
|
||||
if containerpb.Runtime != nil {
|
||||
runtime = containers.RuntimeInfo{
|
||||
Name: containerpb.Runtime.Name,
|
||||
Options: containerpb.Runtime.Options,
|
||||
}
|
||||
}
|
||||
extensions := make(map[string]typeurl.Any)
|
||||
for k, v := range containerpb.Extensions {
|
||||
v := v
|
||||
extensions[k] = v
|
||||
}
|
||||
return containers.Container{
|
||||
ID: containerpb.ID,
|
||||
Labels: containerpb.Labels,
|
||||
Image: containerpb.Image,
|
||||
Runtime: runtime,
|
||||
Spec: containerpb.Spec,
|
||||
Snapshotter: containerpb.Snapshotter,
|
||||
SnapshotKey: containerpb.SnapshotKey,
|
||||
Extensions: extensions,
|
||||
SandboxID: containerpb.Sandbox,
|
||||
}
|
||||
}
|
||||
256
plugins/services/containers/local.go
Normal file
256
plugins/services/containers/local.go
Normal file
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package containers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
eventstypes "github.com/containerd/containerd/v2/api/events"
|
||||
api "github.com/containerd/containerd/v2/api/services/containers/v1"
|
||||
"github.com/containerd/containerd/v2/core/containers"
|
||||
"github.com/containerd/containerd/v2/core/metadata"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/events"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
grpcm "google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.ServicePlugin,
|
||||
ID: services.ContainersService,
|
||||
Requires: []plugin.Type{
|
||||
plugins.EventPlugin,
|
||||
plugins.MetadataPlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
m, err := ic.GetSingle(plugins.MetadataPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ep, err := ic.GetSingle(plugins.EventPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db := m.(*metadata.DB)
|
||||
return &local{
|
||||
Store: metadata.NewContainerStore(db),
|
||||
db: db,
|
||||
publisher: ep.(events.Publisher),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type local struct {
|
||||
containers.Store
|
||||
db *metadata.DB
|
||||
publisher events.Publisher
|
||||
}
|
||||
|
||||
var _ api.ContainersClient = &local{}
|
||||
|
||||
func (l *local) Get(ctx context.Context, req *api.GetContainerRequest, _ ...grpc.CallOption) (*api.GetContainerResponse, error) {
|
||||
var resp api.GetContainerResponse
|
||||
|
||||
return &resp, errdefs.ToGRPC(l.withStoreView(ctx, func(ctx context.Context) error {
|
||||
container, err := l.Store.Get(ctx, req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
containerpb := containerToProto(&container)
|
||||
resp.Container = containerpb
|
||||
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func (l *local) List(ctx context.Context, req *api.ListContainersRequest, _ ...grpc.CallOption) (*api.ListContainersResponse, error) {
|
||||
var resp api.ListContainersResponse
|
||||
return &resp, errdefs.ToGRPC(l.withStoreView(ctx, func(ctx context.Context) error {
|
||||
containers, err := l.Store.List(ctx, req.Filters...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Containers = containersToProto(containers)
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func (l *local) ListStream(ctx context.Context, req *api.ListContainersRequest, _ ...grpc.CallOption) (api.Containers_ListStreamClient, error) {
|
||||
stream := &localStream{
|
||||
ctx: ctx,
|
||||
}
|
||||
return stream, errdefs.ToGRPC(l.withStoreView(ctx, func(ctx context.Context) error {
|
||||
containers, err := l.Store.List(ctx, req.Filters...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stream.containers = containersToProto(containers)
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func (l *local) Create(ctx context.Context, req *api.CreateContainerRequest, _ ...grpc.CallOption) (*api.CreateContainerResponse, error) {
|
||||
var resp api.CreateContainerResponse
|
||||
|
||||
if err := l.withStoreUpdate(ctx, func(ctx context.Context) error {
|
||||
container := containerFromProto(req.Container)
|
||||
|
||||
created, err := l.Store.Create(ctx, container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp.Container = containerToProto(&created)
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return &resp, errdefs.ToGRPC(err)
|
||||
}
|
||||
if err := l.publisher.Publish(ctx, "/containers/create", &eventstypes.ContainerCreate{
|
||||
ID: resp.Container.ID,
|
||||
Image: resp.Container.Image,
|
||||
Runtime: &eventstypes.ContainerCreate_Runtime{
|
||||
Name: resp.Container.Runtime.Name,
|
||||
Options: resp.Container.Runtime.Options,
|
||||
},
|
||||
}); err != nil {
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (l *local) Update(ctx context.Context, req *api.UpdateContainerRequest, _ ...grpc.CallOption) (*api.UpdateContainerResponse, error) {
|
||||
if req.Container.ID == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Container.ID required")
|
||||
}
|
||||
var (
|
||||
resp api.UpdateContainerResponse
|
||||
container = containerFromProto(req.Container)
|
||||
)
|
||||
|
||||
if err := l.withStoreUpdate(ctx, func(ctx context.Context) error {
|
||||
var fieldpaths []string
|
||||
if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 {
|
||||
fieldpaths = append(fieldpaths, req.UpdateMask.Paths...)
|
||||
}
|
||||
|
||||
updated, err := l.Store.Update(ctx, container, fieldpaths...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp.Container = containerToProto(&updated)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return &resp, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
if err := l.publisher.Publish(ctx, "/containers/update", &eventstypes.ContainerUpdate{
|
||||
ID: resp.Container.ID,
|
||||
Image: resp.Container.Image,
|
||||
Labels: resp.Container.Labels,
|
||||
SnapshotKey: resp.Container.SnapshotKey,
|
||||
}); err != nil {
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (l *local) Delete(ctx context.Context, req *api.DeleteContainerRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) {
|
||||
if err := l.withStoreUpdate(ctx, func(ctx context.Context) error {
|
||||
return l.Store.Delete(ctx, req.ID)
|
||||
}); err != nil {
|
||||
return &ptypes.Empty{}, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
if err := l.publisher.Publish(ctx, "/containers/delete", &eventstypes.ContainerDelete{
|
||||
ID: req.ID,
|
||||
}); err != nil {
|
||||
return &ptypes.Empty{}, err
|
||||
}
|
||||
|
||||
return &ptypes.Empty{}, nil
|
||||
}
|
||||
|
||||
func (l *local) withStore(ctx context.Context, fn func(ctx context.Context) error) func(tx *bolt.Tx) error {
|
||||
return func(tx *bolt.Tx) error {
|
||||
return fn(metadata.WithTransactionContext(ctx, tx))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *local) withStoreView(ctx context.Context, fn func(ctx context.Context) error) error {
|
||||
return l.db.View(l.withStore(ctx, fn))
|
||||
}
|
||||
|
||||
func (l *local) withStoreUpdate(ctx context.Context, fn func(ctx context.Context) error) error {
|
||||
return l.db.Update(l.withStore(ctx, fn))
|
||||
}
|
||||
|
||||
type localStream struct {
|
||||
ctx context.Context
|
||||
containers []*api.Container
|
||||
i int
|
||||
}
|
||||
|
||||
func (s *localStream) Recv() (*api.ListContainerMessage, error) {
|
||||
if s.i >= len(s.containers) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
c := s.containers[s.i]
|
||||
s.i++
|
||||
return &api.ListContainerMessage{
|
||||
Container: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *localStream) Context() context.Context {
|
||||
return s.ctx
|
||||
}
|
||||
|
||||
func (s *localStream) CloseSend() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *localStream) Header() (grpcm.MD, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *localStream) Trailer() grpcm.MD {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *localStream) SendMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *localStream) RecvMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
103
plugins/services/containers/service.go
Normal file
103
plugins/services/containers/service.go
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package containers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/containers/v1"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "containers",
|
||||
Requires: []plugin.Type{
|
||||
plugins.ServicePlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
i, err := ic.GetByID(plugins.ServicePlugin, services.ContainersService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &service{local: i.(api.ContainersClient)}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type service struct {
|
||||
local api.ContainersClient
|
||||
api.UnimplementedContainersServer
|
||||
}
|
||||
|
||||
var _ api.ContainersServer = &service{}
|
||||
|
||||
func (s *service) Register(server *grpc.Server) error {
|
||||
api.RegisterContainersServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Get(ctx context.Context, req *api.GetContainerRequest) (*api.GetContainerResponse, error) {
|
||||
return s.local.Get(ctx, req)
|
||||
}
|
||||
|
||||
func (s *service) List(ctx context.Context, req *api.ListContainersRequest) (*api.ListContainersResponse, error) {
|
||||
return s.local.List(ctx, req)
|
||||
}
|
||||
|
||||
func (s *service) ListStream(req *api.ListContainersRequest, stream api.Containers_ListStreamServer) error {
|
||||
containers, err := s.local.ListStream(stream.Context(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-stream.Context().Done():
|
||||
return nil
|
||||
default:
|
||||
c, err := containers.Recv()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := stream.Send(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Create(ctx context.Context, req *api.CreateContainerRequest) (*api.CreateContainerResponse, error) {
|
||||
return s.local.Create(ctx, req)
|
||||
}
|
||||
|
||||
func (s *service) Update(ctx context.Context, req *api.UpdateContainerRequest) (*api.UpdateContainerResponse, error) {
|
||||
return s.local.Update(ctx, req)
|
||||
}
|
||||
|
||||
func (s *service) Delete(ctx context.Context, req *api.DeleteContainerRequest) (*ptypes.Empty, error) {
|
||||
return s.local.Delete(ctx, req)
|
||||
}
|
||||
471
plugins/services/content/contentserver/contentserver.go
Normal file
471
plugins/services/content/contentserver/contentserver.go
Normal file
@@ -0,0 +1,471 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package contentserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/content/v1"
|
||||
"github.com/containerd/containerd/v2/core/content"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/protobuf"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/log"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
store content.Store
|
||||
api.UnimplementedContentServer
|
||||
}
|
||||
|
||||
var (
|
||||
empty = &ptypes.Empty{}
|
||||
bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
buffer := make([]byte, 1<<20)
|
||||
return &buffer
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// New returns the content GRPC server
|
||||
func New(cs content.Store) api.ContentServer {
|
||||
return &service{store: cs}
|
||||
}
|
||||
|
||||
func (s *service) Register(server *grpc.Server) error {
|
||||
api.RegisterContentServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Info(ctx context.Context, req *api.InfoRequest) (*api.InfoResponse, error) {
|
||||
dg, err := digest.Parse(req.Digest)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%q failed validation", req.Digest)
|
||||
}
|
||||
|
||||
bi, err := s.store.Info(ctx, dg)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &api.InfoResponse{
|
||||
Info: infoToGRPC(bi),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) Update(ctx context.Context, req *api.UpdateRequest) (*api.UpdateResponse, error) {
|
||||
_, err := digest.Parse(req.Info.Digest)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%q failed validation", req.Info.Digest)
|
||||
}
|
||||
|
||||
info, err := s.store.Update(ctx, infoFromGRPC(req.Info), req.UpdateMask.GetPaths()...)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &api.UpdateResponse{
|
||||
Info: infoToGRPC(info),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) List(req *api.ListContentRequest, session api.Content_ListServer) error {
|
||||
var (
|
||||
buffer []*api.Info
|
||||
sendBlock = func(block []*api.Info) error {
|
||||
// send last block
|
||||
return session.Send(&api.ListContentResponse{
|
||||
Info: block,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
if err := s.store.Walk(session.Context(), func(info content.Info) error {
|
||||
buffer = append(buffer, infoToGRPC(info))
|
||||
|
||||
if len(buffer) >= 100 {
|
||||
if err := sendBlock(buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer = buffer[:0]
|
||||
}
|
||||
|
||||
return nil
|
||||
}, req.Filters...); err != nil {
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
if len(buffer) > 0 {
|
||||
// send last block
|
||||
if err := sendBlock(buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Delete(ctx context.Context, req *api.DeleteContentRequest) (*ptypes.Empty, error) {
|
||||
log.G(ctx).WithField("digest", req.Digest).Debugf("delete content")
|
||||
dg, err := digest.Parse(req.Digest)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
if err := s.store.Delete(ctx, dg); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func (s *service) Read(req *api.ReadContentRequest, session api.Content_ReadServer) error {
|
||||
dg, err := digest.Parse(req.Digest)
|
||||
if err != nil {
|
||||
return status.Errorf(codes.InvalidArgument, "%v: %v", req.Digest, err)
|
||||
}
|
||||
|
||||
oi, err := s.store.Info(session.Context(), dg)
|
||||
if err != nil {
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
ra, err := s.store.ReaderAt(session.Context(), ocispec.Descriptor{Digest: dg})
|
||||
if err != nil {
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
defer ra.Close()
|
||||
|
||||
var (
|
||||
offset = req.Offset
|
||||
// size is read size, not the expected size of the blob (oi.Size), which the caller might not be aware of.
|
||||
// offset+size can be larger than oi.Size.
|
||||
size = req.Size
|
||||
|
||||
// TODO(stevvooe): Using the global buffer pool. At 32KB, it is probably
|
||||
// little inefficient for work over a fast network. We can tune this later.
|
||||
p = bufPool.Get().(*[]byte)
|
||||
)
|
||||
defer bufPool.Put(p)
|
||||
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
if offset > oi.Size {
|
||||
return status.Errorf(codes.OutOfRange, "read past object length %v bytes", oi.Size)
|
||||
}
|
||||
|
||||
if size <= 0 || offset+size > oi.Size {
|
||||
size = oi.Size - offset
|
||||
}
|
||||
|
||||
_, err = io.CopyBuffer(
|
||||
&readResponseWriter{session: session},
|
||||
io.NewSectionReader(ra, offset, size), *p)
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
// readResponseWriter is a writer that places the output into ReadContentRequest messages.
|
||||
//
|
||||
// This allows io.CopyBuffer to do the heavy lifting of chunking the responses
|
||||
// into the buffer size.
|
||||
type readResponseWriter struct {
|
||||
offset int64
|
||||
session api.Content_ReadServer
|
||||
}
|
||||
|
||||
func (rw *readResponseWriter) Write(p []byte) (n int, err error) {
|
||||
if err := rw.session.Send(&api.ReadContentResponse{
|
||||
Offset: rw.offset,
|
||||
Data: p,
|
||||
}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
rw.offset += int64(len(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (s *service) Status(ctx context.Context, req *api.StatusRequest) (*api.StatusResponse, error) {
|
||||
status, err := s.store.Status(ctx, req.Ref)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPCf(err, "could not get status for ref %q", req.Ref)
|
||||
}
|
||||
|
||||
var resp api.StatusResponse
|
||||
resp.Status = &api.Status{
|
||||
StartedAt: protobuf.ToTimestamp(status.StartedAt),
|
||||
UpdatedAt: protobuf.ToTimestamp(status.UpdatedAt),
|
||||
Ref: status.Ref,
|
||||
Offset: status.Offset,
|
||||
Total: status.Total,
|
||||
Expected: status.Expected.String(),
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (s *service) ListStatuses(ctx context.Context, req *api.ListStatusesRequest) (*api.ListStatusesResponse, error) {
|
||||
statuses, err := s.store.ListStatuses(ctx, req.Filters...)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
var resp api.ListStatusesResponse
|
||||
for _, status := range statuses {
|
||||
resp.Statuses = append(resp.Statuses, &api.Status{
|
||||
StartedAt: protobuf.ToTimestamp(status.StartedAt),
|
||||
UpdatedAt: protobuf.ToTimestamp(status.UpdatedAt),
|
||||
Ref: status.Ref,
|
||||
Offset: status.Offset,
|
||||
Total: status.Total,
|
||||
Expected: status.Expected.String(),
|
||||
})
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (s *service) Write(session api.Content_WriteServer) (err error) {
|
||||
var (
|
||||
ctx = session.Context()
|
||||
msg api.WriteContentResponse
|
||||
req *api.WriteContentRequest
|
||||
ref string
|
||||
total int64
|
||||
expected digest.Digest
|
||||
)
|
||||
|
||||
defer func(msg *api.WriteContentResponse) {
|
||||
// pump through the last message if no error was encountered
|
||||
if err != nil {
|
||||
if s, ok := status.FromError(err); ok && s.Code() != codes.AlreadyExists {
|
||||
// TODO(stevvooe): Really need a log line here to track which
|
||||
// errors are actually causing failure on the server side. May want
|
||||
// to configure the service with an interceptor to make this work
|
||||
// identically across all GRPC methods.
|
||||
//
|
||||
// This is pretty noisy, so we can remove it but leave it for now.
|
||||
log.G(ctx).WithError(err).Error("(*service).Write failed")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = session.Send(msg)
|
||||
}(&msg)
|
||||
|
||||
// handle the very first request!
|
||||
req, err = session.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ref = req.Ref
|
||||
|
||||
if ref == "" {
|
||||
return status.Errorf(codes.InvalidArgument, "first message must have a reference")
|
||||
}
|
||||
|
||||
fields := log.Fields{
|
||||
"ref": ref,
|
||||
}
|
||||
total = req.Total
|
||||
expected = digest.Digest(req.Expected)
|
||||
if total > 0 {
|
||||
fields["total"] = total
|
||||
}
|
||||
|
||||
if expected != "" {
|
||||
fields["expected"] = expected
|
||||
}
|
||||
|
||||
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(fields))
|
||||
|
||||
log.G(ctx).Debug("(*service).Write started")
|
||||
// this action locks the writer for the session.
|
||||
wr, err := s.store.Writer(ctx,
|
||||
content.WithRef(ref),
|
||||
content.WithDescriptor(ocispec.Descriptor{Size: total, Digest: expected}))
|
||||
if err != nil {
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
defer wr.Close()
|
||||
|
||||
for {
|
||||
msg.Action = req.Action
|
||||
ws, err := wr.Status()
|
||||
if err != nil {
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
msg.Offset = ws.Offset // always set the offset.
|
||||
|
||||
// NOTE(stevvooe): In general, there are two cases underwhich a remote
|
||||
// writer is used.
|
||||
//
|
||||
// For pull, we almost always have this before fetching large content,
|
||||
// through descriptors. We allow predeclaration of the expected size
|
||||
// and digest.
|
||||
//
|
||||
// For push, it is more complex. If we want to cut through content into
|
||||
// storage, we may have no expectation until we are done processing the
|
||||
// content. The case here is the following:
|
||||
//
|
||||
// 1. Start writing content.
|
||||
// 2. Compress inline.
|
||||
// 3. Validate digest and size (maybe).
|
||||
//
|
||||
// Supporting these two paths is quite awkward but it lets both API
|
||||
// users use the same writer style for each with a minimum of overhead.
|
||||
if req.Expected != "" {
|
||||
dg := digest.Digest(req.Expected)
|
||||
if expected != "" && expected != dg {
|
||||
log.G(ctx).Debugf("commit digest differs from writer digest: %v != %v", dg, expected)
|
||||
}
|
||||
expected = dg
|
||||
|
||||
if _, err := s.store.Info(session.Context(), dg); err == nil {
|
||||
if err := wr.Close(); err != nil {
|
||||
log.G(ctx).WithError(err).Error("failed to close writer")
|
||||
}
|
||||
if err := s.store.Abort(session.Context(), ref); err != nil {
|
||||
log.G(ctx).WithError(err).Error("failed to abort write")
|
||||
}
|
||||
|
||||
return status.Errorf(codes.AlreadyExists, "blob with expected digest %v exists", req.Expected)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Total > 0 {
|
||||
// Update the expected total. Typically, this could be seen at
|
||||
// negotiation time or on a commit message.
|
||||
if total > 0 && req.Total != total {
|
||||
log.G(ctx).Debugf("commit size differs from writer size: %v != %v", req.Total, total)
|
||||
}
|
||||
total = req.Total
|
||||
}
|
||||
|
||||
switch req.Action {
|
||||
case api.WriteAction_STAT:
|
||||
msg.Digest = wr.Digest().String()
|
||||
msg.StartedAt = protobuf.ToTimestamp(ws.StartedAt)
|
||||
msg.UpdatedAt = protobuf.ToTimestamp(ws.UpdatedAt)
|
||||
msg.Total = total
|
||||
case api.WriteAction_WRITE, api.WriteAction_COMMIT:
|
||||
if req.Offset > 0 {
|
||||
// validate the offset if provided
|
||||
if req.Offset != ws.Offset {
|
||||
return status.Errorf(codes.OutOfRange, "write @%v must occur at current offset %v", req.Offset, ws.Offset)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Offset == 0 && ws.Offset > 0 {
|
||||
if err := wr.Truncate(req.Offset); err != nil {
|
||||
return fmt.Errorf("truncate failed: %w", err)
|
||||
}
|
||||
msg.Offset = req.Offset
|
||||
}
|
||||
|
||||
// issue the write if we actually have data.
|
||||
if len(req.Data) > 0 {
|
||||
// While this looks like we could use io.WriterAt here, because we
|
||||
// maintain the offset as append only, we just issue the write.
|
||||
n, err := wr.Write(req.Data)
|
||||
if err != nil {
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
if n != len(req.Data) {
|
||||
// TODO(stevvooe): Perhaps, we can recover this by including it
|
||||
// in the offset on the write return.
|
||||
return status.Errorf(codes.DataLoss, "wrote %v of %v bytes", n, len(req.Data))
|
||||
}
|
||||
|
||||
msg.Offset += int64(n)
|
||||
}
|
||||
|
||||
if req.Action == api.WriteAction_COMMIT {
|
||||
var opts []content.Opt
|
||||
if req.Labels != nil {
|
||||
opts = append(opts, content.WithLabels(req.Labels))
|
||||
}
|
||||
if err := wr.Commit(ctx, total, expected, opts...); err != nil {
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
}
|
||||
|
||||
msg.Digest = wr.Digest().String()
|
||||
}
|
||||
|
||||
if err := session.Send(&msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if req.Action == api.WriteAction_COMMIT {
|
||||
return nil
|
||||
}
|
||||
|
||||
req, err = session.Recv()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Abort(ctx context.Context, req *api.AbortRequest) (*ptypes.Empty, error) {
|
||||
if err := s.store.Abort(ctx, req.Ref); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func infoToGRPC(info content.Info) *api.Info {
|
||||
return &api.Info{
|
||||
Digest: info.Digest.String(),
|
||||
Size: info.Size,
|
||||
CreatedAt: protobuf.ToTimestamp(info.CreatedAt),
|
||||
UpdatedAt: protobuf.ToTimestamp(info.UpdatedAt),
|
||||
Labels: info.Labels,
|
||||
}
|
||||
}
|
||||
|
||||
func infoFromGRPC(info *api.Info) content.Info {
|
||||
return content.Info{
|
||||
Digest: digest.Digest(info.Digest),
|
||||
Size: info.Size,
|
||||
CreatedAt: protobuf.FromTimestamp(info.CreatedAt),
|
||||
UpdatedAt: protobuf.FromTimestamp(info.UpdatedAt),
|
||||
Labels: info.Labels,
|
||||
}
|
||||
}
|
||||
43
plugins/services/content/service.go
Normal file
43
plugins/services/content/service.go
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package content
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/v2/core/content"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
"github.com/containerd/containerd/v2/plugins/services/content/contentserver"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "content",
|
||||
Requires: []plugin.Type{
|
||||
plugins.ServicePlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
cs, err := ic.GetByID(plugins.ServicePlugin, services.ContentService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return contentserver.New(cs.(content.Store)), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
78
plugins/services/content/store.go
Normal file
78
plugins/services/content/store.go
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package content
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
eventstypes "github.com/containerd/containerd/v2/api/events"
|
||||
"github.com/containerd/containerd/v2/core/content"
|
||||
"github.com/containerd/containerd/v2/core/metadata"
|
||||
"github.com/containerd/containerd/v2/events"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// store wraps content.Store with proper event published.
|
||||
type store struct {
|
||||
content.Store
|
||||
publisher events.Publisher
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.ServicePlugin,
|
||||
ID: services.ContentService,
|
||||
Requires: []plugin.Type{
|
||||
plugins.EventPlugin,
|
||||
plugins.MetadataPlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
m, err := ic.GetSingle(plugins.MetadataPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ep, err := ic.GetSingle(plugins.EventPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s, err := newContentStore(m.(*metadata.DB).ContentStore(), ep.(events.Publisher))
|
||||
return s, err
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func newContentStore(cs content.Store, publisher events.Publisher) (content.Store, error) {
|
||||
return &store{
|
||||
Store: cs,
|
||||
publisher: publisher,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *store) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||
if err := s.Store.Delete(ctx, dgst); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: Consider whether we should return error here.
|
||||
return s.publisher.Publish(ctx, "/content/delete", &eventstypes.ContentDelete{
|
||||
Digest: dgst.String(),
|
||||
})
|
||||
}
|
||||
164
plugins/services/diff/local.go
Normal file
164
plugins/services/diff/local.go
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package diff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
diffapi "github.com/containerd/containerd/v2/api/services/diff/v1"
|
||||
"github.com/containerd/containerd/v2/core/diff"
|
||||
"github.com/containerd/containerd/v2/core/mount"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/oci"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"github.com/containerd/typeurl/v2"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
// Order is the order of preference in which to try diff algorithms, the
|
||||
// first differ which is supported is used.
|
||||
// Note when multiple differs may be supported, this order will be
|
||||
// respected for which is chosen. Each differ should return the same
|
||||
// correct output, allowing any ordering to be used to prefer
|
||||
// more optimimal implementations.
|
||||
Order []string `toml:"default"`
|
||||
}
|
||||
|
||||
type differ interface {
|
||||
diff.Comparer
|
||||
diff.Applier
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.ServicePlugin,
|
||||
ID: services.DiffService,
|
||||
Requires: []plugin.Type{
|
||||
plugins.DiffPlugin,
|
||||
},
|
||||
Config: defaultDifferConfig,
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
differs, err := ic.GetByType(plugins.DiffPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderedNames := ic.Config.(*config).Order
|
||||
ordered := make([]differ, len(orderedNames))
|
||||
for i, n := range orderedNames {
|
||||
d, ok := differs[n]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("needed differ not loaded: %s", n)
|
||||
}
|
||||
|
||||
ordered[i], ok = d.(differ)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("differ does not implement Comparer and Applier interface: %s", n)
|
||||
}
|
||||
}
|
||||
|
||||
return &local{
|
||||
differs: ordered,
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type local struct {
|
||||
differs []differ
|
||||
}
|
||||
|
||||
var _ diffapi.DiffClient = &local{}
|
||||
|
||||
func (l *local) Apply(ctx context.Context, er *diffapi.ApplyRequest, _ ...grpc.CallOption) (*diffapi.ApplyResponse, error) {
|
||||
var (
|
||||
ocidesc ocispec.Descriptor
|
||||
err error
|
||||
desc = oci.DescriptorFromProto(er.Diff)
|
||||
mounts = mount.FromProto(er.Mounts)
|
||||
)
|
||||
|
||||
var opts []diff.ApplyOpt
|
||||
if er.Payloads != nil {
|
||||
payloads := make(map[string]typeurl.Any)
|
||||
for k, v := range er.Payloads {
|
||||
payloads[k] = v
|
||||
}
|
||||
opts = append(opts, diff.WithPayloads(payloads))
|
||||
}
|
||||
opts = append(opts, diff.WithSyncFs(er.SyncFs))
|
||||
|
||||
for _, differ := range l.differs {
|
||||
ocidesc, err = differ.Apply(ctx, desc, mounts, opts...)
|
||||
if !errdefs.IsNotImplemented(err) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &diffapi.ApplyResponse{
|
||||
Applied: oci.DescriptorToProto(ocidesc),
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (l *local) Diff(ctx context.Context, dr *diffapi.DiffRequest, _ ...grpc.CallOption) (*diffapi.DiffResponse, error) {
|
||||
var (
|
||||
ocidesc ocispec.Descriptor
|
||||
err error
|
||||
aMounts = mount.FromProto(dr.Left)
|
||||
bMounts = mount.FromProto(dr.Right)
|
||||
)
|
||||
|
||||
var opts []diff.Opt
|
||||
if dr.MediaType != "" {
|
||||
opts = append(opts, diff.WithMediaType(dr.MediaType))
|
||||
}
|
||||
if dr.Ref != "" {
|
||||
opts = append(opts, diff.WithReference(dr.Ref))
|
||||
}
|
||||
if dr.Labels != nil {
|
||||
opts = append(opts, diff.WithLabels(dr.Labels))
|
||||
}
|
||||
if dr.SourceDateEpoch != nil {
|
||||
tm := dr.SourceDateEpoch.AsTime()
|
||||
opts = append(opts, diff.WithSourceDateEpoch(&tm))
|
||||
}
|
||||
|
||||
for _, d := range l.differs {
|
||||
ocidesc, err = d.Compare(ctx, aMounts, bMounts, opts...)
|
||||
if !errdefs.IsNotImplemented(err) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &diffapi.DiffResponse{
|
||||
Diff: oci.DescriptorToProto(ocidesc),
|
||||
}, nil
|
||||
}
|
||||
65
plugins/services/diff/service.go
Normal file
65
plugins/services/diff/service.go
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package diff
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
diffapi "github.com/containerd/containerd/v2/api/services/diff/v1"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "diff",
|
||||
Requires: []plugin.Type{
|
||||
plugins.ServicePlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
i, err := ic.GetByID(plugins.ServicePlugin, services.DiffService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &service{local: i.(diffapi.DiffClient)}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type service struct {
|
||||
local diffapi.DiffClient
|
||||
diffapi.UnimplementedDiffServer
|
||||
}
|
||||
|
||||
var _ diffapi.DiffServer = &service{}
|
||||
|
||||
func (s *service) Register(gs *grpc.Server) error {
|
||||
diffapi.RegisterDiffServer(gs, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Apply(ctx context.Context, er *diffapi.ApplyRequest) (*diffapi.ApplyResponse, error) {
|
||||
return s.local.Apply(ctx, er)
|
||||
}
|
||||
|
||||
func (s *service) Diff(ctx context.Context, dr *diffapi.DiffRequest) (*diffapi.DiffResponse, error) {
|
||||
return s.local.Diff(ctx, dr)
|
||||
}
|
||||
23
plugins/services/diff/service_unix.go
Normal file
23
plugins/services/diff/service_unix.go
Normal file
@@ -0,0 +1,23 @@
|
||||
//go:build !windows
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package diff
|
||||
|
||||
var defaultDifferConfig = &config{
|
||||
Order: []string{"walking"},
|
||||
}
|
||||
21
plugins/services/diff/service_windows.go
Normal file
21
plugins/services/diff/service_windows.go
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package diff
|
||||
|
||||
var defaultDifferConfig = &config{
|
||||
Order: []string{"windows", "windows-lcow"},
|
||||
}
|
||||
133
plugins/services/events/service.go
Normal file
133
plugins/services/events/service.go
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/events/v1"
|
||||
apittrpc "github.com/containerd/containerd/v2/api/services/ttrpc/events/v1"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/events"
|
||||
"github.com/containerd/containerd/v2/events/exchange"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/protobuf"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"github.com/containerd/ttrpc"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "events",
|
||||
Requires: []plugin.Type{
|
||||
plugins.EventPlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
ep, err := ic.GetByID(plugins.EventPlugin, "exchange")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewService(ep.(*exchange.Exchange)), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type service struct {
|
||||
ttService *ttrpcService
|
||||
events *exchange.Exchange
|
||||
api.UnimplementedEventsServer
|
||||
}
|
||||
|
||||
// NewService returns the GRPC events server
|
||||
func NewService(events *exchange.Exchange) api.EventsServer {
|
||||
return &service{
|
||||
ttService: &ttrpcService{
|
||||
events: events,
|
||||
},
|
||||
events: events,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Register(server *grpc.Server) error {
|
||||
api.RegisterEventsServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) RegisterTTRPC(server *ttrpc.Server) error {
|
||||
apittrpc.RegisterEventsService(server, s.ttService)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Publish(ctx context.Context, r *api.PublishRequest) (*ptypes.Empty, error) {
|
||||
if err := s.events.Publish(ctx, r.Topic, r.Event); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &ptypes.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *service) Forward(ctx context.Context, r *api.ForwardRequest) (*ptypes.Empty, error) {
|
||||
if err := s.events.Forward(ctx, fromProto(r.Envelope)); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &ptypes.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *service) Subscribe(req *api.SubscribeRequest, srv api.Events_SubscribeServer) error {
|
||||
ctx, cancel := context.WithCancel(srv.Context())
|
||||
defer cancel()
|
||||
|
||||
eventq, errq := s.events.Subscribe(ctx, req.Filters...)
|
||||
for {
|
||||
select {
|
||||
case ev := <-eventq:
|
||||
if err := srv.Send(toProto(ev)); err != nil {
|
||||
return fmt.Errorf("failed sending event to subscriber: %w", err)
|
||||
}
|
||||
case err := <-errq:
|
||||
if err != nil {
|
||||
return fmt.Errorf("subscription error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toProto(env *events.Envelope) *api.Envelope {
|
||||
return &api.Envelope{
|
||||
Timestamp: protobuf.ToTimestamp(env.Timestamp),
|
||||
Namespace: env.Namespace,
|
||||
Topic: env.Topic,
|
||||
Event: protobuf.FromAny(env.Event),
|
||||
}
|
||||
}
|
||||
|
||||
func fromProto(env *api.Envelope) *events.Envelope {
|
||||
return &events.Envelope{
|
||||
Timestamp: protobuf.FromTimestamp(env.Timestamp),
|
||||
Namespace: env.Namespace,
|
||||
Topic: env.Topic,
|
||||
Event: env.Event,
|
||||
}
|
||||
}
|
||||
49
plugins/services/events/ttrpc.go
Normal file
49
plugins/services/events/ttrpc.go
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/ttrpc/events/v1"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/events"
|
||||
"github.com/containerd/containerd/v2/events/exchange"
|
||||
"github.com/containerd/containerd/v2/protobuf"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
)
|
||||
|
||||
type ttrpcService struct {
|
||||
events *exchange.Exchange
|
||||
}
|
||||
|
||||
func (s *ttrpcService) Forward(ctx context.Context, r *api.ForwardRequest) (*ptypes.Empty, error) {
|
||||
if err := s.events.Forward(ctx, fromTProto(r.Envelope)); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &ptypes.Empty{}, nil
|
||||
}
|
||||
|
||||
func fromTProto(env *api.Envelope) *events.Envelope {
|
||||
return &events.Envelope{
|
||||
Timestamp: protobuf.FromTimestamp(env.Timestamp),
|
||||
Namespace: env.Namespace,
|
||||
Topic: env.Topic,
|
||||
Event: env.Event,
|
||||
}
|
||||
}
|
||||
52
plugins/services/healthcheck/service.go
Normal file
52
plugins/services/healthcheck/service.go
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/health"
|
||||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
serve *health.Server
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "healthcheck",
|
||||
InitFn: func(*plugin.InitContext) (interface{}, error) {
|
||||
return newService()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func newService() (*service, error) {
|
||||
return &service{
|
||||
health.NewServer(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) Register(server *grpc.Server) error {
|
||||
grpc_health_v1.RegisterHealthServer(server, s.serve)
|
||||
return nil
|
||||
}
|
||||
75
plugins/services/images/helpers.go
Normal file
75
plugins/services/images/helpers.go
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package images
|
||||
|
||||
import (
|
||||
imagesapi "github.com/containerd/containerd/v2/api/services/images/v1"
|
||||
"github.com/containerd/containerd/v2/api/types"
|
||||
"github.com/containerd/containerd/v2/core/images"
|
||||
"github.com/containerd/containerd/v2/protobuf"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
func imagesToProto(images []images.Image) []*imagesapi.Image {
|
||||
var imagespb []*imagesapi.Image
|
||||
|
||||
for _, image := range images {
|
||||
image := image
|
||||
imagespb = append(imagespb, imageToProto(&image))
|
||||
}
|
||||
|
||||
return imagespb
|
||||
}
|
||||
|
||||
func imageToProto(image *images.Image) *imagesapi.Image {
|
||||
return &imagesapi.Image{
|
||||
Name: image.Name,
|
||||
Labels: image.Labels,
|
||||
Target: descToProto(&image.Target),
|
||||
CreatedAt: protobuf.ToTimestamp(image.CreatedAt),
|
||||
UpdatedAt: protobuf.ToTimestamp(image.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func imageFromProto(imagepb *imagesapi.Image) images.Image {
|
||||
return images.Image{
|
||||
Name: imagepb.Name,
|
||||
Labels: imagepb.Labels,
|
||||
Target: descFromProto(imagepb.Target),
|
||||
CreatedAt: protobuf.FromTimestamp(imagepb.CreatedAt),
|
||||
UpdatedAt: protobuf.FromTimestamp(imagepb.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func descFromProto(desc *types.Descriptor) ocispec.Descriptor {
|
||||
return ocispec.Descriptor{
|
||||
MediaType: desc.MediaType,
|
||||
Size: desc.Size,
|
||||
Digest: digest.Digest(desc.Digest),
|
||||
Annotations: desc.Annotations,
|
||||
}
|
||||
}
|
||||
|
||||
func descToProto(desc *ocispec.Descriptor) *types.Descriptor {
|
||||
return &types.Descriptor{
|
||||
MediaType: desc.MediaType,
|
||||
Size: desc.Size,
|
||||
Digest: desc.Digest.String(),
|
||||
Annotations: desc.Annotations,
|
||||
}
|
||||
}
|
||||
228
plugins/services/images/local.go
Normal file
228
plugins/services/images/local.go
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package images
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
eventstypes "github.com/containerd/containerd/v2/api/events"
|
||||
imagesapi "github.com/containerd/containerd/v2/api/services/images/v1"
|
||||
"github.com/containerd/containerd/v2/core/images"
|
||||
"github.com/containerd/containerd/v2/core/metadata"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/events"
|
||||
"github.com/containerd/containerd/v2/gc"
|
||||
"github.com/containerd/containerd/v2/pkg/deprecation"
|
||||
"github.com/containerd/containerd/v2/pkg/epoch"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
"github.com/containerd/containerd/v2/plugins/services/warning"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.ServicePlugin,
|
||||
ID: services.ImagesService,
|
||||
Requires: []plugin.Type{
|
||||
plugins.EventPlugin,
|
||||
plugins.MetadataPlugin,
|
||||
plugins.GCPlugin,
|
||||
plugins.WarningPlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
m, err := ic.GetSingle(plugins.MetadataPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g, err := ic.GetSingle(plugins.GCPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ep, err := ic.GetSingle(plugins.EventPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w, err := ic.GetSingle(plugins.WarningPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &local{
|
||||
store: metadata.NewImageStore(m.(*metadata.DB)),
|
||||
publisher: ep.(events.Publisher),
|
||||
gc: g.(gcScheduler),
|
||||
warnings: w.(warning.Service),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type gcScheduler interface {
|
||||
ScheduleAndWait(context.Context) (gc.Stats, error)
|
||||
}
|
||||
|
||||
type local struct {
|
||||
store images.Store
|
||||
gc gcScheduler
|
||||
publisher events.Publisher
|
||||
warnings warning.Service
|
||||
}
|
||||
|
||||
var _ imagesapi.ImagesClient = &local{}
|
||||
|
||||
func (l *local) Get(ctx context.Context, req *imagesapi.GetImageRequest, _ ...grpc.CallOption) (*imagesapi.GetImageResponse, error) {
|
||||
image, err := l.store.Get(ctx, req.Name)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
imagepb := imageToProto(&image)
|
||||
return &imagesapi.GetImageResponse{
|
||||
Image: imagepb,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *local) List(ctx context.Context, req *imagesapi.ListImagesRequest, _ ...grpc.CallOption) (*imagesapi.ListImagesResponse, error) {
|
||||
images, err := l.store.List(ctx, req.Filters...)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &imagesapi.ListImagesResponse{
|
||||
Images: imagesToProto(images),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *local) Create(ctx context.Context, req *imagesapi.CreateImageRequest, _ ...grpc.CallOption) (*imagesapi.CreateImageResponse, error) {
|
||||
log.G(ctx).WithField("name", req.Image.Name).WithField("target", req.Image.Target.Digest).Debugf("create image")
|
||||
if req.Image.Name == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Image.Name required")
|
||||
}
|
||||
|
||||
var (
|
||||
image = imageFromProto(req.Image)
|
||||
resp imagesapi.CreateImageResponse
|
||||
)
|
||||
if req.SourceDateEpoch != nil {
|
||||
tm := req.SourceDateEpoch.AsTime()
|
||||
ctx = epoch.WithSourceDateEpoch(ctx, &tm)
|
||||
}
|
||||
created, err := l.store.Create(ctx, image)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
resp.Image = imageToProto(&created)
|
||||
|
||||
if err := l.publisher.Publish(ctx, "/images/create", &eventstypes.ImageCreate{
|
||||
Name: resp.Image.Name,
|
||||
Labels: resp.Image.Labels,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.emitSchema1DeprecationWarning(ctx, &image)
|
||||
return &resp, nil
|
||||
|
||||
}
|
||||
|
||||
func (l *local) Update(ctx context.Context, req *imagesapi.UpdateImageRequest, _ ...grpc.CallOption) (*imagesapi.UpdateImageResponse, error) {
|
||||
if req.Image.Name == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Image.Name required")
|
||||
}
|
||||
|
||||
var (
|
||||
image = imageFromProto(req.Image)
|
||||
resp imagesapi.UpdateImageResponse
|
||||
fieldpaths []string
|
||||
)
|
||||
|
||||
if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 {
|
||||
fieldpaths = append(fieldpaths, req.UpdateMask.Paths...)
|
||||
}
|
||||
|
||||
if req.SourceDateEpoch != nil {
|
||||
tm := req.SourceDateEpoch.AsTime()
|
||||
ctx = epoch.WithSourceDateEpoch(ctx, &tm)
|
||||
}
|
||||
|
||||
updated, err := l.store.Update(ctx, image, fieldpaths...)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
resp.Image = imageToProto(&updated)
|
||||
|
||||
if err := l.publisher.Publish(ctx, "/images/update", &eventstypes.ImageUpdate{
|
||||
Name: resp.Image.Name,
|
||||
Labels: resp.Image.Labels,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.emitSchema1DeprecationWarning(ctx, &image)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (l *local) Delete(ctx context.Context, req *imagesapi.DeleteImageRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) {
|
||||
log.G(ctx).WithField("name", req.Name).Debugf("delete image")
|
||||
|
||||
var opts []images.DeleteOpt
|
||||
if req.Target != nil {
|
||||
desc := descFromProto(req.Target)
|
||||
opts = append(opts, images.DeleteTarget(&desc))
|
||||
}
|
||||
|
||||
// Sync option handled here after event is published
|
||||
if err := l.store.Delete(ctx, req.Name, opts...); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
if err := l.publisher.Publish(ctx, "/images/delete", &eventstypes.ImageDelete{
|
||||
Name: req.Name,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.Sync {
|
||||
if _, err := l.gc.ScheduleAndWait(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &ptypes.Empty{}, nil
|
||||
}
|
||||
|
||||
func (l *local) emitSchema1DeprecationWarning(ctx context.Context, image *images.Image) {
|
||||
if image == nil {
|
||||
return
|
||||
}
|
||||
dgst, ok := image.Labels[images.ConvertedDockerSchema1LabelKey]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.G(ctx).WithField("name", image.Name).WithField("schema1digest", dgst).Warn("conversion from schema 1 images is deprecated")
|
||||
l.warnings.Emit(ctx, deprecation.PullSchema1Image)
|
||||
}
|
||||
78
plugins/services/images/service.go
Normal file
78
plugins/services/images/service.go
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package images
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
imagesapi "github.com/containerd/containerd/v2/api/services/images/v1"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "images",
|
||||
Requires: []plugin.Type{
|
||||
plugins.ServicePlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
i, err := ic.GetByID(plugins.ServicePlugin, services.ImagesService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &service{local: i.(imagesapi.ImagesClient)}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type service struct {
|
||||
local imagesapi.ImagesClient
|
||||
imagesapi.UnimplementedImagesServer
|
||||
}
|
||||
|
||||
var _ imagesapi.ImagesServer = &service{}
|
||||
|
||||
func (s *service) Register(server *grpc.Server) error {
|
||||
imagesapi.RegisterImagesServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Get(ctx context.Context, req *imagesapi.GetImageRequest) (*imagesapi.GetImageResponse, error) {
|
||||
return s.local.Get(ctx, req)
|
||||
}
|
||||
|
||||
func (s *service) List(ctx context.Context, req *imagesapi.ListImagesRequest) (*imagesapi.ListImagesResponse, error) {
|
||||
return s.local.List(ctx, req)
|
||||
}
|
||||
|
||||
func (s *service) Create(ctx context.Context, req *imagesapi.CreateImageRequest) (*imagesapi.CreateImageResponse, error) {
|
||||
return s.local.Create(ctx, req)
|
||||
}
|
||||
|
||||
func (s *service) Update(ctx context.Context, req *imagesapi.UpdateImageRequest) (*imagesapi.UpdateImageResponse, error) {
|
||||
return s.local.Update(ctx, req)
|
||||
}
|
||||
|
||||
func (s *service) Delete(ctx context.Context, req *imagesapi.DeleteImageRequest) (*ptypes.Empty, error) {
|
||||
return s.local.Delete(ctx, req)
|
||||
}
|
||||
66
plugins/services/introspection/introspection.go
Normal file
66
plugins/services/introspection/introspection.go
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package introspection
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/introspection/v1"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/log"
|
||||
)
|
||||
|
||||
// Service defines the introspection service interface
|
||||
type Service interface {
|
||||
Plugins(context.Context, []string) (*api.PluginsResponse, error)
|
||||
Server(context.Context, *ptypes.Empty) (*api.ServerResponse, error)
|
||||
}
|
||||
|
||||
type introspectionRemote struct {
|
||||
client api.IntrospectionClient
|
||||
}
|
||||
|
||||
var _ = (Service)(&introspectionRemote{})
|
||||
|
||||
// NewIntrospectionServiceFromClient creates a new introspection service from an API client
|
||||
func NewIntrospectionServiceFromClient(c api.IntrospectionClient) Service {
|
||||
return &introspectionRemote{client: c}
|
||||
}
|
||||
|
||||
func (i *introspectionRemote) Plugins(ctx context.Context, filters []string) (*api.PluginsResponse, error) {
|
||||
log.G(ctx).WithField("filters", filters).Debug("remote introspection plugin filters")
|
||||
resp, err := i.client.Plugins(ctx, &api.PluginsRequest{
|
||||
Filters: filters,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errdefs.FromGRPC(err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (i *introspectionRemote) Server(ctx context.Context, in *ptypes.Empty) (*api.ServerResponse, error) {
|
||||
resp, err := i.client.Server(ctx, in)
|
||||
|
||||
if err != nil {
|
||||
return nil, errdefs.FromGRPC(err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
269
plugins/services/introspection/local.go
Normal file
269
plugins/services/introspection/local.go
Normal file
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package introspection
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/genproto/googleapis/rpc/code"
|
||||
rpc "google.golang.org/genproto/googleapis/rpc/status"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/introspection/v1"
|
||||
"github.com/containerd/containerd/v2/api/types"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/filters"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
"github.com/containerd/containerd/v2/plugins/services/warning"
|
||||
"github.com/containerd/containerd/v2/protobuf"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.ServicePlugin,
|
||||
ID: services.IntrospectionService,
|
||||
Requires: []plugin.Type{plugins.WarningPlugin},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
i, err := ic.GetByID(plugins.WarningPlugin, plugins.DeprecationsPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
warningClient, ok := i.(warning.Service)
|
||||
if !ok {
|
||||
return nil, errors.New("could not create a local client for warning service")
|
||||
}
|
||||
|
||||
// this service fetches all plugins through the plugin set of the plugin context
|
||||
return &Local{
|
||||
plugins: ic.Plugins(),
|
||||
root: ic.Properties[plugins.PropertyRootDir],
|
||||
warningClient: warningClient,
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Local is a local implementation of the introspection service
|
||||
type Local struct {
|
||||
mu sync.Mutex
|
||||
root string
|
||||
plugins *plugin.Set
|
||||
pluginCache []*api.Plugin
|
||||
warningClient warning.Service
|
||||
}
|
||||
|
||||
var _ = (api.IntrospectionClient)(&Local{})
|
||||
|
||||
// UpdateLocal updates the local introspection service
|
||||
func (l *Local) UpdateLocal(root string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.root = root
|
||||
}
|
||||
|
||||
// Plugins returns the locally defined plugins
|
||||
func (l *Local) Plugins(ctx context.Context, req *api.PluginsRequest, _ ...grpc.CallOption) (*api.PluginsResponse, error) {
|
||||
filter, err := filters.ParseAll(req.Filters...)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPCf(errdefs.ErrInvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
var plugins []*api.Plugin
|
||||
allPlugins := l.getPlugins()
|
||||
for _, p := range allPlugins {
|
||||
p := p
|
||||
if filter.Match(adaptPlugin(p)) {
|
||||
plugins = append(plugins, p)
|
||||
}
|
||||
}
|
||||
|
||||
return &api.PluginsResponse{
|
||||
Plugins: plugins,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *Local) getPlugins() []*api.Plugin {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
plugins := l.plugins.GetAll()
|
||||
if l.pluginCache == nil || len(plugins) != len(l.pluginCache) {
|
||||
l.pluginCache = pluginsToPB(plugins)
|
||||
}
|
||||
return l.pluginCache
|
||||
}
|
||||
|
||||
// Server returns the local server information
|
||||
func (l *Local) Server(ctx context.Context, _ *ptypes.Empty, _ ...grpc.CallOption) (*api.ServerResponse, error) {
|
||||
u, err := l.getUUID()
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
pid := os.Getpid()
|
||||
var pidns uint64
|
||||
if runtime.GOOS == "linux" {
|
||||
pidns, err = statPIDNS(pid)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
}
|
||||
return &api.ServerResponse{
|
||||
UUID: u,
|
||||
Pid: uint64(pid),
|
||||
Pidns: pidns,
|
||||
Deprecations: l.getWarnings(ctx),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *Local) getUUID() (string, error) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
data, err := os.ReadFile(l.uuidPath())
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return l.generateUUID()
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
u := string(data)
|
||||
if _, err := uuid.Parse(u); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (l *Local) generateUUID() (string, error) {
|
||||
u, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
path := l.uuidPath()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
uu := u.String()
|
||||
if err := os.WriteFile(path, []byte(uu), 0666); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return uu, nil
|
||||
}
|
||||
|
||||
func (l *Local) uuidPath() string {
|
||||
return filepath.Join(l.root, "uuid")
|
||||
}
|
||||
|
||||
func (l *Local) getWarnings(ctx context.Context) []*api.DeprecationWarning {
|
||||
return warningsPB(ctx, l.warningClient.Warnings())
|
||||
}
|
||||
|
||||
func adaptPlugin(o interface{}) filters.Adaptor {
|
||||
obj := o.(*api.Plugin)
|
||||
return filters.AdapterFunc(func(fieldpath []string) (string, bool) {
|
||||
if len(fieldpath) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
switch fieldpath[0] {
|
||||
case "type":
|
||||
return obj.Type, len(obj.Type) > 0
|
||||
case "id":
|
||||
return obj.ID, len(obj.ID) > 0
|
||||
case "platforms":
|
||||
// TODO(stevvooe): Another case here where have multiple values.
|
||||
// May need to refactor the filter system to allow filtering by
|
||||
// platform, if this is required.
|
||||
case "capabilities":
|
||||
// TODO(stevvooe): Need a better way to match against
|
||||
// collections. We can only return "the value" but really it
|
||||
// would be best if we could return a set of values for the
|
||||
// path, any of which could match.
|
||||
}
|
||||
|
||||
return "", false
|
||||
})
|
||||
}
|
||||
|
||||
func pluginsToPB(plugins []*plugin.Plugin) []*api.Plugin {
|
||||
var pluginsPB []*api.Plugin
|
||||
for _, p := range plugins {
|
||||
var requires []string
|
||||
for _, r := range p.Registration.Requires {
|
||||
requires = append(requires, r.String())
|
||||
}
|
||||
|
||||
var initErr *rpc.Status
|
||||
if err := p.Err(); err != nil {
|
||||
st, ok := status.FromError(errdefs.ToGRPC(err))
|
||||
if ok {
|
||||
var details []*ptypes.Any
|
||||
for _, d := range st.Proto().Details {
|
||||
details = append(details, &ptypes.Any{
|
||||
TypeUrl: d.TypeUrl,
|
||||
Value: d.Value,
|
||||
})
|
||||
}
|
||||
initErr = &rpc.Status{
|
||||
Code: int32(st.Code()),
|
||||
Message: st.Message(),
|
||||
Details: details,
|
||||
}
|
||||
} else {
|
||||
initErr = &rpc.Status{
|
||||
Code: int32(code.Code_UNKNOWN),
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pluginsPB = append(pluginsPB, &api.Plugin{
|
||||
Type: p.Registration.Type.String(),
|
||||
ID: p.Registration.ID,
|
||||
Requires: requires,
|
||||
Platforms: types.OCIPlatformToProto(p.Meta.Platforms),
|
||||
Capabilities: p.Meta.Capabilities,
|
||||
Exports: p.Meta.Exports,
|
||||
InitErr: initErr,
|
||||
})
|
||||
}
|
||||
|
||||
return pluginsPB
|
||||
}
|
||||
|
||||
func warningsPB(ctx context.Context, warnings []warning.Warning) []*api.DeprecationWarning {
|
||||
var pb []*api.DeprecationWarning
|
||||
|
||||
for _, w := range warnings {
|
||||
pb = append(pb, &api.DeprecationWarning{
|
||||
ID: string(w.ID),
|
||||
Message: w.Message,
|
||||
LastOccurrence: protobuf.ToTimestamp(w.LastOccurrence),
|
||||
})
|
||||
}
|
||||
return pb
|
||||
}
|
||||
36
plugins/services/introspection/pidns_linux.go
Normal file
36
plugins/services/introspection/pidns_linux.go
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package introspection
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func statPIDNS(pid int) (uint64, error) {
|
||||
f := fmt.Sprintf("/proc/%d/ns/pid", pid)
|
||||
st, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
stSys, ok := st.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("%T is not *syscall.Stat_t", st.Sys())
|
||||
}
|
||||
return stSys.Ino, nil
|
||||
}
|
||||
23
plugins/services/introspection/pidns_others.go
Normal file
23
plugins/services/introspection/pidns_others.go
Normal file
@@ -0,0 +1,23 @@
|
||||
//go:build !linux
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package introspection
|
||||
|
||||
func statPIDNS(pid int) (uint64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
74
plugins/services/introspection/service.go
Normal file
74
plugins/services/introspection/service.go
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package introspection
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"errors"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/introspection/v1"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "introspection",
|
||||
Requires: []plugin.Type{plugins.ServicePlugin},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
i, err := ic.GetByID(plugins.ServicePlugin, services.IntrospectionService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localClient, ok := i.(*Local)
|
||||
if !ok {
|
||||
return nil, errors.New("could not create a local client for introspection service")
|
||||
}
|
||||
localClient.UpdateLocal(ic.Properties[plugins.PropertyRootDir])
|
||||
|
||||
return &server{
|
||||
local: localClient,
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type server struct {
|
||||
local api.IntrospectionClient
|
||||
api.UnimplementedIntrospectionServer
|
||||
}
|
||||
|
||||
var _ = (api.IntrospectionServer)(&server{})
|
||||
|
||||
func (s *server) Register(server *grpc.Server) error {
|
||||
api.RegisterIntrospectionServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) Plugins(ctx context.Context, req *api.PluginsRequest) (*api.PluginsResponse, error) {
|
||||
return s.local.Plugins(ctx, req)
|
||||
}
|
||||
|
||||
func (s *server) Server(ctx context.Context, empty *ptypes.Empty) (*api.ServerResponse, error) {
|
||||
return s.local.Server(ctx, empty)
|
||||
}
|
||||
165
plugins/services/leases/service.go
Normal file
165
plugins/services/leases/service.go
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package leases
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/leases/v1"
|
||||
"github.com/containerd/containerd/v2/core/leases"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/protobuf"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "leases",
|
||||
Requires: []plugin.Type{
|
||||
plugins.LeasePlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
i, err := ic.GetByID(plugins.LeasePlugin, "manager")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &service{lm: i.(leases.Manager)}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type service struct {
|
||||
lm leases.Manager
|
||||
api.UnimplementedLeasesServer
|
||||
}
|
||||
|
||||
func (s *service) Register(server *grpc.Server) error {
|
||||
api.RegisterLeasesServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Create(ctx context.Context, r *api.CreateRequest) (*api.CreateResponse, error) {
|
||||
opts := []leases.Opt{
|
||||
leases.WithLabels(r.Labels),
|
||||
}
|
||||
if r.ID == "" {
|
||||
opts = append(opts, leases.WithRandomID())
|
||||
} else {
|
||||
opts = append(opts, leases.WithID(r.ID))
|
||||
}
|
||||
|
||||
l, err := s.lm.Create(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &api.CreateResponse{
|
||||
Lease: leaseToGRPC(l),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) Delete(ctx context.Context, r *api.DeleteRequest) (*ptypes.Empty, error) {
|
||||
var opts []leases.DeleteOpt
|
||||
if r.Sync {
|
||||
opts = append(opts, leases.SynchronousDelete)
|
||||
}
|
||||
if err := s.lm.Delete(ctx, leases.Lease{
|
||||
ID: r.ID,
|
||||
}, opts...); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &ptypes.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *service) List(ctx context.Context, r *api.ListRequest) (*api.ListResponse, error) {
|
||||
l, err := s.lm.List(ctx, r.Filters...)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
apileases := make([]*api.Lease, len(l))
|
||||
for i := range l {
|
||||
apileases[i] = leaseToGRPC(l[i])
|
||||
}
|
||||
|
||||
return &api.ListResponse{
|
||||
Leases: apileases,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) AddResource(ctx context.Context, r *api.AddResourceRequest) (*ptypes.Empty, error) {
|
||||
lease := leases.Lease{
|
||||
ID: r.ID,
|
||||
}
|
||||
|
||||
if err := s.lm.AddResource(ctx, lease, leases.Resource{
|
||||
ID: r.Resource.ID,
|
||||
Type: r.Resource.Type,
|
||||
}); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &ptypes.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *service) DeleteResource(ctx context.Context, r *api.DeleteResourceRequest) (*ptypes.Empty, error) {
|
||||
lease := leases.Lease{
|
||||
ID: r.ID,
|
||||
}
|
||||
|
||||
if err := s.lm.DeleteResource(ctx, lease, leases.Resource{
|
||||
ID: r.Resource.ID,
|
||||
Type: r.Resource.Type,
|
||||
}); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &ptypes.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *service) ListResources(ctx context.Context, r *api.ListResourcesRequest) (*api.ListResourcesResponse, error) {
|
||||
lease := leases.Lease{
|
||||
ID: r.ID,
|
||||
}
|
||||
|
||||
rs, err := s.lm.ListResources(ctx, lease)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
apiResources := make([]*api.Resource, 0, len(rs))
|
||||
for _, i := range rs {
|
||||
apiResources = append(apiResources, &api.Resource{
|
||||
ID: i.ID,
|
||||
Type: i.Type,
|
||||
})
|
||||
}
|
||||
return &api.ListResourcesResponse{
|
||||
Resources: apiResources,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func leaseToGRPC(l leases.Lease) *api.Lease {
|
||||
return &api.Lease{
|
||||
ID: l.ID,
|
||||
Labels: l.Labels,
|
||||
CreatedAt: protobuf.ToTimestamp(l.CreatedAt),
|
||||
}
|
||||
}
|
||||
232
plugins/services/namespaces/local.go
Normal file
232
plugins/services/namespaces/local.go
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package namespaces
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
eventstypes "github.com/containerd/containerd/v2/api/events"
|
||||
api "github.com/containerd/containerd/v2/api/services/namespaces/v1"
|
||||
"github.com/containerd/containerd/v2/core/metadata"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/events"
|
||||
"github.com/containerd/containerd/v2/namespaces"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.ServicePlugin,
|
||||
ID: services.NamespacesService,
|
||||
Requires: []plugin.Type{
|
||||
plugins.EventPlugin,
|
||||
plugins.MetadataPlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
m, err := ic.GetSingle(plugins.MetadataPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ep, err := ic.GetSingle(plugins.EventPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &local{
|
||||
db: m.(*metadata.DB),
|
||||
publisher: ep.(events.Publisher),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Provide local namespaces service instead of local namespace store,
|
||||
// because namespace store interface doesn't provide enough functionality
|
||||
// for namespaces service.
|
||||
type local struct {
|
||||
db *metadata.DB
|
||||
publisher events.Publisher
|
||||
}
|
||||
|
||||
var _ api.NamespacesClient = &local{}
|
||||
|
||||
func (l *local) Get(ctx context.Context, req *api.GetNamespaceRequest, _ ...grpc.CallOption) (*api.GetNamespaceResponse, error) {
|
||||
var resp api.GetNamespaceResponse
|
||||
|
||||
return &resp, l.withStoreView(ctx, func(ctx context.Context, store namespaces.Store) error {
|
||||
labels, err := store.Labels(ctx, req.Name)
|
||||
if err != nil {
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
resp.Namespace = &api.Namespace{
|
||||
Name: req.Name,
|
||||
Labels: labels,
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (l *local) List(ctx context.Context, req *api.ListNamespacesRequest, _ ...grpc.CallOption) (*api.ListNamespacesResponse, error) {
|
||||
var resp api.ListNamespacesResponse
|
||||
|
||||
return &resp, l.withStoreView(ctx, func(ctx context.Context, store namespaces.Store) error {
|
||||
namespaces, err := store.List(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, namespace := range namespaces {
|
||||
labels, err := store.Labels(ctx, namespace)
|
||||
if err != nil {
|
||||
// In general, this should be unlikely, since we are holding a
|
||||
// transaction to service this request.
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
resp.Namespaces = append(resp.Namespaces, &api.Namespace{
|
||||
Name: namespace,
|
||||
Labels: labels,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (l *local) Create(ctx context.Context, req *api.CreateNamespaceRequest, _ ...grpc.CallOption) (*api.CreateNamespaceResponse, error) {
|
||||
var resp api.CreateNamespaceResponse
|
||||
|
||||
if err := l.withStoreUpdate(ctx, func(ctx context.Context, store namespaces.Store) error {
|
||||
if err := store.Create(ctx, req.Namespace.Name, req.Namespace.Labels); err != nil {
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
for k, v := range req.Namespace.Labels {
|
||||
if err := store.SetLabel(ctx, req.Namespace.Name, k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
resp.Namespace = req.Namespace
|
||||
return nil
|
||||
}); err != nil {
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
ctx = namespaces.WithNamespace(ctx, req.Namespace.Name)
|
||||
if err := l.publisher.Publish(ctx, "/namespaces/create", &eventstypes.NamespaceCreate{
|
||||
Name: req.Namespace.Name,
|
||||
Labels: req.Namespace.Labels,
|
||||
}); err != nil {
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
|
||||
}
|
||||
|
||||
func (l *local) Update(ctx context.Context, req *api.UpdateNamespaceRequest, _ ...grpc.CallOption) (*api.UpdateNamespaceResponse, error) {
|
||||
var resp api.UpdateNamespaceResponse
|
||||
if err := l.withStoreUpdate(ctx, func(ctx context.Context, store namespaces.Store) error {
|
||||
if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 {
|
||||
for _, path := range req.UpdateMask.Paths {
|
||||
switch {
|
||||
case strings.HasPrefix(path, "labels."):
|
||||
key := strings.TrimPrefix(path, "labels.")
|
||||
if err := store.SetLabel(ctx, req.Namespace.Name, key, req.Namespace.Labels[key]); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return status.Errorf(codes.InvalidArgument, "cannot update %q field", path)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// clear out the existing labels and then set them to the incoming request.
|
||||
// get current set of labels
|
||||
labels, err := store.Labels(ctx, req.Namespace.Name)
|
||||
if err != nil {
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
for k := range labels {
|
||||
if err := store.SetLabel(ctx, req.Namespace.Name, k, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range req.Namespace.Labels {
|
||||
if err := store.SetLabel(ctx, req.Namespace.Name, k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
ctx = namespaces.WithNamespace(ctx, req.Namespace.Name)
|
||||
if err := l.publisher.Publish(ctx, "/namespaces/update", &eventstypes.NamespaceUpdate{
|
||||
Name: req.Namespace.Name,
|
||||
Labels: req.Namespace.Labels,
|
||||
}); err != nil {
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (l *local) Delete(ctx context.Context, req *api.DeleteNamespaceRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) {
|
||||
if err := l.withStoreUpdate(ctx, func(ctx context.Context, store namespaces.Store) error {
|
||||
return errdefs.ToGRPC(store.Delete(ctx, req.Name))
|
||||
}); err != nil {
|
||||
return &ptypes.Empty{}, err
|
||||
}
|
||||
// set the namespace in the context before publishing the event
|
||||
ctx = namespaces.WithNamespace(ctx, req.Name)
|
||||
if err := l.publisher.Publish(ctx, "/namespaces/delete", &eventstypes.NamespaceDelete{
|
||||
Name: req.Name,
|
||||
}); err != nil {
|
||||
return &ptypes.Empty{}, err
|
||||
}
|
||||
|
||||
return &ptypes.Empty{}, nil
|
||||
}
|
||||
|
||||
func (l *local) withStore(ctx context.Context, fn func(ctx context.Context, store namespaces.Store) error) func(tx *bolt.Tx) error {
|
||||
return func(tx *bolt.Tx) error { return fn(ctx, metadata.NewNamespaceStore(tx)) }
|
||||
}
|
||||
|
||||
func (l *local) withStoreView(ctx context.Context, fn func(ctx context.Context, store namespaces.Store) error) error {
|
||||
return l.db.View(l.withStore(ctx, fn))
|
||||
}
|
||||
|
||||
func (l *local) withStoreUpdate(ctx context.Context, fn func(ctx context.Context, store namespaces.Store) error) error {
|
||||
return l.db.Update(l.withStore(ctx, fn))
|
||||
}
|
||||
78
plugins/services/namespaces/service.go
Normal file
78
plugins/services/namespaces/service.go
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package namespaces
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/namespaces/v1"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "namespaces",
|
||||
Requires: []plugin.Type{
|
||||
plugins.ServicePlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
i, err := ic.GetByID(plugins.ServicePlugin, services.NamespacesService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &service{local: i.(api.NamespacesClient)}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type service struct {
|
||||
local api.NamespacesClient
|
||||
api.UnimplementedNamespacesServer
|
||||
}
|
||||
|
||||
var _ api.NamespacesServer = &service{}
|
||||
|
||||
func (s *service) Register(server *grpc.Server) error {
|
||||
api.RegisterNamespacesServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Get(ctx context.Context, req *api.GetNamespaceRequest) (*api.GetNamespaceResponse, error) {
|
||||
return s.local.Get(ctx, req)
|
||||
}
|
||||
|
||||
func (s *service) List(ctx context.Context, req *api.ListNamespacesRequest) (*api.ListNamespacesResponse, error) {
|
||||
return s.local.List(ctx, req)
|
||||
}
|
||||
|
||||
func (s *service) Create(ctx context.Context, req *api.CreateNamespaceRequest) (*api.CreateNamespaceResponse, error) {
|
||||
return s.local.Create(ctx, req)
|
||||
}
|
||||
|
||||
func (s *service) Update(ctx context.Context, req *api.UpdateNamespaceRequest) (*api.UpdateNamespaceResponse, error) {
|
||||
return s.local.Update(ctx, req)
|
||||
}
|
||||
|
||||
func (s *service) Delete(ctx context.Context, req *api.DeleteNamespaceRequest) (*ptypes.Empty, error) {
|
||||
return s.local.Delete(ctx, req)
|
||||
}
|
||||
21
plugins/services/opt/path_unix.go
Normal file
21
plugins/services/opt/path_unix.go
Normal file
@@ -0,0 +1,21 @@
|
||||
//go:build !windows
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package opt
|
||||
|
||||
const defaultPath = "/opt/containerd"
|
||||
25
plugins/services/opt/path_windows.go
Normal file
25
plugins/services/opt/path_windows.go
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package opt
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/containerd/v2/defaults"
|
||||
)
|
||||
|
||||
var defaultPath = filepath.Join(defaults.DefaultRootDir, "opt")
|
||||
66
plugins/services/opt/service.go
Normal file
66
plugins/services/opt/service.go
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package opt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
)
|
||||
|
||||
// Config for the opt manager
|
||||
type Config struct {
|
||||
// Path for the opt directory
|
||||
Path string `toml:"path"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.InternalPlugin,
|
||||
ID: "opt",
|
||||
Config: &Config{
|
||||
Path: defaultPath,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
path := ic.Config.(*Config).Path
|
||||
ic.Meta.Exports["path"] = path
|
||||
bin := filepath.Join(path, "bin")
|
||||
if err := os.MkdirAll(bin, 0711); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.Setenv("PATH", fmt.Sprintf("%s%c%s", bin, os.PathListSeparator, os.Getenv("PATH"))); err != nil {
|
||||
return nil, fmt.Errorf("set binary image directory in path %s: %w", bin, err)
|
||||
}
|
||||
|
||||
lib := filepath.Join(path, "lib")
|
||||
if err := os.MkdirAll(lib, 0711); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.Setenv("LD_LIBRARY_PATH", fmt.Sprintf("%s%c%s", lib, os.PathListSeparator, os.Getenv("LD_LIBRARY_PATH"))); err != nil {
|
||||
return nil, fmt.Errorf("set binary lib directory in path %s: %w", lib, err)
|
||||
}
|
||||
return &manager{}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
}
|
||||
226
plugins/services/sandbox/controller_service.go
Normal file
226
plugins/services/sandbox/controller_service.go
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
|
||||
eventtypes "github.com/containerd/containerd/v2/api/events"
|
||||
api "github.com/containerd/containerd/v2/api/services/sandbox/v1"
|
||||
"github.com/containerd/containerd/v2/core/sandbox"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/events"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/protobuf"
|
||||
"github.com/containerd/log"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "sandbox-controllers",
|
||||
Requires: []plugin.Type{
|
||||
plugins.SandboxControllerPlugin,
|
||||
plugins.EventPlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
sandboxers, err := ic.GetByType(plugins.SandboxControllerPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sc := make(map[string]sandbox.Controller)
|
||||
for name, p := range sandboxers {
|
||||
sc[name] = p.(sandbox.Controller)
|
||||
}
|
||||
|
||||
ep, err := ic.GetSingle(plugins.EventPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &controllerService{
|
||||
sc: sc,
|
||||
publisher: ep.(events.Publisher),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type controllerService struct {
|
||||
sc map[string]sandbox.Controller
|
||||
publisher events.Publisher
|
||||
api.UnimplementedControllerServer
|
||||
}
|
||||
|
||||
var _ api.ControllerServer = (*controllerService)(nil)
|
||||
|
||||
func (s *controllerService) Register(server *grpc.Server) error {
|
||||
api.RegisterControllerServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *controllerService) getController(name string) (sandbox.Controller, error) {
|
||||
if len(name) == 0 {
|
||||
return nil, fmt.Errorf("%w: sandbox controller name can not be empty", errdefs.ErrInvalidArgument)
|
||||
}
|
||||
if ctrl, ok := s.sc[name]; ok {
|
||||
return ctrl, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: failed to get sandbox controller by %s", errdefs.ErrNotFound, name)
|
||||
}
|
||||
|
||||
func (s *controllerService) Create(ctx context.Context, req *api.ControllerCreateRequest) (*api.ControllerCreateResponse, error) {
|
||||
log.G(ctx).WithField("req", req).Debug("create sandbox")
|
||||
// TODO: Rootfs
|
||||
ctrl, err := s.getController(req.Sandboxer)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
err = ctrl.Create(ctx, sandbox.Sandbox{ID: req.GetSandboxID()}, sandbox.WithOptions(req.GetOptions()))
|
||||
if err != nil {
|
||||
return &api.ControllerCreateResponse{}, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
if err := s.publisher.Publish(ctx, "sandboxes/create", &eventtypes.SandboxCreate{
|
||||
SandboxID: req.GetSandboxID(),
|
||||
}); err != nil {
|
||||
return &api.ControllerCreateResponse{}, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &api.ControllerCreateResponse{
|
||||
SandboxID: req.GetSandboxID(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *controllerService) Start(ctx context.Context, req *api.ControllerStartRequest) (*api.ControllerStartResponse, error) {
|
||||
log.G(ctx).WithField("req", req).Debug("start sandbox")
|
||||
ctrl, err := s.getController(req.Sandboxer)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
inst, err := ctrl.Start(ctx, req.GetSandboxID())
|
||||
if err != nil {
|
||||
return &api.ControllerStartResponse{}, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
if err := s.publisher.Publish(ctx, "sandboxes/start", &eventtypes.SandboxStart{
|
||||
SandboxID: req.GetSandboxID(),
|
||||
}); err != nil {
|
||||
return &api.ControllerStartResponse{}, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &api.ControllerStartResponse{
|
||||
SandboxID: inst.SandboxID,
|
||||
Pid: inst.Pid,
|
||||
CreatedAt: protobuf.ToTimestamp(inst.CreatedAt),
|
||||
Labels: inst.Labels,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *controllerService) Stop(ctx context.Context, req *api.ControllerStopRequest) (*api.ControllerStopResponse, error) {
|
||||
log.G(ctx).WithField("req", req).Debug("delete sandbox")
|
||||
ctrl, err := s.getController(req.Sandboxer)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &api.ControllerStopResponse{}, errdefs.ToGRPC(ctrl.Stop(ctx, req.GetSandboxID(), sandbox.WithTimeout(time.Duration(req.TimeoutSecs)*time.Second)))
|
||||
}
|
||||
|
||||
func (s *controllerService) Wait(ctx context.Context, req *api.ControllerWaitRequest) (*api.ControllerWaitResponse, error) {
|
||||
log.G(ctx).WithField("req", req).Debug("wait sandbox")
|
||||
ctrl, err := s.getController(req.Sandboxer)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
exitStatus, err := ctrl.Wait(ctx, req.GetSandboxID())
|
||||
if err != nil {
|
||||
return &api.ControllerWaitResponse{}, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
if err := s.publisher.Publish(ctx, "sandboxes/exit", &eventtypes.SandboxExit{
|
||||
SandboxID: req.GetSandboxID(),
|
||||
ExitStatus: exitStatus.ExitStatus,
|
||||
ExitedAt: protobuf.ToTimestamp(exitStatus.ExitedAt),
|
||||
}); err != nil {
|
||||
return &api.ControllerWaitResponse{}, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &api.ControllerWaitResponse{
|
||||
ExitStatus: exitStatus.ExitStatus,
|
||||
ExitedAt: protobuf.ToTimestamp(exitStatus.ExitedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *controllerService) Status(ctx context.Context, req *api.ControllerStatusRequest) (*api.ControllerStatusResponse, error) {
|
||||
log.G(ctx).WithField("req", req).Debug("sandbox status")
|
||||
ctrl, err := s.getController(req.Sandboxer)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
cstatus, err := ctrl.Status(ctx, req.GetSandboxID(), req.GetVerbose())
|
||||
if err != nil {
|
||||
return &api.ControllerStatusResponse{}, errdefs.ToGRPC(err)
|
||||
}
|
||||
extra := &anypb.Any{}
|
||||
if cstatus.Extra != nil {
|
||||
extra = &anypb.Any{
|
||||
TypeUrl: cstatus.Extra.GetTypeUrl(),
|
||||
Value: cstatus.Extra.GetValue(),
|
||||
}
|
||||
}
|
||||
return &api.ControllerStatusResponse{
|
||||
SandboxID: cstatus.SandboxID,
|
||||
Pid: cstatus.Pid,
|
||||
State: cstatus.State,
|
||||
Info: cstatus.Info,
|
||||
CreatedAt: protobuf.ToTimestamp(cstatus.CreatedAt),
|
||||
ExitedAt: protobuf.ToTimestamp(cstatus.ExitedAt),
|
||||
Extra: extra,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *controllerService) Shutdown(ctx context.Context, req *api.ControllerShutdownRequest) (*api.ControllerShutdownResponse, error) {
|
||||
log.G(ctx).WithField("req", req).Debug("shutdown sandbox")
|
||||
ctrl, err := s.getController(req.Sandboxer)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &api.ControllerShutdownResponse{}, errdefs.ToGRPC(ctrl.Shutdown(ctx, req.GetSandboxID()))
|
||||
}
|
||||
|
||||
func (s *controllerService) Metrics(ctx context.Context, req *api.ControllerMetricsRequest) (*api.ControllerMetricsResponse, error) {
|
||||
log.G(ctx).WithField("req", req).Debug("sandbox metrics")
|
||||
ctrl, err := s.getController(req.Sandboxer)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
metrics, err := ctrl.Metrics(ctx, req.GetSandboxID())
|
||||
if err != nil {
|
||||
return &api.ControllerMetricsResponse{}, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &api.ControllerMetricsResponse{
|
||||
Metrics: metrics,
|
||||
}, nil
|
||||
}
|
||||
119
plugins/services/sandbox/store_service.go
Normal file
119
plugins/services/sandbox/store_service.go
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/sandbox/v1"
|
||||
"github.com/containerd/containerd/v2/api/types"
|
||||
"github.com/containerd/containerd/v2/core/sandbox"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/log"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "sandboxes",
|
||||
Requires: []plugin.Type{
|
||||
plugins.SandboxStorePlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
sp, err := ic.GetByID(plugins.SandboxStorePlugin, "local")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &sandboxService{store: sp.(sandbox.Store)}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type sandboxService struct {
|
||||
store sandbox.Store
|
||||
api.UnimplementedStoreServer
|
||||
}
|
||||
|
||||
var _ api.StoreServer = (*sandboxService)(nil)
|
||||
|
||||
func (s *sandboxService) Register(server *grpc.Server) error {
|
||||
api.RegisterStoreServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sandboxService) Create(ctx context.Context, req *api.StoreCreateRequest) (*api.StoreCreateResponse, error) {
|
||||
log.G(ctx).WithField("req", req).Debug("create sandbox")
|
||||
sb, err := s.store.Create(ctx, sandbox.FromProto(req.Sandbox))
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &api.StoreCreateResponse{Sandbox: sandbox.ToProto(&sb)}, nil
|
||||
}
|
||||
|
||||
func (s *sandboxService) Update(ctx context.Context, req *api.StoreUpdateRequest) (*api.StoreUpdateResponse, error) {
|
||||
log.G(ctx).WithField("req", req).Debug("update sandbox")
|
||||
|
||||
sb, err := s.store.Update(ctx, sandbox.FromProto(req.Sandbox), req.Fields...)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &api.StoreUpdateResponse{Sandbox: sandbox.ToProto(&sb)}, nil
|
||||
}
|
||||
|
||||
func (s *sandboxService) List(ctx context.Context, req *api.StoreListRequest) (*api.StoreListResponse, error) {
|
||||
log.G(ctx).WithField("req", req).Debug("list sandboxes")
|
||||
|
||||
resp, err := s.store.List(ctx, req.Filters...)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
list := make([]*types.Sandbox, len(resp))
|
||||
for i := range resp {
|
||||
list[i] = sandbox.ToProto(&resp[i])
|
||||
}
|
||||
|
||||
return &api.StoreListResponse{List: list}, nil
|
||||
}
|
||||
|
||||
func (s *sandboxService) Get(ctx context.Context, req *api.StoreGetRequest) (*api.StoreGetResponse, error) {
|
||||
log.G(ctx).WithField("req", req).Debug("get sandbox")
|
||||
resp, err := s.store.Get(ctx, req.SandboxID)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
desc := sandbox.ToProto(&resp)
|
||||
return &api.StoreGetResponse{Sandbox: desc}, nil
|
||||
}
|
||||
|
||||
func (s *sandboxService) Delete(ctx context.Context, req *api.StoreDeleteRequest) (*api.StoreDeleteResponse, error) {
|
||||
log.G(ctx).WithField("req", req).Debug("delete sandbox")
|
||||
if err := s.store.Delete(ctx, req.SandboxID); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &api.StoreDeleteResponse{}, nil
|
||||
}
|
||||
40
plugins/services/services.go
Normal file
40
plugins/services/services.go
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package services
|
||||
|
||||
const (
|
||||
// ContentService is id of content service.
|
||||
ContentService = "content-service"
|
||||
// SnapshotsService is id of snapshots service.
|
||||
SnapshotsService = "snapshots-service"
|
||||
// SandboxControllersService is id of snapshots service.
|
||||
SandboxControllersService = "sandboxes-service"
|
||||
// ImagesService is id of images service.
|
||||
ImagesService = "images-service"
|
||||
// ContainersService is id of containers service.
|
||||
ContainersService = "containers-service"
|
||||
// TasksService is id of tasks service.
|
||||
TasksService = "tasks-service"
|
||||
// NamespacesService is id of namespaces service.
|
||||
NamespacesService = "namespaces-service"
|
||||
// DiffService is id of diff service.
|
||||
DiffService = "diff-service"
|
||||
// IntrospectionService is the id of introspection service
|
||||
IntrospectionService = "introspection-service"
|
||||
// StreamingService is the id of the streaming service
|
||||
StreamingService = "streaming-service"
|
||||
)
|
||||
267
plugins/services/snapshots/service.go
Normal file
267
plugins/services/snapshots/service.go
Normal file
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package snapshots
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
snapshotsapi "github.com/containerd/containerd/v2/api/services/snapshots/v1"
|
||||
"github.com/containerd/containerd/v2/core/mount"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/containerd/v2/snapshots"
|
||||
"github.com/containerd/log"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "snapshots",
|
||||
Requires: []plugin.Type{
|
||||
plugins.ServicePlugin,
|
||||
},
|
||||
InitFn: newService,
|
||||
})
|
||||
}
|
||||
|
||||
var empty = &ptypes.Empty{}
|
||||
|
||||
type service struct {
|
||||
ss map[string]snapshots.Snapshotter
|
||||
snapshotsapi.UnimplementedSnapshotsServer
|
||||
}
|
||||
|
||||
func newService(ic *plugin.InitContext) (interface{}, error) {
|
||||
i, err := ic.GetByID(plugins.ServicePlugin, services.SnapshotsService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &service{ss: i.(map[string]snapshots.Snapshotter)}, nil
|
||||
}
|
||||
|
||||
func (s *service) getSnapshotter(name string) (snapshots.Snapshotter, error) {
|
||||
if name == "" {
|
||||
return nil, errdefs.ToGRPCf(errdefs.ErrInvalidArgument, "snapshotter argument missing")
|
||||
}
|
||||
|
||||
sn := s.ss[name]
|
||||
if sn == nil {
|
||||
return nil, errdefs.ToGRPCf(errdefs.ErrInvalidArgument, "snapshotter not loaded: %s", name)
|
||||
}
|
||||
return sn, nil
|
||||
}
|
||||
|
||||
func (s *service) Register(gs *grpc.Server) error {
|
||||
snapshotsapi.RegisterSnapshotsServer(gs, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Prepare(ctx context.Context, pr *snapshotsapi.PrepareSnapshotRequest) (*snapshotsapi.PrepareSnapshotResponse, error) {
|
||||
log.G(ctx).WithField("parent", pr.Parent).WithField("key", pr.Key).Debugf("prepare snapshot")
|
||||
sn, err := s.getSnapshotter(pr.Snapshotter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var opts []snapshots.Opt
|
||||
if pr.Labels != nil {
|
||||
opts = append(opts, snapshots.WithLabels(pr.Labels))
|
||||
}
|
||||
mounts, err := sn.Prepare(ctx, pr.Key, pr.Parent, opts...)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &snapshotsapi.PrepareSnapshotResponse{
|
||||
Mounts: mount.ToProto(mounts),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) View(ctx context.Context, pr *snapshotsapi.ViewSnapshotRequest) (*snapshotsapi.ViewSnapshotResponse, error) {
|
||||
log.G(ctx).WithField("parent", pr.Parent).WithField("key", pr.Key).Debugf("prepare view snapshot")
|
||||
sn, err := s.getSnapshotter(pr.Snapshotter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var opts []snapshots.Opt
|
||||
if pr.Labels != nil {
|
||||
opts = append(opts, snapshots.WithLabels(pr.Labels))
|
||||
}
|
||||
mounts, err := sn.View(ctx, pr.Key, pr.Parent, opts...)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &snapshotsapi.ViewSnapshotResponse{
|
||||
Mounts: mount.ToProto(mounts),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) Mounts(ctx context.Context, mr *snapshotsapi.MountsRequest) (*snapshotsapi.MountsResponse, error) {
|
||||
log.G(ctx).WithField("key", mr.Key).Debugf("get snapshot mounts")
|
||||
sn, err := s.getSnapshotter(mr.Snapshotter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mounts, err := sn.Mounts(ctx, mr.Key)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &snapshotsapi.MountsResponse{
|
||||
Mounts: mount.ToProto(mounts),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) Commit(ctx context.Context, cr *snapshotsapi.CommitSnapshotRequest) (*ptypes.Empty, error) {
|
||||
log.G(ctx).WithField("key", cr.Key).WithField("name", cr.Name).Debugf("commit snapshot")
|
||||
sn, err := s.getSnapshotter(cr.Snapshotter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var opts []snapshots.Opt
|
||||
if cr.Labels != nil {
|
||||
opts = append(opts, snapshots.WithLabels(cr.Labels))
|
||||
}
|
||||
if err := sn.Commit(ctx, cr.Name, cr.Key, opts...); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func (s *service) Remove(ctx context.Context, rr *snapshotsapi.RemoveSnapshotRequest) (*ptypes.Empty, error) {
|
||||
log.G(ctx).WithField("key", rr.Key).Debugf("remove snapshot")
|
||||
sn, err := s.getSnapshotter(rr.Snapshotter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := sn.Remove(ctx, rr.Key); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func (s *service) Stat(ctx context.Context, sr *snapshotsapi.StatSnapshotRequest) (*snapshotsapi.StatSnapshotResponse, error) {
|
||||
log.G(ctx).WithField("key", sr.Key).Debugf("stat snapshot")
|
||||
sn, err := s.getSnapshotter(sr.Snapshotter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := sn.Stat(ctx, sr.Key)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &snapshotsapi.StatSnapshotResponse{Info: snapshots.InfoToProto(info)}, nil
|
||||
}
|
||||
|
||||
func (s *service) Update(ctx context.Context, sr *snapshotsapi.UpdateSnapshotRequest) (*snapshotsapi.UpdateSnapshotResponse, error) {
|
||||
log.G(ctx).WithField("key", sr.Info.Name).Debugf("update snapshot")
|
||||
sn, err := s.getSnapshotter(sr.Snapshotter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := sn.Update(ctx, snapshots.InfoFromProto(sr.Info), sr.UpdateMask.GetPaths()...)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &snapshotsapi.UpdateSnapshotResponse{Info: snapshots.InfoToProto(info)}, nil
|
||||
}
|
||||
|
||||
func (s *service) List(sr *snapshotsapi.ListSnapshotsRequest, ss snapshotsapi.Snapshots_ListServer) error {
|
||||
sn, err := s.getSnapshotter(sr.Snapshotter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
buffer []*snapshotsapi.Info
|
||||
sendBlock = func(block []*snapshotsapi.Info) error {
|
||||
return ss.Send(&snapshotsapi.ListSnapshotsResponse{
|
||||
Info: block,
|
||||
})
|
||||
}
|
||||
)
|
||||
err = sn.Walk(ss.Context(), func(ctx context.Context, info snapshots.Info) error {
|
||||
buffer = append(buffer, snapshots.InfoToProto(info))
|
||||
|
||||
if len(buffer) >= 100 {
|
||||
if err := sendBlock(buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer = buffer[:0]
|
||||
}
|
||||
|
||||
return nil
|
||||
}, sr.Filters...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(buffer) > 0 {
|
||||
// Send remaining infos
|
||||
if err := sendBlock(buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Usage(ctx context.Context, ur *snapshotsapi.UsageRequest) (*snapshotsapi.UsageResponse, error) {
|
||||
sn, err := s.getSnapshotter(ur.Snapshotter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
usage, err := sn.Usage(ctx, ur.Key)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return snapshots.UsageToProto(usage), nil
|
||||
}
|
||||
|
||||
func (s *service) Cleanup(ctx context.Context, cr *snapshotsapi.CleanupRequest) (*ptypes.Empty, error) {
|
||||
sn, err := s.getSnapshotter(cr.Snapshotter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, ok := sn.(snapshots.Cleaner)
|
||||
if !ok {
|
||||
return nil, errdefs.ToGRPCf(errdefs.ErrNotImplemented, "snapshotter does not implement Cleanup method")
|
||||
}
|
||||
|
||||
err = c.Cleanup(ctx)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return empty, nil
|
||||
}
|
||||
43
plugins/services/snapshots/snapshotters.go
Normal file
43
plugins/services/snapshots/snapshotters.go
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package snapshots
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/v2/core/metadata"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.ServicePlugin,
|
||||
ID: services.SnapshotsService,
|
||||
Requires: []plugin.Type{
|
||||
plugins.MetadataPlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
m, err := ic.GetSingle(plugins.MetadataPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.(*metadata.DB).Snapshotters(), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
136
plugins/services/streaming/service.go
Normal file
136
plugins/services/streaming/service.go
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package streaming
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/streaming/v1"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/pkg/streaming"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/protobuf"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/log"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"github.com/containerd/typeurl/v2"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var emptyResponse typeurl.Any
|
||||
|
||||
func init() {
|
||||
// save marshalled empty response to avoid marshaling everytime
|
||||
var err error
|
||||
emptyResponse, err = typeurl.MarshalAny(&ptypes.Empty{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "streaming",
|
||||
Requires: []plugin.Type{
|
||||
plugins.StreamingPlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
i, err := ic.GetByID(plugins.StreamingPlugin, "manager")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &service{manager: i.(streaming.StreamManager)}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type service struct {
|
||||
manager streaming.StreamManager
|
||||
api.UnimplementedStreamingServer
|
||||
}
|
||||
|
||||
func (s *service) Register(server *grpc.Server) error {
|
||||
api.RegisterStreamingServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Stream(srv api.Streaming_StreamServer) error {
|
||||
// TODO: Timeout waiting
|
||||
a, err := srv.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var i api.StreamInit
|
||||
if err := typeurl.UnmarshalTo(a, &i); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cc := make(chan struct{})
|
||||
ss := &serviceStream{
|
||||
s: srv,
|
||||
cc: cc,
|
||||
}
|
||||
|
||||
log.G(srv.Context()).WithField("stream", i.ID).Debug("registering stream")
|
||||
if err := s.manager.Register(srv.Context(), i.ID, ss); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send response packet after registering stream
|
||||
if err := srv.Send(protobuf.FromAny(emptyResponse)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-srv.Context().Done():
|
||||
// TODO: Should return error if not cancelled?
|
||||
case <-cc:
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type serviceStream struct {
|
||||
s api.Streaming_StreamServer
|
||||
cc chan struct{}
|
||||
}
|
||||
|
||||
func (ss *serviceStream) Send(a typeurl.Any) (err error) {
|
||||
err = errdefs.FromGRPC(ss.s.Send(protobuf.FromAny(a)))
|
||||
if !errors.Is(err, io.EOF) {
|
||||
err = errdefs.FromGRPC(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ss *serviceStream) Recv() (a typeurl.Any, err error) {
|
||||
a, err = ss.s.Recv()
|
||||
if !errors.Is(err, io.EOF) {
|
||||
err = errdefs.FromGRPC(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ss *serviceStream) Close() error {
|
||||
select {
|
||||
case <-ss.cc:
|
||||
default:
|
||||
close(ss.cc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
744
plugins/services/tasks/local.go
Normal file
744
plugins/services/tasks/local.go
Normal file
@@ -0,0 +1,744 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/tasks/v1"
|
||||
"github.com/containerd/containerd/v2/api/types"
|
||||
"github.com/containerd/containerd/v2/api/types/task"
|
||||
"github.com/containerd/containerd/v2/archive"
|
||||
"github.com/containerd/containerd/v2/core/containers"
|
||||
"github.com/containerd/containerd/v2/core/content"
|
||||
"github.com/containerd/containerd/v2/core/images"
|
||||
"github.com/containerd/containerd/v2/core/metadata"
|
||||
"github.com/containerd/containerd/v2/core/mount"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/events"
|
||||
"github.com/containerd/containerd/v2/filters"
|
||||
"github.com/containerd/containerd/v2/pkg/blockio"
|
||||
"github.com/containerd/containerd/v2/pkg/rdt"
|
||||
"github.com/containerd/containerd/v2/pkg/timeout"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
"github.com/containerd/containerd/v2/protobuf"
|
||||
"github.com/containerd/containerd/v2/protobuf/proto"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/containerd/v2/runtime"
|
||||
"github.com/containerd/containerd/v2/runtime/v2/runc/options"
|
||||
"github.com/containerd/log"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"github.com/containerd/typeurl/v2"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var (
|
||||
_ = (api.TasksClient)(&local{})
|
||||
empty = &ptypes.Empty{}
|
||||
)
|
||||
|
||||
const (
|
||||
stateTimeout = "io.containerd.timeout.task.state"
|
||||
)
|
||||
|
||||
// Config for the tasks service plugin
|
||||
type Config struct {
|
||||
// BlockIOConfigFile specifies the path to blockio configuration file
|
||||
BlockIOConfigFile string `toml:"blockio_config_file" json:"blockioConfigFile"`
|
||||
// RdtConfigFile specifies the path to RDT configuration file
|
||||
RdtConfigFile string `toml:"rdt_config_file" json:"rdtConfigFile"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.ServicePlugin,
|
||||
ID: services.TasksService,
|
||||
Requires: tasksServiceRequires,
|
||||
Config: &Config{},
|
||||
InitFn: initFunc,
|
||||
})
|
||||
|
||||
timeout.Set(stateTimeout, 2*time.Second)
|
||||
}
|
||||
|
||||
func initFunc(ic *plugin.InitContext) (interface{}, error) {
|
||||
config := ic.Config.(*Config)
|
||||
|
||||
v2r, err := ic.GetByID(plugins.RuntimePluginV2, "task")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m, err := ic.GetSingle(plugins.MetadataPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ep, err := ic.GetSingle(plugins.EventPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
monitor, err := ic.GetSingle(plugins.TaskMonitorPlugin)
|
||||
if err != nil {
|
||||
if !errors.Is(err, plugin.ErrPluginNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
monitor = runtime.NewNoopMonitor()
|
||||
}
|
||||
|
||||
db := m.(*metadata.DB)
|
||||
l := &local{
|
||||
containers: metadata.NewContainerStore(db),
|
||||
store: db.ContentStore(),
|
||||
publisher: ep.(events.Publisher),
|
||||
monitor: monitor.(runtime.TaskMonitor),
|
||||
v2Runtime: v2r.(runtime.PlatformRuntime),
|
||||
}
|
||||
|
||||
v2Tasks, err := l.v2Runtime.Tasks(ic.Context, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range v2Tasks {
|
||||
l.monitor.Monitor(t, nil)
|
||||
}
|
||||
|
||||
if err := blockio.SetConfig(config.BlockIOConfigFile); err != nil {
|
||||
log.G(ic.Context).WithError(err).Errorf("blockio initialization failed")
|
||||
}
|
||||
if err := rdt.SetConfig(config.RdtConfigFile); err != nil {
|
||||
log.G(ic.Context).WithError(err).Errorf("RDT initialization failed")
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
type local struct {
|
||||
containers containers.Store
|
||||
store content.Store
|
||||
publisher events.Publisher
|
||||
|
||||
monitor runtime.TaskMonitor
|
||||
v2Runtime runtime.PlatformRuntime
|
||||
}
|
||||
|
||||
func (l *local) Create(ctx context.Context, r *api.CreateTaskRequest, _ ...grpc.CallOption) (*api.CreateTaskResponse, error) {
|
||||
container, err := l.getContainer(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
checkpointPath, err := getRestorePath(container.Runtime.Name, r.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// jump get checkpointPath from checkpoint image
|
||||
if checkpointPath == "" && r.Checkpoint != nil {
|
||||
checkpointPath, err = os.MkdirTemp(os.Getenv("XDG_RUNTIME_DIR"), "ctrd-checkpoint")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.Checkpoint.MediaType != images.MediaTypeContainerd1Checkpoint {
|
||||
return nil, fmt.Errorf("unsupported checkpoint type %q", r.Checkpoint.MediaType)
|
||||
}
|
||||
reader, err := l.store.ReaderAt(ctx, ocispec.Descriptor{
|
||||
MediaType: r.Checkpoint.MediaType,
|
||||
Digest: digest.Digest(r.Checkpoint.Digest),
|
||||
Size: r.Checkpoint.Size,
|
||||
Annotations: r.Checkpoint.Annotations,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = archive.Apply(ctx, checkpointPath, content.NewReader(reader))
|
||||
reader.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
opts := runtime.CreateOpts{
|
||||
Spec: container.Spec,
|
||||
IO: runtime.IO{
|
||||
Stdin: r.Stdin,
|
||||
Stdout: r.Stdout,
|
||||
Stderr: r.Stderr,
|
||||
Terminal: r.Terminal,
|
||||
},
|
||||
Checkpoint: checkpointPath,
|
||||
Runtime: container.Runtime.Name,
|
||||
RuntimeOptions: container.Runtime.Options,
|
||||
TaskOptions: r.Options,
|
||||
SandboxID: container.SandboxID,
|
||||
}
|
||||
if r.RuntimePath != "" {
|
||||
opts.Runtime = r.RuntimePath
|
||||
}
|
||||
for _, m := range r.Rootfs {
|
||||
opts.Rootfs = append(opts.Rootfs, mount.Mount{
|
||||
Type: m.Type,
|
||||
Source: m.Source,
|
||||
Target: m.Target,
|
||||
Options: m.Options,
|
||||
})
|
||||
}
|
||||
|
||||
rtime := l.v2Runtime
|
||||
|
||||
_, err = rtime.Get(ctx, r.ContainerID)
|
||||
if err != nil && !errdefs.IsNotFound(err) {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
if err == nil {
|
||||
return nil, errdefs.ToGRPC(fmt.Errorf("task %s: %w", r.ContainerID, errdefs.ErrAlreadyExists))
|
||||
}
|
||||
c, err := rtime.Create(ctx, r.ContainerID, opts)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
labels := map[string]string{"runtime": container.Runtime.Name}
|
||||
if err := l.monitor.Monitor(c, labels); err != nil {
|
||||
return nil, fmt.Errorf("monitor task: %w", err)
|
||||
}
|
||||
pid, err := c.PID(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get task pid: %w", err)
|
||||
}
|
||||
return &api.CreateTaskResponse{
|
||||
ContainerID: r.ContainerID,
|
||||
Pid: pid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *local) Start(ctx context.Context, r *api.StartRequest, _ ...grpc.CallOption) (*api.StartResponse, error) {
|
||||
t, err := l.getTask(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := runtime.Process(t)
|
||||
if r.ExecID != "" {
|
||||
if p, err = t.Process(ctx, r.ExecID); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
}
|
||||
if err := p.Start(ctx); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
state, err := p.State(ctx)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &api.StartResponse{
|
||||
Pid: state.Pid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *local) Delete(ctx context.Context, r *api.DeleteTaskRequest, _ ...grpc.CallOption) (*api.DeleteResponse, error) {
|
||||
container, err := l.getContainer(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get task object
|
||||
t, err := l.v2Runtime.Get(ctx, container.ID)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "task %v not found", container.ID)
|
||||
}
|
||||
|
||||
if err := l.monitor.Stop(t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exit, err := l.v2Runtime.Delete(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
return &api.DeleteResponse{
|
||||
ExitStatus: exit.Status,
|
||||
ExitedAt: protobuf.ToTimestamp(exit.Timestamp),
|
||||
Pid: exit.Pid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *local) DeleteProcess(ctx context.Context, r *api.DeleteProcessRequest, _ ...grpc.CallOption) (*api.DeleteResponse, error) {
|
||||
t, err := l.getTask(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
process, err := t.Process(ctx, r.ExecID)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
exit, err := process.Delete(ctx)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &api.DeleteResponse{
|
||||
ID: r.ExecID,
|
||||
ExitStatus: exit.Status,
|
||||
ExitedAt: protobuf.ToTimestamp(exit.Timestamp),
|
||||
Pid: exit.Pid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getProcessState(ctx context.Context, p runtime.Process) (*task.Process, error) {
|
||||
ctx, cancel := timeout.WithContext(ctx, stateTimeout)
|
||||
defer cancel()
|
||||
|
||||
state, err := p.State(ctx)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) || errdefs.IsUnavailable(err) {
|
||||
return nil, err
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("get state for %s", p.ID())
|
||||
}
|
||||
status := task.Status_UNKNOWN
|
||||
switch state.Status {
|
||||
case runtime.CreatedStatus:
|
||||
status = task.Status_CREATED
|
||||
case runtime.RunningStatus:
|
||||
status = task.Status_RUNNING
|
||||
case runtime.StoppedStatus:
|
||||
status = task.Status_STOPPED
|
||||
case runtime.PausedStatus:
|
||||
status = task.Status_PAUSED
|
||||
case runtime.PausingStatus:
|
||||
status = task.Status_PAUSING
|
||||
default:
|
||||
log.G(ctx).WithField("status", state.Status).Warn("unknown status")
|
||||
}
|
||||
return &task.Process{
|
||||
ID: p.ID(),
|
||||
Pid: state.Pid,
|
||||
Status: status,
|
||||
Stdin: state.Stdin,
|
||||
Stdout: state.Stdout,
|
||||
Stderr: state.Stderr,
|
||||
Terminal: state.Terminal,
|
||||
ExitStatus: state.ExitStatus,
|
||||
ExitedAt: protobuf.ToTimestamp(state.ExitedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *local) Get(ctx context.Context, r *api.GetRequest, _ ...grpc.CallOption) (*api.GetResponse, error) {
|
||||
task, err := l.getTask(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := runtime.Process(task)
|
||||
if r.ExecID != "" {
|
||||
if p, err = task.Process(ctx, r.ExecID); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
}
|
||||
t, err := getProcessState(ctx, p)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &api.GetResponse{
|
||||
Process: t,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *local) List(ctx context.Context, r *api.ListTasksRequest, _ ...grpc.CallOption) (*api.ListTasksResponse, error) {
|
||||
resp := &api.ListTasksResponse{}
|
||||
tasks, err := l.v2Runtime.Tasks(ctx, false)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
addTasks(ctx, resp, tasks)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func addTasks(ctx context.Context, r *api.ListTasksResponse, tasks []runtime.Task) {
|
||||
for _, t := range tasks {
|
||||
tt, err := getProcessState(ctx, t)
|
||||
if err != nil {
|
||||
if !errdefs.IsNotFound(err) { // handle race with deletion
|
||||
log.G(ctx).WithError(err).WithField("id", t.ID()).Error("converting task to protobuf")
|
||||
}
|
||||
continue
|
||||
}
|
||||
r.Tasks = append(r.Tasks, tt)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *local) Pause(ctx context.Context, r *api.PauseTaskRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) {
|
||||
t, err := l.getTask(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = t.Pause(ctx)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func (l *local) Resume(ctx context.Context, r *api.ResumeTaskRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) {
|
||||
t, err := l.getTask(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = t.Resume(ctx)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func (l *local) Kill(ctx context.Context, r *api.KillRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) {
|
||||
t, err := l.getTask(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := runtime.Process(t)
|
||||
if r.ExecID != "" {
|
||||
if p, err = t.Process(ctx, r.ExecID); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
}
|
||||
if err := p.Kill(ctx, r.Signal, r.All); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func (l *local) ListPids(ctx context.Context, r *api.ListPidsRequest, _ ...grpc.CallOption) (*api.ListPidsResponse, error) {
|
||||
t, err := l.getTask(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
processList, err := t.Pids(ctx)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
var processes []*task.ProcessInfo
|
||||
for _, p := range processList {
|
||||
pInfo := task.ProcessInfo{
|
||||
Pid: p.Pid,
|
||||
}
|
||||
if p.Info != nil {
|
||||
a, err := protobuf.MarshalAnyToProto(p.Info)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal process %d info: %w", p.Pid, err)
|
||||
}
|
||||
pInfo.Info = a
|
||||
}
|
||||
processes = append(processes, &pInfo)
|
||||
}
|
||||
return &api.ListPidsResponse{
|
||||
Processes: processes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *local) Exec(ctx context.Context, r *api.ExecProcessRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) {
|
||||
if r.ExecID == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "exec id cannot be empty")
|
||||
}
|
||||
t, err := l.getTask(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := t.Exec(ctx, r.ExecID, runtime.ExecOpts{
|
||||
Spec: r.Spec,
|
||||
IO: runtime.IO{
|
||||
Stdin: r.Stdin,
|
||||
Stdout: r.Stdout,
|
||||
Stderr: r.Stderr,
|
||||
Terminal: r.Terminal,
|
||||
},
|
||||
}); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func (l *local) ResizePty(ctx context.Context, r *api.ResizePtyRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) {
|
||||
t, err := l.getTask(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := runtime.Process(t)
|
||||
if r.ExecID != "" {
|
||||
if p, err = t.Process(ctx, r.ExecID); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
}
|
||||
if err := p.ResizePty(ctx, runtime.ConsoleSize{
|
||||
Width: r.Width,
|
||||
Height: r.Height,
|
||||
}); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func (l *local) CloseIO(ctx context.Context, r *api.CloseIORequest, _ ...grpc.CallOption) (*ptypes.Empty, error) {
|
||||
t, err := l.getTask(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := runtime.Process(t)
|
||||
if r.ExecID != "" {
|
||||
if p, err = t.Process(ctx, r.ExecID); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
}
|
||||
if r.Stdin {
|
||||
if err := p.CloseIO(ctx); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
}
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func (l *local) Checkpoint(ctx context.Context, r *api.CheckpointTaskRequest, _ ...grpc.CallOption) (*api.CheckpointTaskResponse, error) {
|
||||
container, err := l.getContainer(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := l.getTaskFromContainer(ctx, container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
image, err := getCheckpointPath(container.Runtime.Name, r.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
checkpointImageExists := false
|
||||
if image == "" {
|
||||
checkpointImageExists = true
|
||||
image, err = os.MkdirTemp(os.Getenv("XDG_RUNTIME_DIR"), "ctrd-checkpoint")
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
defer os.RemoveAll(image)
|
||||
}
|
||||
if err := t.Checkpoint(ctx, image, r.Options); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
// do not commit checkpoint image if checkpoint ImagePath is passed,
|
||||
// return if checkpointImageExists is false
|
||||
if !checkpointImageExists {
|
||||
return &api.CheckpointTaskResponse{}, nil
|
||||
}
|
||||
// write checkpoint to the content store
|
||||
tar := archive.Diff(ctx, "", image)
|
||||
cp, err := l.writeContent(ctx, images.MediaTypeContainerd1Checkpoint, image, tar)
|
||||
// close tar first after write
|
||||
if err := tar.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// write the config to the content store
|
||||
pbany := protobuf.FromAny(container.Spec)
|
||||
data, err := proto.Marshal(pbany)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec := bytes.NewReader(data)
|
||||
specD, err := l.writeContent(ctx, images.MediaTypeContainerd1CheckpointConfig, filepath.Join(image, "spec"), spec)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &api.CheckpointTaskResponse{
|
||||
Descriptors: []*types.Descriptor{
|
||||
cp,
|
||||
specD,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *local) Update(ctx context.Context, r *api.UpdateTaskRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) {
|
||||
t, err := l.getTask(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.Update(ctx, r.Resources, r.Annotations); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func (l *local) Metrics(ctx context.Context, r *api.MetricsRequest, _ ...grpc.CallOption) (*api.MetricsResponse, error) {
|
||||
filter, err := filters.ParseAll(r.Filters...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp api.MetricsResponse
|
||||
tasks, err := l.v2Runtime.Tasks(ctx, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
getTasksMetrics(ctx, filter, tasks, &resp)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (l *local) Wait(ctx context.Context, r *api.WaitRequest, _ ...grpc.CallOption) (*api.WaitResponse, error) {
|
||||
t, err := l.getTask(ctx, r.ContainerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := runtime.Process(t)
|
||||
if r.ExecID != "" {
|
||||
if p, err = t.Process(ctx, r.ExecID); err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
}
|
||||
exit, err := p.Wait(ctx)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &api.WaitResponse{
|
||||
ExitStatus: exit.Status,
|
||||
ExitedAt: protobuf.ToTimestamp(exit.Timestamp),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTasksMetrics(ctx context.Context, filter filters.Filter, tasks []runtime.Task, r *api.MetricsResponse) {
|
||||
for _, tk := range tasks {
|
||||
if !filter.Match(filters.AdapterFunc(func(fieldpath []string) (string, bool) {
|
||||
t := tk
|
||||
switch fieldpath[0] {
|
||||
case "id":
|
||||
return t.ID(), true
|
||||
case "namespace":
|
||||
return t.Namespace(), true
|
||||
case "runtime":
|
||||
// return t.Info().Runtime, true
|
||||
}
|
||||
return "", false
|
||||
})) {
|
||||
continue
|
||||
}
|
||||
collected := time.Now()
|
||||
stats, err := tk.Stats(ctx)
|
||||
if err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
log.G(ctx).WithError(err).Errorf("collecting metrics for %s", tk.ID())
|
||||
}
|
||||
continue
|
||||
}
|
||||
r.Metrics = append(r.Metrics, &types.Metric{
|
||||
Timestamp: protobuf.ToTimestamp(collected),
|
||||
ID: tk.ID(),
|
||||
Data: stats,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (l *local) writeContent(ctx context.Context, mediaType, ref string, r io.Reader) (*types.Descriptor, error) {
|
||||
writer, err := l.store.Writer(ctx, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{MediaType: mediaType}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer writer.Close()
|
||||
size, err := io.Copy(writer, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := writer.Commit(ctx, 0, ""); err != nil && !errdefs.IsAlreadyExists(err) {
|
||||
return nil, err
|
||||
}
|
||||
return &types.Descriptor{
|
||||
MediaType: mediaType,
|
||||
Digest: writer.Digest().String(),
|
||||
Size: size,
|
||||
Annotations: make(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *local) getContainer(ctx context.Context, id string) (*containers.Container, error) {
|
||||
var container containers.Container
|
||||
container, err := l.containers.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
return &container, nil
|
||||
}
|
||||
|
||||
func (l *local) getTask(ctx context.Context, id string) (runtime.Task, error) {
|
||||
container, err := l.getContainer(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l.getTaskFromContainer(ctx, container)
|
||||
}
|
||||
|
||||
func (l *local) getTaskFromContainer(ctx context.Context, container *containers.Container) (runtime.Task, error) {
|
||||
t, err := l.v2Runtime.Get(ctx, container.ID)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "task %v not found", container.ID)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// getCheckpointPath only suitable for runc runtime now
|
||||
func getCheckpointPath(runtime string, option *ptypes.Any) (string, error) {
|
||||
if option == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var checkpointPath string
|
||||
v, err := typeurl.UnmarshalAny(option)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
opts, ok := v.(*options.CheckpointOptions)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid task checkpoint option for %s", runtime)
|
||||
}
|
||||
checkpointPath = opts.ImagePath
|
||||
|
||||
return checkpointPath, nil
|
||||
}
|
||||
|
||||
// getRestorePath only suitable for runc runtime now
|
||||
func getRestorePath(runtime string, option *ptypes.Any) (string, error) {
|
||||
if option == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var restorePath string
|
||||
v, err := typeurl.UnmarshalAny(option)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
opts, ok := v.(*options.Options)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid task create option for %s", runtime)
|
||||
}
|
||||
restorePath = opts.CriuImagePath
|
||||
|
||||
return restorePath, nil
|
||||
}
|
||||
30
plugins/services/tasks/local_darwin.go
Normal file
30
plugins/services/tasks/local_darwin.go
Normal file
@@ -0,0 +1,30 @@
|
||||
//go:build darwin
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/plugin"
|
||||
)
|
||||
|
||||
var tasksServiceRequires = []plugin.Type{
|
||||
plugins.RuntimePluginV2,
|
||||
plugins.MetadataPlugin,
|
||||
plugins.TaskMonitorPlugin,
|
||||
}
|
||||
29
plugins/services/tasks/local_freebsd.go
Normal file
29
plugins/services/tasks/local_freebsd.go
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/plugin"
|
||||
)
|
||||
|
||||
var tasksServiceRequires = []plugin.Type{
|
||||
plugins.EventPlugin,
|
||||
plugins.RuntimePluginV2,
|
||||
plugins.MetadataPlugin,
|
||||
plugins.TaskMonitorPlugin,
|
||||
}
|
||||
31
plugins/services/tasks/local_unix.go
Normal file
31
plugins/services/tasks/local_unix.go
Normal file
@@ -0,0 +1,31 @@
|
||||
//go:build !windows && !freebsd && !darwin
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/plugin"
|
||||
)
|
||||
|
||||
var tasksServiceRequires = []plugin.Type{
|
||||
plugins.EventPlugin,
|
||||
plugins.RuntimePluginV2,
|
||||
plugins.MetadataPlugin,
|
||||
plugins.TaskMonitorPlugin,
|
||||
}
|
||||
29
plugins/services/tasks/local_windows.go
Normal file
29
plugins/services/tasks/local_windows.go
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/plugin"
|
||||
)
|
||||
|
||||
var tasksServiceRequires = []plugin.Type{
|
||||
plugins.EventPlugin,
|
||||
plugins.RuntimePluginV2,
|
||||
plugins.MetadataPlugin,
|
||||
plugins.TaskMonitorPlugin,
|
||||
}
|
||||
128
plugins/services/tasks/service.go
Normal file
128
plugins/services/tasks/service.go
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/tasks/v1"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins/services"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var (
|
||||
_ = (api.TasksServer)(&service{})
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "tasks",
|
||||
Requires: []plugin.Type{
|
||||
plugins.ServicePlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
i, err := ic.GetByID(plugins.ServicePlugin, services.TasksService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &service{local: i.(api.TasksClient)}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type service struct {
|
||||
local api.TasksClient
|
||||
api.UnimplementedTasksServer
|
||||
}
|
||||
|
||||
func (s *service) Register(server *grpc.Server) error {
|
||||
api.RegisterTasksServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Create(ctx context.Context, r *api.CreateTaskRequest) (*api.CreateTaskResponse, error) {
|
||||
return s.local.Create(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) Start(ctx context.Context, r *api.StartRequest) (*api.StartResponse, error) {
|
||||
return s.local.Start(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) Delete(ctx context.Context, r *api.DeleteTaskRequest) (*api.DeleteResponse, error) {
|
||||
return s.local.Delete(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) DeleteProcess(ctx context.Context, r *api.DeleteProcessRequest) (*api.DeleteResponse, error) {
|
||||
return s.local.DeleteProcess(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) Get(ctx context.Context, r *api.GetRequest) (*api.GetResponse, error) {
|
||||
return s.local.Get(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) List(ctx context.Context, r *api.ListTasksRequest) (*api.ListTasksResponse, error) {
|
||||
return s.local.List(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) Pause(ctx context.Context, r *api.PauseTaskRequest) (*ptypes.Empty, error) {
|
||||
return s.local.Pause(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) Resume(ctx context.Context, r *api.ResumeTaskRequest) (*ptypes.Empty, error) {
|
||||
return s.local.Resume(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) Kill(ctx context.Context, r *api.KillRequest) (*ptypes.Empty, error) {
|
||||
return s.local.Kill(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) ListPids(ctx context.Context, r *api.ListPidsRequest) (*api.ListPidsResponse, error) {
|
||||
return s.local.ListPids(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) Exec(ctx context.Context, r *api.ExecProcessRequest) (*ptypes.Empty, error) {
|
||||
return s.local.Exec(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) ResizePty(ctx context.Context, r *api.ResizePtyRequest) (*ptypes.Empty, error) {
|
||||
return s.local.ResizePty(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) CloseIO(ctx context.Context, r *api.CloseIORequest) (*ptypes.Empty, error) {
|
||||
return s.local.CloseIO(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) Checkpoint(ctx context.Context, r *api.CheckpointTaskRequest) (*api.CheckpointTaskResponse, error) {
|
||||
return s.local.Checkpoint(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) Update(ctx context.Context, r *api.UpdateTaskRequest) (*ptypes.Empty, error) {
|
||||
return s.local.Update(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) Metrics(ctx context.Context, r *api.MetricsRequest) (*api.MetricsResponse, error) {
|
||||
return s.local.Metrics(ctx, r)
|
||||
}
|
||||
|
||||
func (s *service) Wait(ctx context.Context, r *api.WaitRequest) (*api.WaitResponse, error) {
|
||||
return s.local.Wait(ctx, r)
|
||||
}
|
||||
162
plugins/services/transfer/service.go
Normal file
162
plugins/services/transfer/service.go
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
transferapi "github.com/containerd/containerd/v2/api/services/transfer/v1"
|
||||
"github.com/containerd/containerd/v2/api/types"
|
||||
transferTypes "github.com/containerd/containerd/v2/api/types/transfer"
|
||||
"github.com/containerd/containerd/v2/errdefs"
|
||||
"github.com/containerd/containerd/v2/oci"
|
||||
"github.com/containerd/containerd/v2/pkg/streaming"
|
||||
"github.com/containerd/containerd/v2/pkg/transfer"
|
||||
tplugins "github.com/containerd/containerd/v2/pkg/transfer/plugins"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
"github.com/containerd/log"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"github.com/containerd/typeurl/v2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "transfer",
|
||||
Requires: []plugin.Type{
|
||||
plugins.TransferPlugin,
|
||||
plugins.StreamingPlugin,
|
||||
},
|
||||
InitFn: newService,
|
||||
})
|
||||
}
|
||||
|
||||
type service struct {
|
||||
transferrers []transfer.Transferrer
|
||||
streamManager streaming.StreamManager
|
||||
transferapi.UnimplementedTransferServer
|
||||
}
|
||||
|
||||
func newService(ic *plugin.InitContext) (interface{}, error) {
|
||||
sps, err := ic.GetByType(plugins.TransferPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: how to determine order?
|
||||
t := make([]transfer.Transferrer, 0, len(sps))
|
||||
for _, p := range sps {
|
||||
t = append(t, p.(transfer.Transferrer))
|
||||
}
|
||||
sp, err := ic.GetByID(plugins.StreamingPlugin, "manager")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &service{
|
||||
transferrers: t,
|
||||
streamManager: sp.(streaming.StreamManager),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) Register(gs *grpc.Server) error {
|
||||
transferapi.RegisterTransferServer(gs, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Transfer(ctx context.Context, req *transferapi.TransferRequest) (*emptypb.Empty, error) {
|
||||
var transferOpts []transfer.Opt
|
||||
if req.Options != nil {
|
||||
if req.Options.ProgressStream != "" {
|
||||
stream, err := s.streamManager.Get(ctx, req.Options.ProgressStream)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
pf := func(p transfer.Progress) {
|
||||
var descp *types.Descriptor
|
||||
if p.Desc != nil {
|
||||
descp = oci.DescriptorToProto(*p.Desc)
|
||||
}
|
||||
progress, err := typeurl.MarshalAny(&transferTypes.Progress{
|
||||
Event: p.Event,
|
||||
Name: p.Name,
|
||||
Parents: p.Parents,
|
||||
Progress: p.Progress,
|
||||
Total: p.Total,
|
||||
Desc: descp,
|
||||
})
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Warnf("event could not be marshaled: %v/%v", p.Event, p.Name)
|
||||
return
|
||||
}
|
||||
if err := stream.Send(progress); err != nil {
|
||||
log.G(ctx).WithError(err).Warnf("event not sent: %v/%v", p.Event, p.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
transferOpts = append(transferOpts, transfer.WithProgress(pf))
|
||||
}
|
||||
}
|
||||
src, err := s.convertAny(ctx, req.Source)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
dst, err := s.convertAny(ctx, req.Destination)
|
||||
if err != nil {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
for _, t := range s.transferrers {
|
||||
if err := t.Transfer(ctx, src, dst, transferOpts...); err == nil {
|
||||
return &ptypes.Empty{}, nil
|
||||
} else if !errdefs.IsNotImplemented(err) {
|
||||
return nil, errdefs.ToGRPC(err)
|
||||
}
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Transfer not implemented for %s to %s", req.Source.GetTypeUrl(), req.Destination.GetTypeUrl())
|
||||
}
|
||||
|
||||
func (s *service) convertAny(ctx context.Context, a typeurl.Any) (interface{}, error) {
|
||||
obj, err := tplugins.ResolveType(a)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
return typeurl.UnmarshalAny(a)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
switch v := obj.(type) {
|
||||
case streamUnmarshaler:
|
||||
err = v.UnmarshalAny(ctx, s.streamManager, a)
|
||||
return obj, err
|
||||
default:
|
||||
log.G(ctx).Debug("unmarshling to..")
|
||||
err = typeurl.UnmarshalTo(a, obj)
|
||||
return obj, err
|
||||
}
|
||||
}
|
||||
|
||||
type streamUnmarshaler interface {
|
||||
UnmarshalAny(context.Context, streaming.StreamGetter, typeurl.Any) error
|
||||
}
|
||||
59
plugins/services/version/service.go
Normal file
59
plugins/services/version/service.go
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
api "github.com/containerd/containerd/v2/api/services/version/v1"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
ptypes "github.com/containerd/containerd/v2/protobuf/types"
|
||||
ctrdversion "github.com/containerd/containerd/v2/version"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var _ api.VersionServer = &service{}
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.GRPCPlugin,
|
||||
ID: "version",
|
||||
InitFn: initFunc,
|
||||
})
|
||||
}
|
||||
|
||||
func initFunc(ic *plugin.InitContext) (interface{}, error) {
|
||||
return &service{}, nil
|
||||
}
|
||||
|
||||
type service struct {
|
||||
api.UnimplementedVersionServer
|
||||
}
|
||||
|
||||
func (s *service) Register(server *grpc.Server) error {
|
||||
api.RegisterVersionServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Version(ctx context.Context, _ *ptypes.Empty) (*api.VersionResponse, error) {
|
||||
return &api.VersionResponse{
|
||||
Version: ctrdversion.Version,
|
||||
Revision: ctrdversion.Revision,
|
||||
}, nil
|
||||
}
|
||||
85
plugins/services/warning/service.go
Normal file
85
plugins/services/warning/service.go
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package warning
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/log"
|
||||
|
||||
deprecation "github.com/containerd/containerd/v2/pkg/deprecation"
|
||||
"github.com/containerd/containerd/v2/plugins"
|
||||
"github.com/containerd/plugin"
|
||||
"github.com/containerd/plugin/registry"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
Emit(context.Context, deprecation.Warning)
|
||||
Warnings() []Warning
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Register(&plugin.Registration{
|
||||
Type: plugins.WarningPlugin,
|
||||
ID: plugins.DeprecationsPlugin,
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
return &service{warnings: make(map[deprecation.Warning]time.Time)}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type Warning struct {
|
||||
ID deprecation.Warning
|
||||
LastOccurrence time.Time
|
||||
Message string
|
||||
}
|
||||
|
||||
var _ Service = (*service)(nil)
|
||||
|
||||
type service struct {
|
||||
warnings map[deprecation.Warning]time.Time
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *service) Emit(ctx context.Context, warning deprecation.Warning) {
|
||||
if !deprecation.Valid(warning) {
|
||||
log.G(ctx).WithField("warningID", string(warning)).Warn("invalid deprecation warning")
|
||||
return
|
||||
}
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
s.warnings[warning] = time.Now()
|
||||
}
|
||||
func (s *service) Warnings() []Warning {
|
||||
s.m.RLock()
|
||||
defer s.m.RUnlock()
|
||||
var warnings []Warning
|
||||
for k, v := range s.warnings {
|
||||
msg, ok := deprecation.Message(k)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
warnings = append(warnings, Warning{
|
||||
ID: k,
|
||||
LastOccurrence: v,
|
||||
Message: msg,
|
||||
})
|
||||
}
|
||||
return warnings
|
||||
}
|
||||
Reference in New Issue
Block a user