github.com/vmware/govmomi@v0.37.1/govc/importx/ovf.go (about) 1 /* 2 Copyright (c) 2014-2023 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 importx 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "flag" 24 "fmt" 25 "path" 26 "strings" 27 28 "github.com/vmware/govmomi/find" 29 "github.com/vmware/govmomi/govc/cli" 30 "github.com/vmware/govmomi/govc/flags" 31 "github.com/vmware/govmomi/nfc" 32 "github.com/vmware/govmomi/object" 33 "github.com/vmware/govmomi/ovf" 34 "github.com/vmware/govmomi/vim25" 35 "github.com/vmware/govmomi/vim25/soap" 36 "github.com/vmware/govmomi/vim25/types" 37 ) 38 39 type ovfx struct { 40 *flags.DatastoreFlag 41 *flags.HostSystemFlag 42 *flags.OutputFlag 43 *flags.ResourcePoolFlag 44 *flags.FolderFlag 45 46 *ArchiveFlag 47 *OptionsFlag 48 49 Name string 50 VerifyManifest bool 51 Hidden bool 52 53 Client *vim25.Client 54 Datacenter *object.Datacenter 55 Datastore *object.Datastore 56 ResourcePool *object.ResourcePool 57 } 58 59 func init() { 60 cli.Register("import.ovf", &ovfx{}) 61 } 62 63 func (cmd *ovfx) Register(ctx context.Context, f *flag.FlagSet) { 64 cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx) 65 cmd.DatastoreFlag.Register(ctx, f) 66 cmd.HostSystemFlag, ctx = flags.NewHostSystemFlag(ctx) 67 cmd.HostSystemFlag.Register(ctx, f) 68 cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx) 69 cmd.OutputFlag.Register(ctx, f) 70 cmd.ResourcePoolFlag, ctx = flags.NewResourcePoolFlag(ctx) 71 cmd.ResourcePoolFlag.Register(ctx, f) 72 cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx) 73 cmd.FolderFlag.Register(ctx, f) 74 75 cmd.ArchiveFlag, ctx = newArchiveFlag(ctx) 76 cmd.ArchiveFlag.Register(ctx, f) 77 cmd.OptionsFlag, ctx = newOptionsFlag(ctx) 78 cmd.OptionsFlag.Register(ctx, f) 79 80 f.StringVar(&cmd.Name, "name", "", "Name to use for new entity") 81 f.BoolVar(&cmd.VerifyManifest, "m", false, "Verify checksum of uploaded files against manifest (.mf)") 82 f.BoolVar(&cmd.Hidden, "hidden", false, "Enable hidden properties") 83 } 84 85 func (cmd *ovfx) Process(ctx context.Context) error { 86 if err := cmd.DatastoreFlag.Process(ctx); err != nil { 87 return err 88 } 89 if err := cmd.HostSystemFlag.Process(ctx); err != nil { 90 return err 91 } 92 if err := cmd.OutputFlag.Process(ctx); err != nil { 93 return err 94 } 95 if err := cmd.ResourcePoolFlag.Process(ctx); err != nil { 96 return err 97 } 98 if err := cmd.ArchiveFlag.Process(ctx); err != nil { 99 return err 100 } 101 if err := cmd.OptionsFlag.Process(ctx); err != nil { 102 return err 103 } 104 if err := cmd.FolderFlag.Process(ctx); err != nil { 105 return err 106 } 107 return nil 108 } 109 110 func (cmd *ovfx) Usage() string { 111 return "PATH_TO_OVF" 112 } 113 114 func (cmd *ovfx) Run(ctx context.Context, f *flag.FlagSet) error { 115 fpath, err := cmd.Prepare(f) 116 if err != nil { 117 return err 118 } 119 120 archive := &FileArchive{Path: fpath} 121 archive.Client = cmd.Client 122 123 cmd.Archive = archive 124 125 moref, err := cmd.Import(fpath) 126 if err != nil { 127 return err 128 } 129 130 vm := object.NewVirtualMachine(cmd.Client, *moref) 131 return cmd.Deploy(vm, cmd.OutputFlag) 132 } 133 134 func (cmd *ovfx) Prepare(f *flag.FlagSet) (string, error) { 135 var err error 136 137 args := f.Args() 138 if len(args) != 1 { 139 return "", errors.New("no file specified") 140 } 141 142 cmd.Client, err = cmd.DatastoreFlag.Client() 143 if err != nil { 144 return "", err 145 } 146 147 cmd.Datacenter, err = cmd.DatastoreFlag.Datacenter() 148 if err != nil { 149 return "", err 150 } 151 152 cmd.Datastore, err = cmd.DatastoreFlag.Datastore() 153 if err != nil { 154 return "", err 155 } 156 157 cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePoolIfSpecified() 158 if err != nil { 159 return "", err 160 } 161 162 return f.Arg(0), nil 163 } 164 165 func (cmd *ovfx) Map(op []Property) (p []types.KeyValue) { 166 for _, v := range op { 167 p = append(p, types.KeyValue{ 168 Key: v.Key, 169 Value: v.Value, 170 }) 171 } 172 173 return 174 } 175 176 func (cmd *ovfx) validateNetwork(e *ovf.Envelope, net Network) { 177 var names []string 178 179 if e.Network != nil { 180 for _, n := range e.Network.Networks { 181 if n.Name == net.Name { 182 return 183 } 184 names = append(names, n.Name) 185 } 186 } 187 188 _, _ = cmd.Log(fmt.Sprintf("Warning: invalid NetworkMapping.Name=%q, valid names=%s\n", net.Name, names)) 189 } 190 191 func (cmd *ovfx) NetworkMap(e *ovf.Envelope) ([]types.OvfNetworkMapping, error) { 192 ctx := context.TODO() 193 finder, err := cmd.DatastoreFlag.Finder() 194 if err != nil { 195 return nil, err 196 } 197 198 var nmap []types.OvfNetworkMapping 199 for _, m := range cmd.Options.NetworkMapping { 200 if m.Network == "" { 201 continue // Not set, let vSphere choose the default network 202 } 203 cmd.validateNetwork(e, m) 204 205 var ref types.ManagedObjectReference 206 207 net, err := finder.Network(ctx, m.Network) 208 if err != nil { 209 switch err.(type) { 210 case *find.NotFoundError: 211 if !ref.FromString(m.Network) { 212 return nil, err 213 } // else this is a raw MO ref 214 default: 215 return nil, err 216 } 217 } else { 218 ref = net.Reference() 219 } 220 221 nmap = append(nmap, types.OvfNetworkMapping{ 222 Name: m.Name, 223 Network: ref, 224 }) 225 } 226 227 return nmap, err 228 } 229 230 func (cmd *ovfx) Import(fpath string) (*types.ManagedObjectReference, error) { 231 ctx := context.TODO() 232 233 o, err := cmd.ReadOvf(fpath) 234 if err != nil { 235 return nil, err 236 } 237 238 e, err := cmd.ReadEnvelope(o) 239 if err != nil { 240 return nil, fmt.Errorf("failed to parse ovf: %s", err) 241 } 242 243 name := "Govc Virtual Appliance" 244 if e.VirtualSystem != nil { 245 name = e.VirtualSystem.ID 246 if e.VirtualSystem.Name != nil { 247 name = *e.VirtualSystem.Name 248 } 249 250 if cmd.Hidden { 251 // TODO: userConfigurable is optional and defaults to false, so we should *add* userConfigurable=true 252 // if not set for a Property. But, there'd be a bunch more work involved to preserve other data in doing 253 // a complete xml.Marshal of the .ovf 254 o = bytes.ReplaceAll(o, []byte(`userConfigurable="false"`), []byte(`userConfigurable="true"`)) 255 } 256 } 257 258 // Override name from options if specified 259 if cmd.Options.Name != nil { 260 name = *cmd.Options.Name 261 } 262 263 // Override name from arguments if specified 264 if cmd.Name != "" { 265 name = cmd.Name 266 } 267 268 nmap, err := cmd.NetworkMap(e) 269 if err != nil { 270 return nil, err 271 } 272 273 cisp := types.OvfCreateImportSpecParams{ 274 DiskProvisioning: cmd.Options.DiskProvisioning, 275 EntityName: name, 276 IpAllocationPolicy: cmd.Options.IPAllocationPolicy, 277 IpProtocol: cmd.Options.IPProtocol, 278 OvfManagerCommonParams: types.OvfManagerCommonParams{ 279 DeploymentOption: cmd.Options.Deployment, 280 Locale: "US"}, 281 PropertyMapping: cmd.Map(cmd.Options.PropertyMapping), 282 NetworkMapping: nmap, 283 } 284 285 host, err := cmd.HostSystemIfSpecified() 286 if err != nil { 287 return nil, err 288 } 289 290 if cmd.ResourcePool == nil { 291 if host == nil { 292 cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool() 293 } else { 294 cmd.ResourcePool, err = host.ResourcePool(ctx) 295 } 296 if err != nil { 297 return nil, err 298 } 299 } 300 301 m := ovf.NewManager(cmd.Client) 302 spec, err := m.CreateImportSpec(ctx, string(o), cmd.ResourcePool, cmd.Datastore, cisp) 303 if err != nil { 304 return nil, err 305 } 306 if spec.Error != nil { 307 return nil, errors.New(spec.Error[0].LocalizedMessage) 308 } 309 if spec.Warning != nil { 310 for _, w := range spec.Warning { 311 _, _ = cmd.Log(fmt.Sprintf("Warning: %s\n", w.LocalizedMessage)) 312 } 313 } 314 315 if cmd.Options.Annotation != "" { 316 switch s := spec.ImportSpec.(type) { 317 case *types.VirtualMachineImportSpec: 318 s.ConfigSpec.Annotation = cmd.Options.Annotation 319 case *types.VirtualAppImportSpec: 320 s.VAppConfigSpec.Annotation = cmd.Options.Annotation 321 } 322 } 323 324 var folder *object.Folder 325 // The folder argument must not be set on a VM in a vApp, otherwise causes 326 // InvalidArgument fault: A specified parameter was not correct: pool 327 if cmd.ResourcePool.Reference().Type != "VirtualApp" { 328 folder, err = cmd.FolderOrDefault("vm") 329 if err != nil { 330 return nil, err 331 } 332 } 333 334 if cmd.VerifyManifest { 335 err = cmd.readManifest(fpath) 336 if err != nil { 337 return nil, err 338 } 339 } 340 341 lease, err := cmd.ResourcePool.ImportVApp(ctx, spec.ImportSpec, folder, host) 342 if err != nil { 343 return nil, err 344 } 345 346 info, err := lease.Wait(ctx, spec.FileItem) 347 if err != nil { 348 return nil, err 349 } 350 351 u := lease.StartUpdater(ctx, info) 352 defer u.Done() 353 354 for _, i := range info.Items { 355 err = cmd.Upload(ctx, lease, i) 356 if err != nil { 357 return nil, err 358 } 359 } 360 361 return &info.Entity, lease.Complete(ctx) 362 } 363 364 func (cmd *ovfx) Upload(ctx context.Context, lease *nfc.Lease, item nfc.FileItem) error { 365 file := item.Path 366 367 f, size, err := cmd.Open(file) 368 if err != nil { 369 return err 370 } 371 defer f.Close() 372 373 logger := cmd.ProgressLogger(fmt.Sprintf("Uploading %s... ", path.Base(file))) 374 defer logger.Wait() 375 376 opts := soap.Upload{ 377 ContentLength: size, 378 Progress: logger, 379 } 380 381 err = lease.Upload(ctx, item, f, opts) 382 if err != nil { 383 return err 384 } 385 386 if cmd.VerifyManifest { 387 mapImportKeyToKey := func(urls []types.HttpNfcLeaseDeviceUrl, importKey string) string { 388 for _, url := range urls { 389 if url.ImportKey == importKey { 390 return url.Key 391 } 392 } 393 return "" 394 } 395 leaseInfo, err := lease.Wait(ctx, nil) 396 if err != nil { 397 return err 398 } 399 return cmd.validateChecksum(ctx, lease, file, mapImportKeyToKey(leaseInfo.DeviceUrl, item.DeviceId)) 400 } 401 return nil 402 } 403 404 func (cmd *ovfx) validateChecksum(ctx context.Context, lease *nfc.Lease, file string, key string) error { 405 sum, found := cmd.manifest[file] 406 if !found { 407 msg := fmt.Sprintf("missing checksum for %v in manifest file", file) 408 return errors.New(msg) 409 } 410 // Perform the checksum match eagerly, after each file upload, instead 411 // of after uploading all the files, to provide fail-fast behavior. 412 // (Trade-off here is multiple GetManifest() API calls to the server.) 413 manifests, err := lease.GetManifest(ctx) 414 if err != nil { 415 return err 416 } 417 for _, m := range manifests { 418 if m.Key == key { 419 // Compare server-side computed checksum of uploaded file 420 // against the client's manifest entry (assuming client's 421 // manifest has correct checksums - client doesn't compute 422 // checksum of the file before uploading). 423 424 // Try matching sha1 first (newer versions have moved to sha256). 425 if strings.ToUpper(sum.Algorithm) == "SHA1" { 426 if sum.Checksum != m.Sha1 { 427 msg := fmt.Sprintf("manifest checksum %v mismatch with uploaded checksum %v for file %v", 428 sum.Checksum, m.Sha1, file) 429 return errors.New(msg) 430 } 431 // Uploaded file checksum computed by server matches with local manifest entry. 432 return nil 433 } 434 // If not sha1, check for other types (in a separate field). 435 if !strings.EqualFold(sum.Algorithm, m.ChecksumType) { 436 msg := fmt.Sprintf("manifest checksum type %v mismatch with uploaded checksum type %v for file %v", 437 sum.Algorithm, m.ChecksumType, file) 438 return errors.New(msg) 439 } 440 if !strings.EqualFold(sum.Checksum, m.Checksum) { 441 msg := fmt.Sprintf("manifest checksum %v mismatch with uploaded checksum %v for file %v", 442 sum.Checksum, m.Checksum, file) 443 return errors.New(msg) 444 } 445 // Uploaded file checksum computed by server matches with local manifest entry. 446 return nil 447 } 448 } 449 msg := fmt.Sprintf("missing manifest entry on server for uploaded file %v (key %v), manifests=%#v", file, key, manifests) 450 return errors.New(msg) 451 }