github.com/vmware/govmomi@v0.37.2/govc/export/ovf.go (about)

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