2344 lines
71 KiB
Diff
2344 lines
71 KiB
Diff
|
|
From c4f400ec1cfaf65d3e83dbd796f1f2b00574ba6e Mon Sep 17 00:00:00 2001
|
||
|
|
From: Lu Jingxiao <lujingxiao@huawei.com>
|
||
|
|
Date: Fri, 10 Dec 2021 13:36:26 +0800
|
||
|
|
Subject: [PATCH] Refactor: refactor image separator related
|
||
|
|
|
||
|
|
Signed-off-by: Lu Jingxiao <lujingxiao@huawei.com>
|
||
|
|
---
|
||
|
|
cmd/cli/save.go | 4 +-
|
||
|
|
cmd/daemon/main.go | 7 +-
|
||
|
|
constant.go | 26 +-
|
||
|
|
daemon/daemon.go | 5 +-
|
||
|
|
daemon/import.go | 4 +-
|
||
|
|
daemon/load.go | 295 ++-------------
|
||
|
|
daemon/load_test.go | 2 +-
|
||
|
|
daemon/save.go | 649 +--------------------------------
|
||
|
|
daemon/separator/image_info.go | 235 ++++++++++++
|
||
|
|
daemon/separator/load.go | 283 ++++++++++++++
|
||
|
|
daemon/separator/save.go | 407 +++++++++++++++++++++
|
||
|
|
daemon/separator/utils.go | 78 ++++
|
||
|
|
12 files changed, 1074 insertions(+), 921 deletions(-)
|
||
|
|
create mode 100644 daemon/separator/image_info.go
|
||
|
|
create mode 100644 daemon/separator/load.go
|
||
|
|
create mode 100644 daemon/separator/save.go
|
||
|
|
create mode 100644 daemon/separator/utils.go
|
||
|
|
|
||
|
|
diff --git a/cmd/cli/save.go b/cmd/cli/save.go
|
||
|
|
index 5a63e02..a30681f 100644
|
||
|
|
--- a/cmd/cli/save.go
|
||
|
|
+++ b/cmd/cli/save.go
|
||
|
|
@@ -144,8 +144,8 @@ func (opt *saveOptions) checkSaveOpts(args []string) error {
|
||
|
|
}
|
||
|
|
// separate image only support docker image spec
|
||
|
|
opt.format = constant.DockerTransport
|
||
|
|
- if err := opt.sep.check(pwd); err != nil {
|
||
|
|
- return err
|
||
|
|
+ if cerr := opt.sep.check(pwd); cerr != nil {
|
||
|
|
+ return cerr
|
||
|
|
}
|
||
|
|
opt.sep.enabled = true
|
||
|
|
|
||
|
|
diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go
|
||
|
|
index 3665f6b..3cecbf9 100644
|
||
|
|
--- a/cmd/daemon/main.go
|
||
|
|
+++ b/cmd/daemon/main.go
|
||
|
|
@@ -36,10 +36,7 @@ import (
|
||
|
|
"isula.org/isula-build/util"
|
||
|
|
)
|
||
|
|
|
||
|
|
-const (
|
||
|
|
- lockFileName = "isula-builder.lock"
|
||
|
|
- dataRootTmpDirPrefix = "tmp"
|
||
|
|
-)
|
||
|
|
+const lockFileName = "isula-builder.lock"
|
||
|
|
|
||
|
|
var daemonOpts daemon.Options
|
||
|
|
|
||
|
|
@@ -331,7 +328,7 @@ func setupWorkingDirectories() error {
|
||
|
|
return errors.Errorf("runroot(%q) and dataroot(%q) must be different paths", daemonOpts.RunRoot, daemonOpts.DataRoot)
|
||
|
|
}
|
||
|
|
|
||
|
|
- buildTmpDir, err := securejoin.SecureJoin(daemonOpts.DataRoot, dataRootTmpDirPrefix)
|
||
|
|
+ buildTmpDir, err := securejoin.SecureJoin(daemonOpts.DataRoot, constant.DataRootTmpDirPrefix)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
diff --git a/constant.go b/constant.go
|
||
|
|
index 4d1596a..5af4fe2 100644
|
||
|
|
--- a/constant.go
|
||
|
|
+++ b/constant.go
|
||
|
|
@@ -31,14 +31,20 @@ const (
|
||
|
|
RegistryDirPath = ConfigRoot + "registries.d"
|
||
|
|
// AuthFilePath is authentication file used for registry connection
|
||
|
|
AuthFilePath = ConfigRoot + "auth.json"
|
||
|
|
- // DefaultGRPCAddress is the local unix socket used by isula-builder
|
||
|
|
- DefaultGRPCAddress = "unix:///var/run/isula_build.sock"
|
||
|
|
- // UnixPrefix is the prefix used on defined an unix sock
|
||
|
|
- UnixPrefix = "unix://"
|
||
|
|
+ // DefaultCertRoot is path of certification used for registry connection
|
||
|
|
+ DefaultCertRoot = ConfigRoot + "certs.d"
|
||
|
|
+
|
||
|
|
// DefaultDataRoot is the default persistent data root used by isula-builder
|
||
|
|
DefaultDataRoot = "/var/lib/isula-build"
|
||
|
|
// DefaultRunRoot is the default run root used by isula-builder
|
||
|
|
DefaultRunRoot = "/var/run/isula-build"
|
||
|
|
+ // UnixPrefix is the prefix used on defined an unix sock
|
||
|
|
+ UnixPrefix = "unix://"
|
||
|
|
+ // DefaultGRPCAddress is the local unix socket used by isula-builder
|
||
|
|
+ DefaultGRPCAddress = UnixPrefix + "/var/run/isula_build.sock"
|
||
|
|
+ // DataRootTmpDirPrefix is the dir for storing temporary items using during images building
|
||
|
|
+ DataRootTmpDirPrefix = "tmp"
|
||
|
|
+
|
||
|
|
// DefaultSharedDirMode is dir perm mode with higher permission
|
||
|
|
DefaultSharedDirMode = 0755
|
||
|
|
// DefaultSharedFileMode is file perm mode with higher permission
|
||
|
|
@@ -53,12 +59,14 @@ const (
|
||
|
|
DefaultReadOnlyFileMode = 0400
|
||
|
|
// DefaultUmask is the working umask of isula-builder as a process, not for users
|
||
|
|
DefaultUmask = 0022
|
||
|
|
- // CliLogBufferLen is log channel buffer size
|
||
|
|
- CliLogBufferLen = 8
|
||
|
|
+
|
||
|
|
// HostsFilePath is the path of file hosts
|
||
|
|
HostsFilePath = "/etc/hosts"
|
||
|
|
// ResolvFilePath is the path of file resolv.conf
|
||
|
|
ResolvFilePath = "/etc/resolv.conf"
|
||
|
|
+
|
||
|
|
+ // CliLogBufferLen is log channel buffer size
|
||
|
|
+ CliLogBufferLen = 8
|
||
|
|
// MaxFileSize is the maximum file size allowed, set 1M
|
||
|
|
MaxFileSize = 1024 * 1024
|
||
|
|
// DefaultHTTPTimeout includes the total time of dial, TLS handshake, request, resp headers and body
|
||
|
|
@@ -67,14 +75,12 @@ const (
|
||
|
|
DefaultFailedCode = 1
|
||
|
|
// DefaultIDLen is the ID length for image ID and build ID
|
||
|
|
DefaultIDLen = 12
|
||
|
|
- // DefaultCertRoot is path of certification used for registry connection
|
||
|
|
- DefaultCertRoot = ConfigRoot + "certs.d"
|
||
|
|
+
|
||
|
|
// LayoutTime is the time format used to parse time from a string
|
||
|
|
LayoutTime = "2006-01-02 15:04:05"
|
||
|
|
// BuildContainerImageType is the default build type
|
||
|
|
BuildContainerImageType = "ctr-img"
|
||
|
|
- // BufferSize is default buffer size for file transportation
|
||
|
|
- BufferSize = 32 * 1024
|
||
|
|
+
|
||
|
|
// DockerTransport used to export docker image format images to registry
|
||
|
|
DockerTransport = "docker"
|
||
|
|
// DockerArchiveTransport used to export docker image format images to local tarball
|
||
|
|
diff --git a/daemon/daemon.go b/daemon/daemon.go
|
||
|
|
index 4e0435a..1847dbb 100644
|
||
|
|
--- a/daemon/daemon.go
|
||
|
|
+++ b/daemon/daemon.go
|
||
|
|
@@ -28,6 +28,7 @@ import (
|
||
|
|
"github.com/sirupsen/logrus"
|
||
|
|
"golang.org/x/sys/unix"
|
||
|
|
|
||
|
|
+ constant "isula.org/isula-build"
|
||
|
|
pb "isula.org/isula-build/api/services"
|
||
|
|
"isula.org/isula-build/builder"
|
||
|
|
"isula.org/isula-build/pkg/gc"
|
||
|
|
@@ -37,8 +38,6 @@ import (
|
||
|
|
"isula.org/isula-build/util"
|
||
|
|
)
|
||
|
|
|
||
|
|
-const dataRootTmpDirPrefix = "tmp"
|
||
|
|
-
|
||
|
|
// Options carries the options configured to daemon
|
||
|
|
type Options struct {
|
||
|
|
Debug bool
|
||
|
|
@@ -137,7 +136,7 @@ func (d *Daemon) NewBuilder(ctx context.Context, req *pb.BuildRequest) (b builde
|
||
|
|
runDir string
|
||
|
|
)
|
||
|
|
// buildDir is used to set directory which is used to store tmp data
|
||
|
|
- buildDir, err = securejoin.SecureJoin(d.opts.DataRoot, filepath.Join(dataRootTmpDirPrefix, req.BuildID))
|
||
|
|
+ buildDir, err = securejoin.SecureJoin(d.opts.DataRoot, filepath.Join(constant.DataRootTmpDirPrefix, req.BuildID))
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
diff --git a/daemon/import.go b/daemon/import.go
|
||
|
|
index 2da36be..21ab729 100644
|
||
|
|
--- a/daemon/import.go
|
||
|
|
+++ b/daemon/import.go
|
||
|
|
@@ -87,7 +87,7 @@ func (b *Backend) Import(req *pb.ImportRequest, stream pb.Control_ImportServer)
|
||
|
|
|
||
|
|
log := logger.NewCliLogger(constant.CliLogBufferLen)
|
||
|
|
imageCopyOptions := image.NewImageCopyOptions(log)
|
||
|
|
- tmpDir, err = securejoin.SecureJoin(b.daemon.opts.DataRoot, filepath.Join(dataRootTmpDirPrefix, importID))
|
||
|
|
+ tmpDir, err = securejoin.SecureJoin(b.daemon.opts.DataRoot, filepath.Join(constant.DataRootTmpDirPrefix, importID))
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
@@ -97,7 +97,7 @@ func (b *Backend) Import(req *pb.ImportRequest, stream pb.Control_ImportServer)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
if rErr := os.RemoveAll(tmpDir); rErr != nil {
|
||
|
|
- logEntry.Errorf("Failed to remove import temporary dir %q, err: %v", filepath.Join(dataRootTmpDirPrefix, importID), rErr)
|
||
|
|
+ logEntry.Errorf("Failed to remove import temporary dir %q, err: %v", filepath.Join(constant.DataRootTmpDirPrefix, importID), rErr)
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
|
||
|
|
diff --git a/daemon/load.go b/daemon/load.go
|
||
|
|
index 894159b..1ee025b 100644
|
||
|
|
--- a/daemon/load.go
|
||
|
|
+++ b/daemon/load.go
|
||
|
|
@@ -15,9 +15,7 @@ package daemon
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
- "io/ioutil"
|
||
|
|
"os"
|
||
|
|
- "path/filepath"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"github.com/containers/image/v5/docker/tarfile"
|
||
|
|
@@ -25,7 +23,6 @@ import (
|
||
|
|
"github.com/containers/image/v5/transports/alltransports"
|
||
|
|
"github.com/containers/image/v5/types"
|
||
|
|
"github.com/containers/storage"
|
||
|
|
- "github.com/containers/storage/pkg/archive"
|
||
|
|
securejoin "github.com/cyphar/filepath-securejoin"
|
||
|
|
digest "github.com/opencontainers/go-digest"
|
||
|
|
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||
|
|
@@ -35,89 +32,47 @@ import (
|
||
|
|
|
||
|
|
constant "isula.org/isula-build"
|
||
|
|
pb "isula.org/isula-build/api/services"
|
||
|
|
+ "isula.org/isula-build/daemon/separator"
|
||
|
|
"isula.org/isula-build/exporter"
|
||
|
|
"isula.org/isula-build/image"
|
||
|
|
"isula.org/isula-build/pkg/logger"
|
||
|
|
"isula.org/isula-build/util"
|
||
|
|
)
|
||
|
|
|
||
|
|
-const (
|
||
|
|
- tmpBaseDirName = "base"
|
||
|
|
- tmpAppDirName = "app"
|
||
|
|
- tmpLibDirName = "lib"
|
||
|
|
- unionCompressedTarName = "all.tar.gz"
|
||
|
|
-)
|
||
|
|
-
|
||
|
|
-type loadImageTmpDir struct {
|
||
|
|
- app string
|
||
|
|
- base string
|
||
|
|
- lib string
|
||
|
|
- root string
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-type separatorLoad struct {
|
||
|
|
- log *logrus.Entry
|
||
|
|
- tmpDir loadImageTmpDir
|
||
|
|
- info tarballInfo
|
||
|
|
- appName string
|
||
|
|
- basePath string
|
||
|
|
- appPath string
|
||
|
|
- libPath string
|
||
|
|
- dir string
|
||
|
|
- skipCheck bool
|
||
|
|
- enabled bool
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-type loadOptions struct {
|
||
|
|
- logEntry *logrus.Entry
|
||
|
|
- path string
|
||
|
|
- format string
|
||
|
|
- sep separatorLoad
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
type singleImage struct {
|
||
|
|
index int
|
||
|
|
id string
|
||
|
|
nameTag []string
|
||
|
|
}
|
||
|
|
|
||
|
|
-func (b *Backend) getLoadOptions(req *pb.LoadRequest) (loadOptions, error) {
|
||
|
|
- var opt = loadOptions{
|
||
|
|
- path: req.GetPath(),
|
||
|
|
- sep: separatorLoad{
|
||
|
|
- appName: req.GetSep().GetApp(),
|
||
|
|
- basePath: req.GetSep().GetBase(),
|
||
|
|
- libPath: req.GetSep().GetLib(),
|
||
|
|
- dir: req.GetSep().GetDir(),
|
||
|
|
- skipCheck: req.GetSep().GetSkipCheck(),
|
||
|
|
- enabled: req.GetSep().GetEnabled(),
|
||
|
|
- },
|
||
|
|
- logEntry: logrus.WithFields(logrus.Fields{"LoadID": req.GetLoadID()}),
|
||
|
|
+// LoadOptions stores the options for image loading
|
||
|
|
+type LoadOptions struct {
|
||
|
|
+ LogEntry *logrus.Entry
|
||
|
|
+ path string
|
||
|
|
+ format string
|
||
|
|
+ sep separator.Loader
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (b *Backend) getLoadOptions(req *pb.LoadRequest) (LoadOptions, error) {
|
||
|
|
+ var err error
|
||
|
|
+ var opt = LoadOptions{
|
||
|
|
+ path: req.GetPath(),
|
||
|
|
+ LogEntry: logrus.WithFields(logrus.Fields{"LoadID": req.GetLoadID()}),
|
||
|
|
}
|
||
|
|
|
||
|
|
- // normal loadOptions
|
||
|
|
- if !opt.sep.enabled {
|
||
|
|
- if err := util.CheckLoadFile(opt.path); err != nil {
|
||
|
|
- return loadOptions{}, err
|
||
|
|
+ // normal image loading
|
||
|
|
+ if !req.GetSep().GetEnabled() {
|
||
|
|
+ if err = util.CheckLoadFile(opt.path); err != nil {
|
||
|
|
+ return LoadOptions{}, err
|
||
|
|
}
|
||
|
|
return opt, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
- // load separated images
|
||
|
|
- // log is used for sep methods
|
||
|
|
- opt.sep.log = opt.logEntry
|
||
|
|
- tmpRoot := filepath.Join(b.daemon.opts.DataRoot, filepath.Join(dataRootTmpDirPrefix, req.GetLoadID()))
|
||
|
|
- opt.sep.tmpDir.root = tmpRoot
|
||
|
|
- opt.sep.tmpDir.base = filepath.Join(tmpRoot, tmpBaseDirName)
|
||
|
|
- opt.sep.tmpDir.app = filepath.Join(tmpRoot, tmpAppDirName)
|
||
|
|
- opt.sep.tmpDir.lib = filepath.Join(tmpRoot, tmpLibDirName)
|
||
|
|
-
|
||
|
|
- // check image name and add "latest" tag if not present
|
||
|
|
- _, appImgName, err := image.GetNamedTaggedReference(opt.sep.appName)
|
||
|
|
+ // separated images loading
|
||
|
|
+ opt.sep, err = separator.GetSepLoadOptions(req, opt.LogEntry, b.daemon.opts.DataRoot)
|
||
|
|
if err != nil {
|
||
|
|
- return loadOptions{}, err
|
||
|
|
+ return LoadOptions{}, err
|
||
|
|
}
|
||
|
|
- opt.sep.appName = appImgName
|
||
|
|
|
||
|
|
return opt, nil
|
||
|
|
}
|
||
|
|
@@ -136,15 +91,16 @@ func (b *Backend) Load(req *pb.LoadRequest, stream pb.Control_LoadServer) error
|
||
|
|
}
|
||
|
|
|
||
|
|
defer func() {
|
||
|
|
- if tErr := os.RemoveAll(opts.sep.tmpDir.root); tErr != nil {
|
||
|
|
- opts.logEntry.Warnf("Removing load tmp directory %q failed: %v", opts.sep.tmpDir.root, tErr)
|
||
|
|
+ if tErr := os.RemoveAll(opts.sep.TmpDirRoot()); tErr != nil {
|
||
|
|
+ opts.LogEntry.Warnf("Removing load tmp directory %q failed: %v", opts.sep.TmpDirRoot(), tErr)
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
|
||
|
|
// construct separated images
|
||
|
|
- if opts.sep.enabled {
|
||
|
|
- if lErr := loadSeparatedImage(&opts); lErr != nil {
|
||
|
|
- opts.logEntry.Errorf("Load separated image for %s failed: %v", opts.sep.appName, lErr)
|
||
|
|
+ if opts.sep.Enabled() {
|
||
|
|
+ var lErr error
|
||
|
|
+ if opts.path, lErr = opts.sep.LoadSeparatedImage(); lErr != nil {
|
||
|
|
+ opts.LogEntry.Errorf("Load separated image for %s failed: %v", opts.sep.AppName(), lErr)
|
||
|
|
return lErr
|
||
|
|
}
|
||
|
|
}
|
||
|
|
@@ -206,9 +162,9 @@ func (b *Backend) Load(req *pb.LoadRequest, stream pb.Control_LoadServer) error
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
-func tryToParseImageFormatFromTarball(dataRoot string, opts *loadOptions) ([]singleImage, error) {
|
||
|
|
+func tryToParseImageFormatFromTarball(dataRoot string, opts *LoadOptions) ([]singleImage, error) {
|
||
|
|
// tmp dir will be removed after NewSourceFromFileWithContext
|
||
|
|
- tmpDir, err := securejoin.SecureJoin(dataRoot, dataRootTmpDirPrefix)
|
||
|
|
+ tmpDir, err := securejoin.SecureJoin(dataRoot, constant.DataRootTmpDirPrefix)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
@@ -323,196 +279,3 @@ func getLoadedImageID(imageRef types.ImageReference, systemContext *types.System
|
||
|
|
|
||
|
|
return "@" + imageDigest.Encoded(), nil
|
||
|
|
}
|
||
|
|
-
|
||
|
|
-func loadSeparatedImage(opt *loadOptions) error {
|
||
|
|
- s := &opt.sep
|
||
|
|
- s.log.Infof("Starting load separated image %s", s.appName)
|
||
|
|
-
|
||
|
|
- // load manifest file to get tarball info
|
||
|
|
- if err := s.getTarballInfo(); err != nil {
|
||
|
|
- return errors.Wrap(err, "failed to get tarball info")
|
||
|
|
- }
|
||
|
|
- if err := s.constructTarballInfo(); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- // checksum for image tarballs
|
||
|
|
- if err := s.tarballCheckSum(); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- // process image tarballs and get final constructed image tarball
|
||
|
|
- tarPath, err := s.processTarballs()
|
||
|
|
- if err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- opt.path = tarPath
|
||
|
|
-
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorLoad) getTarballInfo() error {
|
||
|
|
- manifest, err := securejoin.SecureJoin(s.dir, manifestFile)
|
||
|
|
- if err != nil {
|
||
|
|
- return errors.Wrap(err, "join manifest file path failed")
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- var t = make(map[string]tarballInfo, 1)
|
||
|
|
- if err = util.LoadJSONFile(manifest, &t); err != nil {
|
||
|
|
- return errors.Wrap(err, "load manifest file failed")
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- tarball, ok := t[s.appName]
|
||
|
|
- if !ok {
|
||
|
|
- return errors.Errorf("failed to find app image %s", s.appName)
|
||
|
|
- }
|
||
|
|
- s.info = tarball
|
||
|
|
-
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorLoad) constructTarballInfo() (err error) {
|
||
|
|
- s.log.Infof("Construct image tarball info for %s", s.appName)
|
||
|
|
- // fill up path for separator
|
||
|
|
- // this case should not happened since client side already check this flag
|
||
|
|
- if len(s.appName) == 0 {
|
||
|
|
- return errors.New("app image name should not be empty")
|
||
|
|
- }
|
||
|
|
- s.appPath, err = securejoin.SecureJoin(s.dir, s.info.AppTarName)
|
||
|
|
- if err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- if len(s.basePath) == 0 {
|
||
|
|
- if len(s.info.BaseTarName) == 0 {
|
||
|
|
- return errors.Errorf("base image %s tarball can not be empty", s.info.BaseImageName)
|
||
|
|
- }
|
||
|
|
- s.log.Info("Base image path is empty, use path from manifest")
|
||
|
|
- s.basePath, err = securejoin.SecureJoin(s.dir, s.info.BaseTarName)
|
||
|
|
- if err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- if len(s.libPath) == 0 && len(s.info.LibTarName) != 0 {
|
||
|
|
- s.log.Info("Lib image path is empty, use path from manifest")
|
||
|
|
- s.libPath, err = securejoin.SecureJoin(s.dir, s.info.LibTarName)
|
||
|
|
- if err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorLoad) tarballCheckSum() error {
|
||
|
|
- if s.skipCheck {
|
||
|
|
- s.log.Info("Skip checksum for tarballs")
|
||
|
|
- return nil
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- type checkInfo struct {
|
||
|
|
- path string
|
||
|
|
- hash string
|
||
|
|
- str string
|
||
|
|
- canBeEmpty bool
|
||
|
|
- }
|
||
|
|
- checkLen := 3
|
||
|
|
- var checkList = make([]checkInfo, 0, checkLen)
|
||
|
|
- checkList = append(checkList, checkInfo{path: s.basePath, hash: s.info.BaseHash, canBeEmpty: false, str: "base image"})
|
||
|
|
- checkList = append(checkList, checkInfo{path: s.libPath, hash: s.info.LibHash, canBeEmpty: true, str: "lib image"})
|
||
|
|
- checkList = append(checkList, checkInfo{path: s.appPath, hash: s.info.AppHash, canBeEmpty: false, str: "app image"})
|
||
|
|
- for _, p := range checkList {
|
||
|
|
- if len(p.path) == 0 && !p.canBeEmpty {
|
||
|
|
- return errors.Errorf("%s tarball path can not be empty", p.str)
|
||
|
|
- }
|
||
|
|
- if len(p.path) != 0 {
|
||
|
|
- if err := util.CheckSum(p.path, p.hash); err != nil {
|
||
|
|
- return errors.Wrapf(err, "check sum for file %q failed", p.path)
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorLoad) processTarballs() (string, error) {
|
||
|
|
- if err := s.unpackTarballs(); err != nil {
|
||
|
|
- return "", err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- if err := s.reconstructImage(); err != nil {
|
||
|
|
- return "", err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- // pack app image to tarball
|
||
|
|
- tarPath := filepath.Join(s.tmpDir.root, unionCompressedTarName)
|
||
|
|
- if err := util.PackFiles(s.tmpDir.base, tarPath, archive.Gzip, true); err != nil {
|
||
|
|
- return "", err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return tarPath, nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorLoad) unpackTarballs() error {
|
||
|
|
- if err := s.makeTempDir(); err != nil {
|
||
|
|
- return errors.Wrap(err, "failed to make temporary directories")
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- type unpackInfo struct{ path, dir, str string }
|
||
|
|
- unpackLen := 3
|
||
|
|
- var unpackList = make([]unpackInfo, 0, unpackLen)
|
||
|
|
- unpackList = append(unpackList, unpackInfo{path: s.basePath, dir: s.tmpDir.base, str: "base image"})
|
||
|
|
- unpackList = append(unpackList, unpackInfo{path: s.appPath, dir: s.tmpDir.app, str: "app image"})
|
||
|
|
- unpackList = append(unpackList, unpackInfo{path: s.libPath, dir: s.tmpDir.lib, str: "lib image"})
|
||
|
|
-
|
||
|
|
- for _, p := range unpackList {
|
||
|
|
- if len(p.path) != 0 {
|
||
|
|
- if err := util.UnpackFile(p.path, p.dir, archive.Gzip, false); err != nil {
|
||
|
|
- return errors.Wrapf(err, "unpack %s tarball %q failed", p.str, p.path)
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorLoad) reconstructImage() error {
|
||
|
|
- files, err := ioutil.ReadDir(s.tmpDir.app)
|
||
|
|
- if err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- for _, f := range files {
|
||
|
|
- src := filepath.Join(s.tmpDir.app, f.Name())
|
||
|
|
- dest := filepath.Join(s.tmpDir.base, f.Name())
|
||
|
|
- if err := os.Rename(src, dest); err != nil {
|
||
|
|
- return errors.Wrapf(err, "reconstruct app file %q failed", s.info.AppTarName)
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- if len(s.libPath) != 0 {
|
||
|
|
- files, err := ioutil.ReadDir(s.tmpDir.lib)
|
||
|
|
- if err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- for _, f := range files {
|
||
|
|
- src := filepath.Join(s.tmpDir.lib, f.Name())
|
||
|
|
- dest := filepath.Join(s.tmpDir.base, f.Name())
|
||
|
|
- if err := os.Rename(src, dest); err != nil {
|
||
|
|
- return errors.Wrapf(err, "reconstruct lib file %q failed", s.info.LibTarName)
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorLoad) makeTempDir() error {
|
||
|
|
- dirs := []string{s.tmpDir.root, s.tmpDir.app, s.tmpDir.base, s.tmpDir.lib}
|
||
|
|
- for _, dir := range dirs {
|
||
|
|
- if err := os.MkdirAll(dir, constant.DefaultRootDirMode); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
diff --git a/daemon/load_test.go b/daemon/load_test.go
|
||
|
|
index 860b897..5e1a42b 100644
|
||
|
|
--- a/daemon/load_test.go
|
||
|
|
+++ b/daemon/load_test.go
|
||
|
|
@@ -314,7 +314,7 @@ func TestLoadMultipleImages(t *testing.T) {
|
||
|
|
defer clean(dir)
|
||
|
|
|
||
|
|
path := dir.Join(loadedTarFile)
|
||
|
|
- repoTags, err := tryToParseImageFormatFromTarball(daemon.opts.DataRoot, &loadOptions{path: path})
|
||
|
|
+ repoTags, err := tryToParseImageFormatFromTarball(daemon.opts.DataRoot, &LoadOptions{path: path})
|
||
|
|
assert.NilError(t, err)
|
||
|
|
assert.Equal(t, repoTags[0].nameTag[0], "registry.example.com/sayhello:first")
|
||
|
|
assert.Equal(t, repoTags[1].nameTag[0], "registry.example.com/sayhello:second")
|
||
|
|
diff --git a/daemon/save.go b/daemon/save.go
|
||
|
|
index 7a110bd..708fab3 100644
|
||
|
|
--- a/daemon/save.go
|
||
|
|
+++ b/daemon/save.go
|
||
|
|
@@ -15,24 +15,19 @@ package daemon
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
- "encoding/json"
|
||
|
|
- "fmt"
|
||
|
|
- "io/ioutil"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
- "sort"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"github.com/containers/image/v5/docker/reference"
|
||
|
|
"github.com/containers/image/v5/types"
|
||
|
|
- "github.com/containers/storage/pkg/archive"
|
||
|
|
- "github.com/docker/docker/pkg/ioutils"
|
||
|
|
"github.com/pkg/errors"
|
||
|
|
"github.com/sirupsen/logrus"
|
||
|
|
"golang.org/x/sync/errgroup"
|
||
|
|
|
||
|
|
constant "isula.org/isula-build"
|
||
|
|
pb "isula.org/isula-build/api/services"
|
||
|
|
+ "isula.org/isula-build/daemon/separator"
|
||
|
|
"isula.org/isula-build/exporter"
|
||
|
|
savedocker "isula.org/isula-build/exporter/docker/archive"
|
||
|
|
"isula.org/isula-build/image"
|
||
|
|
@@ -41,28 +36,13 @@ import (
|
||
|
|
"isula.org/isula-build/util"
|
||
|
|
)
|
||
|
|
|
||
|
|
-const (
|
||
|
|
- manifestDataFile = "manifest.json"
|
||
|
|
- manifestFile = "manifest"
|
||
|
|
- repositoriesFile = "repositories"
|
||
|
|
- baseTarNameSuffix = "_base_image.tar.gz"
|
||
|
|
- appTarNameSuffix = "_app_image.tar.gz"
|
||
|
|
- libTarNameSuffix = "_lib_image.tar.gz"
|
||
|
|
- untarTempDirName = "untar"
|
||
|
|
- baseUntarTempDirName = "base_images"
|
||
|
|
- appUntarTempDirName = "app_images"
|
||
|
|
- libUntarTempDirName = "lib_images"
|
||
|
|
- unionTarName = "all.tar"
|
||
|
|
- layerTarName = "layer.tar"
|
||
|
|
- tarSuffix = ".tar"
|
||
|
|
-)
|
||
|
|
-
|
||
|
|
type savedImage struct {
|
||
|
|
exist bool
|
||
|
|
tags []reference.NamedTagged
|
||
|
|
}
|
||
|
|
|
||
|
|
-type saveOptions struct {
|
||
|
|
+// SaveOptions stores the options for saving images
|
||
|
|
+type SaveOptions struct {
|
||
|
|
sysCtx *types.SystemContext
|
||
|
|
localStore *store.Store
|
||
|
|
logger *logger.Logger
|
||
|
|
@@ -73,77 +53,11 @@ type saveOptions struct {
|
||
|
|
oriImgList []string
|
||
|
|
finalImageOrdered []string
|
||
|
|
finalImageSet map[string]*savedImage
|
||
|
|
- sep separatorSave
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-type separatorSave struct {
|
||
|
|
- log *logrus.Entry
|
||
|
|
- renameData []renames
|
||
|
|
- tmpDir imageTmpDir
|
||
|
|
- base string
|
||
|
|
- lib string
|
||
|
|
- dest string
|
||
|
|
- renameFile string
|
||
|
|
- enabled bool
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-type renames struct {
|
||
|
|
- Name string `json:"name"`
|
||
|
|
- Rename string `json:"rename"`
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-type imageTmpDir struct {
|
||
|
|
- app string
|
||
|
|
- base string
|
||
|
|
- lib string
|
||
|
|
- untar string
|
||
|
|
- root string
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-type layer struct {
|
||
|
|
- all []string
|
||
|
|
- base []string
|
||
|
|
- lib []string
|
||
|
|
- app []string
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-type imageInfo struct {
|
||
|
|
- layers layer
|
||
|
|
- repoTags []string
|
||
|
|
- config string
|
||
|
|
- name string
|
||
|
|
- tag string
|
||
|
|
- nameTag string
|
||
|
|
- topLayer string
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-// imageManifest return image's manifest info
|
||
|
|
-type imageManifest struct {
|
||
|
|
- Config string `json:"Config"`
|
||
|
|
- RepoTags []string `json:"RepoTags"`
|
||
|
|
- Layers []string `json:"Layers"`
|
||
|
|
- // Not shown in the json file
|
||
|
|
- HashMap map[string]string `json:"-"`
|
||
|
|
+ sep separator.Saver
|
||
|
|
}
|
||
|
|
|
||
|
|
-type imageLayersMap map[string]string
|
||
|
|
-
|
||
|
|
-type tarballInfo struct {
|
||
|
|
- AppTarName string `json:"app"`
|
||
|
|
- AppHash string `json:"appHash"`
|
||
|
|
- AppLayers []string `json:"appLayers"`
|
||
|
|
- LibTarName string `json:"lib"`
|
||
|
|
- LibHash string `json:"libHash"`
|
||
|
|
- LibImageName string `json:"libImageName"`
|
||
|
|
- LibLayers []string `json:"libLayers"`
|
||
|
|
- BaseTarName string `json:"base"`
|
||
|
|
- BaseHash string `json:"baseHash"`
|
||
|
|
- BaseImageName string `json:"baseImageName"`
|
||
|
|
- BaseLayer string `json:"baseLayer"`
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (b *Backend) getSaveOptions(req *pb.SaveRequest) saveOptions {
|
||
|
|
- var opt = saveOptions{
|
||
|
|
+func (b *Backend) getSaveOptions(req *pb.SaveRequest) SaveOptions {
|
||
|
|
+ var opt = SaveOptions{
|
||
|
|
sysCtx: image.GetSystemContext(),
|
||
|
|
localStore: b.daemon.localStore,
|
||
|
|
saveID: req.GetSaveID(),
|
||
|
|
@@ -160,24 +74,7 @@ func (b *Backend) getSaveOptions(req *pb.SaveRequest) saveOptions {
|
||
|
|
return opt
|
||
|
|
}
|
||
|
|
|
||
|
|
- opt.sep = separatorSave{
|
||
|
|
- base: req.GetSep().GetBase(),
|
||
|
|
- lib: req.GetSep().GetLib(),
|
||
|
|
- dest: req.GetSep().GetDest(),
|
||
|
|
- log: logrus.WithFields(logrus.Fields{"SaveID": req.GetSaveID()}),
|
||
|
|
- enabled: req.GetSep().GetEnabled(),
|
||
|
|
- renameFile: req.GetSep().GetRename(),
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- // save separated image
|
||
|
|
- tmpRoot := filepath.Join(b.daemon.opts.DataRoot, filepath.Join(dataRootTmpDirPrefix, req.GetSaveID()))
|
||
|
|
- untar := filepath.Join(tmpRoot, untarTempDirName)
|
||
|
|
- appDir := filepath.Join(tmpRoot, appUntarTempDirName)
|
||
|
|
- baseDir := filepath.Join(tmpRoot, baseUntarTempDirName)
|
||
|
|
- libDir := filepath.Join(tmpRoot, libUntarTempDirName)
|
||
|
|
-
|
||
|
|
- opt.sep.tmpDir = imageTmpDir{app: appDir, base: baseDir, lib: libDir, untar: untar, root: tmpRoot}
|
||
|
|
- opt.outputPath = filepath.Join(untar, unionTarName)
|
||
|
|
+ opt.sep, opt.outputPath = separator.GetSepSaveOptions(req, opt.logEntry, b.daemon.opts.DataRoot)
|
||
|
|
|
||
|
|
return opt
|
||
|
|
}
|
||
|
|
@@ -213,15 +110,14 @@ func (b *Backend) Save(req *pb.SaveRequest, stream pb.Control_SaveServer) (err e
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
- // separatorSave found
|
||
|
|
- if opts.sep.enabled {
|
||
|
|
- return separateImage(opts)
|
||
|
|
+ if opts.sep.Enabled() {
|
||
|
|
+ return opts.sep.SeparateImage(opts.localStore, opts.oriImgList, opts.outputPath)
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
-func exportHandler(ctx context.Context, opts *saveOptions) func() error {
|
||
|
|
+func exportHandler(ctx context.Context, opts *SaveOptions) func() error {
|
||
|
|
return func() error {
|
||
|
|
defer func() {
|
||
|
|
opts.logger.CloseContent()
|
||
|
|
@@ -278,7 +174,7 @@ func messageHandler(stream pb.Control_SaveServer, cliLogger *logger.Logger) func
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
-func (opts *saveOptions) manage() error {
|
||
|
|
+func (opts *SaveOptions) manage() error {
|
||
|
|
if err := opts.checkImageNameIsID(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
@@ -288,29 +184,22 @@ func (opts *saveOptions) manage() error {
|
||
|
|
if err := opts.filterImageName(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
- if err := opts.loadRenameFile(); err != nil {
|
||
|
|
+ if err := opts.sep.LoadRenameFile(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
-func (opts *saveOptions) checkImageNameIsID() error {
|
||
|
|
+func (opts *SaveOptions) checkImageNameIsID() error {
|
||
|
|
imageNames := opts.oriImgList
|
||
|
|
- if opts.sep.enabled {
|
||
|
|
- if len(opts.sep.base) != 0 {
|
||
|
|
- imageNames = append(imageNames, opts.sep.base)
|
||
|
|
- }
|
||
|
|
- if len(opts.sep.lib) != 0 {
|
||
|
|
- imageNames = append(imageNames, opts.sep.lib)
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
+ imageNames = append(imageNames, opts.sep.ImageNames()...)
|
||
|
|
for _, name := range imageNames {
|
||
|
|
_, img, err := image.FindImage(opts.localStore, name)
|
||
|
|
if err != nil {
|
||
|
|
return errors.Wrapf(err, "check image name failed when finding image name %q", name)
|
||
|
|
}
|
||
|
|
- if strings.HasPrefix(img.ID, name) && opts.sep.enabled {
|
||
|
|
+ if strings.HasPrefix(img.ID, name) && opts.sep.Enabled() {
|
||
|
|
return errors.Errorf("using image ID %q as image name to save separated image is not allowed", name)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
@@ -318,7 +207,7 @@ func (opts *saveOptions) checkImageNameIsID() error {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
-func (opts *saveOptions) setFormat() error {
|
||
|
|
+func (opts *SaveOptions) setFormat() error {
|
||
|
|
switch opts.format {
|
||
|
|
case constant.DockerTransport:
|
||
|
|
opts.format = constant.DockerArchiveTransport
|
||
|
|
@@ -331,7 +220,7 @@ func (opts *saveOptions) setFormat() error {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
-func (opts *saveOptions) filterImageName() error {
|
||
|
|
+func (opts *SaveOptions) filterImageName() error {
|
||
|
|
if opts.format == constant.OCIArchiveTransport {
|
||
|
|
opts.finalImageOrdered = opts.oriImgList
|
||
|
|
return nil
|
||
|
|
@@ -367,507 +256,3 @@ func (opts *saveOptions) filterImageName() error {
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
-
|
||
|
|
-func (opts *saveOptions) loadRenameFile() error {
|
||
|
|
- if len(opts.sep.renameFile) != 0 {
|
||
|
|
- var reName []renames
|
||
|
|
- if err := util.LoadJSONFile(opts.sep.renameFile, &reName); err != nil {
|
||
|
|
- return errors.Wrap(err, "check rename file failed")
|
||
|
|
- }
|
||
|
|
- opts.sep.renameData = reName
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorSave) getLayerHashFromStorage(store *store.Store, name string) ([]string, error) {
|
||
|
|
- if len(name) == 0 {
|
||
|
|
- return nil, nil
|
||
|
|
- }
|
||
|
|
- _, img, err := image.FindImage(store, name)
|
||
|
|
- if err != nil {
|
||
|
|
- return nil, err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- layer, err := store.Layer(img.TopLayer)
|
||
|
|
- if err != nil {
|
||
|
|
- return nil, errors.Wrapf(err, "failed to get top layer for image %s", name)
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- var layers []string
|
||
|
|
- // add each layer in the layers until reach the root layer
|
||
|
|
- for layer != nil {
|
||
|
|
- fields := strings.Split(layer.UncompressedDigest.String(), ":")
|
||
|
|
- if len(fields) != 2 {
|
||
|
|
- return nil, errors.Errorf("error format of layer of image %s", name)
|
||
|
|
- }
|
||
|
|
- layers = append(layers, fields[1])
|
||
|
|
- if layer.Parent == "" {
|
||
|
|
- break
|
||
|
|
- }
|
||
|
|
- layer, err = store.Layer(layer.Parent)
|
||
|
|
- if err != nil {
|
||
|
|
- return nil, errors.Wrapf(err, "unable to read layer %q", layer.Parent)
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return layers, nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-// process physic file
|
||
|
|
-func (s *separatorSave) constructLayerMap() (map[string]string, error) {
|
||
|
|
- path := s.tmpDir.untar
|
||
|
|
- files, rErr := ioutil.ReadDir(path)
|
||
|
|
- if rErr != nil {
|
||
|
|
- return nil, rErr
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- var layerMap = make(map[string]string, len(files))
|
||
|
|
- // process layer's file
|
||
|
|
- for _, file := range files {
|
||
|
|
- if file.IsDir() {
|
||
|
|
- layerFile := filepath.Join(path, file.Name(), layerTarName)
|
||
|
|
- oriFile, err := os.Readlink(layerFile)
|
||
|
|
- if err != nil {
|
||
|
|
- return nil, err
|
||
|
|
- }
|
||
|
|
- physicFile := filepath.Join(path, file.Name(), oriFile)
|
||
|
|
- layerMap[filepath.Base(physicFile)] = filepath.Join(file.Name(), layerTarName)
|
||
|
|
- if err := os.Rename(physicFile, layerFile); err != nil {
|
||
|
|
- return nil, err
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return layerMap, nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func getLayerHashFromTar(layerMap map[string]string, layer []string) map[string]string {
|
||
|
|
- hashMap := make(map[string]string, len(layer))
|
||
|
|
- // first reverse map since it's <k-v> is unique
|
||
|
|
- revMap := make(map[string]string, len(layerMap))
|
||
|
|
- for k, v := range layerMap {
|
||
|
|
- revMap[v] = k
|
||
|
|
- }
|
||
|
|
- for _, l := range layer {
|
||
|
|
- if v, ok := revMap[l]; ok {
|
||
|
|
- // format is like xxx(hash): xxx/layer.tar
|
||
|
|
- hashMap[strings.TrimSuffix(v, tarSuffix)] = l
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return hashMap
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorSave) adjustLayers() ([]imageManifest, error) {
|
||
|
|
- s.log.Info("Adjusting layers for saving separated image")
|
||
|
|
-
|
||
|
|
- layerMap, err := s.constructLayerMap()
|
||
|
|
- if err != nil {
|
||
|
|
- s.log.Errorf("Process layers failed: %v", err)
|
||
|
|
- return nil, err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- // process manifest file
|
||
|
|
- var man []imageManifest
|
||
|
|
- if lErr := util.LoadJSONFile(filepath.Join(s.tmpDir.untar, manifestDataFile), &man); lErr != nil {
|
||
|
|
- return nil, lErr
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- for i, img := range man {
|
||
|
|
- layers := make([]string, len(img.Layers))
|
||
|
|
- for i, layer := range img.Layers {
|
||
|
|
- layers[i] = layerMap[layer]
|
||
|
|
- }
|
||
|
|
- man[i].Layers = layers
|
||
|
|
- man[i].HashMap = getLayerHashFromTar(layerMap, layers)
|
||
|
|
- }
|
||
|
|
- buf, err := json.Marshal(&man)
|
||
|
|
- if err != nil {
|
||
|
|
- return nil, err
|
||
|
|
- }
|
||
|
|
- if err := ioutils.AtomicWriteFile(manifestFile, buf, constant.DefaultSharedFileMode); err != nil {
|
||
|
|
- return nil, err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return man, nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func separateImage(opt saveOptions) (err error) {
|
||
|
|
- s := &opt.sep
|
||
|
|
- s.log.Infof("Start saving separated images %v", opt.oriImgList)
|
||
|
|
-
|
||
|
|
- if err = os.MkdirAll(s.dest, constant.DefaultRootDirMode); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- defer func() {
|
||
|
|
- if tErr := os.RemoveAll(s.tmpDir.root); tErr != nil && !os.IsNotExist(tErr) {
|
||
|
|
- s.log.Warnf("Removing save tmp directory %q failed: %v", s.tmpDir.root, tErr)
|
||
|
|
- }
|
||
|
|
- if err != nil {
|
||
|
|
- if rErr := os.RemoveAll(s.dest); rErr != nil && !os.IsNotExist(rErr) {
|
||
|
|
- s.log.Warnf("Removing save dest directory %q failed: %v", s.dest, rErr)
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- }()
|
||
|
|
- if err = util.UnpackFile(opt.outputPath, s.tmpDir.untar, archive.Gzip, true); err != nil {
|
||
|
|
- return errors.Wrapf(err, "unpack %q failed", opt.outputPath)
|
||
|
|
- }
|
||
|
|
- manifest, aErr := s.adjustLayers()
|
||
|
|
- if aErr != nil {
|
||
|
|
- return errors.Wrap(aErr, "adjust layers failed")
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- imgInfos, cErr := s.constructImageInfos(manifest, opt.localStore)
|
||
|
|
- if cErr != nil {
|
||
|
|
- return errors.Wrap(cErr, "process image infos failed")
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- if err = s.processImageLayers(imgInfos); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorSave) processImageLayers(imgInfos map[string]imageInfo) error {
|
||
|
|
- s.log.Info("Processing image layers")
|
||
|
|
- var (
|
||
|
|
- tarballs = make(map[string]tarballInfo)
|
||
|
|
- baseImagesMap = make(imageLayersMap, 1)
|
||
|
|
- libImagesMap = make(imageLayersMap, 1)
|
||
|
|
- appImagesMap = make(imageLayersMap, 1)
|
||
|
|
- )
|
||
|
|
- var sortedKey []string
|
||
|
|
- for k := range imgInfos {
|
||
|
|
- sortedKey = append(sortedKey, k)
|
||
|
|
- }
|
||
|
|
- sort.Strings(sortedKey)
|
||
|
|
- for _, k := range sortedKey {
|
||
|
|
- info := imgInfos[k]
|
||
|
|
- if err := s.clearTempDirs(); err != nil {
|
||
|
|
- return errors.Wrap(err, "clear tmp dirs failed")
|
||
|
|
- }
|
||
|
|
- var t tarballInfo
|
||
|
|
- // process base
|
||
|
|
- if err := info.processBaseImg(s, baseImagesMap, &t); err != nil {
|
||
|
|
- return errors.Wrapf(err, "process base images %s failed", info.nameTag)
|
||
|
|
- }
|
||
|
|
- // process lib
|
||
|
|
- if err := info.processLibImg(s, libImagesMap, &t); err != nil {
|
||
|
|
- return errors.Wrapf(err, "process lib images %s failed", info.nameTag)
|
||
|
|
- }
|
||
|
|
- // process app
|
||
|
|
- if err := info.processAppImg(s, appImagesMap, &t); err != nil {
|
||
|
|
- return errors.Wrapf(err, "process app images %s failed", info.nameTag)
|
||
|
|
- }
|
||
|
|
- tarballs[info.nameTag] = t
|
||
|
|
- }
|
||
|
|
- buf, err := json.Marshal(&tarballs)
|
||
|
|
- if err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- // manifest file
|
||
|
|
- manifestFile := filepath.Join(s.dest, manifestFile)
|
||
|
|
- if err := ioutils.AtomicWriteFile(manifestFile, buf, constant.DefaultRootFileMode); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- s.log.Info("Save separated image succeed")
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorSave) clearTempDirs() error {
|
||
|
|
- dirs := []string{s.tmpDir.base, s.tmpDir.app, s.tmpDir.lib}
|
||
|
|
- for _, dir := range dirs {
|
||
|
|
- if err := os.RemoveAll(dir); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- if err := os.MkdirAll(dir, constant.DefaultRootDirMode); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-// processTarName will trim the prefix of image name like example.io/library/myapp:v1
|
||
|
|
-// after processed, the name will be myapp_v1_suffix
|
||
|
|
-// mind: suffix here should not contain path separator
|
||
|
|
-func (info imageInfo) processTarName(suffix string) string {
|
||
|
|
- originNames := strings.Split(info.name, string(os.PathSeparator))
|
||
|
|
- originTags := strings.Split(info.tag, string(os.PathSeparator))
|
||
|
|
- // get the last element of the list, which mast be the right name without prefix
|
||
|
|
- name := originNames[len(originNames)-1]
|
||
|
|
- tag := originTags[len(originTags)-1]
|
||
|
|
-
|
||
|
|
- return fmt.Sprintf("%s_%s%s", name, tag, suffix)
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (info *imageInfo) processBaseImg(sep *separatorSave, baseImagesMap map[string]string, tarball *tarballInfo) error {
|
||
|
|
- // process base
|
||
|
|
- tarball.BaseImageName = sep.base
|
||
|
|
- if len(info.layers.base) != 0 {
|
||
|
|
- sep.log.Infof("Base image %s has %d layers", sep.base, len(info.layers.base))
|
||
|
|
- tarball.BaseLayer = info.layers.base[0]
|
||
|
|
- }
|
||
|
|
- for _, layerID := range info.layers.base {
|
||
|
|
- if baseImg, ok := baseImagesMap[layerID]; !ok {
|
||
|
|
- srcLayerPath := filepath.Join(sep.tmpDir.untar, layerID)
|
||
|
|
- destLayerPath := filepath.Join(sep.tmpDir.base, layerID)
|
||
|
|
- if err := os.Rename(srcLayerPath, destLayerPath); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- baseTarName := info.processTarName(baseTarNameSuffix)
|
||
|
|
- baseTarName = sep.rename(baseTarName)
|
||
|
|
- baseTarPath := filepath.Join(sep.dest, baseTarName)
|
||
|
|
- if err := util.PackFiles(sep.tmpDir.base, baseTarPath, archive.Gzip, true); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- baseImagesMap[layerID] = baseTarPath
|
||
|
|
- tarball.BaseTarName = baseTarName
|
||
|
|
- digest, err := util.SHA256Sum(baseTarPath)
|
||
|
|
- if err != nil {
|
||
|
|
- return errors.Wrapf(err, "check sum for new base image %s failed", baseTarName)
|
||
|
|
- }
|
||
|
|
- tarball.BaseHash = digest
|
||
|
|
- } else {
|
||
|
|
- tarball.BaseTarName = filepath.Base(baseImg)
|
||
|
|
- digest, err := util.SHA256Sum(baseImg)
|
||
|
|
- if err != nil {
|
||
|
|
- return errors.Wrapf(err, "check sum for reuse base image %s failed", baseImg)
|
||
|
|
- }
|
||
|
|
- tarball.BaseHash = digest
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (info *imageInfo) processLibImg(sep *separatorSave, libImagesMap map[string]string, tarball *tarballInfo) error {
|
||
|
|
- // process lib
|
||
|
|
- if info.layers.lib == nil {
|
||
|
|
- return nil
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- tarball.LibImageName = sep.lib
|
||
|
|
- sep.log.Infof("Lib image %s has %d layers", sep.lib, len(info.layers.lib))
|
||
|
|
- for _, layerID := range info.layers.lib {
|
||
|
|
- tarball.LibLayers = append(tarball.LibLayers, layerID)
|
||
|
|
- if libImg, ok := libImagesMap[layerID]; !ok {
|
||
|
|
- srcLayerPath := filepath.Join(sep.tmpDir.untar, layerID)
|
||
|
|
- destLayerPath := filepath.Join(sep.tmpDir.lib, layerID)
|
||
|
|
- if err := os.Rename(srcLayerPath, destLayerPath); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- libTarName := info.processTarName(libTarNameSuffix)
|
||
|
|
- libTarName = sep.rename(libTarName)
|
||
|
|
- libTarPath := filepath.Join(sep.dest, libTarName)
|
||
|
|
- if err := util.PackFiles(sep.tmpDir.lib, libTarPath, archive.Gzip, true); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- libImagesMap[layerID] = libTarPath
|
||
|
|
- tarball.LibTarName = libTarName
|
||
|
|
- digest, err := util.SHA256Sum(libTarPath)
|
||
|
|
- if err != nil {
|
||
|
|
- return errors.Wrapf(err, "check sum for lib image %s failed", sep.lib)
|
||
|
|
- }
|
||
|
|
- tarball.LibHash = digest
|
||
|
|
- } else {
|
||
|
|
- tarball.LibTarName = filepath.Base(libImg)
|
||
|
|
- digest, err := util.SHA256Sum(libImg)
|
||
|
|
- if err != nil {
|
||
|
|
- return errors.Wrapf(err, "check sum for lib image %s failed", sep.lib)
|
||
|
|
- }
|
||
|
|
- tarball.LibHash = digest
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (info *imageInfo) processAppImg(sep *separatorSave, appImagesMap map[string]string, tarball *tarballInfo) error {
|
||
|
|
- // process app
|
||
|
|
- sep.log.Infof("App image %s has %d layers", info.nameTag, len(info.layers.app))
|
||
|
|
- appTarName := info.processTarName(appTarNameSuffix)
|
||
|
|
- appTarName = sep.rename(appTarName)
|
||
|
|
- appTarPath := filepath.Join(sep.dest, appTarName)
|
||
|
|
- for _, layerID := range info.layers.app {
|
||
|
|
- srcLayerPath := filepath.Join(sep.tmpDir.untar, layerID)
|
||
|
|
- destLayerPath := filepath.Join(sep.tmpDir.app, layerID)
|
||
|
|
- if err := os.Rename(srcLayerPath, destLayerPath); err != nil {
|
||
|
|
- if appImg, ok := appImagesMap[layerID]; ok {
|
||
|
|
- return errors.Errorf("lib layers %s already saved in %s for image %s",
|
||
|
|
- layerID, appImg, info.nameTag)
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- appImagesMap[layerID] = appTarPath
|
||
|
|
- tarball.AppLayers = append(tarball.AppLayers, layerID)
|
||
|
|
- }
|
||
|
|
- // create config file
|
||
|
|
- if err := info.createManifestFile(sep); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- if err := info.createRepositoriesFile(sep); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- srcConfigPath := filepath.Join(sep.tmpDir.untar, info.config)
|
||
|
|
- destConfigPath := filepath.Join(sep.tmpDir.app, info.config)
|
||
|
|
- if err := os.Rename(srcConfigPath, destConfigPath); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- if err := util.PackFiles(sep.tmpDir.app, appTarPath, archive.Gzip, true); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- tarball.AppTarName = appTarName
|
||
|
|
- digest, err := util.SHA256Sum(appTarPath)
|
||
|
|
- if err != nil {
|
||
|
|
- return errors.Wrapf(err, "check sum for app image %s failed", info.nameTag)
|
||
|
|
- }
|
||
|
|
- tarball.AppHash = digest
|
||
|
|
-
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (info imageInfo) createRepositoriesFile(sep *separatorSave) error {
|
||
|
|
- // create repositories
|
||
|
|
- type repoItem map[string]string
|
||
|
|
- repo := make(map[string]repoItem, 1)
|
||
|
|
- item := make(repoItem, 1)
|
||
|
|
- if _, ok := item[info.tag]; !ok {
|
||
|
|
- item[info.tag] = info.topLayer
|
||
|
|
- }
|
||
|
|
- repo[info.name] = item
|
||
|
|
- buf, err := json.Marshal(repo)
|
||
|
|
- if err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- repositoryFile := filepath.Join(sep.tmpDir.app, repositoriesFile)
|
||
|
|
- if err := ioutils.AtomicWriteFile(repositoryFile, buf, constant.DefaultRootFileMode); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (info imageInfo) createManifestFile(sep *separatorSave) error {
|
||
|
|
- // create manifest.json
|
||
|
|
- var s = imageManifest{
|
||
|
|
- Config: info.config,
|
||
|
|
- Layers: info.layers.all,
|
||
|
|
- RepoTags: info.repoTags,
|
||
|
|
- }
|
||
|
|
- var m []imageManifest
|
||
|
|
- m = append(m, s)
|
||
|
|
- buf, err := json.Marshal(&m)
|
||
|
|
- if err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- data := filepath.Join(sep.tmpDir.app, manifestDataFile)
|
||
|
|
- if err := ioutils.AtomicWriteFile(data, buf, constant.DefaultRootFileMode); err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- return nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func getLayersID(layer []string) []string {
|
||
|
|
- var after = make([]string, len(layer))
|
||
|
|
- for i, v := range layer {
|
||
|
|
- after[i] = strings.Split(v, "/")[0]
|
||
|
|
- }
|
||
|
|
- return after
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorSave) constructSingleImgInfo(mani imageManifest, store *store.Store) (imageInfo, error) {
|
||
|
|
- var libLayers, appLayers []string
|
||
|
|
- // image name should not be empty here
|
||
|
|
- if len(mani.RepoTags) == 0 {
|
||
|
|
- return imageInfo{}, errors.New("image name and tag is empty")
|
||
|
|
- }
|
||
|
|
- // if there is more than one repoTag, will use first one as image name
|
||
|
|
- imageRepoFields := strings.Split(mani.RepoTags[0], ":")
|
||
|
|
- imageLayers := getLayersID(mani.Layers)
|
||
|
|
-
|
||
|
|
- libs, bases, err := s.checkLayersHash(mani.HashMap, store)
|
||
|
|
- if err != nil {
|
||
|
|
- return imageInfo{}, errors.Wrap(err, "compare layers failed")
|
||
|
|
- }
|
||
|
|
- baseLayers := imageLayers[0:len(bases)]
|
||
|
|
- if len(libs) != 0 {
|
||
|
|
- libLayers = imageLayers[len(bases):len(libs)]
|
||
|
|
- appLayers = imageLayers[len(libs):]
|
||
|
|
- } else {
|
||
|
|
- libLayers = nil
|
||
|
|
- appLayers = imageLayers[len(bases):]
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return imageInfo{
|
||
|
|
- config: mani.Config,
|
||
|
|
- repoTags: mani.RepoTags,
|
||
|
|
- nameTag: mani.RepoTags[0],
|
||
|
|
- name: strings.Join(imageRepoFields[0:len(imageRepoFields)-1], ":"),
|
||
|
|
- tag: imageRepoFields[len(imageRepoFields)-1],
|
||
|
|
- layers: layer{app: appLayers, lib: libLayers, base: baseLayers, all: mani.Layers},
|
||
|
|
- topLayer: imageLayers[len(imageLayers)-1],
|
||
|
|
- }, nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorSave) checkLayersHash(layerHashMap map[string]string, store *store.Store) ([]string, []string, error) {
|
||
|
|
- libHash, err := s.getLayerHashFromStorage(store, s.lib)
|
||
|
|
- if err != nil {
|
||
|
|
- return nil, nil, errors.Wrapf(err, "get lib image %s layers failed", s.lib)
|
||
|
|
- }
|
||
|
|
- baseHash, err := s.getLayerHashFromStorage(store, s.base)
|
||
|
|
- if err != nil {
|
||
|
|
- return nil, nil, errors.Wrapf(err, "get base image %s layers failed", s.base)
|
||
|
|
- }
|
||
|
|
- if len(baseHash) > 1 {
|
||
|
|
- return nil, nil, errors.Errorf("number of base layers %d more than one", len(baseHash))
|
||
|
|
- }
|
||
|
|
- if len(libHash) >= len(layerHashMap) || len(baseHash) >= len(layerHashMap) {
|
||
|
|
- return nil, nil, errors.Errorf("number of base or lib layers is equal or greater than saved app layers")
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- for _, l := range libHash {
|
||
|
|
- if _, ok := layerHashMap[l]; !ok {
|
||
|
|
- return nil, nil, errors.Errorf("dismatch checksum for lib image %s", s.lib)
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- for _, b := range baseHash {
|
||
|
|
- if _, ok := layerHashMap[b]; !ok {
|
||
|
|
- return nil, nil, errors.Errorf("dismatch checksum for base image %s", s.base)
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return libHash, baseHash, nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorSave) constructImageInfos(manifest []imageManifest, store *store.Store) (map[string]imageInfo, error) {
|
||
|
|
- s.log.Info("Constructing image info")
|
||
|
|
-
|
||
|
|
- var imgInfos = make(map[string]imageInfo, 1)
|
||
|
|
- for _, mani := range manifest {
|
||
|
|
- imgInfo, err := s.constructSingleImgInfo(mani, store)
|
||
|
|
- if err != nil {
|
||
|
|
- s.log.Errorf("Constructing image info failed: %v", err)
|
||
|
|
- return nil, errors.Wrap(err, "construct image info failed")
|
||
|
|
- }
|
||
|
|
- if _, ok := imgInfos[imgInfo.nameTag]; !ok {
|
||
|
|
- imgInfos[imgInfo.nameTag] = imgInfo
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- return imgInfos, nil
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-func (s *separatorSave) rename(name string) string {
|
||
|
|
- if len(s.renameData) != 0 {
|
||
|
|
- s.log.Info("Renaming image tarballs")
|
||
|
|
- for _, item := range s.renameData {
|
||
|
|
- if item.Name == name {
|
||
|
|
- return item.Rename
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- return name
|
||
|
|
-}
|
||
|
|
diff --git a/daemon/separator/image_info.go b/daemon/separator/image_info.go
|
||
|
|
new file mode 100644
|
||
|
|
index 0000000..53bcd7a
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/daemon/separator/image_info.go
|
||
|
|
@@ -0,0 +1,235 @@
|
||
|
|
+// Copyright (c) Huawei Technologies Co., Ltd. 2020. All rights reserved.
|
||
|
|
+// isula-build licensed under the Mulan PSL v2.
|
||
|
|
+// You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||
|
|
+// You may obtain a copy of Mulan PSL v2 at:
|
||
|
|
+// http://license.coscl.org.cn/MulanPSL2
|
||
|
|
+// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
|
||
|
|
+// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
|
||
|
|
+// PURPOSE.
|
||
|
|
+// See the Mulan PSL v2 for more details.
|
||
|
|
+// Author: Xiang Li
|
||
|
|
+// Create: 2021-12-09
|
||
|
|
+// Description: This file is handling image info for image separator
|
||
|
|
+
|
||
|
|
+package separator
|
||
|
|
+
|
||
|
|
+import (
|
||
|
|
+ "encoding/json"
|
||
|
|
+ "fmt"
|
||
|
|
+ "os"
|
||
|
|
+ "path/filepath"
|
||
|
|
+ "strings"
|
||
|
|
+
|
||
|
|
+ "github.com/containers/storage/pkg/archive"
|
||
|
|
+ "github.com/docker/docker/pkg/ioutils"
|
||
|
|
+ "github.com/pkg/errors"
|
||
|
|
+
|
||
|
|
+ constant "isula.org/isula-build"
|
||
|
|
+ "isula.org/isula-build/util"
|
||
|
|
+)
|
||
|
|
+
|
||
|
|
+type layer struct {
|
||
|
|
+ all []string
|
||
|
|
+ base []string
|
||
|
|
+ lib []string
|
||
|
|
+ app []string
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+type imageInfo struct {
|
||
|
|
+ layers layer
|
||
|
|
+ repoTags []string
|
||
|
|
+ config string
|
||
|
|
+ name string
|
||
|
|
+ tag string
|
||
|
|
+ nameTag string
|
||
|
|
+ topLayer string
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// processTarName will trim the prefix of image name like example.io/library/myapp:v1
|
||
|
|
+// after processed, the name will be myapp_v1_suffix
|
||
|
|
+// mind: suffix here should not contain path separator
|
||
|
|
+func (info *imageInfo) processTarName(suffix string) string {
|
||
|
|
+ originNames := strings.Split(info.name, string(os.PathSeparator))
|
||
|
|
+ originTags := strings.Split(info.tag, string(os.PathSeparator))
|
||
|
|
+ // get the last element of the list, which mast be the right name without prefix
|
||
|
|
+ name := originNames[len(originNames)-1]
|
||
|
|
+ tag := originTags[len(originTags)-1]
|
||
|
|
+
|
||
|
|
+ return fmt.Sprintf("%s_%s%s", name, tag, suffix)
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (info *imageInfo) processBaseImg(sep *Saver, baseImagesMap map[string]string, tarball *tarballInfo) error {
|
||
|
|
+ // process base
|
||
|
|
+ tarball.BaseImageName = sep.base
|
||
|
|
+ if len(info.layers.base) != 0 {
|
||
|
|
+ sep.log.Infof("Base image %s has %d layers", sep.base, len(info.layers.base))
|
||
|
|
+ tarball.BaseLayer = info.layers.base[0]
|
||
|
|
+ }
|
||
|
|
+ for _, layerID := range info.layers.base {
|
||
|
|
+ if baseImg, ok := baseImagesMap[layerID]; !ok {
|
||
|
|
+ srcLayerPath := filepath.Join(sep.tmpDir.untar, layerID)
|
||
|
|
+ destLayerPath := filepath.Join(sep.tmpDir.base, layerID)
|
||
|
|
+ if err := os.Rename(srcLayerPath, destLayerPath); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ baseTarName := info.processTarName(baseTarNameSuffix)
|
||
|
|
+ baseTarName = sep.getRename(baseTarName)
|
||
|
|
+ baseTarPath := filepath.Join(sep.dest, baseTarName)
|
||
|
|
+ if err := util.PackFiles(sep.tmpDir.base, baseTarPath, archive.Gzip, true); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ baseImagesMap[layerID] = baseTarPath
|
||
|
|
+ tarball.BaseTarName = baseTarName
|
||
|
|
+ digest, err := util.SHA256Sum(baseTarPath)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return errors.Wrapf(err, "check sum for new base image %s failed", baseTarName)
|
||
|
|
+ }
|
||
|
|
+ tarball.BaseHash = digest
|
||
|
|
+ } else {
|
||
|
|
+ tarball.BaseTarName = filepath.Base(baseImg)
|
||
|
|
+ digest, err := util.SHA256Sum(baseImg)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return errors.Wrapf(err, "check sum for reuse base image %s failed", baseImg)
|
||
|
|
+ }
|
||
|
|
+ tarball.BaseHash = digest
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (info *imageInfo) processLibImg(sep *Saver, libImagesMap map[string]string, tarball *tarballInfo) error {
|
||
|
|
+ // process lib
|
||
|
|
+ if info.layers.lib == nil {
|
||
|
|
+ return nil
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ tarball.LibImageName = sep.lib
|
||
|
|
+ sep.log.Infof("Lib image %s has %d layers", sep.lib, len(info.layers.lib))
|
||
|
|
+ for _, layerID := range info.layers.lib {
|
||
|
|
+ tarball.LibLayers = append(tarball.LibLayers, layerID)
|
||
|
|
+ if libImg, ok := libImagesMap[layerID]; !ok {
|
||
|
|
+ srcLayerPath := filepath.Join(sep.tmpDir.untar, layerID)
|
||
|
|
+ destLayerPath := filepath.Join(sep.tmpDir.lib, layerID)
|
||
|
|
+ if err := os.Rename(srcLayerPath, destLayerPath); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ libTarName := info.processTarName(libTarNameSuffix)
|
||
|
|
+ libTarName = sep.getRename(libTarName)
|
||
|
|
+ libTarPath := filepath.Join(sep.dest, libTarName)
|
||
|
|
+ if err := util.PackFiles(sep.tmpDir.lib, libTarPath, archive.Gzip, true); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ libImagesMap[layerID] = libTarPath
|
||
|
|
+ tarball.LibTarName = libTarName
|
||
|
|
+ digest, err := util.SHA256Sum(libTarPath)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return errors.Wrapf(err, "check sum for lib image %s failed", sep.lib)
|
||
|
|
+ }
|
||
|
|
+ tarball.LibHash = digest
|
||
|
|
+ } else {
|
||
|
|
+ tarball.LibTarName = filepath.Base(libImg)
|
||
|
|
+ digest, err := util.SHA256Sum(libImg)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return errors.Wrapf(err, "check sum for lib image %s failed", sep.lib)
|
||
|
|
+ }
|
||
|
|
+ tarball.LibHash = digest
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (info *imageInfo) processAppImg(sep *Saver, appImagesMap map[string]string, tarball *tarballInfo) error {
|
||
|
|
+ // process app
|
||
|
|
+ sep.log.Infof("App image %s has %d layers", info.nameTag, len(info.layers.app))
|
||
|
|
+ appTarName := info.processTarName(appTarNameSuffix)
|
||
|
|
+ appTarName = sep.getRename(appTarName)
|
||
|
|
+ appTarPath := filepath.Join(sep.dest, appTarName)
|
||
|
|
+ for _, layerID := range info.layers.app {
|
||
|
|
+ srcLayerPath := filepath.Join(sep.tmpDir.untar, layerID)
|
||
|
|
+ destLayerPath := filepath.Join(sep.tmpDir.app, layerID)
|
||
|
|
+ if err := os.Rename(srcLayerPath, destLayerPath); err != nil {
|
||
|
|
+ if appImg, ok := appImagesMap[layerID]; ok {
|
||
|
|
+ return errors.Errorf("lib layers %s already saved in %s for image %s",
|
||
|
|
+ layerID, appImg, info.nameTag)
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ appImagesMap[layerID] = appTarPath
|
||
|
|
+ tarball.AppLayers = append(tarball.AppLayers, layerID)
|
||
|
|
+ }
|
||
|
|
+ // create config file
|
||
|
|
+ if err := info.createManifestFile(sep); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ if err := info.createRepositoriesFile(sep); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ srcConfigPath := filepath.Join(sep.tmpDir.untar, info.config)
|
||
|
|
+ destConfigPath := filepath.Join(sep.tmpDir.app, info.config)
|
||
|
|
+ if err := os.Rename(srcConfigPath, destConfigPath); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if err := util.PackFiles(sep.tmpDir.app, appTarPath, archive.Gzip, true); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ tarball.AppTarName = appTarName
|
||
|
|
+ digest, err := util.SHA256Sum(appTarPath)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return errors.Wrapf(err, "check sum for app image %s failed", info.nameTag)
|
||
|
|
+ }
|
||
|
|
+ tarball.AppHash = digest
|
||
|
|
+
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (info *imageInfo) createRepositoriesFile(sep *Saver) error {
|
||
|
|
+ // create repositories
|
||
|
|
+ type repoItem map[string]string
|
||
|
|
+ repo := make(map[string]repoItem, 1)
|
||
|
|
+ item := make(repoItem, 1)
|
||
|
|
+ if _, ok := item[info.tag]; !ok {
|
||
|
|
+ item[info.tag] = info.topLayer
|
||
|
|
+ }
|
||
|
|
+ repo[info.name] = item
|
||
|
|
+ buf, err := json.Marshal(repo)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ repositoryFile := filepath.Join(sep.tmpDir.app, repositoriesFile)
|
||
|
|
+ if err := ioutils.AtomicWriteFile(repositoryFile, buf, constant.DefaultRootFileMode); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// imageManifest return image's manifest info
|
||
|
|
+type imageManifest struct {
|
||
|
|
+ Config string `json:"Config"`
|
||
|
|
+ RepoTags []string `json:"RepoTags"`
|
||
|
|
+ Layers []string `json:"Layers"`
|
||
|
|
+ // Not shown in the json file
|
||
|
|
+ HashMap map[string]string `json:"-"`
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (info *imageInfo) createManifestFile(sep *Saver) error {
|
||
|
|
+ // create manifest.json
|
||
|
|
+ var s = imageManifest{
|
||
|
|
+ Config: info.config,
|
||
|
|
+ Layers: info.layers.all,
|
||
|
|
+ RepoTags: info.repoTags,
|
||
|
|
+ }
|
||
|
|
+ var m []imageManifest
|
||
|
|
+ m = append(m, s)
|
||
|
|
+ buf, err := json.Marshal(&m)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ data := filepath.Join(sep.tmpDir.app, manifestDataFile)
|
||
|
|
+ if err := ioutils.AtomicWriteFile(data, buf, constant.DefaultRootFileMode); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
diff --git a/daemon/separator/load.go b/daemon/separator/load.go
|
||
|
|
new file mode 100644
|
||
|
|
index 0000000..d02dccf
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/daemon/separator/load.go
|
||
|
|
@@ -0,0 +1,283 @@
|
||
|
|
+// Copyright (c) Huawei Technologies Co., Ltd. 2020. All rights reserved.
|
||
|
|
+// isula-build licensed under the Mulan PSL v2.
|
||
|
|
+// You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||
|
|
+// You may obtain a copy of Mulan PSL v2 at:
|
||
|
|
+// http://license.coscl.org.cn/MulanPSL2
|
||
|
|
+// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
|
||
|
|
+// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
|
||
|
|
+// PURPOSE.
|
||
|
|
+// See the Mulan PSL v2 for more details.
|
||
|
|
+// Author: Xiang Li
|
||
|
|
+// Create: 2021-12-09
|
||
|
|
+// Description: This file is handling "load" part for image separator at server side
|
||
|
|
+
|
||
|
|
+package separator
|
||
|
|
+
|
||
|
|
+import (
|
||
|
|
+ "io/ioutil"
|
||
|
|
+ "os"
|
||
|
|
+ "path/filepath"
|
||
|
|
+
|
||
|
|
+ "github.com/containers/storage/pkg/archive"
|
||
|
|
+ filepath_securejoin "github.com/cyphar/filepath-securejoin"
|
||
|
|
+ "github.com/pkg/errors"
|
||
|
|
+ "github.com/sirupsen/logrus"
|
||
|
|
+
|
||
|
|
+ constant "isula.org/isula-build"
|
||
|
|
+ pb "isula.org/isula-build/api/services"
|
||
|
|
+ "isula.org/isula-build/image"
|
||
|
|
+ "isula.org/isula-build/util"
|
||
|
|
+)
|
||
|
|
+
|
||
|
|
+type loadImageTmpDir struct {
|
||
|
|
+ app string
|
||
|
|
+ base string
|
||
|
|
+ lib string
|
||
|
|
+ root string
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// Loader the main instance for loading separated images
|
||
|
|
+type Loader struct {
|
||
|
|
+ log *logrus.Entry
|
||
|
|
+ tmpDir loadImageTmpDir
|
||
|
|
+ info tarballInfo
|
||
|
|
+ appName string
|
||
|
|
+ basePath string
|
||
|
|
+ appPath string
|
||
|
|
+ libPath string
|
||
|
|
+ dir string
|
||
|
|
+ skipCheck bool
|
||
|
|
+ enabled bool
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// GetSepLoadOptions returns Loader instance from LoadRequest
|
||
|
|
+func GetSepLoadOptions(req *pb.LoadRequest, logEntry *logrus.Entry, dataRoot string) (Loader, error) {
|
||
|
|
+ var tmpRoot = filepath.Join(dataRoot, filepath.Join(constant.DataRootTmpDirPrefix, req.GetLoadID()))
|
||
|
|
+ var sep = Loader{
|
||
|
|
+ appName: req.GetSep().GetApp(),
|
||
|
|
+ basePath: req.GetSep().GetBase(),
|
||
|
|
+ libPath: req.GetSep().GetLib(),
|
||
|
|
+ dir: req.GetSep().GetDir(),
|
||
|
|
+ log: logEntry,
|
||
|
|
+ tmpDir: loadImageTmpDir{
|
||
|
|
+ root: tmpRoot,
|
||
|
|
+ base: filepath.Join(tmpRoot, tmpBaseDirName),
|
||
|
|
+ lib: filepath.Join(tmpRoot, tmpLibDirName),
|
||
|
|
+ app: filepath.Join(tmpRoot, tmpAppDirName),
|
||
|
|
+ },
|
||
|
|
+ skipCheck: req.GetSep().GetSkipCheck(),
|
||
|
|
+ enabled: req.GetSep().GetEnabled(),
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ // check image name and add "latest" tag if not present
|
||
|
|
+ _, appImgName, err := image.GetNamedTaggedReference(sep.appName)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return Loader{}, err
|
||
|
|
+ }
|
||
|
|
+ if len(appImgName) == 0 {
|
||
|
|
+ return Loader{}, errors.New("app image name should not be empty")
|
||
|
|
+ }
|
||
|
|
+ sep.appName = appImgName
|
||
|
|
+ return sep, nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// LoadSeparatedImage the main method of Loader, tries to load the separated images, and returns the path of the
|
||
|
|
+// reconstructed image tarball for later handling
|
||
|
|
+func (l *Loader) LoadSeparatedImage() (string, error) {
|
||
|
|
+ l.log.Infof("Starting load separated image %s", l.appName)
|
||
|
|
+
|
||
|
|
+ // load manifest file to get tarball info
|
||
|
|
+ if err := l.getTarballInfo(); err != nil {
|
||
|
|
+ return "", errors.Wrap(err, "failed to get tarball info")
|
||
|
|
+ }
|
||
|
|
+ if err := l.constructLayerPath(); err != nil {
|
||
|
|
+ return "", err
|
||
|
|
+ }
|
||
|
|
+ // checksum for image tarballs
|
||
|
|
+ if err := l.tarballCheckSum(); err != nil {
|
||
|
|
+ return "", err
|
||
|
|
+ }
|
||
|
|
+ // process image tarballs and get final constructed image tarball
|
||
|
|
+ return l.processTarballs()
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (l *Loader) getTarballInfo() error {
|
||
|
|
+ manifest, err := filepath_securejoin.SecureJoin(l.dir, manifestFile)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return errors.Wrap(err, "join manifest file path failed")
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ var t = make(map[string]tarballInfo, 1)
|
||
|
|
+ if err = util.LoadJSONFile(manifest, &t); err != nil {
|
||
|
|
+ return errors.Wrap(err, "load manifest file failed")
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ tarball, ok := t[l.appName]
|
||
|
|
+ if !ok {
|
||
|
|
+ return errors.Errorf("failed to find app image %s", l.appName)
|
||
|
|
+ }
|
||
|
|
+ if len(tarball.AppTarName) == 0 {
|
||
|
|
+ return errors.Errorf("app image %s tarball can not be empty", tarball.AppTarName)
|
||
|
|
+ }
|
||
|
|
+ if len(tarball.BaseTarName) == 0 {
|
||
|
|
+ return errors.Errorf("base image %s tarball can not be empty", tarball.BaseImageName)
|
||
|
|
+ }
|
||
|
|
+ l.info = tarball
|
||
|
|
+
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (l *Loader) joinPath(path, tarName, str string, canBeEmpty bool) (string, error) {
|
||
|
|
+ if len(path) != 0 {
|
||
|
|
+ return path, nil
|
||
|
|
+ }
|
||
|
|
+ l.log.Infof("%s image path is empty, use path from manifest", str)
|
||
|
|
+ return filepath_securejoin.SecureJoin(l.dir, tarName)
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (l *Loader) constructLayerPath() error {
|
||
|
|
+ l.log.Infof("Construct image layer pathes for %s\n", l.appName)
|
||
|
|
+
|
||
|
|
+ var err error
|
||
|
|
+ if l.basePath, err = l.joinPath(l.basePath, l.info.BaseTarName, "Base", false); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ if l.libPath, err = l.joinPath(l.libPath, l.info.LibTarName, "Lib", true); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ if l.appPath, err = l.joinPath(l.appPath, l.info.AppTarName, "App", false); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (l *Loader) tarballCheckSum() error {
|
||
|
|
+ if l.skipCheck {
|
||
|
|
+ l.log.Info("Skip checksum for tarballs")
|
||
|
|
+ return nil
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ type checkInfo struct {
|
||
|
|
+ path string
|
||
|
|
+ hash string
|
||
|
|
+ str string
|
||
|
|
+ canBeEmpty bool
|
||
|
|
+ }
|
||
|
|
+ checkLen := 3
|
||
|
|
+ var checkList = make([]checkInfo, 0, checkLen)
|
||
|
|
+ checkList = append(checkList, checkInfo{path: l.basePath, hash: l.info.BaseHash, canBeEmpty: false, str: "base"})
|
||
|
|
+ checkList = append(checkList, checkInfo{path: l.libPath, hash: l.info.LibHash, canBeEmpty: true, str: "lib"})
|
||
|
|
+ checkList = append(checkList, checkInfo{path: l.appPath, hash: l.info.AppHash, canBeEmpty: false, str: "app"})
|
||
|
|
+ for _, p := range checkList {
|
||
|
|
+ if len(p.path) == 0 && !p.canBeEmpty {
|
||
|
|
+ return errors.Errorf("%s image tarball path can not be empty", p.str)
|
||
|
|
+ }
|
||
|
|
+ if len(p.path) != 0 {
|
||
|
|
+ if err := util.CheckSum(p.path, p.hash); err != nil {
|
||
|
|
+ return errors.Wrapf(err, "check sum for file %q failed", p.path)
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (l *Loader) processTarballs() (string, error) {
|
||
|
|
+ if err := l.unpackTarballs(); err != nil {
|
||
|
|
+ return "", err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if err := l.reconstructImage(); err != nil {
|
||
|
|
+ return "", err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ // pack app image to tarball
|
||
|
|
+ tarPath := filepath.Join(l.tmpDir.root, unionCompressedTarName)
|
||
|
|
+ if err := util.PackFiles(l.tmpDir.base, tarPath, archive.Gzip, true); err != nil {
|
||
|
|
+ return "", err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return tarPath, nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (l *Loader) unpackTarballs() error {
|
||
|
|
+ if err := l.makeTempDir(); err != nil {
|
||
|
|
+ return errors.Wrap(err, "failed to make temporary directories")
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ type unpackInfo struct{ path, dir, str string }
|
||
|
|
+ unpackLen := 3
|
||
|
|
+ var unpackList = make([]unpackInfo, 0, unpackLen)
|
||
|
|
+ unpackList = append(unpackList, unpackInfo{path: l.basePath, dir: l.tmpDir.base, str: "base"})
|
||
|
|
+ unpackList = append(unpackList, unpackInfo{path: l.libPath, dir: l.tmpDir.lib, str: "lib"})
|
||
|
|
+ unpackList = append(unpackList, unpackInfo{path: l.appPath, dir: l.tmpDir.app, str: "app"})
|
||
|
|
+
|
||
|
|
+ for _, p := range unpackList {
|
||
|
|
+ if len(p.path) != 0 {
|
||
|
|
+ if err := util.UnpackFile(p.path, p.dir, archive.Gzip, false); err != nil {
|
||
|
|
+ return errors.Wrapf(err, "unpack %s image tarball %q failed", p.str, p.path)
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (l *Loader) reconstructImage() error {
|
||
|
|
+ files, err := ioutil.ReadDir(l.tmpDir.app)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ for _, f := range files {
|
||
|
|
+ src := filepath.Join(l.tmpDir.app, f.Name())
|
||
|
|
+ dest := filepath.Join(l.tmpDir.base, f.Name())
|
||
|
|
+ if err := os.Rename(src, dest); err != nil {
|
||
|
|
+ return errors.Wrapf(err, "reconstruct app file %q failed", l.info.AppTarName)
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if len(l.libPath) != 0 {
|
||
|
|
+ files, err := ioutil.ReadDir(l.tmpDir.lib)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ for _, f := range files {
|
||
|
|
+ src := filepath.Join(l.tmpDir.lib, f.Name())
|
||
|
|
+ dest := filepath.Join(l.tmpDir.base, f.Name())
|
||
|
|
+ if err := os.Rename(src, dest); err != nil {
|
||
|
|
+ return errors.Wrapf(err, "reconstruct lib file %q failed", l.info.LibTarName)
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (l *Loader) makeTempDir() error {
|
||
|
|
+ dirs := []string{l.tmpDir.root, l.tmpDir.app, l.tmpDir.base, l.tmpDir.lib}
|
||
|
|
+ for _, dir := range dirs {
|
||
|
|
+ if err := os.MkdirAll(dir, constant.DefaultRootDirMode); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// AppName returns the AppName of Loader
|
||
|
|
+func (l *Loader) AppName() string {
|
||
|
|
+ return l.appName
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// TmpDirRoot returns the tmpDir.root of Loader
|
||
|
|
+func (l *Loader) TmpDirRoot() string {
|
||
|
|
+ return l.tmpDir.root
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// Enabled returns whether separated-image feature is enabled
|
||
|
|
+func (l *Loader) Enabled() bool {
|
||
|
|
+ return l.enabled
|
||
|
|
+}
|
||
|
|
diff --git a/daemon/separator/save.go b/daemon/separator/save.go
|
||
|
|
new file mode 100644
|
||
|
|
index 0000000..a455335
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/daemon/separator/save.go
|
||
|
|
@@ -0,0 +1,407 @@
|
||
|
|
+// Copyright (c) Huawei Technologies Co., Ltd. 2020. All rights reserved.
|
||
|
|
+// isula-build licensed under the Mulan PSL v2.
|
||
|
|
+// You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||
|
|
+// You may obtain a copy of Mulan PSL v2 at:
|
||
|
|
+// http://license.coscl.org.cn/MulanPSL2
|
||
|
|
+// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
|
||
|
|
+// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
|
||
|
|
+// PURPOSE.
|
||
|
|
+// See the Mulan PSL v2 for more details.
|
||
|
|
+// Author: Xiang Li
|
||
|
|
+// Create: 2021-12-09
|
||
|
|
+// Description: This file is handling "save" part for image separator at server side
|
||
|
|
+
|
||
|
|
+package separator
|
||
|
|
+
|
||
|
|
+import (
|
||
|
|
+ "encoding/json"
|
||
|
|
+ "io/ioutil"
|
||
|
|
+ "os"
|
||
|
|
+ "path/filepath"
|
||
|
|
+ "sort"
|
||
|
|
+ "strings"
|
||
|
|
+
|
||
|
|
+ "github.com/containers/storage/pkg/archive"
|
||
|
|
+ "github.com/docker/docker/pkg/ioutils"
|
||
|
|
+ "github.com/pkg/errors"
|
||
|
|
+ "github.com/sirupsen/logrus"
|
||
|
|
+
|
||
|
|
+ constant "isula.org/isula-build"
|
||
|
|
+ pb "isula.org/isula-build/api/services"
|
||
|
|
+ "isula.org/isula-build/image"
|
||
|
|
+ "isula.org/isula-build/store"
|
||
|
|
+ "isula.org/isula-build/util"
|
||
|
|
+)
|
||
|
|
+
|
||
|
|
+// Saver the main instance for saving separated images
|
||
|
|
+type Saver struct {
|
||
|
|
+ log *logrus.Entry
|
||
|
|
+ renameData []renames
|
||
|
|
+ tmpDir imageTmpDir
|
||
|
|
+ base string
|
||
|
|
+ lib string
|
||
|
|
+ dest string
|
||
|
|
+ renameFile string
|
||
|
|
+ enabled bool
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+type renames struct {
|
||
|
|
+ OriName string `json:"name"`
|
||
|
|
+ NewName string `json:"rename"`
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+type imageTmpDir struct {
|
||
|
|
+ app string
|
||
|
|
+ base string
|
||
|
|
+ lib string
|
||
|
|
+ untar string
|
||
|
|
+ root string
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+type imageLayersMap map[string]string
|
||
|
|
+
|
||
|
|
+// GetSepSaveOptions returns Save instance from SaveRequest
|
||
|
|
+func GetSepSaveOptions(req *pb.SaveRequest, logEntry *logrus.Entry, dataRoot string) (Saver, string) {
|
||
|
|
+ var tmpRoot = filepath.Join(dataRoot, filepath.Join(constant.DataRootTmpDirPrefix, req.GetSaveID()))
|
||
|
|
+ var sep = Saver{
|
||
|
|
+ base: req.GetSep().GetBase(),
|
||
|
|
+ lib: req.GetSep().GetLib(),
|
||
|
|
+ dest: req.GetSep().GetDest(),
|
||
|
|
+ log: logEntry,
|
||
|
|
+ enabled: req.GetSep().GetEnabled(),
|
||
|
|
+ renameFile: req.GetSep().GetRename(),
|
||
|
|
+
|
||
|
|
+ tmpDir: imageTmpDir{
|
||
|
|
+ root: tmpRoot,
|
||
|
|
+ untar: filepath.Join(tmpRoot, untarTempDirName),
|
||
|
|
+ base: filepath.Join(tmpRoot, baseUntarTempDirName),
|
||
|
|
+ lib: filepath.Join(tmpRoot, libUntarTempDirName),
|
||
|
|
+ app: filepath.Join(tmpRoot, appUntarTempDirName),
|
||
|
|
+ },
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return sep, filepath.Join(sep.tmpDir.untar, unionTarName)
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// SeparateImage the main method of Saver, tries to separated the listed images to pieces
|
||
|
|
+func (s *Saver) SeparateImage(localStore *store.Store, oriImgList []string, outputPath string) (err error) {
|
||
|
|
+ s.log.Infof("Start saving separated images %v", oriImgList)
|
||
|
|
+
|
||
|
|
+ if err = os.MkdirAll(s.dest, constant.DefaultRootDirMode); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ defer func() {
|
||
|
|
+ if tErr := os.RemoveAll(s.tmpDir.root); tErr != nil && !os.IsNotExist(tErr) {
|
||
|
|
+ s.log.Warnf("Removing save tmp directory %q failed: %v", s.tmpDir.root, tErr)
|
||
|
|
+ }
|
||
|
|
+ if err != nil {
|
||
|
|
+ if rErr := os.RemoveAll(s.dest); rErr != nil && !os.IsNotExist(rErr) {
|
||
|
|
+ s.log.Warnf("Removing save dest directory %q failed: %v", s.dest, rErr)
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ }()
|
||
|
|
+ if err = util.UnpackFile(outputPath, s.tmpDir.untar, archive.Gzip, true); err != nil {
|
||
|
|
+ return errors.Wrapf(err, "unpack %q failed", outputPath)
|
||
|
|
+ }
|
||
|
|
+ manifest, aErr := s.adjustLayers()
|
||
|
|
+ if aErr != nil {
|
||
|
|
+ return errors.Wrap(aErr, "adjust layers failed")
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ imgInfos, cErr := s.constructImageInfos(manifest, localStore)
|
||
|
|
+ if cErr != nil {
|
||
|
|
+ return errors.Wrap(cErr, "process image infos failed")
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if err = s.processImageLayers(imgInfos); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (s *Saver) getLayerHashFromStorage(store *store.Store, name string) ([]string, error) {
|
||
|
|
+ if len(name) == 0 {
|
||
|
|
+ return nil, nil
|
||
|
|
+ }
|
||
|
|
+ _, img, err := image.FindImage(store, name)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ layer, err := store.Layer(img.TopLayer)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, errors.Wrapf(err, "failed to get top layer for image %s", name)
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ var layers []string
|
||
|
|
+ // add each layer in the layers until reach the root layer
|
||
|
|
+ for layer != nil {
|
||
|
|
+ fields := strings.Split(layer.UncompressedDigest.String(), ":")
|
||
|
|
+ if len(fields) != 2 {
|
||
|
|
+ return nil, errors.Errorf("error format of layer of image %s", name)
|
||
|
|
+ }
|
||
|
|
+ layers = append(layers, fields[1])
|
||
|
|
+ if layer.Parent == "" {
|
||
|
|
+ break
|
||
|
|
+ }
|
||
|
|
+ layer, err = store.Layer(layer.Parent)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, errors.Wrapf(err, "unable to read layer %q", layer.Parent)
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return layers, nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// process physic file
|
||
|
|
+func (s *Saver) constructLayerMap() (map[string]string, error) {
|
||
|
|
+ path := s.tmpDir.untar
|
||
|
|
+ files, rErr := ioutil.ReadDir(path)
|
||
|
|
+ if rErr != nil {
|
||
|
|
+ return nil, rErr
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ var layerMap = make(map[string]string, len(files))
|
||
|
|
+ // process layer's file
|
||
|
|
+ for _, file := range files {
|
||
|
|
+ if file.IsDir() {
|
||
|
|
+ layerFile := filepath.Join(path, file.Name(), layerTarName)
|
||
|
|
+ oriFile, err := os.Readlink(layerFile)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+ physicFile := filepath.Join(path, file.Name(), oriFile)
|
||
|
|
+ layerMap[filepath.Base(physicFile)] = filepath.Join(file.Name(), layerTarName)
|
||
|
|
+ if err := os.Rename(physicFile, layerFile); err != nil {
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return layerMap, nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (s *Saver) adjustLayers() ([]imageManifest, error) {
|
||
|
|
+ s.log.Info("Adjusting layers for saving separated image")
|
||
|
|
+
|
||
|
|
+ layerMap, err := s.constructLayerMap()
|
||
|
|
+ if err != nil {
|
||
|
|
+ s.log.Errorf("Process layers failed: %v", err)
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ // process manifest file
|
||
|
|
+ var man []imageManifest
|
||
|
|
+ if lErr := util.LoadJSONFile(filepath.Join(s.tmpDir.untar, manifestDataFile), &man); lErr != nil {
|
||
|
|
+ return nil, lErr
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ for i, img := range man {
|
||
|
|
+ layers := make([]string, len(img.Layers))
|
||
|
|
+ for i, layer := range img.Layers {
|
||
|
|
+ layers[i] = layerMap[layer]
|
||
|
|
+ }
|
||
|
|
+ man[i].Layers = layers
|
||
|
|
+ man[i].HashMap = getLayerHashFromTar(layerMap, layers)
|
||
|
|
+ }
|
||
|
|
+ buf, err := json.Marshal(&man)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+ if err := ioutils.AtomicWriteFile(manifestFile, buf, constant.DefaultSharedFileMode); err != nil {
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return man, nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (s *Saver) processImageLayers(imgInfos map[string]imageInfo) error {
|
||
|
|
+ s.log.Info("Processing image layers")
|
||
|
|
+ var (
|
||
|
|
+ tarballs = make(map[string]tarballInfo)
|
||
|
|
+ baseImagesMap = make(imageLayersMap, 1)
|
||
|
|
+ libImagesMap = make(imageLayersMap, 1)
|
||
|
|
+ appImagesMap = make(imageLayersMap, 1)
|
||
|
|
+ )
|
||
|
|
+ var sortedKey []string
|
||
|
|
+ for k := range imgInfos {
|
||
|
|
+ sortedKey = append(sortedKey, k)
|
||
|
|
+ }
|
||
|
|
+ sort.Strings(sortedKey)
|
||
|
|
+ for _, k := range sortedKey {
|
||
|
|
+ info := imgInfos[k]
|
||
|
|
+ if err := s.clearTempDirs(); err != nil {
|
||
|
|
+ return errors.Wrap(err, "clear tmp dirs failed")
|
||
|
|
+ }
|
||
|
|
+ var t tarballInfo
|
||
|
|
+ // process base
|
||
|
|
+ if err := info.processBaseImg(s, baseImagesMap, &t); err != nil {
|
||
|
|
+ return errors.Wrapf(err, "process base images %s failed", info.nameTag)
|
||
|
|
+ }
|
||
|
|
+ // process lib
|
||
|
|
+ if err := info.processLibImg(s, libImagesMap, &t); err != nil {
|
||
|
|
+ return errors.Wrapf(err, "process lib images %s failed", info.nameTag)
|
||
|
|
+ }
|
||
|
|
+ // process app
|
||
|
|
+ if err := info.processAppImg(s, appImagesMap, &t); err != nil {
|
||
|
|
+ return errors.Wrapf(err, "process app images %s failed", info.nameTag)
|
||
|
|
+ }
|
||
|
|
+ tarballs[info.nameTag] = t
|
||
|
|
+ }
|
||
|
|
+ buf, err := json.Marshal(&tarballs)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ // manifest file
|
||
|
|
+ manifestFile := filepath.Join(s.dest, manifestFile)
|
||
|
|
+ if err := ioutils.AtomicWriteFile(manifestFile, buf, constant.DefaultRootFileMode); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ s.log.Info("Save separated image succeed")
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (s *Saver) clearTempDirs() error {
|
||
|
|
+ dirs := []string{s.tmpDir.base, s.tmpDir.app, s.tmpDir.lib}
|
||
|
|
+ for _, dir := range dirs {
|
||
|
|
+ if err := os.RemoveAll(dir); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ if err := os.MkdirAll(dir, constant.DefaultRootDirMode); err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// ImageNames returns the images names of Saver
|
||
|
|
+func (s *Saver) ImageNames() []string {
|
||
|
|
+ var names = make([]string, 0, 2)
|
||
|
|
+ if !s.enabled {
|
||
|
|
+ return []string{}
|
||
|
|
+ }
|
||
|
|
+ if len(s.base) != 0 {
|
||
|
|
+ names = append(names, s.base)
|
||
|
|
+ }
|
||
|
|
+ if len(s.lib) != 0 {
|
||
|
|
+ names = append(names, s.lib)
|
||
|
|
+ }
|
||
|
|
+ return names
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (s *Saver) constructSingleImgInfo(mani imageManifest, store *store.Store) (imageInfo, error) {
|
||
|
|
+ var libLayers, appLayers []string
|
||
|
|
+ // image name should not be empty here
|
||
|
|
+ if len(mani.RepoTags) == 0 {
|
||
|
|
+ return imageInfo{}, errors.New("image name and tag is empty")
|
||
|
|
+ }
|
||
|
|
+ // if there is more than one repoTag, will use first one as image name
|
||
|
|
+ imageRepoFields := strings.Split(mani.RepoTags[0], ":")
|
||
|
|
+ imageLayers := getLayersID(mani.Layers)
|
||
|
|
+
|
||
|
|
+ libs, bases, err := s.checkLayersHash(mani.HashMap, store)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return imageInfo{}, errors.Wrap(err, "compare layers failed")
|
||
|
|
+ }
|
||
|
|
+ baseLayers := imageLayers[0:len(bases)]
|
||
|
|
+ if len(libs) != 0 {
|
||
|
|
+ libLayers = imageLayers[len(bases):len(libs)]
|
||
|
|
+ appLayers = imageLayers[len(libs):]
|
||
|
|
+ } else {
|
||
|
|
+ libLayers = nil
|
||
|
|
+ appLayers = imageLayers[len(bases):]
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return imageInfo{
|
||
|
|
+ config: mani.Config,
|
||
|
|
+ repoTags: mani.RepoTags,
|
||
|
|
+ nameTag: mani.RepoTags[0],
|
||
|
|
+ name: strings.Join(imageRepoFields[0:len(imageRepoFields)-1], ":"),
|
||
|
|
+ tag: imageRepoFields[len(imageRepoFields)-1],
|
||
|
|
+ layers: layer{app: appLayers, lib: libLayers, base: baseLayers, all: mani.Layers},
|
||
|
|
+ topLayer: imageLayers[len(imageLayers)-1],
|
||
|
|
+ }, nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (s *Saver) checkLayersHash(layerHashMap map[string]string, store *store.Store) ([]string, []string, error) {
|
||
|
|
+ libHash, err := s.getLayerHashFromStorage(store, s.lib)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, nil, errors.Wrapf(err, "get lib image %s layers failed", s.lib)
|
||
|
|
+ }
|
||
|
|
+ baseHash, err := s.getLayerHashFromStorage(store, s.base)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, nil, errors.Wrapf(err, "get base image %s layers failed", s.base)
|
||
|
|
+ }
|
||
|
|
+ if len(baseHash) > 1 {
|
||
|
|
+ return nil, nil, errors.Errorf("number of base layers %d more than one", len(baseHash))
|
||
|
|
+ }
|
||
|
|
+ if len(libHash) >= len(layerHashMap) || len(baseHash) >= len(layerHashMap) {
|
||
|
|
+ return nil, nil, errors.Errorf("number of base or lib layers is equal or greater than saved app layers")
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ for _, l := range libHash {
|
||
|
|
+ if _, ok := layerHashMap[l]; !ok {
|
||
|
|
+ return nil, nil, errors.Errorf("dismatch checksum for lib image %s", s.lib)
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ for _, b := range baseHash {
|
||
|
|
+ if _, ok := layerHashMap[b]; !ok {
|
||
|
|
+ return nil, nil, errors.Errorf("dismatch checksum for base image %s", s.base)
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return libHash, baseHash, nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (s *Saver) constructImageInfos(manifest []imageManifest, store *store.Store) (map[string]imageInfo, error) {
|
||
|
|
+ s.log.Info("Constructing image info")
|
||
|
|
+
|
||
|
|
+ var imgInfos = make(map[string]imageInfo, 1)
|
||
|
|
+ for _, mani := range manifest {
|
||
|
|
+ imgInfo, err := s.constructSingleImgInfo(mani, store)
|
||
|
|
+ if err != nil {
|
||
|
|
+ s.log.Errorf("Constructing image info failed: %v", err)
|
||
|
|
+ return nil, errors.Wrap(err, "construct image info failed")
|
||
|
|
+ }
|
||
|
|
+ if _, ok := imgInfos[imgInfo.nameTag]; !ok {
|
||
|
|
+ imgInfos[imgInfo.nameTag] = imgInfo
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ return imgInfos, nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// LoadRenameFile Saver tries to load the specified rename json
|
||
|
|
+func (s *Saver) LoadRenameFile() error {
|
||
|
|
+ if len(s.renameFile) == 0 {
|
||
|
|
+ return nil
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ var reName []renames
|
||
|
|
+ if err := util.LoadJSONFile(s.renameFile, &reName); err != nil {
|
||
|
|
+ return errors.Wrap(err, "check rename file failed")
|
||
|
|
+ }
|
||
|
|
+ s.renameData = reName
|
||
|
|
+ return nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func (s *Saver) getRename(name string) string {
|
||
|
|
+ if len(s.renameData) == 0 {
|
||
|
|
+ return name
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ for _, item := range s.renameData {
|
||
|
|
+ if item.OriName == name {
|
||
|
|
+ s.log.Infof("Renaming image tarballs for %s to %s\n", name, item.NewName)
|
||
|
|
+ return item.NewName
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ return name
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// Enabled returns whether separated-image feature is enabled
|
||
|
|
+func (s *Saver) Enabled() bool {
|
||
|
|
+ return s.enabled
|
||
|
|
+}
|
||
|
|
diff --git a/daemon/separator/utils.go b/daemon/separator/utils.go
|
||
|
|
new file mode 100644
|
||
|
|
index 0000000..fb05f58
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/daemon/separator/utils.go
|
||
|
|
@@ -0,0 +1,78 @@
|
||
|
|
+// Copyright (c) Huawei Technologies Co., Ltd. 2020. All rights reserved.
|
||
|
|
+// isula-build licensed under the Mulan PSL v2.
|
||
|
|
+// You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||
|
|
+// You may obtain a copy of Mulan PSL v2 at:
|
||
|
|
+// http://license.coscl.org.cn/MulanPSL2
|
||
|
|
+// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
|
||
|
|
+// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
|
||
|
|
+// PURPOSE.
|
||
|
|
+// See the Mulan PSL v2 for more details.
|
||
|
|
+// Author: Xiang Li
|
||
|
|
+// Create: 2021-12-09
|
||
|
|
+// Description: This file is utils for image separator
|
||
|
|
+
|
||
|
|
+package separator
|
||
|
|
+
|
||
|
|
+import "strings"
|
||
|
|
+
|
||
|
|
+const (
|
||
|
|
+ manifestDataFile = "manifest.json"
|
||
|
|
+ manifestFile = "manifest"
|
||
|
|
+ repositoriesFile = "repositories"
|
||
|
|
+
|
||
|
|
+ tmpBaseDirName = "base"
|
||
|
|
+ tmpAppDirName = "app"
|
||
|
|
+ tmpLibDirName = "lib"
|
||
|
|
+ baseUntarTempDirName = "base_images"
|
||
|
|
+ appUntarTempDirName = "app_images"
|
||
|
|
+ libUntarTempDirName = "lib_images"
|
||
|
|
+ baseTarNameSuffix = "_base_image.tar.gz"
|
||
|
|
+ appTarNameSuffix = "_app_image.tar.gz"
|
||
|
|
+ libTarNameSuffix = "_lib_image.tar.gz"
|
||
|
|
+
|
||
|
|
+ unionTarName = "all.tar"
|
||
|
|
+ unionCompressedTarName = "all.tar.gz"
|
||
|
|
+
|
||
|
|
+ untarTempDirName = "untar"
|
||
|
|
+ layerTarName = "layer.tar"
|
||
|
|
+ tarSuffix = ".tar"
|
||
|
|
+)
|
||
|
|
+
|
||
|
|
+type tarballInfo struct {
|
||
|
|
+ AppTarName string `json:"app"`
|
||
|
|
+ AppHash string `json:"appHash"`
|
||
|
|
+ AppLayers []string `json:"appLayers"`
|
||
|
|
+ LibTarName string `json:"lib"`
|
||
|
|
+ LibHash string `json:"libHash"`
|
||
|
|
+ LibImageName string `json:"libImageName"`
|
||
|
|
+ LibLayers []string `json:"libLayers"`
|
||
|
|
+ BaseTarName string `json:"base"`
|
||
|
|
+ BaseHash string `json:"baseHash"`
|
||
|
|
+ BaseImageName string `json:"baseImageName"`
|
||
|
|
+ BaseLayer string `json:"baseLayer"`
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func getLayerHashFromTar(layerMap map[string]string, layer []string) map[string]string {
|
||
|
|
+ hashMap := make(map[string]string, len(layer))
|
||
|
|
+ // first reverse map since it's <k-v> is unique
|
||
|
|
+ revMap := make(map[string]string, len(layerMap))
|
||
|
|
+ for k, v := range layerMap {
|
||
|
|
+ revMap[v] = k
|
||
|
|
+ }
|
||
|
|
+ for _, l := range layer {
|
||
|
|
+ if v, ok := revMap[l]; ok {
|
||
|
|
+ // format is like xxx(hash): xxx/layer.tar
|
||
|
|
+ hashMap[strings.TrimSuffix(v, tarSuffix)] = l
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return hashMap
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+func getLayersID(layer []string) []string {
|
||
|
|
+ var after = make([]string, len(layer))
|
||
|
|
+ for i, v := range layer {
|
||
|
|
+ after[i] = strings.Split(v, "/")[0]
|
||
|
|
+ }
|
||
|
|
+ return after
|
||
|
|
+}
|
||
|
|
--
|
||
|
|
2.31.0.windows.1
|
||
|
|
|