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