github.phpd.cn/hashicorp/packer@v1.3.2/builder/hyperv/iso/builder.go (about) 1 package iso 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "os" 8 "path/filepath" 9 "strings" 10 11 hypervcommon "github.com/hashicorp/packer/builder/hyperv/common" 12 "github.com/hashicorp/packer/common" 13 "github.com/hashicorp/packer/common/bootcommand" 14 powershell "github.com/hashicorp/packer/common/powershell" 15 "github.com/hashicorp/packer/common/powershell/hyperv" 16 "github.com/hashicorp/packer/helper/communicator" 17 "github.com/hashicorp/packer/helper/config" 18 "github.com/hashicorp/packer/helper/multistep" 19 "github.com/hashicorp/packer/packer" 20 "github.com/hashicorp/packer/template/interpolate" 21 ) 22 23 const ( 24 DefaultDiskSize = 40 * 1024 // ~40GB 25 MinDiskSize = 256 // 256MB 26 MaxDiskSize = 64 * 1024 * 1024 // 64TB 27 MaxVHDSize = 2040 * 1024 // 2040GB 28 29 DefaultDiskBlockSize = 32 // 32MB 30 MinDiskBlockSize = 1 // 1MB 31 MaxDiskBlockSize = 256 // 256MB 32 33 DefaultRamSize = 1 * 1024 // 1GB 34 MinRamSize = 32 // 32MB 35 MaxRamSize = 32 * 1024 // 32GB 36 MinNestedVirtualizationRamSize = 4 * 1024 // 4GB 37 38 LowRam = 256 // 256MB 39 40 DefaultUsername = "" 41 DefaultPassword = "" 42 ) 43 44 // Builder implements packer.Builder and builds the actual Hyperv 45 // images. 46 type Builder struct { 47 config Config 48 runner multistep.Runner 49 } 50 51 type Config struct { 52 common.PackerConfig `mapstructure:",squash"` 53 common.HTTPConfig `mapstructure:",squash"` 54 common.ISOConfig `mapstructure:",squash"` 55 common.FloppyConfig `mapstructure:",squash"` 56 bootcommand.BootConfig `mapstructure:",squash"` 57 hypervcommon.OutputConfig `mapstructure:",squash"` 58 hypervcommon.SSHConfig `mapstructure:",squash"` 59 hypervcommon.ShutdownConfig `mapstructure:",squash"` 60 61 // The size, in megabytes, of the hard disk to create for the VM. 62 // By default, this is 130048 (about 127 GB). 63 DiskSize uint `mapstructure:"disk_size"` 64 65 // The size, in megabytes, of the block size used to create the hard disk. 66 // By default, this is 32768 (about 32 MB) 67 DiskBlockSize uint `mapstructure:"disk_block_size"` 68 69 // The size, in megabytes, of the computer memory in the VM. 70 // By default, this is 1024 (about 1 GB). 71 RamSize uint `mapstructure:"ram_size"` 72 73 // 74 SecondaryDvdImages []string `mapstructure:"secondary_iso_images"` 75 76 // Should integration services iso be mounted 77 GuestAdditionsMode string `mapstructure:"guest_additions_mode"` 78 79 // The path to the integration services iso 80 GuestAdditionsPath string `mapstructure:"guest_additions_path"` 81 82 // This is the name of the new virtual machine. 83 // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. 84 VMName string `mapstructure:"vm_name"` 85 86 SwitchName string `mapstructure:"switch_name"` 87 SwitchVlanId string `mapstructure:"switch_vlan_id"` 88 MacAddress string `mapstructure:"mac_address"` 89 VlanId string `mapstructure:"vlan_id"` 90 Cpu uint `mapstructure:"cpu"` 91 Generation uint `mapstructure:"generation"` 92 EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"` 93 EnableDynamicMemory bool `mapstructure:"enable_dynamic_memory"` 94 EnableSecureBoot bool `mapstructure:"enable_secure_boot"` 95 SecureBootTemplate string `mapstructure:"secure_boot_template"` 96 EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"` 97 TempPath string `mapstructure:"temp_path"` 98 99 Communicator string `mapstructure:"communicator"` 100 101 AdditionalDiskSize []uint `mapstructure:"disk_additional_size"` 102 103 SkipCompaction bool `mapstructure:"skip_compaction"` 104 105 SkipExport bool `mapstructure:"skip_export"` 106 107 // Use differencing disk 108 DifferencingDisk bool `mapstructure:"differencing_disk"` 109 110 // Create the VM with a Fixed VHD format disk instead of Dynamic VHDX 111 FixedVHD bool `mapstructure:"use_fixed_vhd_format"` 112 113 Headless bool `mapstructure:"headless"` 114 115 ctx interpolate.Context 116 } 117 118 // Prepare processes the build configuration parameters. 119 func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 120 err := config.Decode(&b.config, &config.DecodeOpts{ 121 Interpolate: true, 122 InterpolateContext: &b.config.ctx, 123 InterpolateFilter: &interpolate.RenderFilter{ 124 Exclude: []string{ 125 "boot_command", 126 }, 127 }, 128 }, raws...) 129 if err != nil { 130 return nil, err 131 } 132 133 // Accumulate any errors and warnings 134 var errs *packer.MultiError 135 warnings := make([]string, 0) 136 137 isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx) 138 warnings = append(warnings, isoWarnings...) 139 errs = packer.MultiErrorAppend(errs, isoErrs...) 140 141 errs = packer.MultiErrorAppend(errs, b.config.BootConfig.Prepare(&b.config.ctx)...) 142 errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) 143 errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...) 144 errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...) 145 errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...) 146 errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...) 147 148 if len(b.config.ISOConfig.ISOUrls) < 1 || 149 (strings.ToLower(filepath.Ext(b.config.ISOConfig.ISOUrls[0])) != ".vhd" && 150 strings.ToLower(filepath.Ext(b.config.ISOConfig.ISOUrls[0])) != ".vhdx") { 151 //We only create a new hard drive if an existing one to copy from does not exist 152 err = b.checkDiskSize() 153 if err != nil { 154 errs = packer.MultiErrorAppend(errs, err) 155 } 156 } 157 158 err = b.checkDiskBlockSize() 159 if err != nil { 160 errs = packer.MultiErrorAppend(errs, err) 161 } 162 163 err = b.checkRamSize() 164 if err != nil { 165 errs = packer.MultiErrorAppend(errs, err) 166 } 167 168 if b.config.VMName == "" { 169 b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) 170 } 171 172 log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) 173 174 if b.config.SwitchName == "" { 175 b.config.SwitchName = b.detectSwitchName() 176 } 177 178 if b.config.Cpu < 1 { 179 b.config.Cpu = 1 180 } 181 182 if b.config.Generation < 1 || b.config.Generation > 2 { 183 b.config.Generation = 1 184 } 185 186 if b.config.Generation == 2 { 187 if len(b.config.FloppyFiles) > 0 || len(b.config.FloppyDirectories) > 0 { 188 err = errors.New("Generation 2 vms don't support floppy drives. Use ISO image instead.") 189 errs = packer.MultiErrorAppend(errs, err) 190 } 191 } 192 193 if len(b.config.AdditionalDiskSize) > 64 { 194 err = errors.New("VM's currently support a maximum of 64 additional SCSI attached disks.") 195 errs = packer.MultiErrorAppend(errs, err) 196 } 197 198 log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) 199 log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) 200 201 // Errors 202 203 if b.config.GuestAdditionsMode == "" { 204 if b.config.GuestAdditionsPath != "" { 205 b.config.GuestAdditionsMode = "attach" 206 } else { 207 b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso" 208 209 if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { 210 if err != nil { 211 b.config.GuestAdditionsPath = "" 212 b.config.GuestAdditionsMode = "none" 213 } else { 214 b.config.GuestAdditionsMode = "attach" 215 } 216 } 217 } 218 } 219 220 if b.config.GuestAdditionsPath == "" && b.config.GuestAdditionsMode == "attach" { 221 b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso" 222 223 if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { 224 if err != nil { 225 b.config.GuestAdditionsPath = "" 226 } 227 } 228 } 229 230 for _, isoPath := range b.config.SecondaryDvdImages { 231 if _, err := os.Stat(isoPath); os.IsNotExist(err) { 232 if err != nil { 233 errs = packer.MultiErrorAppend( 234 errs, fmt.Errorf("Secondary Dvd image does not exist: %s", err)) 235 } 236 } 237 } 238 239 numberOfIsos := len(b.config.SecondaryDvdImages) 240 241 if b.config.GuestAdditionsMode == "attach" { 242 if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { 243 if err != nil { 244 errs = packer.MultiErrorAppend( 245 errs, fmt.Errorf("Guest additions iso does not exist: %s", err)) 246 } 247 } 248 249 numberOfIsos = numberOfIsos + 1 250 } 251 252 if b.config.Generation < 2 && numberOfIsos > 2 { 253 if b.config.GuestAdditionsMode == "attach" { 254 errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, "+ 255 "so we can't support guest additions and these secondary dvds: %s", 256 strings.Join(b.config.SecondaryDvdImages, ", "))) 257 } else { 258 errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, "+ 259 "so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) 260 } 261 } else if b.config.Generation > 1 && len(b.config.SecondaryDvdImages) > 16 { 262 if b.config.GuestAdditionsMode == "attach" { 263 errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available "+ 264 "for scsi (limited to 16), so we can't support guest additions and these secondary dvds: %s", 265 strings.Join(b.config.SecondaryDvdImages, ", "))) 266 } else { 267 errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available "+ 268 "for scsi (limited to 16), so we can't support these secondary dvds: %s", 269 strings.Join(b.config.SecondaryDvdImages, ", "))) 270 } 271 } 272 273 if b.config.EnableVirtualizationExtensions { 274 hasVirtualMachineVirtualizationExtensions, err := powershell.HasVirtualMachineVirtualizationExtensions() 275 if err != nil { 276 errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting virtual machine virtualization "+ 277 "extensions support: %s", err)) 278 } else { 279 if !hasVirtualMachineVirtualizationExtensions { 280 errs = packer.MultiErrorAppend(errs, fmt.Errorf("This version of Hyper-V does not support "+ 281 "virtual machine virtualization extension. Please use Windows 10 or Windows Server "+ 282 "2016 or newer.")) 283 } 284 } 285 } 286 287 if b.config.Generation > 1 && b.config.FixedVHD { 288 err = errors.New("Fixed VHD disks are only supported on Generation 1 virtual machines.") 289 errs = packer.MultiErrorAppend(errs, err) 290 } 291 292 if !b.config.SkipCompaction && b.config.FixedVHD { 293 err = errors.New("Fixed VHD disks do not support compaction.") 294 errs = packer.MultiErrorAppend(errs, err) 295 } 296 297 if b.config.DifferencingDisk && b.config.FixedVHD { 298 err = errors.New("Fixed VHD disks are not supported with differencing disks.") 299 errs = packer.MultiErrorAppend(errs, err) 300 } 301 302 // Warnings 303 304 if b.config.ShutdownCommand == "" { 305 warnings = append(warnings, 306 "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ 307 "will forcibly halt the virtual machine, which may result in data loss.") 308 } 309 310 warning := b.checkHostAvailableMemory() 311 if warning != "" { 312 warnings = appendWarnings(warnings, warning) 313 } 314 315 if b.config.EnableVirtualizationExtensions { 316 if b.config.EnableDynamicMemory { 317 warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, " + 318 "dynamic memory should not be allowed.") 319 warnings = appendWarnings(warnings, warning) 320 } 321 322 if !b.config.EnableMacSpoofing { 323 warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, " + 324 "mac spoofing should be allowed.") 325 warnings = appendWarnings(warnings, warning) 326 } 327 328 if b.config.RamSize < MinNestedVirtualizationRamSize { 329 warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, " + 330 "there should be 4GB or more memory set for the vm, otherwise Hyper-V may fail to start " + 331 "any nested VMs.") 332 warnings = appendWarnings(warnings, warning) 333 } 334 } 335 336 if b.config.SwitchVlanId != "" { 337 if b.config.SwitchVlanId != b.config.VlanId { 338 warning = fmt.Sprintf("Switch network adaptor vlan should match virtual machine network adaptor " + 339 "vlan. The switch will not be able to see traffic from the VM.") 340 warnings = appendWarnings(warnings, warning) 341 } 342 } 343 344 if errs != nil && len(errs.Errors) > 0 { 345 return warnings, errs 346 } 347 348 return warnings, nil 349 } 350 351 // Run executes a Packer build and returns a packer.Artifact representing 352 // a Hyperv appliance. 353 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 354 // Create the driver that we'll use to communicate with Hyperv 355 driver, err := hypervcommon.NewHypervPS4Driver() 356 if err != nil { 357 return nil, fmt.Errorf("Failed creating Hyper-V driver: %s", err) 358 } 359 360 // Set up the state. 361 state := new(multistep.BasicStateBag) 362 state.Put("cache", cache) 363 state.Put("config", &b.config) 364 state.Put("debug", b.config.PackerDebug) 365 state.Put("driver", driver) 366 state.Put("hook", hook) 367 state.Put("ui", ui) 368 369 steps := []multistep.Step{ 370 &hypervcommon.StepCreateBuildDir{ 371 TempPath: b.config.TempPath, 372 }, 373 &hypervcommon.StepOutputDir{ 374 Force: b.config.PackerForce, 375 Path: b.config.OutputDir, 376 }, 377 &common.StepDownload{ 378 Checksum: b.config.ISOChecksum, 379 ChecksumType: b.config.ISOChecksumType, 380 Description: "ISO", 381 ResultKey: "iso_path", 382 Url: b.config.ISOUrls, 383 Extension: b.config.TargetExtension, 384 TargetPath: b.config.TargetPath, 385 }, 386 &common.StepCreateFloppy{ 387 Files: b.config.FloppyConfig.FloppyFiles, 388 Directories: b.config.FloppyConfig.FloppyDirectories, 389 }, 390 &common.StepHTTPServer{ 391 HTTPDir: b.config.HTTPDir, 392 HTTPPortMin: b.config.HTTPPortMin, 393 HTTPPortMax: b.config.HTTPPortMax, 394 }, 395 &hypervcommon.StepCreateSwitch{ 396 SwitchName: b.config.SwitchName, 397 }, 398 &hypervcommon.StepCreateVM{ 399 VMName: b.config.VMName, 400 SwitchName: b.config.SwitchName, 401 RamSize: b.config.RamSize, 402 DiskSize: b.config.DiskSize, 403 DiskBlockSize: b.config.DiskBlockSize, 404 Generation: b.config.Generation, 405 Cpu: b.config.Cpu, 406 EnableMacSpoofing: b.config.EnableMacSpoofing, 407 EnableDynamicMemory: b.config.EnableDynamicMemory, 408 EnableSecureBoot: b.config.EnableSecureBoot, 409 SecureBootTemplate: b.config.SecureBootTemplate, 410 EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions, 411 AdditionalDiskSize: b.config.AdditionalDiskSize, 412 DifferencingDisk: b.config.DifferencingDisk, 413 MacAddress: b.config.MacAddress, 414 FixedVHD: b.config.FixedVHD, 415 }, 416 &hypervcommon.StepEnableIntegrationService{}, 417 418 &hypervcommon.StepMountDvdDrive{ 419 Generation: b.config.Generation, 420 }, 421 &hypervcommon.StepMountFloppydrive{ 422 Generation: b.config.Generation, 423 }, 424 425 &hypervcommon.StepMountGuestAdditions{ 426 GuestAdditionsMode: b.config.GuestAdditionsMode, 427 GuestAdditionsPath: b.config.GuestAdditionsPath, 428 Generation: b.config.Generation, 429 }, 430 431 &hypervcommon.StepMountSecondaryDvdImages{ 432 IsoPaths: b.config.SecondaryDvdImages, 433 Generation: b.config.Generation, 434 }, 435 436 &hypervcommon.StepConfigureVlan{ 437 VlanId: b.config.VlanId, 438 SwitchVlanId: b.config.SwitchVlanId, 439 }, 440 441 &hypervcommon.StepRun{ 442 Headless: b.config.Headless, 443 }, 444 445 &hypervcommon.StepTypeBootCommand{ 446 BootCommand: b.config.FlatBootCommand(), 447 BootWait: b.config.BootWait, 448 SwitchName: b.config.SwitchName, 449 Ctx: b.config.ctx, 450 GroupInterval: b.config.BootConfig.BootGroupInterval, 451 }, 452 453 // configure the communicator ssh, winrm 454 &communicator.StepConnect{ 455 Config: &b.config.SSHConfig.Comm, 456 Host: hypervcommon.CommHost, 457 SSHConfig: b.config.SSHConfig.Comm.SSHConfigFunc(), 458 }, 459 460 // provision requires communicator to be setup 461 &common.StepProvision{}, 462 463 // Remove ephemeral key from authorized_hosts if using SSH communicator 464 &common.StepCleanupTempKeys{ 465 Comm: &b.config.SSHConfig.Comm, 466 }, 467 468 &hypervcommon.StepShutdown{ 469 Command: b.config.ShutdownCommand, 470 Timeout: b.config.ShutdownTimeout, 471 }, 472 473 // wait for the vm to be powered off 474 &hypervcommon.StepWaitForPowerOff{}, 475 476 // remove the secondary dvd images 477 // after we power down 478 &hypervcommon.StepUnmountSecondaryDvdImages{}, 479 &hypervcommon.StepUnmountGuestAdditions{}, 480 &hypervcommon.StepUnmountDvdDrive{}, 481 &hypervcommon.StepUnmountFloppyDrive{ 482 Generation: b.config.Generation, 483 }, 484 &hypervcommon.StepCompactDisk{ 485 SkipCompaction: b.config.SkipCompaction, 486 }, 487 &hypervcommon.StepExportVm{ 488 OutputDir: b.config.OutputDir, 489 SkipExport: b.config.SkipExport, 490 }, 491 &hypervcommon.StepCollateArtifacts{ 492 OutputDir: b.config.OutputDir, 493 SkipExport: b.config.SkipExport, 494 }, 495 496 // the clean up actions for each step will be executed reverse order 497 } 498 499 // Run the steps. 500 b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) 501 b.runner.Run(state) 502 503 // Report any errors. 504 if rawErr, ok := state.GetOk("error"); ok { 505 return nil, rawErr.(error) 506 } 507 508 // If we were interrupted or cancelled, then just exit. 509 if _, ok := state.GetOk(multistep.StateCancelled); ok { 510 return nil, errors.New("Build was cancelled.") 511 } 512 513 if _, ok := state.GetOk(multistep.StateHalted); ok { 514 return nil, errors.New("Build was halted.") 515 } 516 517 return hypervcommon.NewArtifact(b.config.OutputDir) 518 } 519 520 // Cancel. 521 func (b *Builder) Cancel() { 522 if b.runner != nil { 523 log.Println("Cancelling the step runner...") 524 b.runner.Cancel() 525 } 526 } 527 528 func appendWarnings(slice []string, data ...string) []string { 529 m := len(slice) 530 n := m + len(data) 531 if n > cap(slice) { // if necessary, reallocate 532 // allocate double what's needed, for future growth. 533 newSlice := make([]string, (n+1)*2) 534 copy(newSlice, slice) 535 slice = newSlice 536 } 537 slice = slice[0:n] 538 copy(slice[m:n], data) 539 return slice 540 } 541 542 func (b *Builder) checkDiskSize() error { 543 if b.config.DiskSize == 0 { 544 b.config.DiskSize = DefaultDiskSize 545 } 546 547 log.Println(fmt.Sprintf("%s: %v", "DiskSize", b.config.DiskSize)) 548 549 if b.config.DiskSize < MinDiskSize { 550 return fmt.Errorf("disk_size: Virtual machine requires disk space >= %v GB, but defined: %v", 551 MinDiskSize, b.config.DiskSize/1024) 552 } else if b.config.DiskSize > MaxDiskSize && !b.config.FixedVHD { 553 return fmt.Errorf("disk_size: Virtual machine requires disk space <= %v GB, but defined: %v", 554 MaxDiskSize, b.config.DiskSize/1024) 555 } else if b.config.DiskSize > MaxVHDSize && b.config.FixedVHD { 556 return fmt.Errorf("disk_size: Virtual machine requires disk space <= %v GB, but defined: %v", 557 MaxVHDSize/1024, b.config.DiskSize/1024) 558 } 559 560 return nil 561 } 562 563 func (b *Builder) checkDiskBlockSize() error { 564 if b.config.DiskBlockSize == 0 { 565 b.config.DiskBlockSize = DefaultDiskBlockSize 566 } 567 568 log.Println(fmt.Sprintf("%s: %v", "DiskBlockSize", b.config.DiskBlockSize)) 569 570 if b.config.DiskBlockSize < MinDiskBlockSize { 571 return fmt.Errorf("disk_block_size: Virtual machine requires disk block size >= %v MB, but defined: %v", 572 MinDiskBlockSize, b.config.DiskBlockSize) 573 } else if b.config.DiskBlockSize > MaxDiskBlockSize { 574 return fmt.Errorf("disk_block_size: Virtual machine requires disk block size <= %v MB, but defined: %v", 575 MaxDiskBlockSize, b.config.DiskBlockSize) 576 } 577 578 return nil 579 } 580 581 func (b *Builder) checkRamSize() error { 582 if b.config.RamSize == 0 { 583 b.config.RamSize = DefaultRamSize 584 } 585 586 log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSize)) 587 588 if b.config.RamSize < MinRamSize { 589 return fmt.Errorf("ram_size: Virtual machine requires memory size >= %v MB, but defined: %v", 590 MinRamSize, b.config.RamSize) 591 } else if b.config.RamSize > MaxRamSize { 592 return fmt.Errorf("ram_size: Virtual machine requires memory size <= %v MB, but defined: %v", 593 MaxRamSize, b.config.RamSize) 594 } 595 596 return nil 597 } 598 599 func (b *Builder) checkHostAvailableMemory() string { 600 powershellAvailable, _, _ := powershell.IsPowershellAvailable() 601 602 if powershellAvailable { 603 freeMB := powershell.GetHostAvailableMemory() 604 605 if (freeMB - float64(b.config.RamSize)) < LowRam { 606 return fmt.Sprintf("Hyper-V might fail to create a VM if there is not enough free memory in the system.") 607 } 608 } 609 610 return "" 611 } 612 613 func (b *Builder) detectSwitchName() string { 614 powershellAvailable, _, _ := powershell.IsPowershellAvailable() 615 616 if powershellAvailable { 617 // no switch name, try to get one attached to a online network adapter 618 onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() 619 if onlineSwitchName != "" && err == nil { 620 return onlineSwitchName 621 } 622 } 623 624 return fmt.Sprintf("packer-%s", b.config.PackerBuildName) 625 }