isula-build/patch/0073-cli-finish-client-save-separated-image.patch
DCCooper 3d943142b3 isula-build:support save/load separated image
reason: 1. support save/load separated image
        2. add relative test cases and bugfixes

Signed-off-by: DCCooper <1866858@gmail.com>
2021-11-02 12:40:19 +08:00

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