Adding impl for tar image overwrite

In order to comply with Test Before Uprev, we add the capability to
overwrite files as provided in a linked tar.

BUG=None
TEST=unit

Cq-Depend: chromium:3182687
Change-Id: Ic287a8639c07d02a54bc12f00e94d3f6a935391a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/3183181
Tested-by: Jaques Clapauch <jaquesc@google.com>
Auto-Submit: Jaques Clapauch <jaquesc@google.com>
Commit-Queue: Jaques Clapauch <jaquesc@google.com>
Reviewed-by: Sean McAllister <smcallis@google.com>
diff --git a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice/crosservice.go b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice/crosservice.go
index 7d56ef7..fbe4d60 100644
--- a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice/crosservice.go
+++ b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice/crosservice.go
@@ -24,6 +24,7 @@
 type CrOSService struct {
 	connection        services.ServiceAdapterInterface
 	imagePath         *conf.StoragePath
+	overwritePayload  *conf.StoragePath
 	preserverStateful bool
 	dlcSpecs          []*api.InstallCrosRequest_DLCSpec
 }
@@ -32,6 +33,7 @@
 	return CrOSService{
 		connection:        services.NewServiceAdapter(dutName, dutClient, wiringConn, req.GetPreventReboot()),
 		imagePath:         req.CrosImagePath,
+		overwritePayload:  req.OverwritePayload,
 		preserverStateful: req.PreserveStateful,
 		dlcSpecs:          req.DlcSpecs,
 	}
@@ -39,10 +41,11 @@
 
 // NewCrOSServiceFromExistingConnection is equivalent to the above constructor,
 // but recycles a ServiceAdapter. Generally useful for tests.
-func NewCrOSServiceFromExistingConnection(conn services.ServiceAdapterInterface, imagePath *conf.StoragePath, preserverStateful bool, dlcSpecs []*api.InstallCrosRequest_DLCSpec) CrOSService {
+func NewCrOSServiceFromExistingConnection(conn services.ServiceAdapterInterface, imagePath *conf.StoragePath, overwritePayload *conf.StoragePath, preserverStateful bool, dlcSpecs []*api.InstallCrosRequest_DLCSpec) CrOSService {
 	return CrOSService{
 		connection:        conn,
 		imagePath:         imagePath,
+		overwritePayload:  overwritePayload,
 		preserverStateful: preserverStateful,
 		dlcSpecs:          dlcSpecs,
 	}
@@ -278,6 +281,26 @@
 	return err
 }
 
+func (c *CrOSService) OverwiteInstall(ctx context.Context) error {
+	if c.overwritePayload == nil {
+		log.Printf("skipping overwrite install, because none was specified.")
+		return nil
+	}
+	if c.overwritePayload.HostType == conf.StoragePath_LOCAL || c.overwritePayload.HostType == conf.StoragePath_HOSTTYPE_UNSPECIFIED {
+		return fmt.Errorf("only GS copying is implemented")
+	}
+	url, err := c.connection.CopyData(ctx, c.overwritePayload.GetPath())
+	if err != nil {
+		return fmt.Errorf("failed to get GS Cache URL, %s", err)
+	}
+	_, err = c.connection.RunCmd(ctx, curlWithRetries, []string{
+		url,
+		"|",
+		"tar", "xf", "-", "-C", "/",
+	})
+	return err
+}
+
 // StopDLCService stops a DLC service
 func (c *CrOSService) StopDLCService(ctx context.Context) {
 	if _, err := c.connection.RunCmd(ctx, "stop", []string{"dlcservice"}); err != nil {
diff --git a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice/postinstallstate.go b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice/postinstallstate.go
index 42ff0fe..0d04b2e 100644
--- a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice/postinstallstate.go
+++ b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice/postinstallstate.go
@@ -30,6 +30,10 @@
 		return fmt.Errorf("failed to provision stateful, %s", err)
 	}
 
+	if err := s.service.OverwiteInstall(ctx); err != nil {
+		return fmt.Errorf("failed to overwite install, %s", err)
+	}
+
 	if err := s.service.connection.Restart(ctx); err != nil {
 		return fmt.Errorf("failed to restart dut, %s", err)
 	}
diff --git a/src/chromiumos/test/provision/cmd/provisionserver/provisionserver_test.go b/src/chromiumos/test/provision/cmd/provisionserver/provisionserver_test.go
index f4312e0..530f9c5 100644
--- a/src/chromiumos/test/provision/cmd/provisionserver/provisionserver_test.go
+++ b/src/chromiumos/test/provision/cmd/provisionserver/provisionserver_test.go
@@ -33,6 +33,7 @@
 			HostType: conf.StoragePath_GS,
 			Path:     "path/to/image",
 		},
+		nil,
 		false,
 		[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
 	)
@@ -159,6 +160,7 @@
 			HostType: conf.StoragePath_GS,
 			Path:     "path/to/image",
 		},
