Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ URUNC_SRC += $(wildcard $(CURDIR)/pkg/unikontainers/types/*.go)
URUNC_SRC += $(wildcard $(CURDIR)/pkg/unikontainers/initrd/*.go)
URUNC_SRC += $(wildcard $(CURDIR)/pkg/network/*.go)
SHIM_SRC := $(wildcard $(CURDIR)/cmd/containerd-shim-urunc-v2/*.go)
SHIM_SRC += $(wildcard $(CURDIR)/pkg/containerd-shim/*.go)
SHIM_SRC += $(wildcard $(CURDIR)/pkg/containerd-shim/*/*.go)

#? CNTR_TOOL Tool to run the linter container (default: docker)
CNTR_TOOL ?= docker
Expand Down
2 changes: 1 addition & 1 deletion cmd/containerd-shim-urunc-v2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import (
"context"

"github.com/containerd/containerd/runtime/v2/runc/manager"
_ "github.com/containerd/containerd/runtime/v2/runc/task/plugin"
"github.com/containerd/containerd/runtime/v2/shim"
_ "github.com/urunc-dev/urunc/pkg/containerd-shim"
)

func main() {
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/cavaliergopher/cpio v1.0.1
github.com/containerd/containerd v1.7.30
github.com/containerd/containerd/api v1.10.0
github.com/containerd/ttrpc v1.2.7
github.com/creack/pty v1.1.24
github.com/elastic/go-seccomp-bpf v1.6.0
github.com/hashicorp/go-version v1.9.0
Expand All @@ -26,6 +28,7 @@ require (
github.com/vishvananda/netlink v1.3.1
github.com/vishvananda/netns v0.0.5
golang.org/x/sys v0.43.0
google.golang.org/grpc v1.79.3
k8s.io/cri-api v0.35.4
)

Expand All @@ -36,15 +39,13 @@ require (
github.com/cilium/ebpf v0.20.0 // indirect
github.com/containerd/cgroups/v3 v3.1.0 // indirect
github.com/containerd/console v1.0.5 // indirect
github.com/containerd/containerd/api v1.10.0 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/go-runc v1.0.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
Expand Down Expand Up @@ -79,7 +80,6 @@ require (
golang.org/x/tools v0.41.0 // indirect
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
178 changes: 178 additions & 0 deletions pkg/containerd-shim/containerd_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// Copyright (c) 2023-2026, Nubificus LTD
//
// 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 containerdshim

import (
"context"
"fmt"
"sync"
"time"

containersapi "github.com/containerd/containerd/api/services/containers/v1"
contentapi "github.com/containerd/containerd/api/services/content/v1"
imagesapi "github.com/containerd/containerd/api/services/images/v1"
leasesapi "github.com/containerd/containerd/api/services/leases/v1"
snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1"
"github.com/containerd/containerd/defaults"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/pkg/dialer"
"google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials/insecure"
)

const defaultConnectTimeout = 10 * time.Second

// containerdSession owns one shim-side connection to containerd for a single task
// operation. It intentionally does not expose the underlying grpc connection,
// generated service clients, or a high-level containerd client.
type containerdSession struct {
conn *grpc.ClientConn

namespace string
containerID string

mu sync.Mutex
container *containersapi.Container
}

// openContainerdSession opens a narrow containerd session for a single task operation.
// The containerd namespace must already be present in ctx.
func openContainerdSession(ctx context.Context, address, containerID string) (*containerdSession, error) {
if address == "" {
return nil, fmt.Errorf("containerd address is empty")
}
if containerID == "" {
return nil, fmt.Errorf("container id is empty")
}

namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return nil, err
}

backoffConfig := backoff.DefaultConfig
backoffConfig.MaxDelay = 3 * time.Second
dialOptions := []grpc.DialOption{
grpc.WithBlock(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.FailOnNonTempDialError(true),
grpc.WithConnectParams(grpc.ConnectParams{Backoff: backoffConfig}),
grpc.WithContextDialer(dialer.ContextDialer),
grpc.WithReturnConnectionError(),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize),
grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize),
),
}

dialCtx, cancel := context.WithTimeout(ctx, defaultConnectTimeout)
defer cancel()

conn, err := grpc.DialContext(dialCtx, dialer.DialAddress(address), dialOptions...)
if err != nil {
return nil, fmt.Errorf("dial containerd at %q: %w", address, err)
}

return &containerdSession{
conn: conn,
namespace: namespace,
containerID: containerID,
}, nil
}

