github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/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/arm/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 "github.com/hashicorp/packer/helper/communicator" 26 "github.com/hashicorp/packer/helper/config" 27 "github.com/hashicorp/packer/packer" 28 "github.com/hashicorp/packer/template/interpolate" 29 30 "golang.org/x/crypto/ssh" 31 ) 32 33 const ( 34 DefaultCloudEnvironmentName = "Public" 35 DefaultImageVersion = "latest" 36 DefaultUserName = "packer" 37 DefaultPrivateVirtualNetworkWithPublicIp = false 38 DefaultVMSize = "Standard_A1" 39 ) 40 41 var ( 42 reCaptureContainerName = regexp.MustCompile("^[a-z0-9][a-z0-9\\-]{2,62}$") 43 reCaptureNamePrefix = regexp.MustCompile("^[A-Za-z0-9][A-Za-z0-9_\\-\\.]{0,23}$") 44 ) 45 46 type Config struct { 47 common.PackerConfig `mapstructure:",squash"` 48 49 // Authentication via OAUTH 50 ClientID string `mapstructure:"client_id"` 51 ClientSecret string `mapstructure:"client_secret"` 52 ObjectID string `mapstructure:"object_id"` 53 TenantID string `mapstructure:"tenant_id"` 54 SubscriptionID string `mapstructure:"subscription_id"` 55 56 // Capture 57 CaptureNamePrefix string `mapstructure:"capture_name_prefix"` 58 CaptureContainerName string `mapstructure:"capture_container_name"` 59 60 // Compute 61 ImagePublisher string `mapstructure:"image_publisher"` 62 ImageOffer string `mapstructure:"image_offer"` 63 ImageSku string `mapstructure:"image_sku"` 64 ImageVersion string `mapstructure:"image_version"` 65 ImageUrl string `mapstructure:"image_url"` 66 67 CustomManagedImageResourceGroupName string `mapstructure:"custom_managed_image_resource_group_name"` 68 CustomManagedImageName string `mapstructure:"custom_managed_image_name"` 69 customManagedImageID string 70 71 Location string `mapstructure:"location"` 72 VMSize string `mapstructure:"vm_size"` 73 74 ManagedImageResourceGroupName string `mapstructure:"managed_image_resource_group_name"` 75 ManagedImageName string `mapstructure:"managed_image_name"` 76 ManagedImageStorageAccountType string `mapstructure:"managed_image_storage_account_type"` 77 managedImageStorageAccountType compute.StorageAccountTypes 78 manageImageLocation string 79 80 // Deployment 81 AzureTags map[string]*string `mapstructure:"azure_tags"` 82 ResourceGroupName string `mapstructure:"resource_group_name"` 83 StorageAccount string `mapstructure:"storage_account"` 84 TempComputeName string `mapstructure:"temp_compute_name"` 85 TempResourceGroupName string `mapstructure:"temp_resource_group_name"` 86 storageAccountBlobEndpoint string 87 CloudEnvironmentName string `mapstructure:"cloud_environment_name"` 88 cloudEnvironment *azure.Environment 89 PrivateVirtualNetworkWithPublicIp bool `mapstructure:"private_virtual_network_with_public_ip"` 90 VirtualNetworkName string `mapstructure:"virtual_network_name"` 91 VirtualNetworkSubnetName string `mapstructure:"virtual_network_subnet_name"` 92 VirtualNetworkResourceGroupName string `mapstructure:"virtual_network_resource_group_name"` 93 CustomDataFile string `mapstructure:"custom_data_file"` 94 customData string 95 96 // OS 97 OSType string `mapstructure:"os_type"` 98 OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb"` 99 100 // Runtime Values 101 UserName string 102 Password string 103 tmpAdminPassword string 104 tmpCertificatePassword string 105 tmpResourceGroupName string 106 tmpComputeName string 107 tmpDeploymentName string 108 tmpKeyVaultName string 109 tmpOSDiskName string 110 tmpWinRMCertificateUrl string 111 112 useDeviceLogin bool 113 114 // Authentication with the VM via SSH 115 sshAuthorizedKey string 116 sshPrivateKey string 117 118 // Authentication with the VM via WinRM 119 winrmCertificate string 120 121 Comm communicator.Config `mapstructure:",squash"` 122 ctx *interpolate.Context 123 } 124 125 type keyVaultCertificate struct { 126 Data string `json:"data"` 127 DataType string `json:"dataType"` 128 Password string `json:"password,omitempty"` 129 } 130 131 func (c *Config) toVMID() string { 132 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s", c.SubscriptionID, c.tmpResourceGroupName, c.tmpComputeName) 133 } 134 135 func (c *Config) isManagedImage() bool { 136 return c.ManagedImageName != "" 137 } 138 139 func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCaptureParameters { 140 return &compute.VirtualMachineCaptureParameters{ 141 DestinationContainerName: &c.CaptureContainerName, 142 VhdPrefix: &c.CaptureNamePrefix, 143 OverwriteVhds: to.BoolPtr(false), 144 } 145 } 146 147 func (c *Config) toImageParameters() *compute.Image { 148 return &compute.Image{ 149 ImageProperties: &compute.ImageProperties{ 150 SourceVirtualMachine: &compute.SubResource{ 151 ID: to.StringPtr(c.toVMID()), 152 }, 153 }, 154 Location: to.StringPtr(c.Location), 155 Tags: &c.AzureTags, 156 } 157 } 158 159 func (c *Config) createCertificate() (string, error) { 160 privateKey, err := rsa.GenerateKey(rand.Reader, 2048) 161 if err != nil { 162 err = fmt.Errorf("Failed to Generate Private Key: %s", err) 163 return "", err 164 } 165 166 host := fmt.Sprintf("%s.cloudapp.net", c.tmpComputeName) 167 notBefore := time.Now() 168 notAfter := notBefore.Add(24 * time.Hour) 169 170 serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) 171 if err != nil { 172 err = fmt.Errorf("Failed to Generate Serial Number: %v", err) 173 return "", err 174 } 175 176 template := x509.Certificate{ 177 SerialNumber: serialNumber, 178 Issuer: pkix.Name{ 179 CommonName: host, 180 }, 181 Subject: pkix.Name{ 182 CommonName: host, 183 }, 184 NotBefore: notBefore, 185 NotAfter: notAfter, 186 187 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 188 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 189 BasicConstraintsValid: true, 190 } 191 192 derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) 193 if err != nil { 194 err = fmt.Errorf("Failed to Create Certificate: %s", err) 195 return "", err 196 } 197 198 pfxBytes, err := pkcs12.Encode(derBytes, privateKey, c.tmpCertificatePassword) 199 if err != nil { 200 err = fmt.Errorf("Failed to encode certificate as PFX: %s", err) 201 return "", err 202 } 203 204 keyVaultDescription := keyVaultCertificate{ 205 Data: base64.StdEncoding.EncodeToString(pfxBytes), 206 DataType: "pfx", 207 Password: c.tmpCertificatePassword, 208 } 209 210 bytes, err := json.Marshal(keyVaultDescription) 211 if err != nil { 212 err = fmt.Errorf("Failed to marshal key vault description: %s", err) 213 return "", err 214 } 215 216 return base64.StdEncoding.EncodeToString(bytes), nil 217 } 218 219 func newConfig(raws ...interface{}) (*Config, []string, error) { 220 var c Config 221 222 err := config.Decode(&c, &config.DecodeOpts{ 223 Interpolate: true, 224 InterpolateContext: c.ctx, 225 }, raws...) 226 227 if err != nil { 228 return nil, nil, err 229 } 230 231 provideDefaultValues(&c) 232 setRuntimeValues(&c) 233 setUserNamePassword(&c) 234 err = setCloudEnvironment(&c) 235 if err != nil { 236 return nil, nil, err 237 } 238 239 err = setCustomData(&c) 240 if err != nil { 241 return nil, nil, err 242 } 243 244 // NOTE: if the user did not specify a communicator, then default to both 245 // SSH and WinRM. This is for backwards compatibility because the code did 246 // not specifically force the user to set a communicator. 247 if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "ssh") { 248 err = setSshValues(&c) 249 if err != nil { 250 return nil, nil, err 251 } 252 } 253 254 if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "winrm") { 255 err = setWinRMCertificate(&c) 256 if err != nil { 257 return nil, nil, err 258 } 259 } 260 261 var errs *packer.MultiError 262 errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(c.ctx)...) 263 264 assertRequiredParametersSet(&c, errs) 265 assertTagProperties(&c, errs) 266 if errs != nil && len(errs.Errors) > 0 { 267 return nil, nil, errs 268 } 269 270 return &c, nil, nil 271 } 272 273 func setSshValues(c *Config) error { 274 if c.Comm.SSHTimeout == 0 { 275 c.Comm.SSHTimeout = 20 * time.Minute 276 } 277 278 if c.Comm.SSHPrivateKey != "" { 279 privateKeyBytes, err := ioutil.ReadFile(c.Comm.SSHPrivateKey) 280 if err != nil { 281 return err 282 } 283 signer, err := ssh.ParsePrivateKey(privateKeyBytes) 284 if err != nil { 285 return err 286 } 287 288 publicKey := signer.PublicKey() 289 c.sshAuthorizedKey = fmt.Sprintf("%s %s packer Azure Deployment%s", 290 publicKey.Type(), 291 base64.StdEncoding.EncodeToString(publicKey.Marshal()), 292 time.Now().Format(time.RFC3339)) 293 c.sshPrivateKey = string(privateKeyBytes) 294 295 } else { 296 sshKeyPair, err := NewOpenSshKeyPair() 297 if err != nil { 298 return err 299 } 300 301 c.sshAuthorizedKey = sshKeyPair.AuthorizedKey() 302 c.sshPrivateKey = sshKeyPair.PrivateKey() 303 } 304 305 return nil 306 } 307 308 func setWinRMCertificate(c *Config) error { 309 c.Comm.WinRMTransportDecorator = 310 func() winrm.Transporter { 311 return &winrm.ClientNTLM{} 312 } 313 314 cert, err := c.createCertificate() 315 c.winrmCertificate = cert 316 317 return err 318 } 319 320 func setRuntimeValues(c *Config) { 321 var tempName = NewTempName() 322 323 c.tmpAdminPassword = tempName.AdminPassword 324 c.tmpCertificatePassword = tempName.CertificatePassword 325 if c.TempComputeName == "" { 326 c.tmpComputeName = tempName.ComputeName 327 } else { 328 c.tmpComputeName = c.TempComputeName 329 } 330 c.tmpDeploymentName = tempName.DeploymentName 331 if c.TempResourceGroupName == "" { 332 c.tmpResourceGroupName = tempName.ResourceGroupName 333 } else { 334 c.tmpResourceGroupName = c.TempResourceGroupName 335 } 336 c.tmpOSDiskName = tempName.OSDiskName 337 c.tmpKeyVaultName = tempName.KeyVaultName 338 } 339 340 func setUserNamePassword(c *Config) { 341 if c.Comm.SSHUsername == "" { 342 c.Comm.SSHUsername = DefaultUserName 343 } 344 345 c.UserName = c.Comm.SSHUsername 346 347 if c.Comm.SSHPassword != "" { 348 c.Password = c.Comm.SSHPassword 349 } else { 350 c.Password = c.tmpAdminPassword 351 } 352 } 353 354 func setCloudEnvironment(c *Config) error { 355 lookup := map[string]string{ 356 "CHINA": "AzureChinaCloud", 357 "CHINACLOUD": "AzureChinaCloud", 358 "AZURECHINACLOUD": "AzureChinaCloud", 359 360 "GERMAN": "AzureGermanCloud", 361 "GERMANCLOUD": "AzureGermanCloud", 362 "AZUREGERMANCLOUD": "AzureGermanCloud", 363 364 "GERMANY": "AzureGermanCloud", 365 "GERMANYCLOUD": "AzureGermanCloud", 366 "AZUREGERMANYCLOUD": "AzureGermanCloud", 367 368 "PUBLIC": "AzurePublicCloud", 369 "PUBLICCLOUD": "AzurePublicCloud", 370 "AZUREPUBLICCLOUD": "AzurePublicCloud", 371 372 "USGOVERNMENT": "AzureUSGovernmentCloud", 373 "USGOVERNMENTCLOUD": "AzureUSGovernmentCloud", 374 "AZUREUSGOVERNMENTCLOUD": "AzureUSGovernmentCloud", 375 } 376 377 name := strings.ToUpper(c.CloudEnvironmentName) 378 envName, ok := lookup[name] 379 if !ok { 380 return fmt.Errorf("There is no cloud envionment matching the name '%s'!", c.CloudEnvironmentName) 381 } 382 383 env, err := azure.EnvironmentFromName(envName) 384 c.cloudEnvironment = &env 385 return err 386 } 387 388 func setCustomData(c *Config) error { 389 if c.CustomDataFile == "" { 390 return nil 391 } 392 393 b, err := ioutil.ReadFile(c.CustomDataFile) 394 if err != nil { 395 return err 396 } 397 398 c.customData = base64.StdEncoding.EncodeToString(b) 399 return nil 400 } 401 402 func provideDefaultValues(c *Config) { 403 if c.VMSize == "" { 404 c.VMSize = DefaultVMSize 405 } 406 407 if c.ManagedImageStorageAccountType == "" { 408 c.managedImageStorageAccountType = compute.StandardLRS 409 } 410 411 if c.ImagePublisher != "" && c.ImageVersion == "" { 412 c.ImageVersion = DefaultImageVersion 413 } 414 415 if c.CloudEnvironmentName == "" { 416 c.CloudEnvironmentName = DefaultCloudEnvironmentName 417 } 418 } 419 420 func assertTagProperties(c *Config, errs *packer.MultiError) { 421 if len(c.AzureTags) > 15 { 422 errs = packer.MultiErrorAppend(errs, fmt.Errorf("a max of 15 tags are supported, but %d were provided", len(c.AzureTags))) 423 } 424 425 for k, v := range c.AzureTags { 426 if len(k) > 512 { 427 errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 512 character limit", k, len(k))) 428 } 429 if len(*v) > 256 { 430 errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", v, len(*v))) 431 } 432 } 433 } 434 435 func assertRequiredParametersSet(c *Config, errs *packer.MultiError) { 436 ///////////////////////////////////////////// 437 // Authentication via OAUTH 438 439 // Check if device login is being asked for, and is allowed. 440 // 441 // Device login is enabled if the user only defines SubscriptionID and not 442 // ClientID, ClientSecret, and TenantID. 443 // 444 // Device login is not enabled for Windows because the WinRM certificate is 445 // readable by the ObjectID of the App. There may be another way to handle 446 // this case, but I am not currently aware of it - send feedback. 447 isUseDeviceLogin := func(c *Config) bool { 448 if c.OSType == constants.Target_Windows { 449 return false 450 } 451 452 return c.SubscriptionID != "" && 453 c.ClientID == "" && 454 c.ClientSecret == "" && 455 c.TenantID == "" 456 } 457 458 if isUseDeviceLogin(c) { 459 c.useDeviceLogin = true 460 } else { 461 if c.ClientID == "" { 462 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_id must be specified")) 463 } 464 465 if c.ClientSecret == "" { 466 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_secret must be specified")) 467 } 468 469 if c.SubscriptionID == "" { 470 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A subscription_id must be specified")) 471 } 472 } 473 474 ///////////////////////////////////////////// 475 // Capture 476 if c.CaptureContainerName == "" && c.ManagedImageName == "" { 477 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name or managed_image_name must be specified")) 478 } 479 480 if c.CaptureNamePrefix == "" && c.ManagedImageResourceGroupName == "" { 481 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix or managed_image_resource_group_name must be specified")) 482 } 483 484 if (c.CaptureNamePrefix != "" || c.CaptureContainerName != "") && (c.ManagedImageResourceGroupName != "" || c.ManagedImageName != "") { 485 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.")) 486 } 487 488 if c.CaptureContainerName != "" { 489 if !reCaptureContainerName.MatchString(c.CaptureContainerName) { 490 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must satisfy the regular expression %q.", reCaptureContainerName.String())) 491 } 492 493 if strings.HasSuffix(c.CaptureContainerName, "-") { 494 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not end with a hyphen, e.g. '-'.")) 495 } 496 497 if strings.Contains(c.CaptureContainerName, "--") { 498 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_container_name must not contain consecutive hyphens, e.g. '--'.")) 499 } 500 501 if c.CaptureNamePrefix == "" { 502 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must be specified")) 503 } 504 505 if !reCaptureNamePrefix.MatchString(c.CaptureNamePrefix) { 506 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must satisfy the regular expression %q.", reCaptureNamePrefix.String())) 507 } 508 509 if strings.HasSuffix(c.CaptureNamePrefix, "-") || strings.HasSuffix(c.CaptureNamePrefix, ".") { 510 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A capture_name_prefix must not end with a hyphen or period.")) 511 } 512 } 513 514 ///////////////////////////////////////////// 515 // Compute 516 toInt := func(b bool) int { 517 if b { 518 return 1 519 } else { 520 return 0 521 } 522 } 523 524 isImageUrl := c.ImageUrl != "" 525 isCustomManagedImage := c.CustomManagedImageName != "" || c.CustomManagedImageResourceGroupName != "" 526 isPlatformImage := c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != "" 527 528 countSourceInputs := toInt(isImageUrl) + toInt(isCustomManagedImage) + toInt(isPlatformImage) 529 530 if countSourceInputs > 1 { 531 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")) 532 } 533 534 if isImageUrl && c.ManagedImageResourceGroupName != "" { 535 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A managed image must be created from a managed image, it cannot be created from a VHD.")) 536 } 537 538 if c.ImageUrl == "" && c.CustomManagedImageName == "" { 539 if c.ImagePublisher == "" { 540 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_publisher must be specified")) 541 } 542 if c.ImageOffer == "" { 543 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_offer must be specified")) 544 } 545 if c.ImageSku == "" { 546 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_sku must be specified")) 547 } 548 } else if c.ImageUrl == "" && c.ImagePublisher == "" { 549 if c.CustomManagedImageResourceGroupName == "" { 550 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An custom_managed_image_resource_group_name must be specified")) 551 } 552 if c.CustomManagedImageName == "" { 553 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A custom_managed_image_name must be specified")) 554 } 555 if c.ManagedImageResourceGroupName == "" { 556 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An managed_image_resource_group_name must be specified")) 557 } 558 if c.ManagedImageName == "" { 559 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An managed_image_name must be specified")) 560 } 561 } else { 562 if c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != "" || c.ImageVersion != "" { 563 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")) 564 } 565 } 566 567 if c.Location == "" { 568 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A location must be specified")) 569 } 570 571 ///////////////////////////////////////////// 572 // Deployment 573 xor := func(a, b bool) bool { 574 return (a || b) && !(a && b) 575 } 576 577 if !xor((c.StorageAccount != "" || c.ResourceGroupName != ""), (c.ManagedImageName != "" || c.ManagedImageResourceGroupName != "")) { 578 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")) 579 } 580 581 if c.ManagedImageName == "" && c.ManagedImageResourceGroupName == "" { 582 if c.StorageAccount == "" { 583 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A storage_account must be specified")) 584 } 585 if c.ResourceGroupName == "" { 586 errs = packer.MultiErrorAppend(errs, fmt.Errorf("A resource_group_name must be specified")) 587 } 588 } 589 590 if c.VirtualNetworkName == "" && c.VirtualNetworkResourceGroupName != "" { 591 errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_resource_group_name is specified, so must virtual_network_name")) 592 } 593 if c.VirtualNetworkName == "" && c.VirtualNetworkSubnetName != "" { 594 errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_subnet_name is specified, so must virtual_network_name")) 595 } 596 597 ///////////////////////////////////////////// 598 // OS 599 if strings.EqualFold(c.OSType, constants.Target_Linux) { 600 c.OSType = constants.Target_Linux 601 } else if strings.EqualFold(c.OSType, constants.Target_Windows) { 602 c.OSType = constants.Target_Windows 603 } else if c.OSType == "" { 604 errs = packer.MultiErrorAppend(errs, fmt.Errorf("An os_type must be specified")) 605 } else { 606 errs = packer.MultiErrorAppend(errs, fmt.Errorf("The os_type %q is invalid", c.OSType)) 607 } 608 609 switch c.ManagedImageStorageAccountType { 610 case "", string(compute.StandardLRS): 611 c.managedImageStorageAccountType = compute.StandardLRS 612 case string(compute.PremiumLRS): 613 c.managedImageStorageAccountType = compute.PremiumLRS 614 default: 615 errs = packer.MultiErrorAppend(errs, fmt.Errorf("The managed_image_storage_account_type %q is invalid", c.ManagedImageStorageAccountType)) 616 } 617 }