github.com/vmware/govmomi@v0.51.0/cli/export/ovf.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package export
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"crypto/sha1"
    11  	"crypto/sha256"
    12  	"crypto/sha512"
    13  	"flag"
    14  	"fmt"
    15  	"hash"
    16  	"io"
    17  	"os"
    18  	"path/filepath"
    19  	"strings"
    20  
    21  	"github.com/vmware/govmomi/object"
    22  
    23  	"github.com/vmware/govmomi/cli"
    24  	"github.com/vmware/govmomi/cli/flags"
    25  	"github.com/vmware/govmomi/nfc"
    26  	"github.com/vmware/govmomi/ovf"
    27  	"github.com/vmware/govmomi/vim25/soap"
    28  	"github.com/vmware/govmomi/vim25/types"
    29  )
    30  
    31  type ovfx struct {
    32  	*flags.VirtualMachineFlag
    33  
    34  	dest     string
    35  	name     string
    36  	snapshot string
    37  	force    bool
    38  	images   bool
    39  	prefix   bool
    40  	sha      int
    41  	lease    bool
    42  
    43  	mf bytes.Buffer
    44  }
    45  
    46  var sha = map[int]func() hash.Hash{
    47  	1:   sha1.New,
    48  	256: sha256.New,
    49  	512: sha512.New,
    50  }
    51  
    52  func init() {
    53  	cli.Register("export.ovf", &ovfx{})
    54  }
    55  
    56  func (cmd *ovfx) Register(ctx context.Context, f *flag.FlagSet) {
    57  	cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx)
    58  	cmd.VirtualMachineFlag.Register(ctx, f)
    59  
    60  	f.StringVar(&cmd.name, "name", "", "Specifies target name (defaults to source name)")
    61  	f.StringVar(&cmd.snapshot, "snapshot", "", "Specifies a snapshot to export from (supports running VMs)")
    62  	f.BoolVar(&cmd.force, "f", false, "Overwrite existing")
    63  	f.BoolVar(&cmd.images, "i", false, "Include image files (*.{iso,img})")
    64  	f.BoolVar(&cmd.prefix, "prefix", true, "Prepend target name to image filenames if missing")
    65  	f.IntVar(&cmd.sha, "sha", 0, "Generate manifest using SHA 1, 256, 512 or 0 to skip")
    66  	f.BoolVar(&cmd.lease, "lease", false, "Output NFC Lease only")
    67  }
    68  
    69  func (cmd *ovfx) Usage() string {
    70  	return "DIR"
    71  }
    72  
    73  func (cmd *ovfx) Description() string {
    74  	return `Export VM.
    75  
    76  Examples:
    77    govc export.ovf -vm $vm DIR
    78    govc export.ovf -vm $vm -lease`
    79  }
    80  
    81  func (cmd *ovfx) Run(ctx context.Context, f *flag.FlagSet) error {
    82  	if f.NArg() != 1 && !cmd.lease {
    83  		// TODO: output summary similar to ovftool's
    84  		return flag.ErrHelp
    85  	}
    86  
    87  	vm, err := cmd.VirtualMachine()
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	if vm == nil {
    93  		return flag.ErrHelp
    94  	}
    95  
    96  	if cmd.sha != 0 {
    97  		if _, ok := sha[cmd.sha]; !ok {
    98  			return fmt.Errorf("unknown hash: sha%d", cmd.sha)
    99  		}
   100  	}
   101  
   102  	if cmd.name == "" {
   103  		cmd.name = vm.Name()
   104  	}
   105  
   106  	cmd.dest = filepath.Join(f.Arg(0), cmd.name)
   107  
   108  	target := filepath.Join(cmd.dest, cmd.name+".ovf")
   109  
   110  	if !cmd.force {
   111  		if _, err = os.Stat(target); err == nil {
   112  			return fmt.Errorf("file already exists: %s", target)
   113  		}
   114  	}
   115  
   116  	if err = os.MkdirAll(cmd.dest, 0750); err != nil {
   117  		return err
   118  	}
   119  
   120  	lease, err := cmd.requestExport(ctx, vm)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	info, err := lease.Wait(ctx, nil)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	if cmd.lease {
   131  		o, err := lease.Properties(ctx)
   132  		if err != nil {
   133  			return err
   134  		}
   135  
   136  		return cmd.WriteResult(o)
   137  	}
   138  
   139  	u := lease.StartUpdater(ctx, info)
   140  	defer u.Done()
   141  
   142  	cdp := types.OvfCreateDescriptorParams{
   143  		Name: cmd.name,
   144  	}
   145  
   146  	for _, i := range info.Items {
   147  		if !cmd.include(&i) {
   148  			continue
   149  		}
   150  
   151  		if cmd.prefix && !strings.HasPrefix(i.Path, cmd.name) {
   152  			i.Path = cmd.name + "-" + i.Path
   153  		}
   154  
   155  		err = cmd.Download(ctx, lease, i)
   156  		if err != nil {
   157  			return err
   158  		}
   159  
   160  		cdp.OvfFiles = append(cdp.OvfFiles, i.File())
   161  	}
   162  
   163  	if err = lease.Complete(ctx); err != nil {
   164  		return err
   165  	}
   166  
   167  	m := ovf.NewManager(vm.Client())
   168  
   169  	desc, err := m.CreateDescriptor(ctx, vm, cdp)
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	file, err := os.Create(target)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	var w io.Writer = file
   180  	h, ok := cmd.newHash()
   181  	if ok {
   182  		w = io.MultiWriter(file, h)
   183  	}
   184  
   185  	_, err = io.WriteString(w, desc.OvfDescriptor)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	if err = file.Close(); err != nil {
   191  		return err
   192  	}
   193  
   194  	if cmd.sha == 0 {
   195  		return nil
   196  	}
   197  
   198  	cmd.addHash(filepath.Base(target), h)
   199  
   200  	file, err = os.Create(filepath.Join(cmd.dest, cmd.name+".mf"))
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	_, err = io.Copy(file, &cmd.mf)
   206  	if err != nil {
   207  		return err
   208  	}
   209  
   210  	return file.Close()
   211  }
   212  
   213  func (cmd *ovfx) requestExport(ctx context.Context, vm *object.VirtualMachine) (*nfc.Lease, error) {
   214  	if cmd.snapshot != "" {
   215  		snapRef, err := vm.FindSnapshot(ctx, cmd.snapshot)
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  		return vm.ExportSnapshot(ctx, snapRef)
   220  	}
   221  	return vm.Export(ctx)
   222  }
   223  
   224  func (cmd *ovfx) include(item *nfc.FileItem) bool {
   225  	if cmd.images {
   226  		return true
   227  	}
   228  
   229  	return filepath.Ext(item.Path) == ".vmdk"
   230  }
   231  
   232  func (cmd *ovfx) newHash() (hash.Hash, bool) {
   233  	if h, ok := sha[cmd.sha]; ok {
   234  		return h(), true
   235  	}
   236  
   237  	return nil, false
   238  }
   239  
   240  func (cmd *ovfx) addHash(p string, h hash.Hash) {
   241  	_, _ = fmt.Fprintf(&cmd.mf, "SHA%d(%s)= %x\n", cmd.sha, p, h.Sum(nil))
   242  }
   243  
   244  func (cmd *ovfx) Download(ctx context.Context, lease *nfc.Lease, item nfc.FileItem) error {
   245  	path := filepath.Join(cmd.dest, item.Path)
   246  
   247  	logger := cmd.ProgressLogger(fmt.Sprintf("Downloading %s... ", item.Path))
   248  	defer logger.Wait()
   249  
   250  	opts := soap.Download{
   251  		Progress: logger,
   252  	}
   253  
   254  	if h, ok := cmd.newHash(); ok {
   255  		opts.Writer = h
   256  
   257  		defer cmd.addHash(item.Path, h)
   258  	}
   259  
   260  	return lease.DownloadFile(ctx, path, item, opts)
   261  }