+		nil,
 		false,
 		[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
 	)
@@ -242,6 +244,7 @@
 			HostType: conf.StoragePath_GS,
 			Path:     "path/to/image",
 		},
+		nil,
 		false,
 		[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
 	)
@@ -325,6 +328,7 @@
 			HostType: conf.StoragePath_GS,
 			Path:     "path/to/image",
 		},
+		nil,
 		true, // <- preserve stateful
 		[]*api.InstallCrosRequest_DLCSpec{},
 	)
@@ -361,6 +365,7 @@
 			HostType: conf.StoragePath_GS,
 			Path:     "path/to/image",
 		},
+		nil,
 		true, // <- preserve stateful
 		[]*api.InstallCrosRequest_DLCSpec{},
 	)
@@ -398,6 +403,7 @@
 			HostType: conf.StoragePath_GS,
 			Path:     "path/to/image",
 		},
+		nil,
 		false,
 		[]*api.InstallCrosRequest_DLCSpec{},
 	)
@@ -425,6 +431,7 @@
 			HostType: conf.StoragePath_GS,
 			Path:     "path/to/image",
 		},
+		nil,
 		false,
 		[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
 	)
@@ -450,6 +457,49 @@
 	}
 }
 
+func TestPostInstallOverwriteWhenSpecified(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	sam := mock_services.NewMockServiceAdapterInterface(ctrl)
+
+	cs := crosservice.NewCrOSServiceFromExistingConnection(
+		sam,
+		&conf.StoragePath{
+			HostType: conf.StoragePath_GS,
+			Path:     "path/to/image",
+		},
+		&conf.StoragePath{
+			HostType: conf.StoragePath_GS,
+			Path:     "path/to/image/overwite.tar",
+		},
+		false,
+		[]*api.InstallCrosRequest_DLCSpec{{Id: "1"}},
+	)
+
+	ctx := context.Background()
+
+	// Install -> PostInstall
+	st := cs.GetFirstState().Next()
+
+	gomock.InOrder(
+		sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("echo"), gomock.Eq([]string{"'fast keepimg'", ">", "/mnt/stateful_partition/factory_install_reset"})).Return("", nil),
+		sam.EXPECT().Restart(gomock.Any()).Return(nil),
+		sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"ui"})).Return("", nil),
+		sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("stop"), gomock.Eq([]string{"update-engine"})).Return("", nil),
+		sam.EXPECT().CopyData(gomock.Any(), gomock.Eq("path/to/image/stateful.tgz")).Return("stateful.url", nil),
+		sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq(""), gomock.Eq([]string{"rm -rf /mnt/stateful_partition/.update_available /mnt/stateful_partition/var_new /mnt/stateful_partition/dev_image_new", "&&", "curl -S -s -v -# -C - --retry 3 --retry-delay 60 stateful.url | tar --ignore-command-error --overwrite --directory=/mnt/stateful_partition -xzf -", "&&", "echo -n clobber > /mnt/stateful_partition/.update_available"})).Return("", nil),
+		sam.EXPECT().CopyData(gomock.Any(), gomock.Eq("path/to/image/overwite.tar")).Return("overwrite.url.tar", nil),
+		sam.EXPECT().RunCmd(gomock.Any(), gomock.Eq("curl -S -s -v -# -C - --retry 3 --retry-delay 60"), gomock.Eq([]string{"overwrite.url.tar", "|", "tar", "xf", "-", "-C", "/"})).Return("", nil),
+		sam.EXPECT().Restart(gomock.Any()).Return(nil),
+	)
+
+	if err := st.Execute(ctx); err != nil {
+		t.Fatalf("failed post-install state: %v", err)
+	}
+
+}
+
 func TestLaCrOSInstallStateTransitions(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()