github.com/alkar/terraform@v0.9.6-0.20170517124458-a4cddf6ebf59/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go (about) 1 package azurerm 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "net/http" 8 9 "github.com/Azure/azure-sdk-for-go/arm/compute" 10 "github.com/hashicorp/terraform/helper/hashcode" 11 "github.com/hashicorp/terraform/helper/schema" 12 "github.com/hashicorp/terraform/helper/structure" 13 "github.com/hashicorp/terraform/helper/validation" 14 ) 15 16 func resourceArmVirtualMachineScaleSet() *schema.Resource { 17 return &schema.Resource{ 18 Create: resourceArmVirtualMachineScaleSetCreate, 19 Read: resourceArmVirtualMachineScaleSetRead, 20 Update: resourceArmVirtualMachineScaleSetCreate, 21 Delete: resourceArmVirtualMachineScaleSetDelete, 22 Importer: &schema.ResourceImporter{ 23 State: schema.ImportStatePassthrough, 24 }, 25 26 Schema: map[string]*schema.Schema{ 27 "name": { 28 Type: schema.TypeString, 29 Required: true, 30 ForceNew: true, 31 }, 32 33 "location": locationSchema(), 34 35 "resource_group_name": { 36 Type: schema.TypeString, 37 Required: true, 38 ForceNew: true, 39 }, 40 41 "sku": { 42 Type: schema.TypeSet, 43 Required: true, 44 MaxItems: 1, 45 Elem: &schema.Resource{ 46 Schema: map[string]*schema.Schema{ 47 "name": { 48 Type: schema.TypeString, 49 Required: true, 50 }, 51 52 "tier": { 53 Type: schema.TypeString, 54 Optional: true, 55 Computed: true, 56 }, 57 58 "capacity": { 59 Type: schema.TypeInt, 60 Required: true, 61 }, 62 }, 63 }, 64 Set: resourceArmVirtualMachineScaleSetSkuHash, 65 }, 66 67 "upgrade_policy_mode": { 68 Type: schema.TypeString, 69 Required: true, 70 }, 71 72 "overprovision": { 73 Type: schema.TypeBool, 74 Optional: true, 75 }, 76 77 "single_placement_group": { 78 Type: schema.TypeBool, 79 Optional: true, 80 Default: true, 81 ForceNew: true, 82 }, 83 84 "os_profile": { 85 Type: schema.TypeSet, 86 Required: true, 87 MaxItems: 1, 88 Elem: &schema.Resource{ 89 Schema: map[string]*schema.Schema{ 90 "computer_name_prefix": { 91 Type: schema.TypeString, 92 Required: true, 93 }, 94 95 "admin_username": { 96 Type: schema.TypeString, 97 Required: true, 98 }, 99 100 "admin_password": { 101 Type: schema.TypeString, 102 Required: true, 103 Sensitive: true, 104 }, 105 106 "custom_data": { 107 Type: schema.TypeString, 108 Optional: true, 109 ForceNew: true, 110 StateFunc: userDataStateFunc, 111 }, 112 }, 113 }, 114 Set: resourceArmVirtualMachineScaleSetsOsProfileHash, 115 }, 116 117 "os_profile_secrets": { 118 Type: schema.TypeSet, 119 Optional: true, 120 Elem: &schema.Resource{ 121 Schema: map[string]*schema.Schema{ 122 "source_vault_id": { 123 Type: schema.TypeString, 124 Required: true, 125 }, 126 127 "vault_certificates": { 128 Type: schema.TypeList, 129 Optional: true, 130 Elem: &schema.Resource{ 131 Schema: map[string]*schema.Schema{ 132 "certificate_url": { 133 Type: schema.TypeString, 134 Required: true, 135 }, 136 "certificate_store": { 137 Type: schema.TypeString, 138 Optional: true, 139 }, 140 }, 141 }, 142 }, 143 }, 144 }, 145 }, 146 147 "os_profile_windows_config": { 148 Type: schema.TypeSet, 149 Optional: true, 150 MaxItems: 1, 151 Elem: &schema.Resource{ 152 Schema: map[string]*schema.Schema{ 153 "provision_vm_agent": { 154 Type: schema.TypeBool, 155 Optional: true, 156 }, 157 "enable_automatic_upgrades": { 158 Type: schema.TypeBool, 159 Optional: true, 160 }, 161 "winrm": { 162 Type: schema.TypeSet, 163 Optional: true, 164 Elem: &schema.Resource{ 165 Schema: map[string]*schema.Schema{ 166 "protocol": { 167 Type: schema.TypeString, 168 Required: true, 169 }, 170 "certificate_url": { 171 Type: schema.TypeString, 172 Optional: true, 173 }, 174 }, 175 }, 176 }, 177 "additional_unattend_config": { 178 Type: schema.TypeSet, 179 Optional: true, 180 Elem: &schema.Resource{ 181 Schema: map[string]*schema.Schema{ 182 "pass": { 183 Type: schema.TypeString, 184 Required: true, 185 }, 186 "component": { 187 Type: schema.TypeString, 188 Required: true, 189 }, 190 "setting_name": { 191 Type: schema.TypeString, 192 Required: true, 193 }, 194 "content": { 195 Type: schema.TypeString, 196 Required: true, 197 }, 198 }, 199 }, 200 }, 201 }, 202 }, 203 Set: resourceArmVirtualMachineScaleSetOsProfileLWindowsConfigHash, 204 }, 205 206 "os_profile_linux_config": { 207 Type: schema.TypeSet, 208 Optional: true, 209 Computed: true, 210 MaxItems: 1, 211 Elem: &schema.Resource{ 212 Schema: map[string]*schema.Schema{ 213 "disable_password_authentication": { 214 Type: schema.TypeBool, 215 Optional: true, 216 Default: false, 217 ForceNew: true, 218 }, 219 "ssh_keys": { 220 Type: schema.TypeList, 221 Optional: true, 222 Elem: &schema.Resource{ 223 Schema: map[string]*schema.Schema{ 224 "path": { 225 Type: schema.TypeString, 226 Required: true, 227 }, 228 "key_data": { 229 Type: schema.TypeString, 230 Optional: true, 231 }, 232 }, 233 }, 234 }, 235 }, 236 }, 237 Set: resourceArmVirtualMachineScaleSetOsProfileLinuxConfigHash, 238 }, 239 240 "network_profile": { 241 Type: schema.TypeSet, 242 Required: true, 243 Elem: &schema.Resource{ 244 Schema: map[string]*schema.Schema{ 245 "name": { 246 Type: schema.TypeString, 247 Required: true, 248 }, 249 250 "primary": { 251 Type: schema.TypeBool, 252 Required: true, 253 }, 254 255 "ip_configuration": { 256 Type: schema.TypeList, 257 Required: true, 258 Elem: &schema.Resource{ 259 Schema: map[string]*schema.Schema{ 260 "name": { 261 Type: schema.TypeString, 262 Required: true, 263 }, 264 265 "subnet_id": { 266 Type: schema.TypeString, 267 Required: true, 268 }, 269 270 "load_balancer_backend_address_pool_ids": { 271 Type: schema.TypeSet, 272 Optional: true, 273 Elem: &schema.Schema{Type: schema.TypeString}, 274 Set: schema.HashString, 275 }, 276 277 "load_balancer_inbound_nat_rules_ids": { 278 Type: schema.TypeSet, 279 Optional: true, 280 Computed: true, 281 Elem: &schema.Schema{Type: schema.TypeString}, 282 Set: schema.HashString, 283 }, 284 }, 285 }, 286 }, 287 }, 288 }, 289 Set: resourceArmVirtualMachineScaleSetNetworkConfigurationHash, 290 }, 291 292 "storage_profile_os_disk": { 293 Type: schema.TypeSet, 294 Required: true, 295 MaxItems: 1, 296 Elem: &schema.Resource{ 297 Schema: map[string]*schema.Schema{ 298 "name": { 299 Type: schema.TypeString, 300 Required: true, 301 }, 302 303 "image": { 304 Type: schema.TypeString, 305 Optional: true, 306 }, 307 308 "vhd_containers": { 309 Type: schema.TypeSet, 310 Optional: true, 311 Elem: &schema.Schema{Type: schema.TypeString}, 312 Set: schema.HashString, 313 }, 314 315 "managed_disk_type": { 316 Type: schema.TypeString, 317 Optional: true, 318 Computed: true, 319 ConflictsWith: []string{"storage_profile_os_disk.vhd_containers"}, 320 ValidateFunc: validation.StringInSlice([]string{ 321 string(compute.PremiumLRS), 322 string(compute.StandardLRS), 323 }, true), 324 }, 325 326 "caching": { 327 Type: schema.TypeString, 328 Optional: true, 329 Computed: true, 330 }, 331 332 "os_type": { 333 Type: schema.TypeString, 334 Optional: true, 335 }, 336 337 "create_option": { 338 Type: schema.TypeString, 339 Required: true, 340 }, 341 }, 342 }, 343 Set: resourceArmVirtualMachineScaleSetStorageProfileOsDiskHash, 344 }, 345 346 "storage_profile_image_reference": { 347 Type: schema.TypeSet, 348 Optional: true, 349 Computed: true, 350 MaxItems: 1, 351 Elem: &schema.Resource{ 352 Schema: map[string]*schema.Schema{ 353 "publisher": { 354 Type: schema.TypeString, 355 Required: true, 356 }, 357 358 "offer": { 359 Type: schema.TypeString, 360 Required: true, 361 }, 362 363 "sku": { 364 Type: schema.TypeString, 365 Required: true, 366 }, 367 368 "version": { 369 Type: schema.TypeString, 370 Required: true, 371 }, 372 }, 373 }, 374 Set: resourceArmVirtualMachineScaleSetStorageProfileImageReferenceHash, 375 }, 376 377 "extension": { 378 Type: schema.TypeSet, 379 Optional: true, 380 Elem: &schema.Resource{ 381 Schema: map[string]*schema.Schema{ 382 "name": { 383 Type: schema.TypeString, 384 Required: true, 385 }, 386 387 "publisher": { 388 Type: schema.TypeString, 389 Required: true, 390 }, 391 392 "type": { 393 Type: schema.TypeString, 394 Required: true, 395 }, 396 397 "type_handler_version": { 398 Type: schema.TypeString, 399 Required: true, 400 }, 401 402 "auto_upgrade_minor_version": { 403 Type: schema.TypeBool, 404 Optional: true, 405 }, 406 407 "settings": { 408 Type: schema.TypeString, 409 Optional: true, 410 ValidateFunc: validation.ValidateJsonString, 411 DiffSuppressFunc: structure.SuppressJsonDiff, 412 }, 413 414 "protected_settings": { 415 Type: schema.TypeString, 416 Optional: true, 417 Sensitive: true, 418 ValidateFunc: validation.ValidateJsonString, 419 DiffSuppressFunc: structure.SuppressJsonDiff, 420 }, 421 }, 422 }, 423 Set: resourceArmVirtualMachineScaleSetExtensionHash, 424 }, 425 426 "tags": tagsSchema(), 427 }, 428 } 429 } 430 431 func resourceArmVirtualMachineScaleSetCreate(d *schema.ResourceData, meta interface{}) error { 432 client := meta.(*ArmClient) 433 vmScaleSetClient := client.vmScaleSetClient 434 435 log.Printf("[INFO] preparing arguments for Azure ARM Virtual Machine Scale Set creation.") 436 437 name := d.Get("name").(string) 438 location := d.Get("location").(string) 439 resGroup := d.Get("resource_group_name").(string) 440 tags := d.Get("tags").(map[string]interface{}) 441 442 sku, err := expandVirtualMachineScaleSetSku(d) 443 if err != nil { 444 return err 445 } 446 447 storageProfile := compute.VirtualMachineScaleSetStorageProfile{} 448 osDisk, err := expandAzureRMVirtualMachineScaleSetsStorageProfileOsDisk(d) 449 if err != nil { 450 return err 451 } 452 storageProfile.OsDisk = osDisk 453 if _, ok := d.GetOk("storage_profile_image_reference"); ok { 454 imageRef, err := expandAzureRmVirtualMachineScaleSetStorageProfileImageReference(d) 455 if err != nil { 456 return err 457 } 458 storageProfile.ImageReference = imageRef 459 } 460 461 osProfile, err := expandAzureRMVirtualMachineScaleSetsOsProfile(d) 462 if err != nil { 463 return err 464 } 465 466 extensions, err := expandAzureRMVirtualMachineScaleSetExtensions(d) 467 if err != nil { 468 return err 469 } 470 471 updatePolicy := d.Get("upgrade_policy_mode").(string) 472 overprovision := d.Get("overprovision").(bool) 473 singlePlacementGroup := d.Get("single_placement_group").(bool) 474 475 scaleSetProps := compute.VirtualMachineScaleSetProperties{ 476 UpgradePolicy: &compute.UpgradePolicy{ 477 Mode: compute.UpgradeMode(updatePolicy), 478 }, 479 VirtualMachineProfile: &compute.VirtualMachineScaleSetVMProfile{ 480 NetworkProfile: expandAzureRmVirtualMachineScaleSetNetworkProfile(d), 481 StorageProfile: &storageProfile, 482 OsProfile: osProfile, 483 ExtensionProfile: extensions, 484 }, 485 Overprovision: &overprovision, 486 SinglePlacementGroup: &singlePlacementGroup, 487 } 488 489 scaleSetParams := compute.VirtualMachineScaleSet{ 490 Name: &name, 491 Location: &location, 492 Tags: expandTags(tags), 493 Sku: sku, 494 VirtualMachineScaleSetProperties: &scaleSetProps, 495 } 496 _, vmErr := vmScaleSetClient.CreateOrUpdate(resGroup, name, scaleSetParams, make(chan struct{})) 497 if vmErr != nil { 498 return vmErr 499 } 500 501 read, err := vmScaleSetClient.Get(resGroup, name) 502 if err != nil { 503 return err 504 } 505 if read.ID == nil { 506 return fmt.Errorf("Cannot read Virtual Machine Scale Set %s (resource group %s) ID", name, resGroup) 507 } 508 509 d.SetId(*read.ID) 510 511 return resourceArmVirtualMachineScaleSetRead(d, meta) 512 } 513 514 func resourceArmVirtualMachineScaleSetRead(d *schema.ResourceData, meta interface{}) error { 515 vmScaleSetClient := meta.(*ArmClient).vmScaleSetClient 516 517 id, err := parseAzureResourceID(d.Id()) 518 if err != nil { 519 return err 520 } 521 resGroup := id.ResourceGroup 522 name := id.Path["virtualMachineScaleSets"] 523 524 resp, err := vmScaleSetClient.Get(resGroup, name) 525 if err != nil { 526 if resp.StatusCode == http.StatusNotFound { 527 log.Printf("[INFO] AzureRM Virtual Machine Scale Set (%s) Not Found. Removing from State", name) 528 d.SetId("") 529 return nil 530 } 531 return fmt.Errorf("Error making Read request on Azure Virtual Machine Scale Set %s: %s", name, err) 532 } 533 534 d.Set("name", resp.Name) 535 d.Set("resource_group_name", resGroup) 536 d.Set("location", azureRMNormalizeLocation(*resp.Location)) 537 538 if err := d.Set("sku", flattenAzureRmVirtualMachineScaleSetSku(resp.Sku)); err != nil { 539 return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set Sku error: %#v", err) 540 } 541 542 properties := resp.VirtualMachineScaleSetProperties 543 544 d.Set("upgrade_policy_mode", properties.UpgradePolicy.Mode) 545 d.Set("overprovision", properties.Overprovision) 546 d.Set("single_placement_group", properties.SinglePlacementGroup) 547 548 osProfile, err := flattenAzureRMVirtualMachineScaleSetOsProfile(properties.VirtualMachineProfile.OsProfile) 549 if err != nil { 550 return fmt.Errorf("[DEBUG] Error flattening Virtual Machine Scale Set OS Profile. Error: %#v", err) 551 } 552 553 if err := d.Set("os_profile", osProfile); err != nil { 554 return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set OS Profile error: %#v", err) 555 } 556 557 if properties.VirtualMachineProfile.OsProfile.Secrets != nil { 558 if err := d.Set("os_profile_secrets", flattenAzureRmVirtualMachineScaleSetOsProfileSecrets(properties.VirtualMachineProfile.OsProfile.Secrets)); err != nil { 559 return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set OS Profile Secrets error: %#v", err) 560 } 561 } 562 563 if properties.VirtualMachineProfile.OsProfile.WindowsConfiguration != nil { 564 if err := d.Set("os_profile_windows_config", flattenAzureRmVirtualMachineScaleSetOsProfileWindowsConfig(properties.VirtualMachineProfile.OsProfile.WindowsConfiguration)); err != nil { 565 return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set OS Profile Windows config error: %#v", err) 566 } 567 } 568 569 if properties.VirtualMachineProfile.OsProfile.LinuxConfiguration != nil { 570 if err := d.Set("os_profile_linux_config", flattenAzureRmVirtualMachineScaleSetOsProfileLinuxConfig(properties.VirtualMachineProfile.OsProfile.LinuxConfiguration)); err != nil { 571 return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set OS Profile Windows config error: %#v", err) 572 } 573 } 574 575 if err := d.Set("network_profile", flattenAzureRmVirtualMachineScaleSetNetworkProfile(properties.VirtualMachineProfile.NetworkProfile)); err != nil { 576 return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set Network Profile error: %#v", err) 577 } 578 579 if properties.VirtualMachineProfile.StorageProfile.ImageReference != nil { 580 if err := d.Set("storage_profile_image_reference", flattenAzureRmVirtualMachineScaleSetStorageProfileImageReference(properties.VirtualMachineProfile.StorageProfile.ImageReference)); err != nil { 581 return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set Storage Profile Image Reference error: %#v", err) 582 } 583 } 584 585 if err := d.Set("storage_profile_os_disk", flattenAzureRmVirtualMachineScaleSetStorageProfileOSDisk(properties.VirtualMachineProfile.StorageProfile.OsDisk)); err != nil { 586 return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set Storage Profile OS Disk error: %#v", err) 587 } 588 589 if properties.VirtualMachineProfile.ExtensionProfile != nil { 590 extension, err := flattenAzureRmVirtualMachineScaleSetExtensionProfile(properties.VirtualMachineProfile.ExtensionProfile) 591 if err != nil { 592 return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set Extension Profile error: %#v", err) 593 } 594 d.Set("extension", extension) 595 } 596 597 flattenAndSetTags(d, resp.Tags) 598 599 return nil 600 } 601 602 func resourceArmVirtualMachineScaleSetDelete(d *schema.ResourceData, meta interface{}) error { 603 vmScaleSetClient := meta.(*ArmClient).vmScaleSetClient 604 605 id, err := parseAzureResourceID(d.Id()) 606 if err != nil { 607 return err 608 } 609 resGroup := id.ResourceGroup 610 name := id.Path["virtualMachineScaleSets"] 611 612 _, err = vmScaleSetClient.Delete(resGroup, name, make(chan struct{})) 613 614 return err 615 } 616 617 func flattenAzureRmVirtualMachineScaleSetOsProfileLinuxConfig(config *compute.LinuxConfiguration) []interface{} { 618 result := make(map[string]interface{}) 619 result["disable_password_authentication"] = *config.DisablePasswordAuthentication 620 621 if config.SSH != nil && len(*config.SSH.PublicKeys) > 0 { 622 ssh_keys := make([]map[string]interface{}, 0, len(*config.SSH.PublicKeys)) 623 for _, i := range *config.SSH.PublicKeys { 624 key := make(map[string]interface{}) 625 key["path"] = *i.Path 626 627 if i.KeyData != nil { 628 key["key_data"] = *i.KeyData 629 } 630 631 ssh_keys = append(ssh_keys, key) 632 } 633 634 result["ssh_keys"] = ssh_keys 635 } 636 637 return []interface{}{result} 638 } 639 640 func flattenAzureRmVirtualMachineScaleSetOsProfileWindowsConfig(config *compute.WindowsConfiguration) []interface{} { 641 result := make(map[string]interface{}) 642 643 if config.ProvisionVMAgent != nil { 644 result["provision_vm_agent"] = *config.ProvisionVMAgent 645 } 646 647 if config.EnableAutomaticUpdates != nil { 648 result["enable_automatic_upgrades"] = *config.EnableAutomaticUpdates 649 } 650 651 if config.WinRM != nil { 652 listeners := make([]map[string]interface{}, 0, len(*config.WinRM.Listeners)) 653 for _, i := range *config.WinRM.Listeners { 654 listener := make(map[string]interface{}) 655 listener["protocol"] = i.Protocol 656 657 if i.CertificateURL != nil { 658 listener["certificate_url"] = *i.CertificateURL 659 } 660 661 listeners = append(listeners, listener) 662 } 663 664 result["winrm"] = listeners 665 } 666 667 if config.AdditionalUnattendContent != nil { 668 content := make([]map[string]interface{}, 0, len(*config.AdditionalUnattendContent)) 669 for _, i := range *config.AdditionalUnattendContent { 670 c := make(map[string]interface{}) 671 c["pass"] = i.PassName 672 c["component"] = i.ComponentName 673 c["setting_name"] = i.SettingName 674 c["content"] = *i.Content 675 676 content = append(content, c) 677 } 678 679 result["additional_unattend_config"] = content 680 } 681 682 return []interface{}{result} 683 } 684 685 func flattenAzureRmVirtualMachineScaleSetOsProfileSecrets(secrets *[]compute.VaultSecretGroup) []map[string]interface{} { 686 result := make([]map[string]interface{}, 0, len(*secrets)) 687 for _, secret := range *secrets { 688 s := map[string]interface{}{ 689 "source_vault_id": *secret.SourceVault.ID, 690 } 691 692 if secret.VaultCertificates != nil { 693 certs := make([]map[string]interface{}, 0, len(*secret.VaultCertificates)) 694 for _, cert := range *secret.VaultCertificates { 695 vaultCert := make(map[string]interface{}) 696 vaultCert["certificate_url"] = *cert.CertificateURL 697 698 if cert.CertificateStore != nil { 699 vaultCert["certificate_store"] = *cert.CertificateStore 700 } 701 702 certs = append(certs, vaultCert) 703 } 704 705 s["vault_certificates"] = certs 706 } 707 708 result = append(result, s) 709 } 710 return result 711 } 712 713 func flattenAzureRmVirtualMachineScaleSetNetworkProfile(profile *compute.VirtualMachineScaleSetNetworkProfile) []map[string]interface{} { 714 networkConfigurations := profile.NetworkInterfaceConfigurations 715 result := make([]map[string]interface{}, 0, len(*networkConfigurations)) 716 for _, netConfig := range *networkConfigurations { 717 s := map[string]interface{}{ 718 "name": *netConfig.Name, 719 "primary": *netConfig.VirtualMachineScaleSetNetworkConfigurationProperties.Primary, 720 } 721 722 if netConfig.VirtualMachineScaleSetNetworkConfigurationProperties.IPConfigurations != nil { 723 ipConfigs := make([]map[string]interface{}, 0, len(*netConfig.VirtualMachineScaleSetNetworkConfigurationProperties.IPConfigurations)) 724 for _, ipConfig := range *netConfig.VirtualMachineScaleSetNetworkConfigurationProperties.IPConfigurations { 725 config := make(map[string]interface{}) 726 config["name"] = *ipConfig.Name 727 728 properties := ipConfig.VirtualMachineScaleSetIPConfigurationProperties 729 730 if ipConfig.VirtualMachineScaleSetIPConfigurationProperties.Subnet != nil { 731 config["subnet_id"] = *properties.Subnet.ID 732 } 733 734 if properties.LoadBalancerBackendAddressPools != nil { 735 addressPools := make([]interface{}, 0, len(*properties.LoadBalancerBackendAddressPools)) 736 for _, pool := range *properties.LoadBalancerBackendAddressPools { 737 addressPools = append(addressPools, *pool.ID) 738 } 739 config["load_balancer_backend_address_pool_ids"] = schema.NewSet(schema.HashString, addressPools) 740 } 741 742 if properties.LoadBalancerInboundNatPools != nil { 743 inboundNatPools := make([]interface{}, 0, len(*properties.LoadBalancerInboundNatPools)) 744 for _, rule := range *properties.LoadBalancerInboundNatPools { 745 inboundNatPools = append(inboundNatPools, *rule.ID) 746 } 747 config["load_balancer_inbound_nat_rules_ids"] = schema.NewSet(schema.HashString, inboundNatPools) 748 } 749 750 ipConfigs = append(ipConfigs, config) 751 } 752 753 s["ip_configuration"] = ipConfigs 754 } 755 756 result = append(result, s) 757 } 758 759 return result 760 } 761 762 func flattenAzureRMVirtualMachineScaleSetOsProfile(profile *compute.VirtualMachineScaleSetOSProfile) ([]interface{}, error) { 763 result := make(map[string]interface{}) 764 765 result["computer_name_prefix"] = *profile.ComputerNamePrefix 766 result["admin_username"] = *profile.AdminUsername 767 768 if profile.CustomData != nil { 769 result["custom_data"] = *profile.CustomData 770 } 771 772 return []interface{}{result}, nil 773 } 774 775 func flattenAzureRmVirtualMachineScaleSetStorageProfileOSDisk(profile *compute.VirtualMachineScaleSetOSDisk) []interface{} { 776 result := make(map[string]interface{}) 777 778 if profile.Name != nil { 779 result["name"] = *profile.Name 780 } 781 782 if profile.Image != nil { 783 result["image"] = *profile.Image.URI 784 } 785 786 if profile.VhdContainers != nil { 787 containers := make([]interface{}, 0, len(*profile.VhdContainers)) 788 for _, container := range *profile.VhdContainers { 789 containers = append(containers, container) 790 } 791 result["vhd_containers"] = schema.NewSet(schema.HashString, containers) 792 } 793 794 if profile.ManagedDisk != nil { 795 result["managed_disk_type"] = string(profile.ManagedDisk.StorageAccountType) 796 } 797 798 result["caching"] = profile.Caching 799 result["create_option"] = profile.CreateOption 800 result["os_type"] = profile.OsType 801 802 return []interface{}{result} 803 } 804 805 func flattenAzureRmVirtualMachineScaleSetStorageProfileImageReference(profile *compute.ImageReference) []interface{} { 806 result := make(map[string]interface{}) 807 result["publisher"] = *profile.Publisher 808 result["offer"] = *profile.Offer 809 result["sku"] = *profile.Sku 810 result["version"] = *profile.Version 811 812 return []interface{}{result} 813 } 814 815 func flattenAzureRmVirtualMachineScaleSetSku(sku *compute.Sku) []interface{} { 816 result := make(map[string]interface{}) 817 result["name"] = *sku.Name 818 result["capacity"] = *sku.Capacity 819 820 if *sku.Tier != "" { 821 result["tier"] = *sku.Tier 822 } 823 824 return []interface{}{result} 825 } 826 827 func flattenAzureRmVirtualMachineScaleSetExtensionProfile(profile *compute.VirtualMachineScaleSetExtensionProfile) ([]map[string]interface{}, error) { 828 if profile.Extensions == nil { 829 return nil, nil 830 } 831 832 result := make([]map[string]interface{}, 0, len(*profile.Extensions)) 833 for _, extension := range *profile.Extensions { 834 e := make(map[string]interface{}) 835 e["name"] = *extension.Name 836 properties := extension.VirtualMachineScaleSetExtensionProperties 837 if properties != nil { 838 e["publisher"] = *properties.Publisher 839 e["type"] = *properties.Type 840 e["type_handler_version"] = *properties.TypeHandlerVersion 841 if properties.AutoUpgradeMinorVersion != nil { 842 e["auto_upgrade_minor_version"] = *properties.AutoUpgradeMinorVersion 843 } 844 845 if properties.Settings != nil { 846 settings, err := structure.FlattenJsonToString(*properties.Settings) 847 if err != nil { 848 return nil, err 849 } 850 e["settings"] = settings 851 } 852 } 853 854 result = append(result, e) 855 } 856 857 return result, nil 858 } 859 860 func resourceArmVirtualMachineScaleSetStorageProfileImageReferenceHash(v interface{}) int { 861 var buf bytes.Buffer 862 m := v.(map[string]interface{}) 863 buf.WriteString(fmt.Sprintf("%s-", m["publisher"].(string))) 864 buf.WriteString(fmt.Sprintf("%s-", m["offer"].(string))) 865 buf.WriteString(fmt.Sprintf("%s-", m["sku"].(string))) 866 buf.WriteString(fmt.Sprintf("%s-", m["version"].(string))) 867 868 return hashcode.String(buf.String()) 869 } 870 871 func resourceArmVirtualMachineScaleSetSkuHash(v interface{}) int { 872 var buf bytes.Buffer 873 m := v.(map[string]interface{}) 874 buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) 875 if m["tier"] != nil { 876 buf.WriteString(fmt.Sprintf("%s-", m["tier"].(string))) 877 } 878 buf.WriteString(fmt.Sprintf("%d-", m["capacity"].(int))) 879 880 return hashcode.String(buf.String()) 881 } 882 883 func resourceArmVirtualMachineScaleSetStorageProfileOsDiskHash(v interface{}) int { 884 var buf bytes.Buffer 885 m := v.(map[string]interface{}) 886 buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) 887 888 if m["vhd_containers"] != nil { 889 buf.WriteString(fmt.Sprintf("%s-", m["vhd_containers"].(*schema.Set).List())) 890 } 891 892 return hashcode.String(buf.String()) 893 } 894 895 func resourceArmVirtualMachineScaleSetNetworkConfigurationHash(v interface{}) int { 896 var buf bytes.Buffer 897 m := v.(map[string]interface{}) 898 buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) 899 buf.WriteString(fmt.Sprintf("%t-", m["primary"].(bool))) 900 return hashcode.String(buf.String()) 901 } 902 903 func resourceArmVirtualMachineScaleSetsOsProfileHash(v interface{}) int { 904 var buf bytes.Buffer 905 m := v.(map[string]interface{}) 906 buf.WriteString(fmt.Sprintf("%s-", m["computer_name_prefix"].(string))) 907 buf.WriteString(fmt.Sprintf("%s-", m["admin_username"].(string))) 908 if m["custom_data"] != nil { 909 customData := m["custom_data"].(string) 910 if !isBase64Encoded(customData) { 911 customData = base64Encode(customData) 912 } 913 buf.WriteString(fmt.Sprintf("%s-", customData)) 914 } 915 return hashcode.String(buf.String()) 916 } 917 918 func resourceArmVirtualMachineScaleSetOsProfileLinuxConfigHash(v interface{}) int { 919 var buf bytes.Buffer 920 m := v.(map[string]interface{}) 921 buf.WriteString(fmt.Sprintf("%t-", m["disable_password_authentication"].(bool))) 922 923 return hashcode.String(buf.String()) 924 } 925 926 func resourceArmVirtualMachineScaleSetOsProfileLWindowsConfigHash(v interface{}) int { 927 var buf bytes.Buffer 928 m := v.(map[string]interface{}) 929 if m["provision_vm_agent"] != nil { 930 buf.WriteString(fmt.Sprintf("%t-", m["provision_vm_agent"].(bool))) 931 } 932 if m["enable_automatic_upgrades"] != nil { 933 buf.WriteString(fmt.Sprintf("%t-", m["enable_automatic_upgrades"].(bool))) 934 } 935 return hashcode.String(buf.String()) 936 } 937 938 func resourceArmVirtualMachineScaleSetExtensionHash(v interface{}) int { 939 var buf bytes.Buffer 940 m := v.(map[string]interface{}) 941 buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) 942 buf.WriteString(fmt.Sprintf("%s-", m["publisher"].(string))) 943 buf.WriteString(fmt.Sprintf("%s-", m["type"].(string))) 944 buf.WriteString(fmt.Sprintf("%s-", m["type_handler_version"].(string))) 945 if m["auto_upgrade_minor_version"] != nil { 946 buf.WriteString(fmt.Sprintf("%t-", m["auto_upgrade_minor_version"].(bool))) 947 } 948 949 return hashcode.String(buf.String()) 950 } 951 952 func expandVirtualMachineScaleSetSku(d *schema.ResourceData) (*compute.Sku, error) { 953 skuConfig := d.Get("sku").(*schema.Set).List() 954 955 config := skuConfig[0].(map[string]interface{}) 956 957 name := config["name"].(string) 958 tier := config["tier"].(string) 959 capacity := int64(config["capacity"].(int)) 960 961 sku := &compute.Sku{ 962 Name: &name, 963 Capacity: &capacity, 964 } 965 966 if tier != "" { 967 sku.Tier = &tier 968 } 969 970 return sku, nil 971 } 972 973 func expandAzureRmVirtualMachineScaleSetNetworkProfile(d *schema.ResourceData) *compute.VirtualMachineScaleSetNetworkProfile { 974 scaleSetNetworkProfileConfigs := d.Get("network_profile").(*schema.Set).List() 975 networkProfileConfig := make([]compute.VirtualMachineScaleSetNetworkConfiguration, 0, len(scaleSetNetworkProfileConfigs)) 976 977 for _, npProfileConfig := range scaleSetNetworkProfileConfigs { 978 config := npProfileConfig.(map[string]interface{}) 979 980 name := config["name"].(string) 981 primary := config["primary"].(bool) 982 983 ipConfigurationConfigs := config["ip_configuration"].([]interface{}) 984 ipConfigurations := make([]compute.VirtualMachineScaleSetIPConfiguration, 0, len(ipConfigurationConfigs)) 985 for _, ipConfigConfig := range ipConfigurationConfigs { 986 ipconfig := ipConfigConfig.(map[string]interface{}) 987 name := ipconfig["name"].(string) 988 subnetId := ipconfig["subnet_id"].(string) 989 990 ipConfiguration := compute.VirtualMachineScaleSetIPConfiguration{ 991 Name: &name, 992 VirtualMachineScaleSetIPConfigurationProperties: &compute.VirtualMachineScaleSetIPConfigurationProperties{ 993 Subnet: &compute.APIEntityReference{ 994 ID: &subnetId, 995 }, 996 }, 997 } 998 999 if v := ipconfig["load_balancer_backend_address_pool_ids"]; v != nil { 1000 pools := v.(*schema.Set).List() 1001 resources := make([]compute.SubResource, 0, len(pools)) 1002 for _, p := range pools { 1003 id := p.(string) 1004 resources = append(resources, compute.SubResource{ 1005 ID: &id, 1006 }) 1007 } 1008 ipConfiguration.LoadBalancerBackendAddressPools = &resources 1009 } 1010 1011 if v := ipconfig["load_balancer_inbound_nat_rules_ids"]; v != nil { 1012 rules := v.(*schema.Set).List() 1013 rulesResources := make([]compute.SubResource, 0, len(rules)) 1014 for _, m := range rules { 1015 id := m.(string) 1016 rulesResources = append(rulesResources, compute.SubResource{ 1017 ID: &id, 1018 }) 1019 } 1020 ipConfiguration.LoadBalancerInboundNatPools = &rulesResources 1021 } 1022 1023 ipConfigurations = append(ipConfigurations, ipConfiguration) 1024 } 1025 1026 nProfile := compute.VirtualMachineScaleSetNetworkConfiguration{ 1027 Name: &name, 1028 VirtualMachineScaleSetNetworkConfigurationProperties: &compute.VirtualMachineScaleSetNetworkConfigurationProperties{ 1029 Primary: &primary, 1030 IPConfigurations: &ipConfigurations, 1031 }, 1032 } 1033 1034 networkProfileConfig = append(networkProfileConfig, nProfile) 1035 } 1036 1037 return &compute.VirtualMachineScaleSetNetworkProfile{ 1038 NetworkInterfaceConfigurations: &networkProfileConfig, 1039 } 1040 } 1041 1042 func expandAzureRMVirtualMachineScaleSetsOsProfile(d *schema.ResourceData) (*compute.VirtualMachineScaleSetOSProfile, error) { 1043 osProfileConfigs := d.Get("os_profile").(*schema.Set).List() 1044 1045 osProfileConfig := osProfileConfigs[0].(map[string]interface{}) 1046 namePrefix := osProfileConfig["computer_name_prefix"].(string) 1047 username := osProfileConfig["admin_username"].(string) 1048 password := osProfileConfig["admin_password"].(string) 1049 customData := osProfileConfig["custom_data"].(string) 1050 1051 osProfile := &compute.VirtualMachineScaleSetOSProfile{ 1052 ComputerNamePrefix: &namePrefix, 1053 AdminUsername: &username, 1054 } 1055 1056 if password != "" { 1057 osProfile.AdminPassword = &password 1058 } 1059 1060 if customData != "" { 1061 customData = base64Encode(customData) 1062 osProfile.CustomData = &customData 1063 } 1064 1065 if _, ok := d.GetOk("os_profile_secrets"); ok { 1066 secrets := expandAzureRmVirtualMachineScaleSetOsProfileSecrets(d) 1067 if secrets != nil { 1068 osProfile.Secrets = secrets 1069 } 1070 } 1071 1072 if _, ok := d.GetOk("os_profile_linux_config"); ok { 1073 linuxConfig, err := expandAzureRmVirtualMachineScaleSetOsProfileLinuxConfig(d) 1074 if err != nil { 1075 return nil, err 1076 } 1077 osProfile.LinuxConfiguration = linuxConfig 1078 } 1079 1080 if _, ok := d.GetOk("os_profile_windows_config"); ok { 1081 winConfig, err := expandAzureRmVirtualMachineScaleSetOsProfileWindowsConfig(d) 1082 if err != nil { 1083 return nil, err 1084 } 1085 if winConfig != nil { 1086 osProfile.WindowsConfiguration = winConfig 1087 } 1088 } 1089 1090 return osProfile, nil 1091 } 1092 1093 func expandAzureRMVirtualMachineScaleSetsStorageProfileOsDisk(d *schema.ResourceData) (*compute.VirtualMachineScaleSetOSDisk, error) { 1094 osDiskConfigs := d.Get("storage_profile_os_disk").(*schema.Set).List() 1095 1096 osDiskConfig := osDiskConfigs[0].(map[string]interface{}) 1097 name := osDiskConfig["name"].(string) 1098 image := osDiskConfig["image"].(string) 1099 vhd_containers := osDiskConfig["vhd_containers"].(*schema.Set).List() 1100 caching := osDiskConfig["caching"].(string) 1101 osType := osDiskConfig["os_type"].(string) 1102 createOption := osDiskConfig["create_option"].(string) 1103 managedDiskType := osDiskConfig["managed_disk_type"].(string) 1104 1105 osDisk := &compute.VirtualMachineScaleSetOSDisk{ 1106 Name: &name, 1107 Caching: compute.CachingTypes(caching), 1108 OsType: compute.OperatingSystemTypes(osType), 1109 CreateOption: compute.DiskCreateOptionTypes(createOption), 1110 } 1111 1112 if image != "" { 1113 osDisk.Image = &compute.VirtualHardDisk{ 1114 URI: &image, 1115 } 1116 } 1117 1118 if len(vhd_containers) > 0 { 1119 var vhdContainers []string 1120 for _, v := range vhd_containers { 1121 str := v.(string) 1122 vhdContainers = append(vhdContainers, str) 1123 } 1124 osDisk.VhdContainers = &vhdContainers 1125 } 1126 1127 managedDisk := &compute.VirtualMachineScaleSetManagedDiskParameters{} 1128 1129 if managedDiskType != "" { 1130 if name == "" { 1131 osDisk.Name = nil 1132 managedDisk.StorageAccountType = compute.StorageAccountTypes(managedDiskType) 1133 osDisk.ManagedDisk = managedDisk 1134 } else { 1135 return nil, fmt.Errorf("[ERROR] Conflict between `name` and `managed_disk_type` on `storage_profile_os_disk` (please set name to blank)") 1136 } 1137 } 1138 1139 //BEGIN: code to be removed after GH-13016 is merged 1140 if image != "" && managedDiskType != "" { 1141 return nil, fmt.Errorf("[ERROR] Conflict between `image` and `managed_disk_type` on `storage_profile_os_disk` (only one or the other can be used)") 1142 } 1143 1144 if len(vhd_containers) > 0 && managedDiskType != "" { 1145 return nil, fmt.Errorf("[ERROR] Conflict between `vhd_containers` and `managed_disk_type` on `storage_profile_os_disk` (only one or the other can be used)") 1146 } 1147 //END: code to be removed after GH-13016 is merged 1148 1149 return osDisk, nil 1150 } 1151 1152 func expandAzureRmVirtualMachineScaleSetStorageProfileImageReference(d *schema.ResourceData) (*compute.ImageReference, error) { 1153 storageImageRefs := d.Get("storage_profile_image_reference").(*schema.Set).List() 1154 1155 storageImageRef := storageImageRefs[0].(map[string]interface{}) 1156 1157 publisher := storageImageRef["publisher"].(string) 1158 offer := storageImageRef["offer"].(string) 1159 sku := storageImageRef["sku"].(string) 1160 version := storageImageRef["version"].(string) 1161 1162 return &compute.ImageReference{ 1163 Publisher: &publisher, 1164 Offer: &offer, 1165 Sku: &sku, 1166 Version: &version, 1167 }, nil 1168 } 1169 1170 func expandAzureRmVirtualMachineScaleSetOsProfileLinuxConfig(d *schema.ResourceData) (*compute.LinuxConfiguration, error) { 1171 osProfilesLinuxConfig := d.Get("os_profile_linux_config").(*schema.Set).List() 1172 1173 linuxConfig := osProfilesLinuxConfig[0].(map[string]interface{}) 1174 disablePasswordAuth := linuxConfig["disable_password_authentication"].(bool) 1175 1176 linuxKeys := linuxConfig["ssh_keys"].([]interface{}) 1177 sshPublicKeys := make([]compute.SSHPublicKey, 0, len(linuxKeys)) 1178 for _, key := range linuxKeys { 1179 if key == nil { 1180 continue 1181 } 1182 sshKey := key.(map[string]interface{}) 1183 path := sshKey["path"].(string) 1184 keyData := sshKey["key_data"].(string) 1185 1186 sshPublicKey := compute.SSHPublicKey{ 1187 Path: &path, 1188 KeyData: &keyData, 1189 } 1190 1191 sshPublicKeys = append(sshPublicKeys, sshPublicKey) 1192 } 1193 1194 config := &compute.LinuxConfiguration{ 1195 DisablePasswordAuthentication: &disablePasswordAuth, 1196 SSH: &compute.SSHConfiguration{ 1197 PublicKeys: &sshPublicKeys, 1198 }, 1199 } 1200 1201 return config, nil 1202 } 1203 1204 func expandAzureRmVirtualMachineScaleSetOsProfileWindowsConfig(d *schema.ResourceData) (*compute.WindowsConfiguration, error) { 1205 osProfilesWindowsConfig := d.Get("os_profile_windows_config").(*schema.Set).List() 1206 1207 osProfileConfig := osProfilesWindowsConfig[0].(map[string]interface{}) 1208 config := &compute.WindowsConfiguration{} 1209 1210 if v := osProfileConfig["provision_vm_agent"]; v != nil { 1211 provision := v.(bool) 1212 config.ProvisionVMAgent = &provision 1213 } 1214 1215 if v := osProfileConfig["enable_automatic_upgrades"]; v != nil { 1216 update := v.(bool) 1217 config.EnableAutomaticUpdates = &update 1218 } 1219 1220 if v := osProfileConfig["winrm"]; v != nil { 1221 winRm := v.(*schema.Set).List() 1222 if len(winRm) > 0 { 1223 winRmListeners := make([]compute.WinRMListener, 0, len(winRm)) 1224 for _, winRmConfig := range winRm { 1225 config := winRmConfig.(map[string]interface{}) 1226 1227 protocol := config["protocol"].(string) 1228 winRmListener := compute.WinRMListener{ 1229 Protocol: compute.ProtocolTypes(protocol), 1230 } 1231 if v := config["certificate_url"].(string); v != "" { 1232 winRmListener.CertificateURL = &v 1233 } 1234 1235 winRmListeners = append(winRmListeners, winRmListener) 1236 } 1237 config.WinRM = &compute.WinRMConfiguration{ 1238 Listeners: &winRmListeners, 1239 } 1240 } 1241 } 1242 if v := osProfileConfig["additional_unattend_config"]; v != nil { 1243 additionalConfig := v.(*schema.Set).List() 1244 if len(additionalConfig) > 0 { 1245 additionalConfigContent := make([]compute.AdditionalUnattendContent, 0, len(additionalConfig)) 1246 for _, addConfig := range additionalConfig { 1247 config := addConfig.(map[string]interface{}) 1248 pass := config["pass"].(string) 1249 component := config["component"].(string) 1250 settingName := config["setting_name"].(string) 1251 content := config["content"].(string) 1252 1253 addContent := compute.AdditionalUnattendContent{ 1254 PassName: compute.PassNames(pass), 1255 ComponentName: compute.ComponentNames(component), 1256 SettingName: compute.SettingNames(settingName), 1257 Content: &content, 1258 } 1259 1260 additionalConfigContent = append(additionalConfigContent, addContent) 1261 } 1262 config.AdditionalUnattendContent = &additionalConfigContent 1263 } 1264 } 1265 return config, nil 1266 } 1267 1268 func expandAzureRmVirtualMachineScaleSetOsProfileSecrets(d *schema.ResourceData) *[]compute.VaultSecretGroup { 1269 secretsConfig := d.Get("os_profile_secrets").(*schema.Set).List() 1270 secrets := make([]compute.VaultSecretGroup, 0, len(secretsConfig)) 1271 1272 for _, secretConfig := range secretsConfig { 1273 config := secretConfig.(map[string]interface{}) 1274 sourceVaultId := config["source_vault_id"].(string) 1275 1276 vaultSecretGroup := compute.VaultSecretGroup{ 1277 SourceVault: &compute.SubResource{ 1278 ID: &sourceVaultId, 1279 }, 1280 } 1281 1282 if v := config["vault_certificates"]; v != nil { 1283 certsConfig := v.([]interface{}) 1284 certs := make([]compute.VaultCertificate, 0, len(certsConfig)) 1285 for _, certConfig := range certsConfig { 1286 config := certConfig.(map[string]interface{}) 1287 1288 certUrl := config["certificate_url"].(string) 1289 cert := compute.VaultCertificate{ 1290 CertificateURL: &certUrl, 1291 } 1292 if v := config["certificate_store"].(string); v != "" { 1293 cert.CertificateStore = &v 1294 } 1295 1296 certs = append(certs, cert) 1297 } 1298 vaultSecretGroup.VaultCertificates = &certs 1299 } 1300 1301 secrets = append(secrets, vaultSecretGroup) 1302 } 1303 1304 return &secrets 1305 } 1306 1307 func expandAzureRMVirtualMachineScaleSetExtensions(d *schema.ResourceData) (*compute.VirtualMachineScaleSetExtensionProfile, error) { 1308 extensions := d.Get("extension").(*schema.Set).List() 1309 resources := make([]compute.VirtualMachineScaleSetExtension, 0, len(extensions)) 1310 for _, e := range extensions { 1311 config := e.(map[string]interface{}) 1312 name := config["name"].(string) 1313 publisher := config["publisher"].(string) 1314 t := config["type"].(string) 1315 version := config["type_handler_version"].(string) 1316 1317 extension := compute.VirtualMachineScaleSetExtension{ 1318 Name: &name, 1319 VirtualMachineScaleSetExtensionProperties: &compute.VirtualMachineScaleSetExtensionProperties{ 1320 Publisher: &publisher, 1321 Type: &t, 1322 TypeHandlerVersion: &version, 1323 }, 1324 } 1325 1326 if u := config["auto_upgrade_minor_version"]; u != nil { 1327 upgrade := u.(bool) 1328 extension.VirtualMachineScaleSetExtensionProperties.AutoUpgradeMinorVersion = &upgrade 1329 } 1330 1331 if s := config["settings"].(string); s != "" { 1332 settings, err := structure.ExpandJsonFromString(s) 1333 if err != nil { 1334 return nil, fmt.Errorf("unable to parse settings: %s", err) 1335 } 1336 extension.VirtualMachineScaleSetExtensionProperties.Settings = &settings 1337 } 1338 1339 if s := config["protected_settings"].(string); s != "" { 1340 protectedSettings, err := structure.ExpandJsonFromString(s) 1341 if err != nil { 1342 return nil, fmt.Errorf("unable to parse protected_settings: %s", err) 1343 } 1344 extension.VirtualMachineScaleSetExtensionProperties.ProtectedSettings = &protectedSettings 1345 } 1346 1347 resources = append(resources, extension) 1348 } 1349 1350 return &compute.VirtualMachineScaleSetExtensionProfile{ 1351 Extensions: &resources, 1352 }, nil 1353 }