This repository has been archived by the owner on Jun 21, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 100
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support get local image by containerd
- Loading branch information
Showing
11 changed files
with
484 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
package daemon | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"os" | ||
"time" | ||
|
||
"github.com/containerd/containerd" | ||
"github.com/containerd/containerd/content" | ||
"github.com/containerd/containerd/images" | ||
"github.com/containerd/containerd/images/archive" | ||
"github.com/containerd/containerd/namespaces" | ||
"github.com/docker/docker/api/types/container" | ||
"github.com/google/go-containerregistry/pkg/name" | ||
"golang.org/x/xerrors" | ||
|
||
api "github.com/docker/docker/api/types" | ||
"github.com/opencontainers/go-digest" | ||
ocispec "github.com/opencontainers/image-spec/specs-go/v1" | ||
) | ||
|
||
const ( | ||
containerdNamespace = "default" | ||
defaultContainerdSocket = "/run/containerd/containerd.sock" | ||
tagTemplate = "%s:%s" | ||
digestTemplate = "%s@sha256:%s" | ||
) | ||
|
||
type ImageReference struct { | ||
Named string | ||
Tag string | ||
Digest string | ||
} | ||
|
||
type containerdClient struct { | ||
client *containerd.Client | ||
} | ||
|
||
func NewContainerdClient(socket string) (containerdClient, error) { | ||
cli, err := containerd.New(socket) | ||
if err != nil { | ||
return containerdClient{}, err | ||
} | ||
return containerdClient{client: cli}, nil | ||
} | ||
|
||
// ContainerdImage implements v1.Image by extending | ||
func ContainerdImage(ref name.Reference) (Image, func(), error) { | ||
ctx := context.Background() | ||
cleanup := func() {} | ||
|
||
cli, err := NewContainerdClient(defaultContainerdSocket) | ||
if err != nil { | ||
return nil, cleanup, xerrors.Errorf("ContainerImage: failed to initialize a docker client: %w", err) | ||
} | ||
defer func() { | ||
if err != nil { | ||
cli.client.Close() | ||
} | ||
}() | ||
|
||
ctx = namespaces.WithNamespace(ctx, containerdNamespace) | ||
img, err := cli.client.GetImage(ctx, ref.Name()) | ||
if err != nil { | ||
return nil, cleanup, xerrors.Errorf("ContainerImage: unable to get the image (%s): %w", ref.Name(), err) | ||
} | ||
inspect, err := imageInspect(ctx, img) | ||
if err != nil { | ||
return nil, cleanup, err | ||
} | ||
|
||
f, err := os.CreateTemp("", "fanal-*") | ||
if err != nil { | ||
return nil, cleanup, xerrors.Errorf("ContainerImage: failed to create a temporary file") | ||
} | ||
|
||
cleanup = func() { | ||
cli.client.Close() | ||
f.Close() | ||
_ = os.Remove(f.Name()) | ||
} | ||
|
||
return &image{ | ||
opener: imageOpener(ctx, ref.Name(), f, cli.imageWriter), | ||
inspect: inspect, | ||
}, cleanup, nil | ||
} | ||
|
||
func (c *containerdClient) imageWriter(ctx context.Context, ref []string) (io.ReadCloser, error) { | ||
if len(ref) == 0 { | ||
return nil, xerrors.Errorf("imageWriter: failed to get iamge name: %v", ref) | ||
} | ||
img, err := c.client.GetImage(ctx, ref[0]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
pr, pw := io.Pipe() | ||
go func() { | ||
pw.CloseWithError(archive.Export(ctx, img.ContentStore(), pw)) | ||
}() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return pr, nil | ||
} | ||
|
||
// imageInspect returns ImageInspect struct | ||
func imageInspect(ctx context.Context, img containerd.Image) (inspect api.ImageInspect, err error) { | ||
descriptor, err := img.Config(ctx) | ||
if err != nil { | ||
return api.ImageInspect{}, err | ||
} | ||
ociImage, err := containerToOci(ctx, img) | ||
if err != nil { | ||
return api.ImageInspect{}, err | ||
} | ||
var createAt string | ||
if ociImage.Created != nil { | ||
createAt = ociImage.Created.Format(time.RFC3339Nano) | ||
} | ||
repoDigests, repoTags := getRepoInfo(ctx, img) | ||
var architecture string | ||
if descriptor.Platform != nil { | ||
architecture = descriptor.Platform.Architecture | ||
} else { | ||
architecture = ociImage.Architecture | ||
} | ||
|
||
return api.ImageInspect{ | ||
Architecture: architecture, | ||
Config: getImageInfoConfigFromOciImage(ociImage), | ||
Created: createAt, | ||
ID: string(descriptor.Digest), | ||
Os: ociImage.OS, | ||
RepoDigests: repoDigests, | ||
RepoTags: repoTags, | ||
RootFS: api.RootFS{ | ||
Type: ociImage.RootFS.Type, | ||
Layers: digestToString(ociImage.RootFS.DiffIDs), | ||
}, | ||
Size: descriptor.Size, | ||
}, nil | ||
} | ||
|
||
func digestToString(digests []digest.Digest) []string { | ||
strs := make([]string, 0, len(digests)) | ||
for _, d := range digests { | ||
strs = append(strs, d.String()) | ||
} | ||
return strs | ||
} | ||
|
||
// getImageInfoConfigFromOciImage returns config of ImageConfig from oci image. | ||
func getImageInfoConfigFromOciImage(img ocispec.Image) *container.Config { | ||
volumes := make(map[string]struct{}) | ||
for k, obj := range img.Config.Volumes { | ||
volumes[k] = obj | ||
} | ||
|
||
return &container.Config{ | ||
User: img.Config.User, | ||
Env: img.Config.Env, | ||
Entrypoint: img.Config.Entrypoint, | ||
Cmd: img.Config.Cmd, | ||
WorkingDir: img.Config.WorkingDir, | ||
Labels: img.Config.Labels, | ||
StopSignal: img.Config.StopSignal, | ||
Volumes: volumes, | ||
} | ||
} | ||
|
||
func containerToOci(ctx context.Context, img containerd.Image) (ocispec.Image, error) { | ||
var ociImage ocispec.Image | ||
|
||
cfg, err := img.Config(ctx) | ||
if err != nil { | ||
return ocispec.Image{}, err | ||
} | ||
|
||
switch cfg.MediaType { | ||
case ocispec.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config, "application/octet-stream": | ||
data, err := content.ReadBlob(ctx, img.ContentStore(), cfg) | ||
if err != nil { | ||
return ocispec.Image{}, err | ||
} | ||
err = json.Unmarshal(data, &ociImage) | ||
if err != nil { | ||
return ocispec.Image{}, err | ||
} | ||
default: | ||
return ocispec.Image{}, xerrors.Errorf("containerToOci: invalid image config media type: %v", cfg.MediaType) | ||
} | ||
return ociImage, nil | ||
} | ||
|
||
// splitReference splits reference into name, tag and digest in string format. | ||
func splitReference(ref string) (name string, tag string, digStr string) { | ||
name = ref | ||
|
||
if loc := regDigest.FindStringIndex(name); loc != nil { | ||
name, digStr = name[:loc[0]], name[loc[0]+1:] | ||
} | ||
|
||
if loc := regTag.FindStringIndex(name); loc != nil { | ||
name, tag = name[:loc[0]], name[loc[0]+1:] | ||
} | ||
return | ||
} | ||
|
||
// Parse parses ref into. | ||
func Parse(ctx context.Context, img containerd.Image) (ImageReference, error) { | ||
ref := img.Name() | ||
if ok := regRef.MatchString(ref); !ok { | ||
return ImageReference{}, xerrors.Errorf("Parse: invalid reference: %s", ref) | ||
} | ||
|
||
name, tag, digStr := splitReference(ref) | ||
|
||
if digStr != "" { | ||
dig, err := digest.Parse(digStr) | ||
if err != nil { | ||
return ImageReference{}, err | ||
} | ||
|
||
return ImageReference{ | ||
Named: name, | ||
Digest: dig.String(), | ||
Tag: tag, | ||
}, nil | ||
} | ||
|
||
desp, err := img.Config(ctx) | ||
if err != nil { | ||
return ImageReference{}, xerrors.Errorf("Parse: get img config err: %v", err) | ||
} | ||
if desp.Digest != "" { | ||
return ImageReference{ | ||
Named: name, | ||
Digest: desp.Digest.String(), | ||
Tag: tag, | ||
}, nil | ||
} | ||
|
||
return ImageReference{ | ||
Named: name, | ||
Tag: tag, | ||
}, nil | ||
} | ||
|
||
func getRepoInfo(ctx context.Context, img containerd.Image) (repoDigests, repoTags []string) { | ||
|
||
fmt.Printf("imageInspect name: %+v; \n", img.Name()) | ||
reference, _ := Parse(ctx, img) | ||
fmt.Printf("imageInspect referenece: %+v; \n", reference) | ||
if reference.Tag != "" { | ||
repoTags = append(repoTags, fmt.Sprintf(tagTemplate, reference.Named, reference.Tag)) | ||
} | ||
if reference.Digest != "" { | ||
repoDigests = append(repoDigests, fmt.Sprintf(digestTemplate, reference.Named, reference.Digest)) | ||
} | ||
return | ||
} |
Oops, something went wrong.