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 }