Skip to content

Commit a888c83

Browse files
committed
feat(shim): add containerd access session
Signed-off-by: sidneychang <2190206983@qq.com>
1 parent a7c63c7 commit a888c83

2 files changed

Lines changed: 185 additions & 1 deletion

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ require (
2828
github.com/vishvananda/netlink v1.3.1
2929
github.com/vishvananda/netns v0.0.5
3030
golang.org/x/sys v0.43.0
31+
google.golang.org/grpc v1.79.3
3132
k8s.io/cri-api v0.35.4
3233
)
3334

@@ -79,7 +80,6 @@ require (
7980
golang.org/x/tools v0.41.0 // indirect
8081
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
8182
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
82-
google.golang.org/grpc v1.79.3 // indirect
8383
google.golang.org/protobuf v1.36.11 // indirect
8484
gopkg.in/yaml.v3 v3.0.1 // indirect
8585
)
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Copyright (c) 2023-2026, Nubificus LTD
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package containerdshim
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"time"
21+
22+
containersapi "github.com/containerd/containerd/api/services/containers/v1"
23+
contentapi "github.com/containerd/containerd/api/services/content/v1"
24+
imagesapi "github.com/containerd/containerd/api/services/images/v1"
25+
leasesapi "github.com/containerd/containerd/api/services/leases/v1"
26+
snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1"
27+
"github.com/containerd/containerd/defaults"
28+
"github.com/containerd/containerd/errdefs"
29+
"github.com/containerd/containerd/namespaces"
30+
"github.com/containerd/containerd/pkg/dialer"
31+
"google.golang.org/grpc"
32+
"google.golang.org/grpc/backoff"
33+
"google.golang.org/grpc/credentials/insecure"
34+
)
35+
36+
const defaultConnectTimeout = 10 * time.Second
37+
38+
// ContainerdSession owns one shim-side connection to containerd for a single task
39+
// operation. It intentionally does not expose the underlying grpc connection,
40+
// but it does expose narrow generated service clients for feature helpers.
41+
type ContainerdSession struct {
42+
conn *grpc.ClientConn
43+
44+
namespace string
45+
containerID string
46+
container *containersapi.Container
47+
}
48+
49+
// OpenContainerdSession opens a narrow containerd session for a single task operation.
50+
// The containerd namespace must already be present in ctx.
51+
func OpenContainerdSession(ctx context.Context, address, containerID string) (*ContainerdSession, error) {
52+
if address == "" {
53+
return nil, fmt.Errorf("containerd address is empty")
54+
}
55+
if containerID == "" {
56+
return nil, fmt.Errorf("container id is empty")
57+
}
58+
59+
namespace, err := namespaces.NamespaceRequired(ctx)
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
backoffConfig := backoff.DefaultConfig
65+
backoffConfig.MaxDelay = 3 * time.Second
66+
dialOptions := []grpc.DialOption{
67+
grpc.WithBlock(),
68+
grpc.WithTransportCredentials(insecure.NewCredentials()),
69+
grpc.FailOnNonTempDialError(true),
70+
grpc.WithConnectParams(grpc.ConnectParams{Backoff: backoffConfig}),
71+
grpc.WithContextDialer(dialer.ContextDialer),
72+
grpc.WithReturnConnectionError(),
73+
grpc.WithDefaultCallOptions(
74+
grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize),
75+
grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize),
76+
),
77+
}
78+
79+
dialCtx, cancel := context.WithTimeout(ctx, defaultConnectTimeout)
80+
defer cancel()
81+
82+
conn, err := grpc.DialContext(dialCtx, dialer.DialAddress(address), dialOptions...)
83+
if err != nil {
84+
return nil, fmt.Errorf("dial containerd at %q: %w", address, err)
85+
}
86+
87+
session := &ContainerdSession{
88+
conn: conn,
89+
namespace: namespace,
90+
containerID: containerID,
91+
}
92+
93+
if err := session.loadContainer(ctx); err != nil {
94+
if closeErr := conn.Close(); closeErr != nil {
95+
return nil, fmt.Errorf("%w; close containerd connection: %v", err, closeErr)
96+
}
97+
return nil, err
98+
}
99+
100+
return session, nil
101+
}
102+
103+
// Close closes the underlying containerd connection.
104+
func (s *ContainerdSession) Close() error {
105+
if s == nil || s.conn == nil {
106+
return nil
107+
}
108+
return s.conn.Close()
109+
}
110+
111+
// Namespace returns the containerd namespace bound to this session.
112+
func (s *ContainerdSession) Namespace() string {
113+
return s.namespace
114+
}
115+
116+
// ContainerID returns the container ID bound to this session.
117+
func (s *ContainerdSession) ContainerID() string {
118+
return s.containerID
119+
}
120+
121+
// Container returns the task-level container metadata loaded during session
122+
// creation.
123+
func (s *ContainerdSession) Container() *containersapi.Container {
124+
return s.container
125+
}
126+
127+
func (s *ContainerdSession) loadContainer(ctx context.Context) error {
128+
resp, err := s.ContainersClient().Get(s.withNamespace(ctx), &containersapi.GetContainerRequest{
129+
ID: s.containerID,
130+
})
131+
if err != nil {
132+
return fmt.Errorf("get container %q: %w", s.containerID, containerdErr(err))
133+
}
134+
if resp.GetContainer() == nil {
135+
return fmt.Errorf("get container %q: empty response", s.containerID)
136+
}
137+
138+
s.container = resp.GetContainer()
139+
return nil
140+
}
141+
142+
func (s *ContainerdSession) withNamespace(ctx context.Context) context.Context {
143+
if ctx == nil {
144+
ctx = context.Background()
145+
}
146+
return namespaces.WithNamespace(ctx, s.namespace)
147+
}
148+
149+
func containerdErr(err error) error {
150+
if err == nil {
151+
return nil
152+
}
153+
return errdefs.FromGRPC(err)
154+
}
155+
156+
// ContainersClient returns a generated containers service client scoped to the
157+
// session connection.
158+
func (s *ContainerdSession) ContainersClient() containersapi.ContainersClient {
159+
return containersapi.NewContainersClient(s.conn)
160+
}
161+
162+
// ImagesClient returns a generated images service client scoped to the session
163+
// connection.
164+
func (s *ContainerdSession) ImagesClient() imagesapi.ImagesClient {
165+
return imagesapi.NewImagesClient(s.conn)
166+
}
167+
168+
// ContentClient returns a generated content service client scoped to the
169+
// session connection.
170+
func (s *ContainerdSession) ContentClient() contentapi.ContentClient {
171+
return contentapi.NewContentClient(s.conn)
172+
}
173+
174+
// SnapshotsClient returns a generated snapshots service client scoped to the
175+
// session connection.
176+
func (s *ContainerdSession) SnapshotsClient() snapshotsapi.SnapshotsClient {
177+
return snapshotsapi.NewSnapshotsClient(s.conn)
178+
}
179+
180+
// LeasesClient returns a generated leases service client scoped to the session
181+
// connection.
182+
func (s *ContainerdSession) LeasesClient() leasesapi.LeasesClient {
183+
return leasesapi.NewLeasesClient(s.conn)
184+
}

0 commit comments

Comments
 (0)