github.phpd.cn/hashicorp/packer@v1.3.2/builder/azure/arm/config.go (about) 1 package arm 2 3 import ( 4 "crypto/rand" 5 "crypto/rsa" 6 "crypto/x509" 7 "crypto/x509/pkix" 8 "encoding/base64" 9 "encoding/json" 10 "fmt" 11 "io/ioutil" 12 "math/big" 13 "regexp" 14 "strings" 15 "time" 16 17 "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute" 18 "github.com/Azure/go-autorest/autorest/azure" 19 "github.com/Azure/go-autorest/autorest/to" 20 "github.com/masterzen/winrm" 21 22 "github.com/hashicorp/packer/builder/azure/common/constants" 23 "github.com/hashicorp/packer/builder/azure/pkcs12" 24 "github.com/hashicorp/packer/common" 25 commonhelper "github.com/hashicorp/packer/helper/common" 26 "github.com/hashicorp/packer/helper/communicator" 27 "github.com/hashicorp/packer/helper/config" 28 "github.com/hashicorp/packer/packer" 29 "github.com/hashicorp/packer/template/interpolate" 30 31 "golang.org/x/crypto/ssh" 32 ) 33 34 const ( 35 DefaultCloudEnvironmentName = "Public" 36 DefaultImageVersion = "latest" 37 DefaultUserName = "packer" 38 DefaultPrivateVirtualNetworkWithPublicIp = false 39 DefaultVMSize = "Standard_A1" 40 ) 41 42 const ( 43 // https://docs.microsoft.com/en-us/azure/architecture/best-practices/naming-conventions#naming-rules-and-restrictions 44 // Regular expressions in Go are not expressive enough, such that the regular expression returned by Azure 45 // can be used (no backtracking). 46 // 47 // -> ^[^_\W][\w-._]{0,79}(?<![-.])$ 48 // 49 // This is not an exhaustive match, but it should be extremely close. 50 validResourceGroupNameRe = "^[^_\\W][\\w-._\\(\\)]{0,89}$" 51 validManagedDiskName = "^[^_\\W][\\w-._)]{0,79}$" 52 ) 53 54 var ( 55 reCaptureContainerName = regexp.MustCompile("^[a-z0-9][a-z0-9\\-]{2,62}$") 56 reCaptureNamePrefix = regexp.MustCompile("^[A-Za-z0-9][A-Za-z0-9_\\-\\.]{0,23}$") 57 reManagedDiskName = regexp.MustCompile(validManagedDiskName) 58 reResourceGroupName = regexp.MustCompile(validResourceGroupNameRe) 59 ) 60 61 type PlanInformation struct { 62 PlanName string `mapstructure:"plan_name"` 63 PlanProduct string `mapstructure:"plan_product"` 64 PlanPublisher string `mapstructure:"plan_publisher"` 65 PlanPromotionCode string `mapstructure:"plan_promotion_code"` 66 } 67 68 type SharedImageGallery struct { 69 Subscription string `mapstructure:"subscription"` 70 ResourceGroup string `mapstructure:"resource_group"` 71 GalleryName string `mapstructure:"gallery_name"` 72 ImageName string `mapstructure:"image_name"` 73 ImageVersion string `mapstructure:"image_version"` 74 } 75 76 type Config struct { 77 common.PackerConfig `mapstructure:",squash"` 78 79 // Authentication via OAUTH 80 ClientID string `mapstructure:"client_id"` 81 ClientSecret string `mapstructure:"client_secret"` 82 ObjectID string `mapstructure:"object_id"` 83 TenantID string `mapstructure:"tenant_id"` 84 SubscriptionID string `mapstructure:"subscription_id"` 85 86 // Capture 87 CaptureNamePrefix string `mapstructure:"capture_name_prefix"` 88 CaptureContainerName string `mapstructure:"capture_container_name"` 89 90 // Shared Gallery 91 SharedGallery SharedImageGallery `mapstructure:"shared_image_gallery"` 92 93 // Compute 94 ImagePublisher string `mapstructure:"image_publisher"` 95 ImageOffer string `mapstructure:"image_offer"` 96 ImageSku string `mapstructure:"image_sku"` 97 ImageVersion string `mapstructure:"image_version"` 98 ImageUrl string `mapstructure:"image_url"` 99 100 CustomManagedImageResourceGroupName string `mapstructure:"custom_managed_image_resource_group_name"` 101 CustomManagedImageName string `mapstructure:"custom_managed_image_name"` 102 customManagedImageID string 103 104 Location string `mapstructure:"location"` 105 VMSize string `mapstructure:"vm_size"` 106 107 ManagedImageResourceGroupName string `mapstructure:"managed_image_resource_group_name"` 108 ManagedImageName string `mapstructure:"managed_image_name"` 109 ManagedImageStorageAccountType string `mapstructure:"managed_image_storage_account_type"` 110 managedImageStorageAccountType compute.StorageAccountTypes 111 manageImageLocation string 112 113 // Deployment 114 AzureTags map[string]*string `mapstructure:"azure_tags"` 115 ResourceGroupName string `mapstructure:"resource_group_name"` 116 StorageAccount string `mapstructure:"storage_account"` 117 TempComputeName string `mapstructure:"temp_compute_name"` 118 TempResourceGroupName string `mapstructure:"temp_resource_group_name"` 119 BuildResourceGroupName string `mapstructure:"build_resource_group_name"` 120 storageAccountBlobEndpoint string 121 CloudEnvironmentName string `mapstructure:"cloud_environment_name"` 122 cloudEnvironment *azure.Environment 123 PrivateVirtualNetworkWithPublicIp bool `mapstructure:"private_virtual_network_with_public_ip"` 124 VirtualNetworkName string `mapstructure:"virtual_network_name"` 125 VirtualNetworkSubnetName string `mapstructure:"virtual_network_subnet_name"` 126 VirtualNetworkResourceGroupName string `mapstructure:"virtual_network_resource_group_name"` 127 CustomDataFile string `mapstructure:"custom_data_file"` 128 customData string 129 PlanInfo PlanInformation `mapstructure:"plan_info"` 130 131 // OS 132 OSType string `mapstructure:"os_type"` 133 OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb"` 134 135 // Additional Disks 136 AdditionalDiskSize []int32 `mapstructure:"disk_additional_size"` 137 138 // Runtime Values 139 UserName string 140 Password string 141 tmpAdminPassword string 142 tmpCertificatePassword string 143 tmpResourceGroupName string 144 tmpComputeName string 145 tmpNicName string 146 tmpPublicIPAddressName string 147 tmpDeploymentName string 148 tmpKeyVaultName string 149 tmpOSDiskName string 150 tmpSubnetName string 151 tmpVirtualNetworkName string 152 tmpWinRMCertificateUrl string 153 154 useDeviceLogin bool 155 156 // Authentication with the VM via SSH 157 sshAuthorizedKey string 158 159 // Authentication with the VM via WinRM 160 winrmCertificate string 161 162 Comm communicator.Config `mapstructure:",squash"` 163 ctx interpolate.Context 164 165 //Cleanup 166 AsyncResourceGroupDelete bool `mapstructure:"async_resourcegroup_delete"` 167 } 168 169 type keyVaultCertificate struct { 170 Data string `json:"data"` 171 DataType string `json:"dataType"` 172 Password string `json:"password,omitempty"` 173 } 174 175 func (c *Config) toVMID() string { 176 var resourceGroupName string 177 if c.tmpResourceGroupName != "" { 178 resourceGroupName = c.tmpResourceGroupName 179 } else { 180 resourceGroupName = c.BuildResourceGroupName 181 } 182 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s", c.SubscriptionID, resourceGroupName, c.tmpComputeName) 183 } 184 185 func (c *Config) isManagedImage() bool { 186 return c.ManagedImageName != "" 187 } 188 189 func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCaptureParameters { 190 return &compute.VirtualMachineCaptureParameters{ 191 DestinationContainerName: &c.CaptureContainerName, 192 VhdPrefix: &c.CaptureNamePrefix, 193 OverwriteVhds: to.BoolPtr(false), 194 } 195 } 196 197 func (c *Config) toImageParameters() *compute.Image { 198 return &compute.Image{ 199 ImageProperties: &compute.ImageProperties{ 200 SourceVirtualMachine: &compute.SubResource{ 201 ID: to.StringPtr(c.toVMID()), 202 }, 203 }, 204 Location: to.StringPtr(c.Location), 205 Tags: c.AzureTags, 206 } 207 } 208 209 func (c *Config) createCertificate() (string, error) { 210 privateKey, err := rsa.GenerateKey(rand.Reader, 2048) 211 if err != nil { 212 err = fmt.Errorf("Failed to Generate Private Key: %s", err) 213 return "", err 214 } 215 216 host := fmt.Sprintf("%s.cloudapp.net", c.tmpComputeName) 217 notBefore := time.Now() 218 notAfter := notBefore.Add(24 * time.Hour) 219 220 serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) 221 if err != nil { 222 err = fmt.Errorf("Failed to Generate Serial Number: %v", err) 223 return "", err 224 } 225 226 template := x509.Certificate{ 227 SerialNumber: serialNumber, 228 Issuer: pkix.Name{ 229 CommonName: host, 230 }, 231 Subject: pkix.Name{ 232 CommonName: host, 233 }, 234 NotBefore: notBefore, 235 NotAfter: notAfter, 236 237 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 238 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 239 BasicConstraintsValid: true, 240 } 241 242 derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) 243 if err != nil { 244 err = fmt.Errorf("Failed to Create Certificate: %s", err) 245 return "", err 246 } 247 248 pfxBytes, err := pkcs12.Encode(derBytes, privateKey, c.tmpCertificatePassword) 249 if err != nil { 250 err = fmt.Errorf("Failed to encode certificate as PFX: %s", err) 251 return "", err 252 } 253 254 keyVaultDescription := keyVaultCertificate{ 255 Data: base64.StdEncoding.EncodeToString(pfxBytes), 256 DataType: "pfx", 257 Password: c.tmpCertificatePassword, 258 } 259 260 bytes, err := json.Marshal(keyVaultDescription) 261 if err != nil { 262 err = fmt.Errorf("Failed to marshal key vault description: %s", err) 263 return "", err 264 } 265 266 return base64.StdEncoding.EncodeToString(bytes), nil 267 } 268 269 func newConfig(raws ...interface{}) (*Config, []string, error) { 270 var c Config 271 c.ctx.Funcs = TemplateFuncs 272 err := config.Decode(&c, &config.DecodeOpts{ 273 Interpolate: true, 274 InterpolateContext: &c.ctx, 275 }, raws...) 276 277 if err != nil { 278 return nil, nil, err 279 } 280 281 provideDefaultValues(&c) 282 setRuntimeValues(&c) 283 setUserNamePassword(&c) 284 err = setCloudEnvironment(&c) 285 if err != nil { 286 return nil, nil, err 287 } 288 289 err = setCustomData(&c) 290 if err != nil { 291 return nil, nil, err 292 } 293 294 // NOTE: if the user did not specify a communicator, then default to both 295 // SSH and WinRM. This is for backwards compatibility because the code did 296 // not specifically force the user to set a communicator. 297 if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "ssh") { 298 err = setSshValues(&c) 299 if err != nil { 300 return nil, nil, err 301 } 302 } 303 304 if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "winrm") { 305 err = setWinRMCertificate(&c) 306 if err != nil { 307 return nil, nil, err 308 } 309 } 310 311 var errs *packer.MultiError 312 errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...) 313 314 assertRequiredParametersSet(&c, errs) 315 assertTagProperties(&c, errs) 316 if errs != nil && len(errs.Errors) > 0 { 317 return nil, nil, errs 318 } 319 320 return &c, nil, nil 321 } 322 323 func setSshValues(c *Config) error { 324 if c.Comm.SSHTimeout == 0 { 325 c.Comm.SSHTimeout = 20 * time.Minute 326 } 327 328 if c.Comm.SSHPrivateKeyFile != "" { 329 privateKeyBytes, err := ioutil.ReadFile(c.Comm.SSHPrivateKeyFile) 330 if err != nil { 331 return err 332 } 333 signer, err := ssh.ParsePrivateKey(privateKeyBytes) 334 if err != nil { 335 return err 336 } 337 338 publicKey := signer.PublicKey() 339 c.sshAuthorizedKey = fmt.Sprintf("%s %s packer Azure Deployment%s", 340 publicKey.Type(), 341 base64.StdEncoding.EncodeToString(publicKey.Marshal()), 342 time.Now().Format(time.RFC3339)) 343 c.Comm.SSHPrivateKey = privateKeyBytes 344 345 } else { 346 sshKeyPair, err := NewOpenSshKeyPair() 347 if err != nil { 348 return err 349 } 350 351 c.sshAuthorizedKey = sshKeyPair.AuthorizedKey() 352 c.Comm.SSHPrivateKey = sshKeyPair.PrivateKey() 353 } 354 355 return nil 356 } 357 358 func setWinRMCertificate(c *Config) error { 359 c.Comm.WinRMTransportDecorator = 360 func() winrm.Transporter { 361 return &winrm.ClientNTLM{} 362 } 363 364 cert, err := c.createCertificate() 365 c.winrmCertificate = cert 366 367 return err 368 } 369 370 func setRuntimeValues(c *Config) { 371 var tempName = NewTempName() 372 373 c.tmpAdminPassword = tempName.AdminPassword 374 // store so that we can access this later during provisioning 375 commonhelper.SetSharedState("winrm_password", c.tmpAdminPassword, c.PackerConfig.PackerBuildName) 376 packer.LogSecretFilter.Set(c.tmpAdminPassword) 377 378 c.tmpCertificatePassword = tempName.CertificatePassword 379 if c.TempComputeName == "" { 380 c.tmpComputeName = tempName.ComputeName 381 } else { 382 c.tmpComputeName = c.TempComputeName 383 } 384 c.tmpDeploymentName = tempName.DeploymentName 385 // Only set tmpResourceGroupName if no name has been specified 386 if c.TempResourceGroupName == "" && c.BuildResourceGroupName == "" { 387 c.tmpResourceGroupName = tempName.ResourceGroupName 388 } else if c.TempResourceGroupName != "" && c.BuildResourceGroupName == "" { 389 c.tmpResourceGroupName = c.TempResourceGroupName 390 } 391 c.tmpNicName = tempName.NicName 392 c.tmpPublicIPAddressName = tempName.PublicIPAddressName 393 c.tmpOSDiskName = tempName.OSDiskName 394 c.tmpSubnetName = tempName.SubnetName 395 c.tmpVirtualNetworkName = tempName.VirtualNetworkName 396 c.tmpKeyVaultName = tempName.KeyVaultName 397 } 398 399 func setUserNamePassword(c *Config) { 400 if c.Comm.SSHUsername == "" { 401 c.Comm.SSHUsername = DefaultUserName 402 } 403 404 c.UserName = c.Comm.SSHUsername 405 406 if c.Comm.SSHPassword != "" { 407 c.Password = c.Comm.SSHPassword 408 } else { 409 c.Password = c.tmpAdminPassword 410 } 411 } 412 413 func setCloudEnvironment(c *Config) error { 414 lookup := map[string]string{ 415 "CHINA": "AzureChinaCloud", 416 "CHINACLOUD": "AzureChinaCloud", 417 "AZURECHINACLOUD": "AzureChinaCloud", 418 419 "GERMAN": "AzureGermanCloud", 420 "GERMANCLOUD": "AzureGermanCloud", 421 "AZUREGERMANCLOUD": "AzureGermanCloud", 422 423 "GERMANY": "AzureGermanCloud", 424 "GERMANYCLOUD": "AzureGermanCloud", 425 "AZUREGERMANYCLOUD": "AzureGermanCloud", 426 427 "PUBLIC": "AzurePublicCloud", 428 "PUBLICCLOUD": "AzurePublicCloud", 429 "AZUREPUBLICCLOUD": "AzurePublicCloud", 430 431 "USGOVERNMENT": "AzureUSGovernmentCloud", 432 "USGOVERNMENTCLOUD": "AzureUSGovernmentCloud", 433 "AZUREUSGOVERNMENTCLOUD": "AzureUSGovernmentCloud", 434 } 435 436 name := strings.ToUpper(c.CloudEnvironmentName) 437 envName, ok := lookup[name] 438 if !ok { 439 return fmt.Errorf("There is no cloud environment matching the name '%s'!", c.CloudEnvironmentName) 440 } 441 442 env, err := azure.EnvironmentFromName(envName) 443 c.cloudEnvironment = &env 444 return err 445 } 446 447 func setCustomData(c *Config) error { 448 if c.CustomDataFile == "" { 449 return nil 450 } 451 452 b, err := ioutil.ReadFile(c.CustomDataFile) 453 if err != nil { 454 return err 455 } 456 457 c.customData = base64.StdEncoding.EncodeToString(b) 458 return nil 459 } 460 461 func provideDefaultValues(c *Config) { 462 if c.VMSize == "" { 463 c.VMSize = DefaultVMSize 464 } 465 466 if c.ManagedImageStorageAccountType == "" { 467 c.managedImageStorageAccountType = compute.StorageAccountTypesStandardLRS 468 } 469 470 if c.ImagePublisher != "" && c.ImageVersion == "" { 471 c.ImageVersion = DefaultImageVersion 472 } 473 474 if c.CloudEnvironmentName == "" { 475 c.CloudEnvironmentName = DefaultCloudEnvironmentName 476 } 477 } 478 479 func assertTagProperties(c *Config, errs *packer.MultiError) { 480 if len(c.AzureTags) > 15 { 481 errs = packer.MultiErrorAppend(errs, fmt.Errorf("a max of 15 tags are supported, but %d were provided", len(c.AzureTags))) 482 } 483 484 for k, v := range c.AzureTags { 485 if len(k) > 512 { 486 errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 512 character limit", k, len(k))) 487 } 488 if len(*v) > 256 { 489 errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", *v, len(*v))) 490 } 491 } 492 } 493 494 func assertRequiredParametersSet(c *Config, errs *packer.MultiError) { 495 ///////////////////////////////////////////// 496 // Authentication via OAUTH 497 498 // Check if device login is being asked for, and is allowed. 499 // 500 // Device login is enabled if the user only defines SubscriptionID and not 501 // ClientID, ClientSecret, and TenantID. 502 // 503 // Device login is not enabled for Windows because the WinRM certificate is 504 // readable by the ObjectID of the App. There may be another way to handle 505 // this case, but I am not currently aware of it - send feedback. 506 isUseDeviceLogin := func(c *Config) bool { 507 508 return c.SubscriptionID != "" && 509 c.ClientID == "" && 510 c.ClientSecret == "" && 511 c.TenantID == "" 512 } 513 514 if isUseDeviceLogin(c) { 515 c.useDeviceLogin = true 516 } else { 517 if c.ClientID == "" { 518 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_id must be specified")) 519 } 520 521 if c.ClientSecret == "" { 522 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_secret must be specified")) 523 } 524 525 if c.SubscriptionID == "" { 526 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A subscription_id must be specified")) 527 } 528 } 529 530 ///////////////////////////////////////////// 531 // Capture 532 if c.CaptureContainerName == "" && c.ManagedImageName == "" { 533 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name or managed_image_name must be specified")) 534 } 535 536 if c.CaptureNamePrefix == "" && c.ManagedImageResourceGroupName == "" { 537 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix or managed_image_resource_group_name must be specified")) 538 } 539 540 if (c.CaptureNamePrefix != "" || c.CaptureContainerName != "") && (c.ManagedImageResourceGroupName != "" || c.ManagedImageName != "") { 541 errs = packer.MultiErrorAppend(errs, fmt.Errorf("Either a VHD or a managed image can be built, but not both. Please specify either capture_container_name and capture_name_prefix or managed_image_resource_group_name and managed_image_name.")) 542 } 543 544 if c.CaptureContainerName != "" { 545 if !reCaptureContainerName.MatchString(c.CaptureContainerName) { 546 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must satisfy the regular expression %q.", reCaptureContainerName.String())) 547 } 548 549 if strings.HasSuffix(c.CaptureContainerName, "-") { 550 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not end with a hyphen, e.g. '-'.")) 551 } 552 553 if strings.Contains(c.CaptureContainerName, "--") { 554 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not contain consecutive hyphens, e.g. '--'.")) 555 } 556 557 if c.CaptureNamePrefix == "" { 558 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must be specified")) 559 } 560 561 if !reCaptureNamePrefix.MatchString(c.CaptureNamePrefix) { 562 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must satisfy the regular expression %q.", reCaptureNamePrefix.String())) 563 } 564 565 if strings.HasSuffix(c.CaptureNamePrefix, "-") || strings.HasSuffix(c.CaptureNamePrefix, ".") { 566 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must not end with a hyphen or period.")) 567 } 568 } 569 570 if c.TempResourceGroupName != "" && c.BuildResourceGroupName != "" { 571 errs = packer.MultiErrorAppend(errs, fmt.Errorf("The settings temp_resource_group_name and build_resource_group_name cannot both be defined. Please define one or neither.")) 572 } 573 574 ///////////////////////////////////////////// 575 // Compute 576 toInt := func(b bool) int { 577 if b { 578 return 1 579 } else { 580 return 0 581 } 582 } 583 584 isImageUrl := c.ImageUrl != "" 585 isCustomManagedImage := c.CustomManagedImageName != "" || c.CustomManagedImageResourceGroupName != "" 586 isSharedGallery := c.SharedGallery.GalleryName != "" 587 isPlatformImage := c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != "" 588 589 countSourceInputs := toInt(isImageUrl) + toInt(isCustomManagedImage) + toInt(isPlatformImage) + toInt(isSharedGallery) 590 591 if countSourceInputs > 1 { 592 errs = packer.MultiErrorAppend(errs, fmt.Errorf("Specify either a VHD (image_url), Image Reference (image_publisher, image_offer, image_sku), a Managed Disk (custom_managed_disk_image_name, custom_managed_disk_resource_group_name), or a Shared Gallery Image (shared_image_gallery)")) 593 } 594 595 if isImageUrl && c.ManagedImageResourceGroupName != "" { 596 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A managed image must be created from a managed image, it cannot be created from a VHD.")) 597 } 598 599 if c.SharedGallery.GalleryName != "" { 600 if c.SharedGallery.Subscription == "" { 601 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A shared_image_gallery.subscription must be specified")) 602 } 603 if c.SharedGallery.ResourceGroup == "" { 604 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A shared_image_gallery.resource_group must be specified")) 605 } 606 if c.SharedGallery.ImageName == "" { 607 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A shared_image_gallery.image_name must be specified")) 608 } 609 if c.CaptureContainerName != "" { 610 errs = packer.MultiErrorAppend(errs, fmt.Errorf("VHD Target [capture_container_name] is not supported when using Shared Image Gallery as source. Use managed_image_resource_group_name instead.")) 611 } 612 if c.CaptureNamePrefix != "" { 613 errs = packer.MultiErrorAppend(errs, fmt.Errorf("VHD Target [capture_name_prefix] is not supported when using Shared Image Gallery as source. Use managed_image_name instead.")) 614 } 615 } else if c.ImageUrl == "" && c.CustomManagedImageName == "" { 616 if c.ImagePublisher == "" { 617 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_publisher must be specified")) 618 } 619 if c.ImageOffer == "" { 620 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_offer must be specified")) 621 } 622 if c.ImageSku == "" { 623 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_sku must be specified")) 624 } 625 } else if c.ImageUrl == "" && c.ImagePublisher == "" { 626 if c.CustomManagedImageResourceGroupName == "" { 627 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An custom_managed_image_resource_group_name must be specified")) 628 } 629 if c.CustomManagedImageName == "" { 630 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A custom_managed_image_name must be specified")) 631 } 632 if c.ManagedImageResourceGroupName == "" { 633 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An managed_image_resource_group_name must be specified")) 634 } 635 if c.ManagedImageName == "" { 636 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An managed_image_name must be specified")) 637 } 638 } else { 639 if c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != "" || c.ImageVersion != "" { 640 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_url must not be specified if image_publisher, image_offer, image_sku, or image_version is specified")) 641 } 642 } 643 644 ///////////////////////////////////////////// 645 // Deployment 646 xor := func(a, b bool) bool { 647 return (a || b) && !(a && b) 648 } 649 650 if !xor((c.StorageAccount != "" || c.ResourceGroupName != ""), (c.ManagedImageName != "" || c.ManagedImageResourceGroupName != "")) { 651 errs = packer.MultiErrorAppend(errs, fmt.Errorf("Specify either a VHD (storage_account and resource_group_name) or Managed Image (managed_image_resource_group_name and managed_image_name) output")) 652 } 653 654 if !xor(c.Location != "", c.BuildResourceGroupName != "") { 655 errs = packer.MultiErrorAppend(errs, fmt.Errorf("Specify either a location to create the resource group in or an existing build_resource_group_name, but not both.")) 656 } 657 658 if c.ManagedImageName == "" && c.ManagedImageResourceGroupName == "" { 659 if c.StorageAccount == "" { 660 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A storage_account must be specified")) 661 } 662 if c.ResourceGroupName == "" { 663 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A resource_group_name must be specified")) 664 } 665 } 666 667 if c.TempResourceGroupName != "" { 668 if ok, err := assertResourceGroupName(c.TempResourceGroupName, "temp_resource_group_name"); !ok { 669 errs = packer.MultiErrorAppend(errs, err) 670 } 671 } 672 673 if c.BuildResourceGroupName != "" { 674 if ok, err := assertResourceGroupName(c.BuildResourceGroupName, "build_resource_group_name"); !ok { 675 errs = packer.MultiErrorAppend(errs, err) 676 } 677 } 678 679 if c.ManagedImageResourceGroupName != "" { 680 if ok, err := assertResourceGroupName(c.ManagedImageResourceGroupName, "managed_image_resource_group_name"); !ok { 681 errs = packer.MultiErrorAppend(errs, err) 682 } 683 } 684 685 if c.ManagedImageName != "" { 686 if ok, err := assertManagedImageName(c.ManagedImageName, "managed_image_name"); !ok { 687 errs = packer.MultiErrorAppend(errs, err) 688 } 689 } 690 691 if c.VirtualNetworkName == "" && c.VirtualNetworkResourceGroupName != "" { 692 errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_resource_group_name is specified, so must virtual_network_name")) 693 } 694 if c.VirtualNetworkName == "" && c.VirtualNetworkSubnetName != "" { 695 errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_subnet_name is specified, so must virtual_network_name")) 696 } 697 698 ///////////////////////////////////////////// 699 // Plan Info 700 if c.PlanInfo.PlanName != "" || c.PlanInfo.PlanProduct != "" || c.PlanInfo.PlanPublisher != "" || c.PlanInfo.PlanPromotionCode != "" { 701 if c.PlanInfo.PlanName == "" || c.PlanInfo.PlanProduct == "" || c.PlanInfo.PlanPublisher == "" { 702 errs = packer.MultiErrorAppend(errs, fmt.Errorf("if either plan_name, plan_product, plan_publisher, or plan_promotion_code are defined then plan_name, plan_product, and plan_publisher must be defined")) 703 } else { 704 if c.AzureTags == nil { 705 c.AzureTags = make(map[string]*string) 706 } 707 708 c.AzureTags["PlanInfo"] = &c.PlanInfo.PlanName 709 c.AzureTags["PlanProduct"] = &c.PlanInfo.PlanProduct 710 c.AzureTags["PlanPublisher"] = &c.PlanInfo.PlanPublisher 711 c.AzureTags["PlanPromotionCode"] = &c.PlanInfo.PlanPromotionCode 712 } 713 } 714 715 ///////////////////////////////////////////// 716 // OS 717 if strings.EqualFold(c.OSType, constants.Target_Linux) { 718 c.OSType = constants.Target_Linux 719 } else if strings.EqualFold(c.OSType, constants.Target_Windows) { 720 c.OSType = constants.Target_Windows 721 } else if c.OSType == "" { 722 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An os_type must be specified")) 723 } else { 724 errs = packer.MultiErrorAppend(errs, fmt.Errorf("The os_type %q is invalid", c.OSType)) 725 } 726 727 switch c.ManagedImageStorageAccountType { 728 case "", string(compute.StorageAccountTypesStandardLRS): 729 c.managedImageStorageAccountType = compute.StorageAccountTypesStandardLRS 730 case string(compute.StorageAccountTypesPremiumLRS): 731 c.managedImageStorageAccountType = compute.StorageAccountTypesPremiumLRS 732 default: 733 errs = packer.MultiErrorAppend(errs, fmt.Errorf("The managed_image_storage_account_type %q is invalid", c.ManagedImageStorageAccountType)) 734 } 735 } 736 737 func assertManagedImageName(name, setting string) (bool, error) { 738 if !isValidAzureName(reManagedDiskName, name) { 739 return false, fmt.Errorf("The setting %s must match the regular expression %q, and not end with a '-' or '.'.", setting, validManagedDiskName) 740 } 741 return true, nil 742 } 743 744 func assertResourceGroupName(rgn, setting string) (bool, error) { 745 if !isValidAzureName(reResourceGroupName, rgn) { 746 return false, fmt.Errorf("The setting %s must match the regular expression %q, and not end with a '-' or '.'.", setting, validResourceGroupNameRe) 747 } 748 return true, nil 749 } 750 751 func isValidAzureName(re *regexp.Regexp, rgn string) bool { 752 return re.Match([]byte(rgn)) && 753 !strings.HasSuffix(rgn, ".") && 754 !strings.HasSuffix(rgn, "-") 755 }