// Close closes the underlying containerd connection.
func (s *containerdSession) Close() error {
if s == nil || s.conn == nil {
return nil
}
return s.conn.Close()
}

// Namespace returns the containerd namespace bound to this session.
func (s *containerdSession) Namespace() string {
return s.namespace
}

// ContainerID returns the container ID bound to this session.
func (s *containerdSession) ContainerID() string {
return s.containerID
}

// Container returns the task-level container metadata, fetching it once from
// containerd and reusing it for subsequent feature helpers.
func (s *containerdSession) Container(ctx context.Context) (*containersapi.Container, error) {
s.mu.Lock()
if s.container != nil {
container := s.container
s.mu.Unlock()
return container, nil
}
s.mu.Unlock()

resp, err := s.containersClient().Get(s.withNamespace(ctx), &containersapi.GetContainerRequest{
ID: s.containerID,
})
if err != nil {
return nil, fmt.Errorf("get container %q: %w", s.containerID, containerdErr(err))
}
if resp.GetContainer() == nil {
return nil, fmt.Errorf("get container %q: empty response", s.containerID)
}

s.mu.Lock()
if s.container == nil {
s.container = resp.GetContainer()
}
container := s.container
s.mu.Unlock()

return container, nil
}

func (s *containerdSession) withNamespace(ctx context.Context) context.Context {
if ctx == nil {
ctx = context.Background()
}
return namespaces.WithNamespace(ctx, s.namespace)
}

func containerdErr(err error) error {
if err == nil {
return nil
}
return errdefs.FromGRPC(err)
}

func (s *containerdSession) containersClient() containersapi.ContainersClient {
return containersapi.NewContainersClient(s.conn)
}

func (s *containerdSession) imagesClient() imagesapi.ImagesClient {
return imagesapi.NewImagesClient(s.conn)
}

func (s *containerdSession) contentClient() contentapi.ContentClient {
return contentapi.NewContentClient(s.conn)
}

func (s *containerdSession) snapshotsClient() snapshotsapi.SnapshotsClient {
return snapshotsapi.NewSnapshotsClient(s.conn)
}

func (s *containerdSession) leasesClient() leasesapi.LeasesClient {
return leasesapi.NewLeasesClient(s.conn)
}
54 changes: 54 additions & 0 deletions pkg/containerd-shim/task_plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2023-2026, Nubificus LTD
//
// 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 containerdshim

import (
"github.com/containerd/containerd/pkg/shutdown"
"github.com/containerd/containerd/plugin"
runcTask "github.com/containerd/containerd/runtime/v2/runc/task"
"github.com/containerd/containerd/runtime/v2/shim"
)

func init() {
plugin.Register(&plugin.Registration{
Type: plugin.TTRPCPlugin,
ID: "task",
Requires: []plugin.Type{
plugin.EventPlugin,
plugin.InternalPlugin,
},
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
pp, err := ic.GetByID(plugin.EventPlugin, "publisher")
if err != nil {
return nil, err
}

ss, err := ic.GetByID(plugin.InternalPlugin, "shutdown")
if err != nil {
return nil, err
}

inner, err := runcTask.NewTaskService(ic.Context, pp.(shim.Publisher), ss.(shutdown.Service))
if err != nil {
return nil, err
}

return &taskService{
TaskService: inner,
containerdAddress: ic.Address,
}, nil
},
})
}
46 changes: 46 additions & 0 deletions pkg/containerd-shim/task_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) 2023-2026, Nubificus LTD
//
// 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 containerdshim

import (
"context"

taskAPI "github.com/containerd/containerd/api/runtime/task/v2"
"github.com/containerd/ttrpc"
)

// taskService is urunc's shim-side wrapper around containerd's runc task
// service. It is intentionally behavior-preserving for now; feature-specific
// helpers can hook into Create/Delete here without replacing the plugin again.
type taskService struct {
taskAPI.TaskService

containerdAddress string
}

var _ taskAPI.TaskService = (*taskService)(nil)

func (s *taskService) Create(ctx context.Context, r *taskAPI.CreateTaskRequest) (*taskAPI.CreateTaskResponse, error) {
return s.TaskService.Create(ctx, r)
}

func (s *taskService) Delete(ctx context.Context, r *taskAPI.DeleteRequest) (*taskAPI.DeleteResponse, error) {
return s.TaskService.Delete(ctx, r)
}

func (s *taskService) RegisterTTRPC(server *ttrpc.Server) error {
taskAPI.RegisterTaskService(server, s)
return nil
}