github.com/vmware/govmomi@v0.37.1/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 }