tools: add cos_ova_converter to the tools

To simplify handling OVA images to and from GCE during preloading
process using COS Customizer, adding a simple tools.

BUG=b/188836459
TEST=manual
RELEASE_NOTE=Add cos_ova_converter tools

Change-Id: Ie4a16e7d79de61048c2e0e5a21946584cb964008
Reviewed-on: https://cos-review.googlesource.com/c/cos/tools/+/24390
Reviewed-by: Robert Kolchmeyer <rkolchmeyer@google.com>
Cloud-Build: GCB Service account <228075978874@cloudbuild.gserviceaccount.com>
Tested-by: Varsha Teratipally <teratipally@google.com>
diff --git a/go.mod b/go.mod
index 69ee986..0644b3b 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,8 @@
 require (
 	cloud.google.com/go v0.81.0
 	cloud.google.com/go/logging v1.4.2
-	cloud.google.com/go/storage v1.13.0
+	cloud.google.com/go/storage v1.14.0
+	github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20211102200636-e7e49ca6dac0 // indirect
 	github.com/andygrunwald/go-gerrit v0.0.0-20201231163137-46815e48bfe0
 	github.com/beevik/etree v1.1.0
 	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
diff --git a/go.sum b/go.sum
index e1756e9..653fd08 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,7 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg=
 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
@@ -30,6 +31,7 @@
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/logging v1.0.0/go.mod h1:V1cc3ogwobYzQq5f2R7DS/GvRIrI4FKj01Gs5glwAls=
 cloud.google.com/go/logging v1.4.2 h1:Mu2Q75VBDQlW1HlBMjTX4X84UFR73G1TiLlRYc/b7tA=
 cloud.google.com/go/logging v1.4.2/go.mod h1:jco9QZSx8HiVVqLJReq7z7bVdj0P1Jb9PDFs63T+axo=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@@ -43,11 +45,15 @@
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.13.0 h1:amPvhCOI+Hltp6rPu+62YdwhIrjf+34PKVAL4HwgYwk=
 cloud.google.com/go/storage v1.13.0/go.mod h1:pqFyBUK3zZqMIIU5+8NaZq6/Ma3ClgUg9Hv5jfuJnvo=
+cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU=
+cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20211102200636-e7e49ca6dac0 h1:b4WGGawVW/lMUywy4/hapK01bJ+QB8twMKwe0A0Gw3E=
+github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20211102200636-e7e49ca6dac0/go.mod h1:Z9jsyfegJlbSvjxGJnLf0vFf5yn20YyYkFaANYscwCU=
 github.com/andygrunwald/go-gerrit v0.0.0-20201231163137-46815e48bfe0 h1:1IlIh8TmY+eAX17cPIUzT4e5R5bQoEngAO5QFcGHbrA=
 github.com/andygrunwald/go-gerrit v0.0.0-20201231163137-46815e48bfe0/go.mod h1:soxaYLbAFToS0OelBriItCts/mtUZOuLBkCk1Xv4ZSo=
 github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
@@ -173,6 +179,7 @@
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -229,6 +236,7 @@
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -305,6 +313,7 @@
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
@@ -354,10 +363,12 @@
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
@@ -452,6 +463,7 @@
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
+google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
 google.golang.org/api v0.46.0 h1:jkDWHOBIoNSD0OQpq4rtBVu+Rh325MPjXG1rakAp8JU=
 google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@@ -467,6 +479,8 @@
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
+google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
@@ -499,6 +513,7 @@
 google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210203152818-3206188e46ba/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
diff --git a/src/cmd/cos_ova_converter/Dockerfile b/src/cmd/cos_ova_converter/Dockerfile
new file mode 100644
index 0000000..ba1445b
--- /dev/null
+++ b/src/cmd/cos_ova_converter/Dockerfile
@@ -0,0 +1,32 @@
+FROM golang:1.16 as cosovaconverter
+
+COPY . /work/
+WORKDIR /work/src/cmd/cos_ova_converter
+RUN go build -o  cos_ova_converter .
+
+FROM gcr.io/compute-image-tools/daisy as daisyworkflow
+
+FROM debian:buster-slim
+
+RUN apt-get update && DEBIAN_FRONTEND=noninteractive \
+    apt-get install --no-install-recommends -y -qq \
+    ca-certificates \
+    apt-transport-https \
+    gnupg \
+    curl \
+    qemu-utils \
+    python3 \
+    python3-pip python3-setuptools\
+    xmlstarlet \
+    git
+
+RUN pip3 install cot
+
+RUN git clone https://cos.googlesource.com/third_party/platform/crosutils.git
+RUN cd crosutils && git checkout 74d0afda96dc8c58863f76b2e144c373f92451f6
+
+COPY --from=cosovaconverter /work/src/cmd/cos_ova_converter/cos_ova_converter /cos_ova_converter
+COPY --from=daisyworkflow  /daisy /daisy
+COPY --from=daisyworkflow /workflows /workflows
+
+ENTRYPOINT ["/cos_ova_converter"]
\ No newline at end of file
diff --git a/src/cmd/cos_ova_converter/Makefile b/src/cmd/cos_ova_converter/Makefile
new file mode 100644
index 0000000..8f9e02e
--- /dev/null
+++ b/src/cmd/cos_ova_converter/Makefile
@@ -0,0 +1,11 @@
+COS_CONVERTER_BINARY=cos_ova_converter
+
+build:
+	go build -o ${COS_CONVERTER_BINARY} .
+
+clean:
+	go clean
+	rm -f ${COS_CONVERTER_BINARY}
+
+build-image:
+	cd ../../../ && docker build -t ${COS_CONVERTER_BINARY} -f src/cmd/cos_ova_converter/Dockerfile .
diff --git a/src/cmd/cos_ova_converter/README.md b/src/cmd/cos_ova_converter/README.md
new file mode 100644
index 0000000..6c66ca2
--- /dev/null
+++ b/src/cmd/cos_ova_converter/README.md
@@ -0,0 +1,54 @@
+# COS Ova Converter
+
+COS Ova Converter is a simple interface to convert the OVA images to GCE image in
+a GCP project and also exports the GCE image to OVA. It is available as a Docker image.
+
+The main motivation is to provide a simple tool for handling the OVA images
+during the COS preloading process using the COS Customizer as it exclusively
+deals with the GCE images.
+
+## How to get started
+
+### Compile COS OVA Converter
+
+``` shell
+make
+```
+will build the COS OVA Converter application
+
+### Try COS OVA Converter
+
+COS OVA Converter is available as a docker image (cos_ova_converter). It can be
+run as one of the steps in the Cloud build workflow for converting the OVA images
+to GCE and back to OVA from GCE images.
+
+
+To convert the OVA to GCE image,
+
+```shell
+steps:
+- name: 'cos-ova-converter'
+  args: ['to-gce',
+         '-image-name=cos-ova-converted-image',
+         '-image-project=${PROJECT_ID}',
+         '-gcs-bucket=${PROJECT_ID}_cloudbuild',
+         '-input-url=gs://sample-gcs/cos.ova']
+```
+
+This will download the image specified in the `input-url` and creates and image with
+name `image-name` in `image-project`. `gcs-bucket` here is a workspace.
+
+To convert the OVA from the GCE image
+
+```shell
+steps:
+- name: 'cos-ova-converter'
+  args: ['from-gce',
+         '-source-image=cos-preloaded-image',
+         '-image-project=${PROJECT_ID}',
+         '-gcs-bucket=${PROJECT_ID}_cloudbuild',
+         '-destination-path=gs://sample-gcs/cos.ova']
+```
+
+This will export the image with name `source-image` in `image-project` to `destination-path`
+as OVA. `gcs-bucket` here is a workspace.
diff --git a/src/cmd/cos_ova_converter/main.go b/src/cmd/cos_ova_converter/main.go
new file mode 100644
index 0000000..683c478
--- /dev/null
+++ b/src/cmd/cos_ova_converter/main.go
@@ -0,0 +1,39 @@
+// Copyright 2021 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"context"
+	"flag"
+	"os"
+
+	"cos.googlesource.com/cos/tools.git/src/pkg/ovaconverter"
+	"github.com/google/subcommands"
+)
+
+func main() {
+
+	flag.Parse()
+	//register commands
+	subcommands.Register(subcommands.HelpCommand(), "")
+	subcommands.Register(subcommands.FlagsCommand(), "")
+	subcommands.Register(new(ToGCECmd), "")
+	subcommands.Register(new(FromGCECmd), "")
+
+	ctx := context.Background()
+	converterConfig := ovaconverter.GetDefaultOVAConverterConfig()
+	ret := int(subcommands.Execute(ctx, converterConfig))
+	os.Exit(ret)
+}
diff --git a/src/cmd/cos_ova_converter/ova_from_gce.go b/src/cmd/cos_ova_converter/ova_from_gce.go
new file mode 100644
index 0000000..eb7e2b5
--- /dev/null
+++ b/src/cmd/cos_ova_converter/ova_from_gce.go
@@ -0,0 +1,107 @@
+// Copyright 2021 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"context"
+	"errors"
+	"flag"
+	"strings"
+
+	"github.com/golang/glog"
+	"github.com/google/subcommands"
+
+	"cos.googlesource.com/cos/tools.git/src/pkg/ovaconverter"
+)
+
+type FromGCECmd struct {
+	DestinationPath string
+	ImageProject    string
+	SourceImage     string
+	GcsBucket       string
+	Zone            string
+}
+
+// Name returns the name of the command.
+func (fgc *FromGCECmd) Name() string {
+	return "from-gce"
+}
+
+// Synopsis returns short description of the command.
+func (fgc *FromGCECmd) Synopsis() string {
+	return "Converts the Input GCE image " +
+		"to OVA format and uploads to destination path"
+}
+
+// Usage returns instructions on how to use the command.
+func (fgc *FromGCECmd) Usage() string {
+	return "Converts the Input GCE image " +
+		"to OVA format and uploads to destination path"
+}
+
+// SetFlags adds the flags to the specified set.
+func (fgc *FromGCECmd) SetFlags(fs *flag.FlagSet) {
+	fs.StringVar(&fgc.GcsBucket, "gcs-bucket", "",
+		"GCS bucket for the working dir. It is mandatory. Example: sample-bucket")
+	fs.StringVar(&fgc.Zone, "zone", "us-west1-b",
+		"Zone is required when exporting a GCE image. It is optional, by default it is us-west1-b. Example: us-west1-b")
+	fs.StringVar(&fgc.DestinationPath, "destination-path", "",
+		"URL to the save the OVA Image. It is mandatory. Example: gs://output-bucket/output.ova")
+	fs.StringVar(&fgc.ImageProject, "image-project", "",
+		"Project in which the source image is present. It is mandatory. Example: input-project")
+	fs.StringVar(&fgc.SourceImage, "source-image", "",
+		"Name of the Source Image. It is mandatory. Example: input-gce")
+}
+
+// Execute executes the command (converts GCE to OVA) and returns the CommandStatus
+func (fgc *FromGCECmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
+	if err := fgc.validateInput(); err != nil {
+		glog.Errorf("failed to parse flags: %v", err)
+		return subcommands.ExitFailure
+	}
+	exitStatus := subcommands.ExitSuccess
+
+	gceToOVAConverterConfig := args[0].(*ovaconverter.GCEToOVAConverterConfig)
+	converter := ovaconverter.NewConverter(ctx)
+
+	if err := converter.ConvertOVAFromGCE(ctx, fgc.SourceImage, fgc.DestinationPath,
+		fgc.GcsBucket, fgc.ImageProject, fgc.Zone, gceToOVAConverterConfig); err != nil {
+		glog.Errorf("failed to create the OVA image: %v", err)
+		exitStatus = subcommands.ExitFailure
+	}
+	return exitStatus
+}
+
+// validateInput validates the input from flags parsed and returns error when the
+// mandatory input values are not present
+func (tgc *FromGCECmd) validateInput() error {
+	var errMsgs []string
+	if tgc.GcsBucket == "" {
+		errMsgs = append(errMsgs, "gcs-bucket is mandatory")
+	}
+	if tgc.DestinationPath == "" {
+		errMsgs = append(errMsgs, "destination-path is mandatory")
+	}
+	if tgc.SourceImage == "" {
+		errMsgs = append(errMsgs, "source-image is mandatory")
+	}
+	if tgc.ImageProject == "" {
+		errMsgs = append(errMsgs, "image-project is mandatory")
+	}
+	if len(errMsgs) > 0 {
+		return errors.New(strings.Join(errMsgs, ";"))
+	}
+	return nil
+}
diff --git a/src/cmd/cos_ova_converter/ova_to_gce.go b/src/cmd/cos_ova_converter/ova_to_gce.go
new file mode 100644
index 0000000..1b92040
--- /dev/null
+++ b/src/cmd/cos_ova_converter/ova_to_gce.go
@@ -0,0 +1,103 @@
+// Copyright 2021 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"context"
+	"errors"
+	"flag"
+	"strings"
+
+	"github.com/golang/glog"
+	"github.com/google/subcommands"
+
+	"cos.googlesource.com/cos/tools.git/src/pkg/ovaconverter"
+)
+
+type ToGCECmd struct {
+	InputURL     string
+	ImageProject string
+	ImageName    string
+	GcsBucket    string
+}
+
+// Name returns the name of the command.
+func (tgc *ToGCECmd) Name() string {
+	return "to-gce"
+}
+
+// Synopsis returns short description of the command.
+func (tgc *ToGCECmd) Synopsis() string {
+	return "Converts the Input OVA image " +
+		"to raw format and uploads to GCE Project"
+}
+
+// Usage returns instructions on how to use the command.
+func (tgc *ToGCECmd) Usage() string {
+	return "Converts the Input OVA image " +
+		"to raw format and uploads to GCE Project"
+}
+
+// SetFlags adds the flags to the specified set.
+func (tgc *ToGCECmd) SetFlags(fs *flag.FlagSet) {
+	fs.StringVar(&tgc.GcsBucket, "gcs-bucket", "",
+		"GCS bucket for the working dir. It is mandatory. Example: sample-bucket")
+	fs.StringVar(&tgc.InputURL, "input-url", "",
+		"URL to the Input OVA Image. It is mandatory. Example: gs://sample-bucket/input.ova")
+	fs.StringVar(&tgc.ImageProject, "image-project", "",
+		"Project in which the image is to be created. It is mandatory. Example: test-project")
+	fs.StringVar(&tgc.ImageName, "image-name", "",
+		"Name of the Image to be create. It is mandatory. Example: input-gce")
+}
+
+// Execute executes the command (converts OVA to GCE) and returns the CommandStatus
+func (otgc *ToGCECmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
+	if err := otgc.validateInput(); err != nil {
+		glog.Errorf("failed to parse flags: %v", err)
+		return subcommands.ExitFailure
+	}
+	exitStatus := subcommands.ExitSuccess
+
+	converter := ovaconverter.NewConverter(ctx)
+
+	if err := converter.ConvertOVAToGCE(ctx, otgc.InputURL, otgc.ImageName,
+		otgc.GcsBucket, otgc.ImageProject); err != nil {
+		glog.Errorf("failed to convert to the gce image: %v", err)
+		exitStatus = subcommands.ExitFailure
+	}
+	return exitStatus
+}
+
+// validateInput validates the input from flags parsed and returns error when the
+// mandatory input values are not present
+func (tgc *ToGCECmd) validateInput() error {
+	var errMsgs []string
+	if tgc.GcsBucket == "" {
+		errMsgs = append(errMsgs, "gcs-bucket is mandatory")
+	}
+	if tgc.InputURL == "" {
+		errMsgs = append(errMsgs, "input-url is mandatory")
+	}
+	if tgc.ImageName == "" {
+		errMsgs = append(errMsgs, "image-name is mandatory")
+	}
+	if tgc.ImageProject == "" {
+		errMsgs = append(errMsgs, "image-project is mandatory")
+	}
+	if len(errMsgs) > 0 {
+		return errors.New(strings.Join(errMsgs, ";"))
+	}
+	return nil
+}
diff --git a/src/pkg/fs/BUILD.bazel b/src/pkg/fs/BUILD.bazel
index 91d5218..c12f230 100644
--- a/src/pkg/fs/BUILD.bazel
+++ b/src/pkg/fs/BUILD.bazel
@@ -17,6 +17,7 @@
 go_library(
     name = "fs",
     srcs = [
+        "archiver.go",
         "build_context.go",
         "copy.go",
         "file_system.go",
diff --git a/src/pkg/fs/archiver.go b/src/pkg/fs/archiver.go
new file mode 100644
index 0000000..64dbbff
--- /dev/null
+++ b/src/pkg/fs/archiver.go
@@ -0,0 +1,125 @@
+package fs
+
+import (
+	"archive/tar"
+	"compress/gzip"
+	"io"
+	"os"
+	"os/exec"
+	"path/filepath"
+
+	"cos.googlesource.com/cos/tools.git/src/pkg/utils"
+)
+
+const gzipFileExt = ".gz"
+
+// TarFile compresses the file at src to dst.
+func TarFile(src, dst string) error {
+	args := []string{"cf", dst}
+	dirPath := filepath.Dir(src)
+	baseName := filepath.Base(src)
+	// Add the compression type based on the dst, if the
+	// file type is not supported tar using default compression.
+	if filepath.Ext(dst) == gzipFileExt {
+		args = append(args, "-I", "/bin/gzip")
+	}
+	// add inputFilePath args
+	args = append(args, "-C", dirPath, baseName)
+	cmd := exec.Command("tar", args...)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	return cmd.Run()
+}
+
+// TarDir compresses the directory at root to dst.
+func TarDir(root, dst string) error {
+	args := []string{"cf", dst, "-C", root}
+	inputFiles, err := filepath.Glob(filepath.Join(root, "*"))
+	if err != nil {
+		return err
+	}
+	var relInputFiles []string
+	for _, path := range inputFiles {
+		relPath, err := filepath.Rel(root, path)
+		if err != nil {
+			return err
+		}
+		relInputFiles = append(relInputFiles, relPath)
+	}
+	if relInputFiles == nil {
+		relInputFiles = []string{"."}
+	}
+	args = append(args, relInputFiles...)
+	cmd := exec.Command("tar", args...)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	return cmd.Run()
+}
+
+// ExtractFile decompresses the tar file at inputFile to destDir.
+func ExtractFile(inputFile, destDir string) error {
+	var reader io.Reader
+	fileReader, err := os.Open(inputFile)
+	if err != nil {
+		return err
+	}
+	defer utils.CheckClose(fileReader, "error closing the file reader", &err)
+	if filepath.Ext(inputFile) == ".gz" {
+		reader, err = gzip.NewReader(fileReader)
+		if err != nil {
+			return err
+		}
+	} else {
+		reader = fileReader
+	}
+	return extractFile(reader, destDir)
+}
+
+// extractFile decompresses the tar file reader at inputFile to destDir.
+func extractFile(reader io.Reader, destDir string) error {
+	// Open the file for read
+
+	// Use gzip to read from the file
+	tarReader := tar.NewReader(reader)
+	// Read the file sequentially
+	for {
+		fileHeader, err := tarReader.Next()
+		switch {
+		// If no more files are found, return
+		case err == io.EOF:
+			return nil
+		// Return if hit any error
+		case err != nil:
+			return err
+		// If next file's header is nil, just skip it.
+		case fileHeader == nil:
+			continue
+		}
+		// Create a target file locally
+		localTarget := filepath.Join(destDir, fileHeader.Name)
+		switch fileHeader.Typeflag {
+		case tar.TypeDir:
+			if err := os.MkdirAll(localTarget, 0755); err != nil {
+				return err
+			}
+		// This should be tar.TypeReg, e.g regular file.
+		default:
+			localDir := filepath.Dir(localTarget)
+			// Create a dir if it doesn't exist but it should have created dir already.
+			if err := os.MkdirAll(localDir, 0755); err != nil {
+				return err
+			}
+			localFile, err := os.Create(localTarget)
+			if err != nil {
+				return err
+			}
+			// Copy over the contents.
+			if _, err = io.Copy(localFile, tarReader); err != nil {
+				localFile.Close()
+				return err
+			}
+			localFile.Close()
+		}
+	}
+	return nil
+}
diff --git a/src/pkg/fs/build_context.go b/src/pkg/fs/build_context.go
index 44e0269..b531ea2 100644
--- a/src/pkg/fs/build_context.go
+++ b/src/pkg/fs/build_context.go
@@ -19,43 +19,9 @@
 	"fmt"
 	"io"
 	"os"
-	"os/exec"
 	"path/filepath"
 )
 
-func tarFile(src, dst string) error {
-	dirPath := filepath.Dir(src)
-	baseName := filepath.Base(src)
-	cmd := exec.Command("tar", "cf", dst, "-C", dirPath, baseName)
-	cmd.Stdout = os.Stdout
-	cmd.Stderr = os.Stderr
-	return cmd.Run()
-}
-
-func tarDir(root, dst string) error {
-	args := []string{"cf", dst, "-C", root}
-	inputFiles, err := filepath.Glob(filepath.Join(root, "*"))
-	if err != nil {
-		return err
-	}
-	var relInputFiles []string
-	for _, path := range inputFiles {
-		relPath, err := filepath.Rel(root, path)
-		if err != nil {
-			return err
-		}
-		relInputFiles = append(relInputFiles, relPath)
-	}
-	if relInputFiles == nil {
-		relInputFiles = []string{"."}
-	}
-	args = append(args, relInputFiles...)
-	cmd := exec.Command("tar", args...)
-	cmd.Stdout = os.Stdout
-	cmd.Stderr = os.Stderr
-	return cmd.Run()
-}
-
 // CreateBuildContextArchive creates a tar archive of the given build context.
 func CreateBuildContextArchive(src, dst string) error {
 	if _, err := os.Stat(dst); !os.IsNotExist(err) {
@@ -70,9 +36,9 @@
 	}
 	switch {
 	case info.IsDir():
-		return tarDir(src, dst)
+		return TarDir(src, dst)
 	case info.Mode().IsRegular():
-		return tarFile(src, dst)
+		return TarFile(src, dst)
 	default:
 		return fmt.Errorf("input path %s is neither a directory nor a regular file", src)
 	}
diff --git a/src/pkg/gce/gce.go b/src/pkg/gce/gce.go
index a71b320..2205f64 100644
--- a/src/pkg/gce/gce.go
+++ b/src/pkg/gce/gce.go
@@ -35,6 +35,7 @@
 const (
 	defaultOperationTimeout = time.Duration(600) * time.Second
 	defaultRetryInterval    = time.Duration(5) * time.Second
+	gcsURLPrefix            = "https://storage.googleapis.com"
 )
 
 type timePkg struct {
@@ -152,6 +153,23 @@
 	return true, nil
 }
 
+// CreateImage creates an image with imageName with the source-url from gcs storage
+func CreateImage(svc *compute.Service, sourceURL, imageName, imageProject string) error {
+	gcsImageURL := fmt.Sprintf("%s/%s", gcsURLPrefix, sourceURL)
+	image := &compute.Image{
+		Name: imageName,
+		RawDisk: &compute.ImageRawDisk{
+			Source: gcsImageURL,
+		},
+	}
+	createImageOp, err := svc.Images.Insert(imageProject, image).Do()
+	if err != nil {
+		return err
+	}
+	deadline := time.Now().Add(defaultOperationTimeout)
+	return waitForOp(svc, imageProject, createImageOp, deadline, realTime)
+}
+
 type decodedImageName struct {
 	name        string
 	milestone   int
diff --git a/src/pkg/gcs/gcs_client.go b/src/pkg/gcs/gcs_client.go
new file mode 100644
index 0000000..df833a0
--- /dev/null
+++ b/src/pkg/gcs/gcs_client.go
@@ -0,0 +1,93 @@
+// Copyright 2021 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gcs
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"net/url"
+	"os"
+	"strings"
+
+	"cloud.google.com/go/storage"
+)
+
+const schemeGCS = "gs"
+
+// DownloadGCSObject downloads the object at inputURL and saves it at destinationPath
+func DownloadGCSObject(ctx context.Context,
+	gcsClient *storage.Client, inputURL, destinationPath string) error {
+	gcsBucket, name, err := getGCSVariables(inputURL)
+	if err != nil {
+		return err
+	}
+	r, err := gcsClient.Bucket(gcsBucket).Object(name).NewReader(ctx)
+	if err != nil {
+		return err
+	}
+	defer r.Close()
+
+	f, err := os.Create(destinationPath)
+	if err != nil {
+		return err
+	}
+	if _, err := io.Copy(f, r); err != nil {
+		return fmt.Errorf("error copying file from gcs bucket: %v", err)
+	}
+	return nil
+}
+
+// UploadGCSObject uploads an object at inputPath to destination URL
+func UploadGCSObject(ctx context.Context,
+	gcsClient *storage.Client, inputPath, destinationURL string) error {
+
+	gcsBucket, name, err := getGCSVariables(destinationURL)
+	if err != nil {
+		return fmt.Errorf("error parsing destination URL: %v", err)
+	}
+	fileReader, err := os.Open(inputPath)
+	if err != nil {
+		return err
+	}
+
+	w := gcsClient.Bucket(gcsBucket).Object(name).NewWriter(ctx)
+	defer w.Close()
+
+	if _, err := io.Copy(w, fileReader); err != nil {
+		return err
+	}
+	return nil
+}
+
+// DeleteGCSObject deletes an object at the input URL
+func DeleteGCSObject(ctx context.Context,
+	gcsClient *storage.Client, inputURL string) error {
+	gcsBucket, name, err := getGCSVariables(inputURL)
+	if err != nil {
+		return fmt.Errorf("error parsing input URL: %v", err)
+	}
+	return gcsClient.Bucket(gcsBucket).Object(name).Delete(ctx)
+}
+
+// Returns the getGCSVariables(GCSBucket, GCSPath, fileName) based on the input.
+func getGCSVariables(gcsPath string) (string, string, error) {
+	url, err := url.Parse(gcsPath)
+	if err != nil || url.Scheme != schemeGCS {
+		return "", "", fmt.Errorf("error parsing the input GCS path: %s", gcsPath)
+	}
+	// url.EscapedPath returns with the leading /.
+	return url.Hostname(), strings.TrimLeft(url.EscapedPath(), "/"), nil
+}
diff --git a/src/pkg/ovaconverter/converter.go b/src/pkg/ovaconverter/converter.go
new file mode 100644
index 0000000..16bb6a9
--- /dev/null
+++ b/src/pkg/ovaconverter/converter.go
@@ -0,0 +1,199 @@
+// Copyright 2021 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ovaconverter
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"cloud.google.com/go/storage"
+	"github.com/golang/glog"
+	"google.golang.org/api/compute/v1"
+
+	"cos.googlesource.com/cos/tools.git/src/pkg/fs"
+	"cos.googlesource.com/cos/tools.git/src/pkg/gce"
+	"cos.googlesource.com/cos/tools.git/src/pkg/gcs"
+	"cos.googlesource.com/cos/tools.git/src/pkg/utils"
+)
+
+const (
+	vmdkFileExtension = ".vmdk"
+)
+
+type Converter struct {
+	GCSClient      *storage.Client
+	ComputeService *compute.Service
+}
+
+func NewConverter(ctx context.Context) *Converter {
+	gcsClient, err := storage.NewClient(ctx)
+	if err != nil {
+		return nil
+	}
+	svc, err := compute.NewService(ctx)
+	if err != nil {
+		return nil
+	}
+	return &Converter{
+		GCSClient:      gcsClient,
+		ComputeService: svc,
+	}
+}
+
+// ConvertOVAToGCE converts the OVA file at GCS Location to a GCE image.
+func (converter *Converter) ConvertOVAToGCE(ctx context.Context, inputURL, imageName, gcsBucket, imageProject string) error {
+	// Create a temporary working directory
+	tempWorkDir, err := os.MkdirTemp("", "ova_dir")
+	if err != nil {
+		return err
+	}
+	defer utils.RemoveDir(tempWorkDir, "error on removing the temporary working directory", nil)
+
+	glog.Info("Downloading OVA from the input GCS URL")
+	inputFile := filepath.Join(tempWorkDir, "input.ova")
+	if err = gcs.DownloadGCSObject(ctx, converter.GCSClient,
+		inputURL, inputFile); err != nil {
+		return err
+	}
+
+	extractWorkDir := filepath.Join(tempWorkDir, "extractWorkDir")
+	glog.Info("Converting OVA to VMDK...")
+	if err = fs.ExtractFile(inputFile, extractWorkDir); err != nil {
+		return err
+	}
+
+	var vmdkFile string
+	files, _ := ioutil.ReadDir(extractWorkDir)
+	for _, file := range files {
+		if filepath.Ext(file.Name()) == vmdkFileExtension {
+			vmdkFile = file.Name()
+			break
+		}
+	}
+
+	glog.Info("Converting VMDK to Raw...")
+	tempRawImage := filepath.Join(tempWorkDir, "disk.raw")
+	if err = utils.ConvertImageToRaw(filepath.Join(extractWorkDir,
+		vmdkFile), tempRawImage); err != nil {
+		return err
+	}
+
+	glog.Info("Compressing disk.raw to tar.gz...")
+	if err = fs.TarFile(tempRawImage, filepath.Join(tempWorkDir,
+		"cos_gce.tar.gz")); err != nil {
+		return err
+	}
+
+	cosGCETar := "cos_gce.tar.gz"
+	cosTarURL := fmt.Sprintf("gs://%s/%s", gcsBucket, cosGCETar)
+
+	glog.Info("Uploading tar.gz file to a remote GCS location...")
+	if err = gcs.UploadGCSObject(ctx, converter.GCSClient, filepath.Join(tempWorkDir, cosGCETar), cosTarURL); err != nil {
+		return err
+	}
+	// delete the image staged temporarily before creating a GCE image
+	defer func() {
+		if gcs.DeleteGCSObject(ctx, converter.GCSClient, cosTarURL); err != nil {
+			glog.Warningf("error deleting the GCS temporary Object: %v", err)
+		}
+	}()
+
+	glog.Info("Creating a GCS Image...")
+	return gce.CreateImage(converter.ComputeService, filepath.Join(gcsBucket, cosGCETar),
+		imageName, imageProject)
+
+}
+
+// ConvertOVAFromGCE converts GCE Image to OVA Format.
+func (converter *Converter) ConvertOVAFromGCE(ctx context.Context, sourceImage, destinationPath, gcsBucket, imageProject, zone string,
+	gceToOVAConverterConfig *GCEToOVAConverterConfig) error {
+	tempWorkDir, err := ioutil.TempDir("", "ovaDir")
+	if err != nil {
+		return err
+	}
+	defer utils.RemoveDir(tempWorkDir, "error on removing the temporary working directory", nil)
+
+	tempExportedImageName := "cos-exported-image.tar.gz"
+	tempExportImageURL := fmt.Sprintf("gs://%s/%s", gcsBucket, tempExportedImageName)
+
+	glog.Info("Exporting image in tar.gz format to a temporary GCS location")
+	// Export the GCE image to a temporary location
+	if err = exportImageFromGCEUsingDaisy(sourceImage, imageProject, tempExportImageURL, zone,
+		gceToOVAConverterConfig.DaisyBin, gceToOVAConverterConfig.DaisyWorkflowPath); err != nil {
+		return err
+	}
+
+	glog.Info("Downloading the image in tar.gz...")
+	downloadImagePath := filepath.Join(tempWorkDir, tempExportedImageName)
+	if err = gcs.DownloadGCSObject(ctx, converter.GCSClient,
+		tempExportImageURL, downloadImagePath); err != nil {
+		return err
+	}
+
+	defer func() {
+		if err = gcs.DeleteGCSObject(ctx, converter.GCSClient, tempExportImageURL); err != nil {
+			glog.Warningf("error deleting the GCS temporary Object: %v", err)
+
+		}
+	}()
+
+	glog.Info("Extracting the disk.raw image from the tar file...")
+	extractWorkDir := filepath.Join(tempWorkDir, "extractDir")
+	if err = fs.ExtractFile(downloadImagePath, extractWorkDir); err != nil {
+		return err
+	}
+
+	glog.Info("Converting the disk.raw image to vmdk format...")
+	tempVMDKImageName := filepath.Join(tempWorkDir, "chromiumos_image.vmdk")
+	if err = utils.ConvertImageToVMDK(filepath.Join(extractWorkDir, "disk.raw"), tempVMDKImageName); err != nil {
+		return err
+	}
+
+	// convert to OVA image
+	glog.Info("Converting the VMDK to OVA image...")
+	tempOVAImage := filepath.Join(tempWorkDir, filepath.Base(destinationPath))
+	oVAImageName := strings.ReplaceAll(filepath.Base(destinationPath),
+		filepath.Ext(filepath.Base(destinationPath)), "")
+	if err = utils.RunCommand([]string{
+		gceToOVAConverterConfig.MakeOVAScript,
+		"-d", tempVMDKImageName, "-o", tempOVAImage, "-p", "GKE On-Prem", "-n",
+		oVAImageName, "-t", gceToOVAConverterConfig.OVATemplate,
+	}, "", nil); err != nil {
+		return err
+	}
+
+	glog.Info("Uploading the OVA file to the GCS URL...")
+	if err = gcs.UploadGCSObject(ctx, converter.GCSClient, tempOVAImage,
+		destinationPath); err != nil {
+		return err
+	}
+	return nil
+}
+
+// exportImageFromGCEUsingDaisy exports an image to the gce.tar.gz file by initiating a
+// daisy workflow.
+// Input: daisyBin - path to the daisy binary, daisyWorkflowPath - path to the image exporter workflow.
+func exportImageFromGCEUsingDaisy(imageName, imageProject, destinationFile, zone, daisyBin, daisyWorkflowPath string) error {
+	sourceImage := fmt.Sprintf("-var:source_image=projects/%s/global/images/%s", imageProject, imageName)
+	destination := fmt.Sprintf("-var:destination=%s", destinationFile)
+	exportImageUsingDaisyCommand := []string{
+		daisyBin, "-project", imageProject, "-zone", zone, sourceImage, destination, daisyWorkflowPath,
+	}
+	return utils.RunCommand(exportImageUsingDaisyCommand, "", nil)
+}
diff --git a/src/pkg/ovaconverter/ova_converter_config.go b/src/pkg/ovaconverter/ova_converter_config.go
new file mode 100644
index 0000000..f5114b1
--- /dev/null
+++ b/src/pkg/ovaconverter/ova_converter_config.go
@@ -0,0 +1,44 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ovaconverter
+
+const (
+	// makeOVAScriptPath is the location of make_ova.sh script
+	makeOVAScriptPath = "/crosutils/cos/make_ova.sh"
+	// ovaTemplate is the location of template used for OVA
+	ovaTemplatePath = "/crosutils/cos/template.ovf"
+	// daisyBin is the location of the Daisy binary.
+	daisyBin = "/daisy"
+	// daisyWorkflowPath is the location to the image_export.wf.json workflow.
+	daisyWorkflowPath = "/workflows/export/image_export.wf.json"
+)
+
+// GCEToOVAConverterConfig holds the required configuration about the
+// daisy bin path, make OVA script path and template path
+type GCEToOVAConverterConfig struct {
+	DaisyBin          string
+	MakeOVAScript     string
+	OVATemplate       string
+	DaisyWorkflowPath string
+}
+
+func GetDefaultOVAConverterConfig() *GCEToOVAConverterConfig {
+	return &GCEToOVAConverterConfig{
+		DaisyBin:          daisyBin,
+		MakeOVAScript:     makeOVAScriptPath,
+		OVATemplate:       ovaTemplatePath,
+		DaisyWorkflowPath: daisyWorkflowPath,
+	}
+}
diff --git a/src/pkg/utils/qemu_converter.go b/src/pkg/utils/qemu_converter.go
new file mode 100644
index 0000000..d31ae4f
--- /dev/null
+++ b/src/pkg/utils/qemu_converter.go
@@ -0,0 +1,37 @@
+// Copyright 2021 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package utils
+
+// ConvertImageToVMDK converts the image at inputFile to vmdk format
+// and saves it at destFile
+func ConvertImageToVMDK(inputFile, destFile string) error {
+	vmdkConvertCmd := []string{
+		"qemu-img", "convert", "-O", "vmdk", "-o", "subformat=streamOptimized",
+		inputFile,
+		destFile,
+	}
+	return RunCommand(vmdkConvertCmd, "", nil)
+}
+
+// ConvertImageToRaw converts the image at inputFile to raw format
+// and saves it at destFile
+func ConvertImageToRaw(inputFile, destFile string) error {
+	rawConvertCommand := []string{
+		"qemu-img", "convert", "-O", "raw",
+		inputFile,
+		destFile,
+	}
+	return RunCommand(rawConvertCommand, "", nil)
+}
diff --git a/src/pkg/utils/utils.go b/src/pkg/utils/utils.go
index bc975d5..7bc9126 100644
--- a/src/pkg/utils/utils.go
+++ b/src/pkg/utils/utils.go
@@ -31,9 +31,8 @@
 	"syscall"
 	"time"
 
-	"github.com/pkg/errors"
-
 	"github.com/golang/glog"
+	"github.com/pkg/errors"
 )
 
 var (
@@ -379,6 +378,24 @@
 	}
 }
 
+// RemoveDir removes the directory at inputPath and checks its error. Useful for checking the
+// errors on deferred remove().
+func RemoveDir(inputPath string, errMsgOnRemove string, err *error) {
+	if removeErr := os.RemoveAll(inputPath); removeErr != nil {
+		var fullErr error
+		if errMsgOnRemove != "" {
+			fullErr = fmt.Errorf("%s: %v", errMsgOnRemove, fullErr)
+		} else {
+			fullErr = removeErr
+		}
+		if *err == nil {
+			*err = fullErr
+		} else {
+			log.Println(fullErr)
+		}
+	}
+}
+
 func runCommand(args []string, dir string, env []string) error {
 	cmd := exec.Command(args[0], args[1:]...)
 	cmd.Stdout = os.Stdout