Skip to content

Commit 457e776

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

3 files changed

Lines changed: 280 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: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
//nolint:unused // Intentionally unused until feature wiring.
16+
package containerdshim
17+
18+
import (
19+
"context"
20+
21+
containersapi "github.com/containerd/containerd/api/services/containers/v1"
22+
contentapi "github.com/containerd/containerd/api/services/content/v1"
23+
imagesapi "github.com/containerd/containerd/api/services/images/v1"
24+
leasesapi "github.com/containerd/containerd/api/services/leases/v1"
25+
snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1"
26+
)
27+
28+
// containerdResourceBase carries the task-level scope shared by
29+
// feature-specific resource views. It deliberately carries scope metadata, not
30+
// the underlying connection or all generated containerd clients.
31+
type containerdResourceBase struct {
32+
namespace string
33+
containerID string
34+
container *containersapi.Container
35+
}
36+
37+
func newContainerdResourceBase(s *ContainerdSession) containerdResourceBase {
38+
return containerdResourceBase{
39+
namespace: s.GetNamespace(),
40+
containerID: s.GetContainerID(),
41+
container: s.GetContainer(),
42+
}
43+
}
44+
45+
func (s containerdResourceBase) withNamespace(ctx context.Context) context.Context {
46+
return withContainerdNamespace(ctx, s.namespace)
47+
}
48+
49+
// annotationResources is the capability-style view for annotation injection.
50+
// It intentionally exposes only container metadata plus images and content
51+
// access, and does not include snapshots or leases.
52+
type annotationResources struct {
53+
containerdResourceBase
54+
55+
images imagesapi.ImagesClient
56+
content contentapi.ContentClient
57+
}
58+
59+
func newAnnotationResources(s *ContainerdSession) annotationResources {
60+
return annotationResources{
61+
containerdResourceBase: newContainerdResourceBase(s),
62+
images: s.imagesClient(),
63+
content: s.contentClient(),
64+
}
65+
}
66+
67+
// snapshotViewResources is the capability-style view for snapshot inspection.
68+
// It intentionally exposes only container metadata plus snapshots and leases
69+
// access, and does not include images or content.
70+
type snapshotViewResources struct {
71+
containerdResourceBase
72+
73+
snapshots snapshotsapi.SnapshotsClient
74+
leases leasesapi.LeasesClient
75+
}
76+
77+
func newSnapshotViewResources(s *ContainerdSession) snapshotViewResources {
78+
return snapshotViewResources{
79+
containerdResourceBase: newContainerdResourceBase(s),
80+
snapshots: s.snapshotsClient(),
81+
leases: s.leasesClient(),
82+
}
83+
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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+
// and generated service clients remain private implementation details used to
41+
// build feature-specific resource views.
42+
type ContainerdSession struct {
43+
conn *grpc.ClientConn
44+
45+
namespace string
46+
containerID string
47+
container *containersapi.Container
48+
}
49+
50+
// OpenContainerdSession opens a narrow containerd session for a single task operation.
51+
// The containerd namespace must already be present in ctx.
52+
func OpenContainerdSession(ctx context.Context, address, containerID string) (*ContainerdSession, error) {
53+
if address == "" {
54+
return nil, fmt.Errorf("containerd address is empty")
55+
}
56+
if containerID == "" {
57+
return nil, fmt.Errorf("container id is empty")
58+
}
59+
60+
namespace, err := namespaces.NamespaceRequired(ctx)
61+
if err != nil {
62+
return nil, err
63+
}
64+
65+
backoffConfig := backoff.DefaultConfig
66+
backoffConfig.MaxDelay = 3 * time.Second
67+
dialOptions := []grpc.DialOption{
68+
grpc.WithBlock(),
69+
grpc.WithTransportCredentials(insecure.NewCredentials()),
70+
grpc.FailOnNonTempDialError(true),
71+
grpc.WithConnectParams(grpc.ConnectParams{Backoff: backoffConfig}),
72+
grpc.WithContextDialer(dialer.ContextDialer),
73+
grpc.WithReturnConnectionError(),
74+
grpc.WithDefaultCallOptions(
75+
grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize),
76+
grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize),
77+
),
78+
}
79+
80+
dialCtx, cancel := context.WithTimeout(ctx, defaultConnectTimeout)
81+
defer cancel()
82+
83+
conn, err := grpc.DialContext(dialCtx, dialer.DialAddress(address), dialOptions...)
84+
if err != nil {
85+
return nil, fmt.Errorf("dial containerd at %q: %w", address, err)
86+
}
87+
88+
session := &ContainerdSession{
89+
conn: conn,
90+
namespace: namespace,
91+
containerID: containerID,
92+
}
93+
94+
container, err := loadContainer(ctx, namespace, containerID, session.containersClient())
95+
if err != nil {
96+
if closeErr := conn.Close(); closeErr != nil {
97+
return nil, fmt.Errorf("loadContainer failed: %w; close containerd connection: %v", err, closeErr)
98+
}
99+
return nil, fmt.Errorf("loadContainer failed: %w", err)
100+
}
101+
session.container = container
102+
103+
return session, nil
104+
}
105+
106+
// Close closes the underlying containerd connection. The loaded container
107+
// metadata is a protobuf value and does not hold resources that need releasing.
108+
func (s *ContainerdSession) Close() error {
109+
if s == nil || s.conn == nil {
110+
return nil
111+
}
112+
return s.conn.Close()
113+
}
114+
115+
// GetNamespace returns the containerd namespace bound to this session.
116+
func (s *ContainerdSession) GetNamespace() string {
117+
return s.namespace
118+
}
119+
120+
// GetContainerID returns the container ID bound to this session.
121+
func (s *ContainerdSession) GetContainerID() string {
122+
return s.containerID
123+
}
124+
125+
// GetContainer returns the task-level container metadata loaded during session
126+
// creation.
127+
func (s *ContainerdSession) GetContainer() *containersapi.Container {
128+
return s.container
129+
}
130+
131+
func loadContainer(ctx context.Context, namespace, containerID string, client containersapi.ContainersClient) (*containersapi.Container, error) {
132+
resp, err := client.Get(withContainerdNamespace(ctx, namespace), &containersapi.GetContainerRequest{
133+
ID: containerID,
134+
})
135+
if err != nil {
136+
return nil, fmt.Errorf("get container %q: %w", containerID, containerdErr(err))
137+
}
138+
container := resp.GetContainer()
139+
if container == nil {
140+
return nil, fmt.Errorf("get container %q: empty response", containerID)
141+
}
142+
143+
return container, nil
144+
}
145+
146+
func withContainerdNamespace(ctx context.Context, namespace string) context.Context {
147+
if ctx == nil {
148+
ctx = context.Background()
149+
}
150+
return namespaces.WithNamespace(ctx, namespace)
151+
}
152+
153+
func containerdErr(err error) error {
154+
if err == nil {
155+
return nil
156+
}
157+
return errdefs.FromGRPC(err)
158+
}
159+
160+
// containersClient returns a generated containers service client scoped to the
161+
// session connection.
162+
func (s *ContainerdSession) containersClient() containersapi.ContainersClient {
163+
return containersapi.NewContainersClient(s.conn)
164+
}
165+
166+
// imagesClient returns a generated images service client scoped to the session
167+
// connection.
168+
//
169+
//nolint:unused // Intentionally unused until feature wiring.
170+
func (s *ContainerdSession) imagesClient() imagesapi.ImagesClient {
171+
return imagesapi.NewImagesClient(s.conn)
172+
}
173+
174+
// contentClient returns a generated content service client scoped to the
175+
// session connection.
176+
//
177+
//nolint:unused // Intentionally unused until feature wiring.
178+
func (s *ContainerdSession) contentClient() contentapi.ContentClient {
179+
return contentapi.NewContentClient(s.conn)
180+
}
181+
182+
// snapshotsClient returns a generated snapshots service client scoped to the
183+
// session connection.
184+
//
185+
//nolint:unused // Intentionally unused until feature wiring.
186+
func (s *ContainerdSession) snapshotsClient() snapshotsapi.SnapshotsClient {
187+
return snapshotsapi.NewSnapshotsClient(s.conn)
188+
}
189+
190+
// leasesClient returns a generated leases service client scoped to the session
191+
// connection.
192+
//
193+
//nolint:unused // Intentionally unused until feature wiring.
194+
func (s *ContainerdSession) leasesClient() leasesapi.LeasesClient {
195+
return leasesapi.NewLeasesClient(s.conn)
196+
}

0 commit comments

Comments
 (0)