diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index 08ab3ffb2..668a4240a 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -74,9 +74,11 @@ type Config struct { Location string `mapstructure:"location"` VMSize string `mapstructure:"vm_size"` - ManagedImageResourceGroupName string `mapstructure:"managed_image_resource_group_name"` - ManagedImageName string `mapstructure:"managed_image_name"` - manageImageLocation string + ManagedImageResourceGroupName string `mapstructure:"managed_image_resource_group_name"` + ManagedImageName string `mapstructure:"managed_image_name"` + ManagedImageStorageAccountType string `mapstructure:"managed_image_storage_account_type"` + managedImageStorageAccountType compute.StorageAccountTypes + manageImageLocation string // Deployment AzureTags map[string]*string `mapstructure:"azure_tags"` @@ -405,6 +407,10 @@ func provideDefaultValues(c *Config) { c.VMSize = DefaultVMSize } + if c.ManagedImageStorageAccountType == "" { + c.managedImageStorageAccountType = compute.StandardLRS + } + if c.ImagePublisher != "" && c.ImageVersion == "" { c.ImageVersion = DefaultImageVersion } @@ -598,4 +604,13 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) { } else { errs = packer.MultiErrorAppend(errs, fmt.Errorf("The os_type %q is invalid", c.OSType)) } + + switch c.ManagedImageStorageAccountType { + case "", string(compute.StandardLRS): + c.managedImageStorageAccountType = compute.StandardLRS + case string(compute.PremiumLRS): + c.managedImageStorageAccountType = compute.PremiumLRS + default: + errs = packer.MultiErrorAppend(errs, fmt.Errorf("The managed_image_storage_account_type %q is invalid", c.ManagedImageStorageAccountType)) + } } diff --git a/builder/azure/arm/config_test.go b/builder/azure/arm/config_test.go index 835454718..86e4301bc 100644 --- a/builder/azure/arm/config_test.go +++ b/builder/azure/arm/config_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/Azure/azure-sdk-for-go/arm/compute" "github.com/hashicorp/packer/builder/azure/common/constants" "github.com/hashicorp/packer/packer" ) @@ -48,6 +49,10 @@ func TestConfigShouldProvideReasonableDefaultValues(t *testing.T) { if c.ObjectID != "" { t.Errorf("Expected 'ObjectID' to be nil, but it was '%s'!", c.ObjectID) } + + if c.managedImageStorageAccountType == "" { + t.Errorf("Expected 'managedImageStorageAccountType' to be populated, but it was empty!") + } } func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) { @@ -56,6 +61,7 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) { builderValues["ssh_username"] = "override_username" builderValues["vm_size"] = "override_vm_size" builderValues["communicator"] = "ssh" + builderValues["managed_image_storage_account_type"] = "Premium_LRS" c, _, err := newConfig(builderValues, getPackerConfiguration()) @@ -64,23 +70,27 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) { } if c.Password != "override_password" { - t.Errorf("Expected 'Password' to be set to 'override_password', but found '%s'!", c.Password) + t.Errorf("Expected 'Password' to be set to 'override_password', but found %q!", c.Password) } if c.Comm.SSHPassword != "override_password" { - t.Errorf("Expected 'c.Comm.SSHPassword' to be set to 'override_password', but found '%s'!", c.Comm.SSHPassword) + t.Errorf("Expected 'c.Comm.SSHPassword' to be set to 'override_password', but found %q!", c.Comm.SSHPassword) } if c.UserName != "override_username" { - t.Errorf("Expected 'UserName' to be set to 'override_username', but found '%s'!", c.UserName) + t.Errorf("Expected 'UserName' to be set to 'override_username', but found %q!", c.UserName) } if c.Comm.SSHUsername != "override_username" { - t.Errorf("Expected 'c.Comm.SSHUsername' to be set to 'override_username', but found '%s'!", c.Comm.SSHUsername) + t.Errorf("Expected 'c.Comm.SSHUsername' to be set to 'override_username', but found %q!", c.Comm.SSHUsername) } if c.VMSize != "override_vm_size" { - t.Errorf("Expected 'vm_size' to be set to 'override_vm_size', but found '%s'!", c.VMSize) + t.Errorf("Expected 'vm_size' to be set to 'override_vm_size', but found %q!", c.VMSize) + } + + if c.managedImageStorageAccountType != compute.PremiumLRS { + t.Errorf("Expected 'managed_image_storage_account_type' to be set to 'Premium_LRS', but found %q!", c.managedImageStorageAccountType) } } @@ -826,6 +836,52 @@ func TestConfigShouldRejectCustomAndImageUrlForManagedImageBuild(t *testing.T) { } } +func TestConfigShouldRejectMalformedManageImageStorageAccountTypes(t *testing.T) { + config := map[string]interface{}{ + "custom_managed_image_resource_group_name": "ignore", + "custom_managed_image_name": "ignore", + "location": "ignore", + "subscription_id": "ignore", + "communicator": "none", + "managed_image_resource_group_name": "ignore", + "managed_image_name": "ignore", + "managed_image_storage_account_type": "--invalid--", + + // Does not matter for this test case, just pick one. + "os_type": constants.Target_Linux, + } + + _, _, err := newConfig(config, getPackerConfiguration()) + if err == nil { + t.Fatal("expected config to reject custom and platform input for a managed image build") + } +} + +func TestConfigShouldAcceptManagedImageStorageAccountTypes(t *testing.T) { + config := map[string]interface{}{ + "custom_managed_image_resource_group_name": "ignore", + "custom_managed_image_name": "ignore", + "location": "ignore", + "subscription_id": "ignore", + "communicator": "none", + "managed_image_resource_group_name": "ignore", + "managed_image_name": "ignore", + + // Does not matter for this test case, just pick one. + "os_type": constants.Target_Linux, + } + + storage_account_types := []string{"Premium_LRS", "Standard_LRS"} + + for _, x := range storage_account_types { + config["managed_image_storage_account_type"] = x + _, _, err := newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatalf("expected config to accept a managed_image_storage_account_type of %q", x) + } + } +} + func getArmBuilderConfiguration() map[string]string { m := make(map[string]string) for _, v := range requiredConfigValues { diff --git a/builder/azure/arm/template_factory.go b/builder/azure/arm/template_factory.go index fd4d96fe5..16fe7bf93 100644 --- a/builder/azure/arm/template_factory.go +++ b/builder/azure/arm/template_factory.go @@ -53,7 +53,7 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error) if config.ImageUrl != "" { builder.SetImageUrl(config.ImageUrl, osType) } else if config.CustomManagedImageName != "" { - builder.SetManagedDiskUrl(config.customManagedImageID) + builder.SetManagedDiskUrl(config.customManagedImageID, config.managedImageStorageAccountType) } else if config.ManagedImageName != "" && config.ImagePublisher != "" { imageID := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/ArtifactTypes/vmimage/offers/%s/skus/%s/versions/%s", config.SubscriptionID, @@ -63,7 +63,7 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error) config.ImageSku, config.ImageVersion) - builder.SetManagedMarketplaceImage(config.Location, config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion, imageID) + builder.SetManagedMarketplaceImage(config.Location, config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion, imageID, config.managedImageStorageAccountType) } else { builder.SetMarketPlaceImage(config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion) } diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment08.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment08.approved.json index 7089ed59c..c567b774c 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment08.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment08.approved.json @@ -129,6 +129,9 @@ "osDisk": { "caching": "ReadWrite", "createOption": "fromImage", + "managedDisk": { + "storageAccountType": "Standard_LRS" + }, "name": "osdisk", "osType": "Linux" } diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment09.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment09.approved.json index 03a00fa32..fbbb7384e 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment09.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment09.approved.json @@ -132,6 +132,9 @@ "osDisk": { "caching": "ReadWrite", "createOption": "fromImage", + "managedDisk": { + "storageAccountType": "Standard_LRS" + }, "name": "osdisk", "osType": "Linux" } diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment10.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment10.approved.json index 9ad7217c7..0eb130138 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment10.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment10.approved.json @@ -110,6 +110,9 @@ "osDisk": { "caching": "ReadWrite", "createOption": "fromImage", + "managedDisk": { + "storageAccountType": "Standard_LRS" + }, "name": "osdisk", "osType": "Linux" } diff --git a/builder/azure/common/template/template.go b/builder/azure/common/template/template.go index 0ce9d2045..cdea487f0 100644 --- a/builder/azure/common/template/template.go +++ b/builder/azure/common/template/template.go @@ -45,6 +45,7 @@ type OSDiskUnion struct { Caching compute.CachingTypes `json:"caching,omitempty"` CreateOption compute.DiskCreateOptionTypes `json:"createOption,omitempty"` DiskSizeGB *int32 `json:"diskSizeGB,omitempty"` + ManagedDisk *compute.ManagedDiskParameters `json:"managedDisk,omitempty"` } // Union of the StorageProfile and ImageStorageProfile types. diff --git a/builder/azure/common/template/template_builder.go b/builder/azure/common/template/template_builder.go index 7950052dd..7311817c2 100644 --- a/builder/azure/common/template/template_builder.go +++ b/builder/azure/common/template/template_builder.go @@ -101,7 +101,7 @@ func (s *TemplateBuilder) BuildWindows(keyVaultName, winRMCertificateUrl string) return nil } -func (s *TemplateBuilder) SetManagedDiskUrl(managedImageId string) error { +func (s *TemplateBuilder) SetManagedDiskUrl(managedImageId string, storageAccountType compute.StorageAccountTypes) error { resource, err := s.getResourceByType(resourceVirtualMachine) if err != nil { return err @@ -115,11 +115,14 @@ func (s *TemplateBuilder) SetManagedDiskUrl(managedImageId string) error { profile.OsDisk.OsType = s.osType profile.OsDisk.CreateOption = compute.FromImage profile.OsDisk.Vhd = nil + profile.OsDisk.ManagedDisk = &compute.ManagedDiskParameters{ + StorageAccountType: storageAccountType, + } return nil } -func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer, sku, version, imageID string) error { +func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer, sku, version, imageID string, storageAccountType compute.StorageAccountTypes) error { resource, err := s.getResourceByType(resourceVirtualMachine) if err != nil { return err @@ -137,6 +140,9 @@ func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer, profile.OsDisk.OsType = s.osType profile.OsDisk.CreateOption = compute.FromImage profile.OsDisk.Vhd = nil + profile.OsDisk.ManagedDisk = &compute.ManagedDiskParameters{ + StorageAccountType: storageAccountType, + } return nil } diff --git a/website/source/docs/builders/azure.html.md b/website/source/docs/builders/azure.html.md index 2e7422fc3..96fb59ba8 100644 --- a/website/source/docs/builders/azure.html.md +++ b/website/source/docs/builders/azure.html.md @@ -107,6 +107,10 @@ When creating a managed image the following two options are required. - `image_url` (string) Specify a custom VHD to use. If this value is set, do not set image\_publisher, image\_offer, image\_sku, or image\_version. + +- `managed_image_storage_account_type` (string) Specify the storage + account type for a managed image. Valid values are Standard_LRS + and Premium\_LRS. The default is Standard\_LRS. - `object_id` (string) Specify an OAuth Object ID to protect WinRM certificates created at runtime. This variable is required when creating images based on