From 5749a92be53a3e8a135b4f7e59e8fd6d470fbd55 Mon Sep 17 00:00:00 2001 From: DCCooper <1866858@gmail.com> Date: Tue, 26 Oct 2021 14:20:07 +0800 Subject: [PATCH 05/16] cli:finish client load separated image reason: support isula-build client side process info for load separated image ABI change:(client) - --input: name of app images when load separated images - --dir: path to separated images' tarball directory - --base: base image tarball path of separated images - --lib: lib image tarball path of separated images - --no-check: skip sha256 check sum for legacy separated images loading Signed-off-by: DCCooper <1866858@gmail.com> --- cmd/cli/load.go | 113 ++++++++++++++++++++++++++++++++++++++++--- cmd/cli/load_test.go | 50 +++++++++++++++++-- cmd/cli/mock.go | 10 ++++ 3 files changed, 160 insertions(+), 13 deletions(-) diff --git a/cmd/cli/load.go b/cmd/cli/load.go index 16e90a26..2a9df772 100644 --- a/cmd/cli/load.go +++ b/cmd/cli/load.go @@ -25,18 +25,32 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + constant "isula.org/isula-build" pb "isula.org/isula-build/api/services" "isula.org/isula-build/util" ) +type separatorLoadOption struct { + app string + base string + lib string + dir string + skipCheck bool + enabled bool +} + type loadOptions struct { - path string + path string + loadID string + sep separatorLoadOption } var loadOpts loadOptions const ( - loadExample = `isula-build ctr-img load -i busybox.tar` + loadExample = `isula-build ctr-img load -i busybox.tar +isula-build ctr-img load -i app:latest -d /home/Images +isula-build ctr-img load -i app:latest -d /home/Images -b /home/Images/base.tar.gz -l /home/Images/lib.tar.gz` ) // NewLoadCmd returns image load command @@ -49,12 +63,20 @@ func NewLoadCmd() *cobra.Command { RunE: loadCommand, } - loadCmd.PersistentFlags().StringVarP(&loadOpts.path, "input", "i", "", "Path to local tarball") + loadCmd.PersistentFlags().StringVarP(&loadOpts.path, "input", "i", "", "Path to local tarball(or app image name when load separated images)") + loadCmd.PersistentFlags().StringVarP(&loadOpts.sep.dir, "dir", "d", "", "Path to separated image tarballs directory") + loadCmd.PersistentFlags().StringVarP(&loadOpts.sep.base, "base", "b", "", "Base image tarball path of separated images") + loadCmd.PersistentFlags().StringVarP(&loadOpts.sep.lib, "lib", "l", "", "Library image tarball path of separated images") + loadCmd.PersistentFlags().BoolVarP(&loadOpts.sep.skipCheck, "no-check", "", false, "Skip sha256 check sum for legacy separated images loading") return loadCmd } func loadCommand(cmd *cobra.Command, args []string) error { + if err := loadOpts.checkLoadOpts(); err != nil { + return errors.Wrapf(err, "check load options failed") + } + ctx := context.Background() cli, err := NewClient(ctx) if err != nil { @@ -65,14 +87,20 @@ func loadCommand(cmd *cobra.Command, args []string) error { } func runLoad(ctx context.Context, cli Cli) error { - var err error - - if loadOpts.path, err = resolveLoadPath(loadOpts.path); err != nil { - return err + loadOpts.loadID = util.GenerateNonCryptoID()[:constant.DefaultIDLen] + sep := &pb.SeparatorLoad{ + App: loadOpts.sep.app, + Dir: loadOpts.sep.dir, + Base: loadOpts.sep.base, + Lib: loadOpts.sep.lib, + SkipCheck: loadOpts.sep.skipCheck, + Enabled: loadOpts.sep.enabled, } resp, err := cli.Client().Load(ctx, &pb.LoadRequest{ - Path: loadOpts.path, + Path: loadOpts.path, + LoadID: loadOpts.loadID, + Sep: sep, }) if err != nil { return err @@ -114,3 +142,72 @@ func resolveLoadPath(path string) (string, error) { return path, nil } + +func (opt *loadOptions) checkLoadOpts() error { + // normal load + if !opt.sep.isEnabled() { + path, err := resolveLoadPath(opt.path) + if err != nil { + return err + } + opt.path = path + + return nil + } + + // load separated image + opt.sep.enabled = true + if len(opt.path) == 0 { + return errors.New("app image should not be empty") + } + + // Use opt.path as app image name when operating separated images + // this can be mark as a switch for handling separated images + opt.sep.app = opt.path + + if err := opt.sep.check(); err != nil { + return err + } + + return nil +} + +func (sep *separatorLoadOption) isEnabled() bool { + return util.AnyFlagSet(sep.dir, sep.base, sep.lib, sep.app) +} + +func (sep *separatorLoadOption) check() error { + pwd, err := os.Getwd() + if err != nil { + return errors.New("get current path failed") + } + if !util.IsValidImageName(sep.app) { + return errors.Errorf("invalid image name: %s", sep.app) + } + + if len(sep.base) != 0 { + path, err := resolveLoadPath(sep.base) + if err != nil { + return errors.Wrap(err, "resolve base tarball path failed") + } + sep.base = path + } + if len(sep.lib) != 0 { + path, err := resolveLoadPath(sep.lib) + if err != nil { + return errors.Wrap(err, "resolve lib tarball path failed") + } + sep.lib = path + } + if len(sep.dir) == 0 { + return errors.New("image tarball directory should not be empty") + } + if !filepath.IsAbs(sep.dir) { + sep.dir = util.MakeAbsolute(sep.dir, pwd) + } + if !util.IsExist(sep.dir) { + return errors.Errorf("image tarball directory %s is not exist", sep.dir) + } + + return nil +} diff --git a/cmd/cli/load_test.go b/cmd/cli/load_test.go index 9c753e23..b7bf2a57 100644 --- a/cmd/cli/load_test.go +++ b/cmd/cli/load_test.go @@ -15,19 +15,59 @@ package main import ( "context" + "io/ioutil" "path/filepath" "testing" "gotest.tools/v3/assert" "gotest.tools/v3/fs" + constant "isula.org/isula-build" ) func TestLoadCmd(t *testing.T) { - cmd := NewLoadCmd() - err := cmd.Execute() - assert.Equal(t, err != nil, true) - err = loadCommand(cmd, nil) - assert.ErrorContains(t, err, "isula_build") + tmpDir := fs.NewFile(t, t.Name()) + err := ioutil.WriteFile(tmpDir.Path(), []byte("This is test file"), constant.DefaultSharedFileMode) + assert.NilError(t, err) + defer tmpDir.Remove() + + type testcase struct { + name string + path string + errString string + args []string + wantErr bool + sep separatorLoadOption + } + // For normal cases, default err is "invalid socket path: unix:///var/run/isula_build.sock". + // As daemon is not running as we run unit test. + var testcases = []testcase{ + { + name: "TC1 - normal case", + path: tmpDir.Path(), + errString: "isula_build.sock", + wantErr: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + loadCmd := NewLoadCmd() + loadOpts = loadOptions{ + path: tc.path, + sep: tc.sep, + } + err := loadCmd.Execute() + assert.Equal(t, err != nil, true) + + err = loadCommand(loadCmd, tc.args) + if tc.wantErr { + assert.ErrorContains(t, err, tc.errString) + } + if !tc.wantErr { + assert.NilError(t, err) + } + }) + } } func TestRunLoad(t *testing.T) { diff --git a/cmd/cli/mock.go b/cmd/cli/mock.go index 2ae07d56..142c87fa 100644 --- a/cmd/cli/mock.go +++ b/cmd/cli/mock.go @@ -318,6 +318,16 @@ func (f *mockDaemon) importImage(_ context.Context, opts ...grpc.CallOption) (pb func (f *mockDaemon) load(_ context.Context, in *pb.LoadRequest, opts ...grpc.CallOption) (pb.Control_LoadClient, error) { f.loadReq = in + path := f.loadReq.Path + sep := f.loadReq.Sep + if !sep.Enabled { + if path == "" { + return &mockLoadClient{}, errors.Errorf("tarball path should not be empty") + } + _, err := resolveLoadPath(path) + return &mockLoadClient{}, err + } + return &mockLoadClient{}, nil } -- 2.27.0