From 6ce9d998d0b8e15d7a673626a54477a0bfc9f768 Mon Sep 17 00:00:00 2001
From: meilier <xingweizheng@huawei.com>
Date: Wed, 3 Feb 2021 01:04:17 +0800
Subject: [PATCH 08/10] check if add default tag to image name when using push
 and save command

---
 daemon/pull_test.go |   6 +-
 daemon/push.go      |   6 ++
 daemon/push_test.go |  13 ++-
 daemon/save.go      |   8 ++
 daemon/save_test.go | 193 ++++++++++++++++++++++++++++++++++++++++++++
 image/image.go      |  23 ++++++
 6 files changed, 246 insertions(+), 3 deletions(-)
 create mode 100644 daemon/save_test.go

diff --git a/daemon/pull_test.go b/daemon/pull_test.go
index 67459d19..27a4d6e8 100644
--- a/daemon/pull_test.go
+++ b/daemon/pull_test.go
@@ -58,7 +58,6 @@ func (c *controlPullServer) Send(response *pb.PullResponse) error {
 
 func init() {
 	reexec.Init()
-
 }
 
 func prepare(t *testing.T) daemonTestOptions {
@@ -100,7 +99,10 @@ func TestPull(t *testing.T) {
 	defer tmpClean(d)
 
 	options := &storage.ImageOptions{}
-	d.Daemon.localStore.CreateImage(stringid.GenerateRandomID(), []string{"image:test"}, "", "", options)
+	_, err := d.Daemon.localStore.CreateImage(stringid.GenerateRandomID(), []string{"image:test"}, "", "", options)
+	if err != nil {
+		t.Fatalf("create image with error: %v", err)
+	}
 
 	testcases := []struct {
 		name      string
diff --git a/daemon/push.go b/daemon/push.go
index e6053dd8..4e3a6ed9 100644
--- a/daemon/push.go
+++ b/daemon/push.go
@@ -63,6 +63,12 @@ func (b *Backend) Push(req *pb.PushRequest, stream pb.Control_PushServer) error
 		return err
 	}
 
+	imageName, err := image.CheckAndAddDefaultTag(opt.imageName, opt.localStore)
+	if err != nil {
+		return err
+	}
+	opt.imageName = imageName
+
 	manifestType, gErr := exporter.GetManifestType(opt.format)
 	if gErr != nil {
 		return gErr
diff --git a/daemon/push_test.go b/daemon/push_test.go
index 573e97fe..f4a9e2b1 100644
--- a/daemon/push_test.go
+++ b/daemon/push_test.go
@@ -79,10 +79,21 @@ func TestPush(t *testing.T) {
 				Format:    "oci",
 			},
 		},
+		{
+			name: "manifestNotExist fine without tag latest",
+			pushRequest: &pb.PushRequest{
+				PushID:    stringid.GenerateNonCryptoID()[:constant.DefaultIDLen],
+				ImageName: "127.0.0.1/no-repository/no-name",
+				Format:    "oci",
+			},
+		},
 	}
 
 	options := &storage.ImageOptions{}
-	d.Daemon.localStore.CreateImage(stringid.GenerateRandomID(), []string{"127.0.0.1/no-repository/no-name:latest"}, "", "", options)
+	_, err := d.Daemon.localStore.CreateImage(stringid.GenerateRandomID(), []string{"127.0.0.1/no-repository/no-name:latest"}, "", "", options)
+	if err != nil {
+		t.Fatalf("create image with error: %v", err)
+	}
 
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
diff --git a/daemon/save.go b/daemon/save.go
index c6411e04..fd6174b4 100644
--- a/daemon/save.go
+++ b/daemon/save.go
@@ -79,6 +79,14 @@ func (b *Backend) Save(req *pb.SaveRequest, stream pb.Control_SaveServer) error
 		return errors.New("wrong image format provided")
 	}
 
+	for i, imageName := range opts.oriImgList {
+		nameWithTag, cErr := image.CheckAndAddDefaultTag(imageName, opts.localStore)
+		if cErr != nil {
+			return cErr
+		}
+		opts.oriImgList[i] = nameWithTag
+	}
+
 	defer func() {
 		if err != nil {
 			if rErr := os.Remove(opts.outputPath); rErr != nil && !os.IsNotExist(rErr) {
diff --git a/daemon/save_test.go b/daemon/save_test.go
new file mode 100644
index 00000000..a59086a8
--- /dev/null
+++ b/daemon/save_test.go
@@ -0,0 +1,193 @@
+// 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: Weizheng Xing
+// Create: 2020-02-03
+// Description: This file tests Save interface
+
+package daemon
+
+import (
+	"context"
+	"testing"
+
+	"github.com/containers/storage"
+	"github.com/containers/storage/pkg/reexec"
+	"github.com/containers/storage/pkg/stringid"
+	"github.com/pkg/errors"
+	"golang.org/x/sync/errgroup"
+	"google.golang.org/grpc"
+	"gotest.tools/v3/assert"
+	"gotest.tools/v3/fs"
+
+	constant "isula.org/isula-build"
+	pb "isula.org/isula-build/api/services"
+	_ "isula.org/isula-build/exporter/register"
+	"isula.org/isula-build/pkg/logger"
+)
+
+type controlSaveServer struct {
+	grpc.ServerStream
+}
+
+func (c *controlSaveServer) Context() context.Context {
+	return context.Background()
+}
+
+func (c *controlSaveServer) Send(response *pb.SaveResponse) error {
+	if response.Log == "error" {
+		return errors.New("error happened")
+	}
+	return nil
+}
+
+func init() {
+	reexec.Init()
+}
+
+func TestSave(t *testing.T) {
+	d := prepare(t)
+	defer tmpClean(d)
+
+	//TODO: create image manually and save
+	options := &storage.ImageOptions{}
+	img, err := d.Daemon.localStore.CreateImage(stringid.GenerateRandomID(), []string{"image:latest"}, "", "", options)
+	if err != nil {
+		t.Fatalf("create image with error: %v", err)
+	}
+
+	_, err = d.Daemon.localStore.CreateImage(stringid.GenerateRandomID(), []string{"image2:test"}, "", "", options)
+	if err != nil {
+		t.Fatalf("create image with error: %v", err)
+	}
+
+	tempTarfileDir := fs.NewDir(t, t.Name())
+	defer tempTarfileDir.Remove()
+
+	testcases := []struct {
+		name      string
+		req       *pb.SaveRequest
+		wantErr   bool
+		errString string
+	}{
+		{
+			name: "normal case save with repository[:tag]",
+			req: &pb.SaveRequest{
+				SaveID: stringid.GenerateNonCryptoID()[:constant.DefaultIDLen],
+				Images: []string{"image:latest"},
+				Path:   tempTarfileDir.Join("repotag.tar"),
+				Format: "docker",
+			},
+			wantErr:   true,
+			errString: "file does not exist",
+		},
+		{
+			name: "normal case save with repository add default latest",
+			req: &pb.SaveRequest{
+				SaveID: stringid.GenerateNonCryptoID()[:constant.DefaultIDLen],
+				Images: []string{"image"},
+				Path:   tempTarfileDir.Join("repolatest.tar"),
+				Format: "oci",
+			},
+			wantErr:   true,
+			errString: "file does not exist",
+		},
+		{
+			name: "normal case with imageid",
+			req: &pb.SaveRequest{
+				SaveID: stringid.GenerateNonCryptoID()[:constant.DefaultIDLen],
+				Images: []string{img.ID},
+				Path:   tempTarfileDir.Join("imageid.tar"),
+				Format: "docker",
+			},
+			wantErr:   true,
+			errString: "file does not exist",
+		},
+		{
+			name: "normal case save multiple images with repository and ID",
+			req: &pb.SaveRequest{
+				SaveID: stringid.GenerateNonCryptoID()[:constant.DefaultIDLen],
+				Images: []string{"image2:test", img.ID},
+				Path:   tempTarfileDir.Join("double.tar"),
+				Format: "docker",
+			},
+			wantErr:   true,
+			errString: "file does not exist",
+		},
+		{
+			name: "abnormal case save image that not exist in local store",
+			req: &pb.SaveRequest{
+				SaveID: stringid.GenerateNonCryptoID()[:constant.DefaultIDLen],
+				Images: []string{"noexist", img.ID},
+				Path:   tempTarfileDir.Join("notexist.tar"),
+				Format: "docker",
+			},
+			wantErr:   true,
+			errString: "failed to parse image",
+		},
+		{
+			name: "abnormal case wrong image format",
+			req: &pb.SaveRequest{
+				SaveID: stringid.GenerateNonCryptoID()[:constant.DefaultIDLen],
+				Images: []string{"image", img.ID},
+				Path:   tempTarfileDir.Join("image.tar"),
+				Format: "dock",
+			},
+			wantErr:   true,
+			errString: "wrong image format provided",
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			stream := &controlSaveServer{}
+
+			err := d.Daemon.backend.Save(tc.req, stream)
+			if tc.wantErr == true {
+				assert.ErrorContains(t, err, tc.errString)
+			}
+			if tc.wantErr == false {
+				assert.NilError(t, err)
+			}
+		})
+	}
+
+}
+
+func TestSaveHandler(t *testing.T) {
+	ctx := context.TODO()
+	eg, _ := errgroup.WithContext(ctx)
+
+	eg.Go(saveHandlerPrint("Push Response"))
+	eg.Go(saveHandlerPrint(""))
+	eg.Go(saveHandlerPrint("error"))
+
+	eg.Wait()
+}
+
+func saveHandlerPrint(message string) func() error {
+	return func() error {
+		stream := &controlSaveServer{}
+		cliLogger := logger.NewCliLogger(constant.CliLogBufferLen)
+
+		ctx := context.TODO()
+		eg, _ := errgroup.WithContext(ctx)
+
+		eg.Go(messageHandler(stream, cliLogger))
+		eg.Go(func() error {
+			cliLogger.Print(message)
+			cliLogger.CloseContent()
+			return nil
+		})
+
+		eg.Wait()
+
+		return nil
+	}
+}
diff --git a/image/image.go b/image/image.go
index 36785bdf..1e480391 100644
--- a/image/image.go
+++ b/image/image.go
@@ -689,3 +689,26 @@ func tryResolveNameInRegistries(name string, sc *types.SystemContext) ([]string,
 	}
 	return candidates, exporter.DockerTransport
 }
+
+// CheckAndAddDefaultTag checks if src is format of repository[:tag], add default tag if src without tag
+func CheckAndAddDefaultTag(imageName string, store *store.Store) (string, error) {
+	_, img, err := FindImage(store, imageName)
+	if err != nil {
+		return "", errors.Wrapf(err, "find src image: %q failed", imageName)
+	}
+
+	defaultTag := "latest"
+	for _, name := range img.Names {
+		// for imageName is the format of repository[:tag]
+		if imageName == name {
+			return imageName, nil
+		}
+		// for name is the format of repository
+		if fmt.Sprintf("%s:%s", imageName, defaultTag) == name {
+			return name, nil
+		}
+	}
+
+	// for imageName is the format of imageID
+	return imageName, nil
+}
-- 
2.27.0

