forked from aquasecurity/fanal
/
containerd.go
133 lines (116 loc) · 3.61 KB
/
containerd.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package daemon
import (
"context"
"errors"
"io"
"os"
"github.com/containerd/containerd"
"github.com/containerd/containerd/images/archive"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/platforms"
refdocker "github.com/containerd/containerd/reference/docker"
"github.com/containerd/nerdctl/pkg/imageinspector"
"github.com/containerd/nerdctl/pkg/inspecttypes/dockercompat"
api "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"golang.org/x/xerrors"
)
const (
defaultContainerdSocket = "/run/containerd/containerd.sock"
defaultContainerdNamespace = "default"
)
func imageWriter(client *containerd.Client, img containerd.Image) imageSave {
return func(ctx context.Context, ref []string) (io.ReadCloser, error) {
if len(ref) < 1 {
return nil, xerrors.New("no image reference")
}
imgOpts := archive.WithImage(client.ImageService(), ref[0])
manifestOpts := archive.WithManifest(img.Target())
platOpts := archive.WithPlatform(platforms.DefaultStrict())
pr, pw := io.Pipe()
go func() {
pw.CloseWithError(archive.Export(ctx, client.ContentStore(), pw, imgOpts, manifestOpts, platOpts))
}()
return pr, nil
}
}
// ContainerdImage implements v1.Image
func ContainerdImage(ctx context.Context, imageName string) (Image, func(), error) {
cleanup := func() {}
addr := os.Getenv("CONTAINERD_ADDRESS")
if addr == "" {
// TODO: support rootless
addr = defaultContainerdSocket
}
if _, err := os.Stat(addr); errors.Is(err, os.ErrNotExist) {
return nil, cleanup, xerrors.Errorf("containerd socket not found: %s", addr)
}
// Parse the image name
ref, err := refdocker.ParseDockerRef(imageName)
if err != nil {
return nil, cleanup, xerrors.Errorf("parse error: %w", err)
}
client, err := containerd.New(addr)
if err != nil {
return nil, cleanup, xerrors.Errorf("failed to initialize a containerd client: %w", err)
}
// Need to specify a namespace
ctx = namespaces.WithNamespace(ctx, defaultContainerdNamespace)
img, err := client.GetImage(ctx, ref.String())
if err != nil {
return nil, cleanup, xerrors.Errorf("failed to get %s: %w", imageName, err)
}
// Inspect the image
n, err := imageinspector.Inspect(ctx, client, img.Metadata())
if err != nil {
return nil, cleanup, xerrors.Errorf("inspect error: %w", imageName, err)
}
// Convert the result to the docker compat format
d, err := dockercompat.ImageFromNative(n)
if err != nil {
return nil, cleanup, err
}
f, err := os.CreateTemp("", "fanal-containerd-*")
if err != nil {
return nil, cleanup, xerrors.Errorf("failed to create a temporary file: %w", err)
}
cleanup = func() {
_ = client.Close()
_ = f.Close()
_ = os.Remove(f.Name())
}
return &image{
opener: imageOpener(ctx, ref.String(), f, imageWriter(client, img)),
inspect: toDockerInspect(d),
}, cleanup, nil
}
func toDockerInspect(d *dockercompat.Image) api.ImageInspect {
return api.ImageInspect{
ID: d.ID,
RepoTags: d.RepoTags,
RepoDigests: d.RepoDigests,
Comment: d.Comment,
Created: d.Created,
Author: d.Author,
Config: &container.Config{
User: d.Config.User,
ExposedPorts: d.Config.ExposedPorts,
Env: d.Config.Env,
Cmd: d.Config.Cmd,
Volumes: d.Config.Volumes,
WorkingDir: d.Config.WorkingDir,
Entrypoint: d.Config.Entrypoint,
Labels: d.Config.Labels,
},
Architecture: d.Architecture,
Os: d.Os,
RootFS: api.RootFS{
Type: d.RootFS.Type,
Layers: d.RootFS.Layers,
BaseLayer: d.RootFS.BaseLayer,
},
Metadata: api.ImageMetadata{
LastTagTime: d.Metadata.LastTagTime,
},
}
}