reason: 1. support save/load separated image
2. add relative test cases and bugfixes
Signed-off-by: DCCooper <1866858@gmail.com>
249 lines
8.1 KiB
Diff
249 lines
8.1 KiB
Diff
From 8bb2cb6f3904f13d0010cc207e9b00bafe043805 Mon Sep 17 00:00:00 2001
|
|
From: DCCooper <1866858@gmail.com>
|
|
Date: Tue, 26 Oct 2021 14:19:10 +0800
|
|
Subject: [PATCH 02/16] cli:finish client save separated image
|
|
|
|
reason: support isula-build client side process info for save separated image
|
|
ABI change:(client)
|
|
- --dest: destination file direcotry to store seprated image
|
|
- --base: base image name of separated images
|
|
- --lib: lib image name of separated images
|
|
- --rename: rename json file path of separated images
|
|
|
|
Signed-off-by: DCCooper <1866858@gmail.com>
|
|
---
|
|
cmd/cli/save.go | 121 ++++++++++++++++++++++++++++++++++++++----------
|
|
util/common.go | 24 ++++++++++
|
|
2 files changed, 121 insertions(+), 24 deletions(-)
|
|
|
|
diff --git a/cmd/cli/save.go b/cmd/cli/save.go
|
|
index cb78ecfb..4d22798a 100644
|
|
--- a/cmd/cli/save.go
|
|
+++ b/cmd/cli/save.go
|
|
@@ -29,8 +29,17 @@ import (
|
|
"isula.org/isula-build/util"
|
|
)
|
|
|
|
+type separatorSaveOption struct {
|
|
+ baseImgName string
|
|
+ libImageName string
|
|
+ renameFile string
|
|
+ destPath string
|
|
+ enabled bool
|
|
+}
|
|
+
|
|
type saveOptions struct {
|
|
images []string
|
|
+ sep separatorSaveOption
|
|
path string
|
|
saveID string
|
|
format string
|
|
@@ -41,7 +50,9 @@ var saveOpts saveOptions
|
|
const (
|
|
saveExample = `isula-build ctr-img save busybox:latest -o busybox.tar
|
|
isula-build ctr-img save 21c3e96ac411 -o myimage.tar
|
|
-isula-build ctr-img save busybox:latest alpine:3.9 -o all.tar`
|
|
+isula-build ctr-img save busybox:latest alpine:3.9 -o all.tar
|
|
+isula-build ctr-img save app:latest app1:latest -d Images
|
|
+isula-build ctr-img save app:latest app1:latest -d Images -b busybox:latest -l lib:latest -r rename.json`
|
|
)
|
|
|
|
// NewSaveCmd cmd for container image saving
|
|
@@ -54,6 +65,10 @@ func NewSaveCmd() *cobra.Command {
|
|
}
|
|
|
|
saveCmd.PersistentFlags().StringVarP(&saveOpts.path, "output", "o", "", "Path to save the tarball")
|
|
+ saveCmd.PersistentFlags().StringVarP(&saveOpts.sep.destPath, "dest", "d", "Images", "Destination file directory to store separated images")
|
|
+ saveCmd.PersistentFlags().StringVarP(&saveOpts.sep.baseImgName, "base", "b", "", "Base image name of separated images")
|
|
+ saveCmd.PersistentFlags().StringVarP(&saveOpts.sep.libImageName, "lib", "l", "", "Lib image name of separated images")
|
|
+ saveCmd.PersistentFlags().StringVarP(&saveOpts.sep.renameFile, "rename", "r", "", "Rename json file path of separated images")
|
|
if util.CheckCliExperimentalEnabled() {
|
|
saveCmd.PersistentFlags().StringVarP(&saveOpts.format, "format", "f", "oci", "Format of image saving to local tarball")
|
|
} else {
|
|
@@ -67,16 +82,7 @@ func saveCommand(cmd *cobra.Command, args []string) error {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
- if len(args) == 0 {
|
|
- return errors.New("save accepts at least one image")
|
|
- }
|
|
- if saveOpts.format == constant.OCITransport && len(args) >= 2 {
|
|
- return errors.New("oci image format now only supports saving single image")
|
|
- }
|
|
- if err := util.CheckImageFormat(saveOpts.format); err != nil {
|
|
- return err
|
|
- }
|
|
- if err := checkSavePath(); err != nil {
|
|
+ if err := saveOpts.checkSaveOpts(args); err != nil {
|
|
return err
|
|
}
|
|
|
|
@@ -88,25 +94,79 @@ func saveCommand(cmd *cobra.Command, args []string) error {
|
|
return runSave(ctx, cli, args)
|
|
}
|
|
|
|
-func checkSavePath() error {
|
|
- if len(saveOpts.path) == 0 {
|
|
- return errors.New("output path should not be empty")
|
|
+func (sep *separatorSaveOption) check(pwd string) error {
|
|
+ if len(sep.baseImgName) != 0 {
|
|
+ if !util.IsValidImageName(sep.baseImgName) {
|
|
+ return errors.Errorf("invalid base image name %s", sep.baseImgName)
|
|
+ }
|
|
}
|
|
+ if len(sep.libImageName) != 0 {
|
|
+ if !util.IsValidImageName(sep.libImageName) {
|
|
+ return errors.Errorf("invalid lib image name %s", sep.libImageName)
|
|
+ }
|
|
+ }
|
|
+ if len(sep.destPath) == 0 {
|
|
+ sep.destPath = "Images"
|
|
+ }
|
|
+ if !filepath.IsAbs(sep.destPath) {
|
|
+ sep.destPath = util.MakeAbsolute(sep.destPath, pwd)
|
|
+ }
|
|
+ if util.IsExist(sep.destPath) {
|
|
+ return errors.Errorf("output file already exist: %q, try to remove existing tarball or rename output file", sep.destPath)
|
|
+ }
|
|
+ if len(sep.renameFile) != 0 {
|
|
+ if !filepath.IsAbs(sep.renameFile) {
|
|
+ sep.renameFile = util.MakeAbsolute(sep.renameFile, pwd)
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return nil
|
|
+}
|
|
|
|
- if strings.Contains(saveOpts.path, ":") {
|
|
- return errors.Errorf("colon in path %q is not supported", saveOpts.path)
|
|
+func (opt *saveOptions) checkSaveOpts(args []string) error {
|
|
+ if len(args) == 0 {
|
|
+ return errors.New("save accepts at least one image")
|
|
}
|
|
|
|
- if !filepath.IsAbs(saveOpts.path) {
|
|
- pwd, err := os.Getwd()
|
|
- if err != nil {
|
|
- return errors.New("get current path failed")
|
|
+ if strings.Contains(opt.path, ":") || strings.Contains(opt.sep.destPath, ":") {
|
|
+ return errors.Errorf("colon in path %q is not supported", opt.path)
|
|
+ }
|
|
+ pwd, err := os.Getwd()
|
|
+ if err != nil {
|
|
+ return errors.New("get current path failed")
|
|
+ }
|
|
+
|
|
+ // normal save
|
|
+ if !opt.sep.isEnabled() {
|
|
+ // only check oci format when doing normal save operation
|
|
+ if opt.format == constant.OCITransport && len(args) >= 2 {
|
|
+ return errors.New("oci image format now only supports saving single image")
|
|
+ }
|
|
+ if err := util.CheckImageFormat(opt.format); err != nil {
|
|
+ return err
|
|
+ }
|
|
+ if len(opt.path) == 0 {
|
|
+ return errors.New("output path should not be empty")
|
|
}
|
|
- saveOpts.path = util.MakeAbsolute(saveOpts.path, pwd)
|
|
+ if !filepath.IsAbs(opt.path) {
|
|
+ opt.path = util.MakeAbsolute(opt.path, pwd)
|
|
+ }
|
|
+ if util.IsExist(opt.path) {
|
|
+ return errors.Errorf("output file already exist: %q, try to remove existing tarball or rename output file", opt.path)
|
|
+ }
|
|
+ return nil
|
|
}
|
|
|
|
- if util.IsExist(saveOpts.path) {
|
|
- return errors.Errorf("output file already exist: %q, try to remove existing tarball or rename output file", saveOpts.path)
|
|
+ // separator save
|
|
+ opt.sep.enabled = true
|
|
+ if len(opt.path) != 0 {
|
|
+ return errors.New("conflict options between -o and [-b -l -r]")
|
|
+ }
|
|
+ // separate image only support docker image spec
|
|
+ opt.format = constant.DockerTransport
|
|
+
|
|
+ if err := opt.sep.check(pwd); err != nil {
|
|
+ return err
|
|
}
|
|
|
|
return nil
|
|
@@ -116,11 +176,20 @@ func runSave(ctx context.Context, cli Cli, args []string) error {
|
|
saveOpts.saveID = util.GenerateNonCryptoID()[:constant.DefaultIDLen]
|
|
saveOpts.images = args
|
|
|
|
+ sep := &pb.SeparatorSave{
|
|
+ Base: saveOpts.sep.baseImgName,
|
|
+ Lib: saveOpts.sep.libImageName,
|
|
+ Rename: saveOpts.sep.renameFile,
|
|
+ Dest: saveOpts.sep.destPath,
|
|
+ Enabled: saveOpts.sep.enabled,
|
|
+ }
|
|
+
|
|
saveStream, err := cli.Client().Save(ctx, &pb.SaveRequest{
|
|
Images: saveOpts.images,
|
|
Path: saveOpts.path,
|
|
SaveID: saveOpts.saveID,
|
|
Format: saveOpts.format,
|
|
+ Sep: sep,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
@@ -137,7 +206,11 @@ func runSave(ctx context.Context, cli Cli, args []string) error {
|
|
fmt.Printf("Save success with image: %s\n", saveOpts.images)
|
|
return nil
|
|
}
|
|
- return errors.Errorf("save image failed: %v", err)
|
|
+ return errors.Errorf("save image failed: %v", err.Error())
|
|
}
|
|
}
|
|
}
|
|
+
|
|
+func (sep *separatorSaveOption) isEnabled() bool {
|
|
+ return util.AnyFlagSet(sep.baseImgName, sep.libImageName, sep.renameFile)
|
|
+}
|
|
diff --git a/util/common.go b/util/common.go
|
|
index 00b1b941..4782b2ec 100644
|
|
--- a/util/common.go
|
|
+++ b/util/common.go
|
|
@@ -21,6 +21,7 @@ import (
|
|
"strings"
|
|
|
|
securejoin "github.com/cyphar/filepath-securejoin"
|
|
+ "github.com/docker/distribution/reference"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"golang.org/x/sys/unix"
|
|
@@ -184,3 +185,26 @@ func FormatSize(size, base float64) string {
|
|
func CheckCliExperimentalEnabled() bool {
|
|
return os.Getenv("ISULABUILD_CLI_EXPERIMENTAL") == "enabled"
|
|
}
|
|
+
|
|
+// IsValidImageName will check the validity of image name
|
|
+func IsValidImageName(name string) bool {
|
|
+ ref, err := reference.ParseNormalizedNamed(name)
|
|
+ if err != nil {
|
|
+ return false
|
|
+ }
|
|
+ if _, canonical := ref.(reference.Canonical); canonical {
|
|
+ return false
|
|
+ }
|
|
+ return true
|
|
+}
|
|
+
|
|
+// AnyFlagSet is a checker to indicate there exist flag's length not empty
|
|
+// If all flags are empty, will return false
|
|
+func AnyFlagSet(flags ...string) bool {
|
|
+ for _, flag := range flags {
|
|
+ if len(flag) != 0 {
|
|
+ return true
|
|
+ }
|
|
+ }
|
|
+ return false
|
|
+}
|
|
--
|
|
2.27.0
|
|
|