blob: 9621a2c1f7e1042714e78f0b844d3e1022ec6583 [file] [log] [blame]
// 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
}
cosGCETar := fmt.Sprintf("%s_gce.tar.gz", imageName)
glog.Info("Compressing disk.raw to tar.gz...")
if err = fs.TarFile(tempRawImage, filepath.Join(tempWorkDir, cosGCETar)); err != nil {
return err
}
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 := fmt.Sprintf("%s-exported-image.tar.gz", sourceImage)
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)
}