Merge pull request #9323 [azure-chroot] copy data disks between shared image galleries

[azure-chroot] copy data disks between shared image galleries
This commit is contained in:
Paul Meyer 2020-06-03 12:32:19 -07:00 committed by GitHub
commit e260212be3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1324 additions and 783 deletions

View File

@ -88,16 +88,30 @@ type Config struct {
// The [cache type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#cachingtypes)
// specified in the resulting image and for attaching it to the Packer VM. Defaults to `ReadOnly`
OSDiskCacheType string `mapstructure:"os_disk_cache_type"`
// The [storage SKU](https://docs.microsoft.com/en-us/rest/api/compute/disks/createorupdate#diskstorageaccounttypes)
// to use for datadisks. Defaults to `Standard_LRS`.
DataDiskStorageAccountType string `mapstructure:"data_disk_storage_account_type"`
// The [cache type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#cachingtypes)
// specified in the resulting image and for attaching it to the Packer VM. Defaults to `ReadOnly`
DataDiskCacheType string `mapstructure:"data_disk_cache_type"`
// The [Hyper-V generation type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#hypervgenerationtypes) for Managed Image output.
// Defaults to `V1`.
ImageHyperVGeneration string `mapstructure:"image_hyperv_generation"`
// The id of the temporary disk that will be created. Will be generated if not set.
// The id of the temporary OS disk that will be created. Will be generated if not set.
TemporaryOSDiskID string `mapstructure:"temporary_os_disk_id"`
// The id of the temporary snapshot that will be created. Will be generated if not set.
// The id of the temporary OS disk snapshot that will be created. Will be generated if not set.
TemporaryOSDiskSnapshotID string `mapstructure:"temporary_os_disk_snapshot_id"`
// The prefix for the resource ids of the temporary data disks that will be created. The disks will be suffixed with a number. Will be generated if not set.
TemporaryDataDiskIDPrefix string `mapstructure:"temporary_data_disk_id_prefix"`
// The prefix for the resource ids of the temporary data disk snapshots that will be created. The snapshots will be suffixed with a number. Will be generated if not set.
TemporaryDataDiskSnapshotIDPrefix string `mapstructure:"temporary_data_disk_snapshot_id"`
// If set to `true`, leaves the temporary disks and snapshots behind in the Packer VM resource group. Defaults to `false`
SkipCleanup bool `mapstructure:"skip_cleanup"`
@ -219,6 +233,26 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
}
}
if b.config.TemporaryDataDiskIDPrefix == "" {
if def, err := interpolate.Render(
"/subscriptions/{{ vm `subscription_id` }}/resourceGroups/{{ vm `resource_group` }}/providers/Microsoft.Compute/disks/PackerTemp-datadisk-{{timestamp}}-",
&b.config.ctx); err == nil {
b.config.TemporaryDataDiskIDPrefix = def
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("unable to render temporary data disk id prefix: %s", err))
}
}
if b.config.TemporaryDataDiskSnapshotIDPrefix == "" {
if def, err := interpolate.Render(
"/subscriptions/{{ vm `subscription_id` }}/resourceGroups/{{ vm `resource_group` }}/providers/Microsoft.Compute/snapshots/PackerTemp-datadisk-snapshot-{{timestamp}}-",
&b.config.ctx); err == nil {
b.config.TemporaryDataDiskSnapshotIDPrefix = def
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("unable to render temporary data disk snapshot id prefix: %s", err))
}
}
if b.config.OSDiskStorageAccountType == "" {
b.config.OSDiskStorageAccountType = string(compute.PremiumLRS)
}
@ -227,6 +261,14 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
b.config.OSDiskCacheType = string(compute.CachingTypesReadOnly)
}
if b.config.DataDiskStorageAccountType == "" {
b.config.DataDiskStorageAccountType = string(compute.PremiumLRS)
}
if b.config.DataDiskCacheType == "" {
b.config.DataDiskCacheType = string(compute.CachingTypesReadOnly)
}
if b.config.ImageHyperVGeneration == "" {
b.config.ImageHyperVGeneration = string(compute.V1)
}
@ -274,6 +316,14 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("os_disk_storage_account_type: %v", err))
}
if err := checkDiskCacheType(b.config.DataDiskCacheType); err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("data_disk_cache_type: %v", err))
}
if err := checkStorageAccountType(b.config.DataDiskStorageAccountType); err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("data_disk_storage_account_type: %v", err))
}
if b.config.ImageResourceID != "" {
r, err := azure.ParseResourceID(b.config.ImageResourceID)
if err != nil ||
@ -406,11 +456,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
artifact.Resources = append(artifact.Resources, b.config.SharedImageGalleryDestination.ResourceID(info.SubscriptionID))
}
if b.config.SkipCleanup {
if d, ok := state.GetOk(stateBagKey_OSDiskResourceID); ok {
artifact.Resources = append(artifact.Resources, d.(string))
if d, ok := state.GetOk(stateBagKey_Diskset); ok {
for _, disk := range d.(Diskset) {
artifact.Resources = append(artifact.Resources, disk.String())
}
}
if d, ok := state.GetOk(stateBagKey_OSDiskSnapshotResourceID); ok {
artifact.Resources = append(artifact.Resources, d.(string))
if d, ok := state.GetOk(stateBagKey_Snapshotset); ok {
for _, snapshot := range d.(Diskset) {
artifact.Resources = append(artifact.Resources, snapshot.String())
}
}
}
@ -438,12 +492,12 @@ func buildsteps(config Config, info *client.ComputeInfo) []multistep.Step {
}
if config.FromScratch {
addSteps(&StepCreateNewDisk{
ResourceID: config.TemporaryOSDiskID,
DiskSizeGB: config.OSDiskSizeGB,
DiskStorageAccountType: config.OSDiskStorageAccountType,
HyperVGeneration: config.ImageHyperVGeneration,
Location: info.Location})
addSteps(&StepCreateNewDiskset{
OSDiskID: config.TemporaryOSDiskID,
OSDiskSizeGB: config.OSDiskSizeGB,
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
HyperVGeneration: config.ImageHyperVGeneration,
Location: info.Location})
} else {
switch config.sourceType {
case sourcePlatformImage:
@ -456,13 +510,13 @@ func buildsteps(config Config, info *client.ComputeInfo) []multistep.Step {
})
}
addSteps(
&StepCreateNewDisk{
ResourceID: config.TemporaryOSDiskID,
DiskSizeGB: config.OSDiskSizeGB,
DiskStorageAccountType: config.OSDiskStorageAccountType,
HyperVGeneration: config.ImageHyperVGeneration,
Location: info.Location,
PlatformImage: pi,
&StepCreateNewDiskset{
OSDiskID: config.TemporaryOSDiskID,
OSDiskSizeGB: config.OSDiskSizeGB,
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
HyperVGeneration: config.ImageHyperVGeneration,
Location: info.Location,
SourcePlatformImage: pi,
SkipCleanup: config.SkipCleanup,
})
@ -476,13 +530,13 @@ func buildsteps(config Config, info *client.ComputeInfo) []multistep.Step {
SourceDiskResourceID: config.Source,
Location: info.Location,
},
&StepCreateNewDisk{
ResourceID: config.TemporaryOSDiskID,
DiskSizeGB: config.OSDiskSizeGB,
DiskStorageAccountType: config.OSDiskStorageAccountType,
HyperVGeneration: config.ImageHyperVGeneration,
SourceDiskResourceID: config.Source,
Location: info.Location,
&StepCreateNewDiskset{
OSDiskID: config.TemporaryOSDiskID,
OSDiskSizeGB: config.OSDiskSizeGB,
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
HyperVGeneration: config.ImageHyperVGeneration,
SourceOSDiskResourceID: config.Source,
Location: info.Location,
SkipCleanup: config.SkipCleanup,
})
@ -494,11 +548,14 @@ func buildsteps(config Config, info *client.ComputeInfo) []multistep.Step {
SubscriptionID: info.SubscriptionID,
Location: info.Location,
},
&StepCreateNewDisk{
ResourceID: config.TemporaryOSDiskID,
DiskSizeGB: config.OSDiskSizeGB,
SourceImageResourceID: config.Source,
Location: info.Location,
&StepCreateNewDiskset{
OSDiskID: config.TemporaryOSDiskID,
DataDiskIDPrefix: config.TemporaryDataDiskIDPrefix,
OSDiskSizeGB: config.OSDiskSizeGB,
OSDiskStorageAccountType: config.OSDiskStorageAccountType,
DataDiskStorageAccountType: config.DataDiskStorageAccountType,
SourceImageResourceID: config.Source,
Location: info.Location,
SkipCleanup: config.SkipCleanup,
})
@ -542,10 +599,11 @@ func buildsteps(config Config, info *client.ComputeInfo) []multistep.Step {
}
if hasValidSharedImage {
addSteps(
&StepCreateSnapshot{
ResourceID: config.TemporaryOSDiskSnapshotID,
Location: info.Location,
SkipCleanup: config.SkipCleanup,
&StepCreateSnapshotset{
OSDiskSnapshotID: config.TemporaryOSDiskSnapshotID,
DataDiskSnapshotIDPrefix: config.TemporaryDataDiskSnapshotIDPrefix,
Location: info.Location,
SkipCleanup: config.SkipCleanup,
},
&StepCreateSharedImageVersion{
Destination: config.SharedImageGalleryDestination,

View File

@ -9,40 +9,44 @@ import (
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
CloudEnvironmentName *string `mapstructure:"cloud_environment_name" required:"false" cty:"cloud_environment_name" hcl:"cloud_environment_name"`
ClientID *string `mapstructure:"client_id" cty:"client_id" hcl:"client_id"`
ClientSecret *string `mapstructure:"client_secret" cty:"client_secret" hcl:"client_secret"`
ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path" hcl:"client_cert_path"`
ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt" hcl:"client_jwt"`
ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"`
TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"`
SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id" hcl:"subscription_id"`
FromScratch *bool `mapstructure:"from_scratch" cty:"from_scratch" hcl:"from_scratch"`
Source *string `mapstructure:"source" required:"true" cty:"source" hcl:"source"`
CommandWrapper *string `mapstructure:"command_wrapper" cty:"command_wrapper" hcl:"command_wrapper"`
PreMountCommands []string `mapstructure:"pre_mount_commands" cty:"pre_mount_commands" hcl:"pre_mount_commands"`
MountOptions []string `mapstructure:"mount_options" cty:"mount_options" hcl:"mount_options"`
MountPartition *string `mapstructure:"mount_partition" cty:"mount_partition" hcl:"mount_partition"`
MountPath *string `mapstructure:"mount_path" cty:"mount_path" hcl:"mount_path"`
PostMountCommands []string `mapstructure:"post_mount_commands" cty:"post_mount_commands" hcl:"post_mount_commands"`
ChrootMounts [][]string `mapstructure:"chroot_mounts" cty:"chroot_mounts" hcl:"chroot_mounts"`
CopyFiles []string `mapstructure:"copy_files" cty:"copy_files" hcl:"copy_files"`
OSDiskSizeGB *int32 `mapstructure:"os_disk_size_gb" cty:"os_disk_size_gb" hcl:"os_disk_size_gb"`
OSDiskStorageAccountType *string `mapstructure:"os_disk_storage_account_type" cty:"os_disk_storage_account_type" hcl:"os_disk_storage_account_type"`
OSDiskCacheType *string `mapstructure:"os_disk_cache_type" cty:"os_disk_cache_type" hcl:"os_disk_cache_type"`
ImageHyperVGeneration *string `mapstructure:"image_hyperv_generation" cty:"image_hyperv_generation" hcl:"image_hyperv_generation"`
TemporaryOSDiskID *string `mapstructure:"temporary_os_disk_id" cty:"temporary_os_disk_id" hcl:"temporary_os_disk_id"`
TemporaryOSDiskSnapshotID *string `mapstructure:"temporary_os_disk_snapshot_id" cty:"temporary_os_disk_snapshot_id" hcl:"temporary_os_disk_snapshot_id"`
SkipCleanup *bool `mapstructure:"skip_cleanup" cty:"skip_cleanup" hcl:"skip_cleanup"`
ImageResourceID *string `mapstructure:"image_resource_id" cty:"image_resource_id" hcl:"image_resource_id"`
SharedImageGalleryDestination *FlatSharedImageGalleryDestination `mapstructure:"shared_image_destination" cty:"shared_image_destination" hcl:"shared_image_destination"`
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
CloudEnvironmentName *string `mapstructure:"cloud_environment_name" required:"false" cty:"cloud_environment_name" hcl:"cloud_environment_name"`
ClientID *string `mapstructure:"client_id" cty:"client_id" hcl:"client_id"`
ClientSecret *string `mapstructure:"client_secret" cty:"client_secret" hcl:"client_secret"`
ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path" hcl:"client_cert_path"`
ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt" hcl:"client_jwt"`
ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"`
TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"`
SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id" hcl:"subscription_id"`
FromScratch *bool `mapstructure:"from_scratch" cty:"from_scratch" hcl:"from_scratch"`
Source *string `mapstructure:"source" required:"true" cty:"source" hcl:"source"`
CommandWrapper *string `mapstructure:"command_wrapper" cty:"command_wrapper" hcl:"command_wrapper"`
PreMountCommands []string `mapstructure:"pre_mount_commands" cty:"pre_mount_commands" hcl:"pre_mount_commands"`
MountOptions []string `mapstructure:"mount_options" cty:"mount_options" hcl:"mount_options"`
MountPartition *string `mapstructure:"mount_partition" cty:"mount_partition" hcl:"mount_partition"`
MountPath *string `mapstructure:"mount_path" cty:"mount_path" hcl:"mount_path"`
PostMountCommands []string `mapstructure:"post_mount_commands" cty:"post_mount_commands" hcl:"post_mount_commands"`
ChrootMounts [][]string `mapstructure:"chroot_mounts" cty:"chroot_mounts" hcl:"chroot_mounts"`
CopyFiles []string `mapstructure:"copy_files" cty:"copy_files" hcl:"copy_files"`
OSDiskSizeGB *int32 `mapstructure:"os_disk_size_gb" cty:"os_disk_size_gb" hcl:"os_disk_size_gb"`
OSDiskStorageAccountType *string `mapstructure:"os_disk_storage_account_type" cty:"os_disk_storage_account_type" hcl:"os_disk_storage_account_type"`
OSDiskCacheType *string `mapstructure:"os_disk_cache_type" cty:"os_disk_cache_type" hcl:"os_disk_cache_type"`
DataDiskStorageAccountType *string `mapstructure:"data_disk_storage_account_type" cty:"data_disk_storage_account_type" hcl:"data_disk_storage_account_type"`
DataDiskCacheType *string `mapstructure:"data_disk_cache_type" cty:"data_disk_cache_type" hcl:"data_disk_cache_type"`
ImageHyperVGeneration *string `mapstructure:"image_hyperv_generation" cty:"image_hyperv_generation" hcl:"image_hyperv_generation"`
TemporaryOSDiskID *string `mapstructure:"temporary_os_disk_id" cty:"temporary_os_disk_id" hcl:"temporary_os_disk_id"`
TemporaryOSDiskSnapshotID *string `mapstructure:"temporary_os_disk_snapshot_id" cty:"temporary_os_disk_snapshot_id" hcl:"temporary_os_disk_snapshot_id"`
TemporaryDataDiskIDPrefix *string `mapstructure:"temporary_data_disk_id_prefix" cty:"temporary_data_disk_id_prefix" hcl:"temporary_data_disk_id_prefix"`
TemporaryDataDiskSnapshotIDPrefix *string `mapstructure:"temporary_data_disk_snapshot_id" cty:"temporary_data_disk_snapshot_id" hcl:"temporary_data_disk_snapshot_id"`
SkipCleanup *bool `mapstructure:"skip_cleanup" cty:"skip_cleanup" hcl:"skip_cleanup"`
ImageResourceID *string `mapstructure:"image_resource_id" cty:"image_resource_id" hcl:"image_resource_id"`
SharedImageGalleryDestination *FlatSharedImageGalleryDestination `mapstructure:"shared_image_destination" cty:"shared_image_destination" hcl:"shared_image_destination"`
}
// FlatMapstructure returns a new FlatConfig.
@ -57,40 +61,44 @@ func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec }
// The decoded values from this spec will then be applied to a FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"cloud_environment_name": &hcldec.AttrSpec{Name: "cloud_environment_name", Type: cty.String, Required: false},
"client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false},
"client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false},
"client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false},
"client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false},
"object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false},
"tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false},
"subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false},
"from_scratch": &hcldec.AttrSpec{Name: "from_scratch", Type: cty.Bool, Required: false},
"source": &hcldec.AttrSpec{Name: "source", Type: cty.String, Required: false},
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
"pre_mount_commands": &hcldec.AttrSpec{Name: "pre_mount_commands", Type: cty.List(cty.String), Required: false},
"mount_options": &hcldec.AttrSpec{Name: "mount_options", Type: cty.List(cty.String), Required: false},
"mount_partition": &hcldec.AttrSpec{Name: "mount_partition", Type: cty.String, Required: false},
"mount_path": &hcldec.AttrSpec{Name: "mount_path", Type: cty.String, Required: false},
"post_mount_commands": &hcldec.AttrSpec{Name: "post_mount_commands", Type: cty.List(cty.String), Required: false},
"chroot_mounts": &hcldec.AttrSpec{Name: "chroot_mounts", Type: cty.List(cty.List(cty.String)), Required: false},
"copy_files": &hcldec.AttrSpec{Name: "copy_files", Type: cty.List(cty.String), Required: false},
"os_disk_size_gb": &hcldec.AttrSpec{Name: "os_disk_size_gb", Type: cty.Number, Required: false},
"os_disk_storage_account_type": &hcldec.AttrSpec{Name: "os_disk_storage_account_type", Type: cty.String, Required: false},
"os_disk_cache_type": &hcldec.AttrSpec{Name: "os_disk_cache_type", Type: cty.String, Required: false},
"image_hyperv_generation": &hcldec.AttrSpec{Name: "image_hyperv_generation", Type: cty.String, Required: false},
"temporary_os_disk_id": &hcldec.AttrSpec{Name: "temporary_os_disk_id", Type: cty.String, Required: false},
"temporary_os_disk_snapshot_id": &hcldec.AttrSpec{Name: "temporary_os_disk_snapshot_id", Type: cty.String, Required: false},
"skip_cleanup": &hcldec.AttrSpec{Name: "skip_cleanup", Type: cty.Bool, Required: false},
"image_resource_id": &hcldec.AttrSpec{Name: "image_resource_id", Type: cty.String, Required: false},
"shared_image_destination": &hcldec.BlockSpec{TypeName: "shared_image_destination", Nested: hcldec.ObjectSpec((*FlatSharedImageGalleryDestination)(nil).HCL2Spec())},
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"cloud_environment_name": &hcldec.AttrSpec{Name: "cloud_environment_name", Type: cty.String, Required: false},
"client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false},
"client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false},
"client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false},
"client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false},
"object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false},
"tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false},
"subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false},
"from_scratch": &hcldec.AttrSpec{Name: "from_scratch", Type: cty.Bool, Required: false},
"source": &hcldec.AttrSpec{Name: "source", Type: cty.String, Required: false},
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
"pre_mount_commands": &hcldec.AttrSpec{Name: "pre_mount_commands", Type: cty.List(cty.String), Required: false},
"mount_options": &hcldec.AttrSpec{Name: "mount_options", Type: cty.List(cty.String), Required: false},
"mount_partition": &hcldec.AttrSpec{Name: "mount_partition", Type: cty.String, Required: false},
"mount_path": &hcldec.AttrSpec{Name: "mount_path", Type: cty.String, Required: false},
"post_mount_commands": &hcldec.AttrSpec{Name: "post_mount_commands", Type: cty.List(cty.String), Required: false},
"chroot_mounts": &hcldec.AttrSpec{Name: "chroot_mounts", Type: cty.List(cty.List(cty.String)), Required: false},
"copy_files": &hcldec.AttrSpec{Name: "copy_files", Type: cty.List(cty.String), Required: false},
"os_disk_size_gb": &hcldec.AttrSpec{Name: "os_disk_size_gb", Type: cty.Number, Required: false},
"os_disk_storage_account_type": &hcldec.AttrSpec{Name: "os_disk_storage_account_type", Type: cty.String, Required: false},
"os_disk_cache_type": &hcldec.AttrSpec{Name: "os_disk_cache_type", Type: cty.String, Required: false},
"data_disk_storage_account_type": &hcldec.AttrSpec{Name: "data_disk_storage_account_type", Type: cty.String, Required: false},
"data_disk_cache_type": &hcldec.AttrSpec{Name: "data_disk_cache_type", Type: cty.String, Required: false},
"image_hyperv_generation": &hcldec.AttrSpec{Name: "image_hyperv_generation", Type: cty.String, Required: false},
"temporary_os_disk_id": &hcldec.AttrSpec{Name: "temporary_os_disk_id", Type: cty.String, Required: false},
"temporary_os_disk_snapshot_id": &hcldec.AttrSpec{Name: "temporary_os_disk_snapshot_id", Type: cty.String, Required: false},
"temporary_data_disk_id_prefix": &hcldec.AttrSpec{Name: "temporary_data_disk_id_prefix", Type: cty.String, Required: false},
"temporary_data_disk_snapshot_id": &hcldec.AttrSpec{Name: "temporary_data_disk_snapshot_id", Type: cty.String, Required: false},
"skip_cleanup": &hcldec.AttrSpec{Name: "skip_cleanup", Type: cty.Bool, Required: false},
"image_resource_id": &hcldec.AttrSpec{Name: "image_resource_id", Type: cty.String, Required: false},
"shared_image_destination": &hcldec.BlockSpec{TypeName: "shared_image_destination", Nested: hcldec.ObjectSpec((*FlatSharedImageGalleryDestination)(nil).HCL2Spec())},
}
return s
}

View File

@ -151,9 +151,9 @@ func Test_buildsteps(t *testing.T) {
config: Config{FromScratch: true},
verify: func(steps []multistep.Step, _ *testing.T) {
for _, s := range steps {
if s, ok := s.(*StepCreateNewDisk); ok {
if s.SourceDiskResourceID == "" &&
s.PlatformImage == nil {
if s, ok := s.(*StepCreateNewDiskset); ok {
if s.SourceOSDiskResourceID == "" &&
s.SourcePlatformImage == nil {
return
}
t.Errorf("found misconfigured StepCreateNewDisk: %+v", s)
@ -166,10 +166,10 @@ func Test_buildsteps(t *testing.T) {
config: Config{Source: "publisher:offer:sku:version", sourceType: sourcePlatformImage},
verify: func(steps []multistep.Step, _ *testing.T) {
for _, s := range steps {
if s, ok := s.(*StepCreateNewDisk); ok {
if s.SourceDiskResourceID == "" &&
s.PlatformImage != nil &&
s.PlatformImage.Publisher == "publisher" {
if s, ok := s.(*StepCreateNewDiskset); ok {
if s.SourceOSDiskResourceID == "" &&
s.SourcePlatformImage != nil &&
s.SourcePlatformImage.Publisher == "publisher" {
return
}
t.Errorf("found misconfigured StepCreateNewDisk: %+v", s)
@ -197,9 +197,9 @@ func Test_buildsteps(t *testing.T) {
config: Config{Source: "diskresourceid", sourceType: sourceDisk},
verify: func(steps []multistep.Step, _ *testing.T) {
for _, s := range steps {
if s, ok := s.(*StepCreateNewDisk); ok {
if s.SourceDiskResourceID == "diskresourceid" &&
s.PlatformImage == nil {
if s, ok := s.(*StepCreateNewDiskset); ok {
if s.SourceOSDiskResourceID == "diskresourceid" &&
s.SourcePlatformImage == nil {
return
}
t.Errorf("found misconfigured StepCreateNewDisk: %+v", s)

View File

@ -1,6 +1,6 @@
package chroot
const (
stateBagKey_OSDiskResourceID = "os_disk_resource_id"
stateBagKey_OSDiskSnapshotResourceID = "os_disk_snapshot_resource_id"
stateBagKey_Diskset = "diskset"
stateBagKey_Snapshotset = "snapshotset"
)

View File

@ -0,0 +1,23 @@
package chroot
import "github.com/hashicorp/packer/builder/azure/common/client"
// Diskset represents all of the disks or snapshots associated with an image.
// It maps lun to resource ids. The OS disk is stored with lun=-1.
type Diskset map[int32]client.Resource
// OS return the OS disk resource ID or nil if it is not assigned
func (ds Diskset) OS() *client.Resource {
if r, ok := ds[-1]; ok {
return &r
}
return nil
}
// Data return the data disk resource ID or nil if it is not assigned
func (ds Diskset) Data(lun int32) *client.Resource {
if r, ok := ds[lun]; ok {
return &r
}
return nil
}

View File

@ -0,0 +1,16 @@
package chroot
import "github.com/hashicorp/packer/builder/azure/common/client"
// diskset easily creates a diskset for testing
func diskset(ids ...string) Diskset {
diskset := make(Diskset)
for i, id := range ids {
r, err := client.ParseResourceID(id)
if err != nil {
panic(err)
}
diskset[int32(i-1)] = r
}
return diskset
}

View File

@ -20,7 +20,8 @@ type StepAttachDisk struct {
func (s *StepAttachDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
diskResourceID := state.Get(stateBagKey_OSDiskResourceID).(string)
diskset := state.Get(stateBagKey_Diskset).(Diskset)
diskResourceID := diskset.OS().String()
ui.Say(fmt.Sprintf("Attaching disk '%s'", diskResourceID))
@ -67,7 +68,8 @@ func (s *StepAttachDisk) CleanupFunc(state multistep.StateBag) error {
if s.attached {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
diskResourceID := state.Get(stateBagKey_OSDiskResourceID).(string)
diskset := state.Get(stateBagKey_Diskset).(Diskset)
diskResourceID := diskset.OS().String()
ui.Say(fmt.Sprintf("Detaching disk '%s'", diskResourceID))

View File

@ -78,7 +78,7 @@ func TestStepAttachDisk_Run(t *testing.T) {
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{})
state.Put("ui", ui)
state.Put(stateBagKey_OSDiskResourceID, "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/disk1")
state.Put(stateBagKey_Diskset, diskset("/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/disk1"))
got := s.Run(context.TODO(), state)
if !reflect.DeepEqual(got, tt.want) {

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log"
"sort"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
"github.com/Azure/go-autorest/autorest/azure"
@ -16,26 +17,26 @@ import (
var _ multistep.Step = &StepCreateImage{}
type StepCreateImage struct {
ImageResourceID string
ImageOSState string
OSDiskStorageAccountType string
OSDiskCacheType string
Location string
imageResource azure.Resource
ImageResourceID string
ImageOSState string
OSDiskStorageAccountType string
OSDiskCacheType string
DataDiskStorageAccountType string
DataDiskCacheType string
Location string
}
func (s *StepCreateImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
diskResourceID := state.Get(stateBagKey_OSDiskResourceID).(string)
diskset := state.Get(stateBagKey_Diskset).(Diskset)
diskResourceID := diskset.OS().String()
ui.Say(fmt.Sprintf("Creating image %s\n using %s for os disk.",
s.ImageResourceID,
diskResourceID))
var err error
s.imageResource, err = azure.ParseResourceID(s.ImageResourceID)
imageResource, err := azure.ParseResourceID(s.ImageResourceID)
if err != nil {
log.Printf("StepCreateImage.Run: error: %+v", err)
@ -65,10 +66,31 @@ func (s *StepCreateImage) Run(ctx context.Context, state multistep.StateBag) mul
},
// Tags: nil,
}
var datadisks []compute.ImageDataDisk
for lun, resource := range diskset {
if lun != -1 {
ui.Say(fmt.Sprintf(" using %q for data disk (lun %d).", resource, lun))
datadisks = append(datadisks, compute.ImageDataDisk{
Lun: to.Int32Ptr(lun),
ManagedDisk: &compute.SubResource{ID: to.StringPtr(resource.String())},
StorageAccountType: compute.StorageAccountTypes(s.DataDiskStorageAccountType),
Caching: compute.CachingTypes(s.DataDiskCacheType),
})
}
}
if datadisks != nil {
sort.Slice(datadisks, func(i, j int) bool {
return *datadisks[i].Lun < *datadisks[j].Lun
})
image.ImageProperties.StorageProfile.DataDisks = &datadisks
}
f, err := azcli.ImagesClient().CreateOrUpdate(
ctx,
s.imageResource.ResourceGroup,
s.imageResource.ResourceName,
imageResource.ResourceGroup,
imageResource.ResourceName,
image)
if err == nil {
log.Println("Image creation in process...")

View File

@ -0,0 +1,137 @@
package chroot
import (
"context"
"io/ioutil"
"net/http"
"reflect"
"regexp"
"testing"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
"github.com/Azure/go-autorest/autorest"
)
func TestStepCreateImage_Run(t *testing.T) {
type fields struct {
ImageResourceID string
ImageOSState string
OSDiskStorageAccountType string
OSDiskCacheType string
DataDiskStorageAccountType string
DataDiskCacheType string
Location string
}
tests := []struct {
name string
fields fields
diskset Diskset
want multistep.StepAction
wantPutBody string
}{
{
name: "happy path",
fields: fields{
ImageResourceID: "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/images/myImage",
Location: "location1",
OSDiskStorageAccountType: "Standard_LRS",
OSDiskCacheType: "ReadWrite",
DataDiskStorageAccountType: "Premium_LRS",
DataDiskCacheType: "ReadOnly",
},
diskset: diskset(
"/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/osdisk",
"/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/datadisk0",
"/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/datadisk1",
"/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/datadisk2"),
want: multistep.ActionContinue,
wantPutBody: `{
"location": "location1",
"properties": {
"storageProfile": {
"osDisk": {
"osType": "Linux",
"managedDisk": {
"id": "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/osdisk"
},
"caching": "ReadWrite",
"storageAccountType": "Standard_LRS"
},
"dataDisks": [
{
"lun": 0,
"managedDisk": {
"id": "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/datadisk0"
},
"caching": "ReadOnly",
"storageAccountType": "Premium_LRS"
},
{
"lun": 1,
"managedDisk": {
"id": "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/datadisk1"
},
"caching": "ReadOnly",
"storageAccountType": "Premium_LRS"
},
{
"lun": 2,
"managedDisk": {
"id": "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/datadisk2"
},
"caching": "ReadOnly",
"storageAccountType": "Premium_LRS"
}
]
}
}
}`,
},
}
for _, tt := range tests {
ic := compute.NewImagesClient("subscriptionID")
ic.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
if r.Method != "PUT" {
t.Fatal("Expected only a PUT call")
}
if tt.wantPutBody != "" {
b, _ := ioutil.ReadAll(r.Body)
expectedPutBody := regexp.MustCompile(`[\s\n]`).ReplaceAllString(tt.wantPutBody, "")
if string(b) != expectedPutBody {
t.Errorf("expected body to be %v, but got %v", expectedPutBody, string(b))
}
}
return &http.Response{
Request: r,
StatusCode: 200,
}, nil
})
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{
ImagesClientMock: ic,
})
state.Put("ui", packer.TestUi(t))
state.Put(stateBagKey_Diskset, tt.diskset)
t.Run(tt.name, func(t *testing.T) {
s := &StepCreateImage{
ImageResourceID: tt.fields.ImageResourceID,
ImageOSState: tt.fields.ImageOSState,
OSDiskStorageAccountType: tt.fields.OSDiskStorageAccountType,
OSDiskCacheType: tt.fields.OSDiskCacheType,
DataDiskStorageAccountType: tt.fields.DataDiskStorageAccountType,
DataDiskCacheType: tt.fields.DataDiskCacheType,
Location: tt.fields.Location,
}
if got := s.Run(context.TODO(), state); !reflect.DeepEqual(got, tt.want) {
t.Errorf("StepCreateImage.Run() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -1,152 +0,0 @@
package chroot
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
var _ multistep.Step = &StepCreateNewDisk{}
type StepCreateNewDisk struct {
ResourceID string // Disk ID
subscriptionID, resourceGroup, diskName string // split out resource id
DiskSizeGB int32 // optional, ignored if 0
DiskStorageAccountType string // from compute.DiskStorageAccountTypes
HyperVGeneration string
Location string
PlatformImage *client.PlatformImage
SourceDiskResourceID string
SourceImageResourceID string
SkipCleanup bool
}
func parseDiskResourceID(resourceID string) (subscriptionID, resourceGroup, diskName string, err error) {
r, err := azure.ParseResourceID(resourceID)
if err != nil {
return "", "", "", err
}
if !strings.EqualFold(r.Provider, "Microsoft.Compute") ||
!strings.EqualFold(r.ResourceType, "disks") {
return "", "", "", fmt.Errorf("Resource %q is not of type Microsoft.Compute/disks", resourceID)
}
return r.SubscriptionID, r.ResourceGroup, r.ResourceName, nil
}
func (s *StepCreateNewDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
state.Put(stateBagKey_OSDiskResourceID, s.ResourceID)
ui.Say(fmt.Sprintf("Creating disk '%s'", s.ResourceID))
var err error
s.subscriptionID, s.resourceGroup, s.diskName, err = parseDiskResourceID(s.ResourceID)
if err != nil {
log.Printf("StepCreateNewDisk.Run: error: %+v", err)
err := fmt.Errorf(
"error parsing resource id '%s': %v", s.ResourceID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
disk := compute.Disk{
Location: to.StringPtr(s.Location),
DiskProperties: &compute.DiskProperties{
OsType: "Linux",
CreationData: &compute.CreationData{},
},
}
if s.DiskStorageAccountType != "" {
disk.Sku = &compute.DiskSku{
Name: compute.DiskStorageAccountTypes(s.DiskStorageAccountType),
}
}
if s.HyperVGeneration != "" {
disk.DiskProperties.HyperVGeneration = compute.HyperVGeneration(s.HyperVGeneration)
}
if s.DiskSizeGB > 0 {
disk.DiskProperties.DiskSizeGB = to.Int32Ptr(s.DiskSizeGB)
}
switch {
case s.PlatformImage != nil:
disk.CreationData.CreateOption = compute.FromImage
disk.CreationData.ImageReference = &compute.ImageDiskReference{
ID: to.StringPtr(fmt.Sprintf(
"/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions/%s",
s.subscriptionID, s.Location, s.PlatformImage.Publisher, s.PlatformImage.Offer, s.PlatformImage.Sku, s.PlatformImage.Version)),
}
case s.SourceDiskResourceID != "":
disk.CreationData.CreateOption = compute.Copy
disk.CreationData.SourceResourceID = to.StringPtr(s.SourceDiskResourceID)
case s.SourceImageResourceID != "":
disk.CreationData.CreateOption = compute.FromImage
disk.CreationData.GalleryImageReference = &compute.ImageDiskReference{
ID: to.StringPtr(s.SourceImageResourceID),
}
default:
disk.CreationData.CreateOption = compute.Empty
}
f, err := azcli.DisksClient().CreateOrUpdate(ctx, s.resourceGroup, s.diskName, disk)
if err == nil {
cli := azcli.PollClient() // quick polling for quick operations
cli.PollingDelay = time.Second
err = f.WaitForCompletionRef(ctx, cli)
}
if err != nil {
log.Printf("StepCreateNewDisk.Run: error: %+v", err)
err := fmt.Errorf(
"error creating new disk '%s': %v", s.ResourceID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepCreateNewDisk) Cleanup(state multistep.StateBag) {
if !s.SkipCleanup {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Waiting for disk %q detach to complete", s.ResourceID))
err := NewDiskAttacher(azcli).WaitForDetach(context.Background(), s.ResourceID)
if err != nil {
ui.Error(fmt.Sprintf("error detaching disk %q: %s", s.ResourceID, err))
}
ui.Say(fmt.Sprintf("Deleting disk %q", s.ResourceID))
f, err := azcli.DisksClient().Delete(context.TODO(), s.resourceGroup, s.diskName)
if err == nil {
err = f.WaitForCompletionRef(context.TODO(), azcli.PollClient())
}
if err != nil {
log.Printf("StepCreateNewDisk.Cleanup: error: %+v", err)
ui.Error(fmt.Sprintf("error deleting disk '%s': %v.", s.ResourceID, err))
}
}
}

View File

@ -1,140 +0,0 @@
package chroot
import (
"context"
"io/ioutil"
"net/http"
"reflect"
"regexp"
"testing"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
"github.com/Azure/go-autorest/autorest"
)
func TestStepCreateNewDisk_Run(t *testing.T) {
type fields struct {
ResourceID string
DiskSizeGB int32
DiskStorageAccountType string
HyperVGeneration string
Location string
PlatformImage *client.PlatformImage
SourceDiskResourceID string
expectedPutDiskBody string
}
tests := []struct {
name string
fields fields
want multistep.StepAction
}{
{
name: "from disk",
fields: fields{
ResourceID: "/subscriptions/SubscriptionID/resourcegroups/ResourceGroupName/providers/Microsoft.Compute/disks/TemporaryOSDiskName",
DiskSizeGB: 42,
DiskStorageAccountType: string(compute.PremiumLRS),
HyperVGeneration: string(compute.V1),
Location: "westus",
SourceDiskResourceID: "SourceDisk",
expectedPutDiskBody: `
{
"location": "westus",
"properties": {
"osType": "Linux",
"hyperVGeneration": "V1",
"creationData": {
"createOption": "Copy",
"sourceResourceId": "SourceDisk"
},
"diskSizeGB": 42
},
"sku": {
"name": "Premium_LRS"
}
}`,
},
want: multistep.ActionContinue,
},
{
name: "from image",
fields: fields{
ResourceID: "/subscriptions/SubscriptionID/resourcegroups/ResourceGroupName/providers/Microsoft.Compute/disks/TemporaryOSDiskName",
DiskStorageAccountType: string(compute.StandardLRS),
HyperVGeneration: string(compute.V1),
Location: "westus",
PlatformImage: &client.PlatformImage{
Publisher: "Microsoft",
Offer: "Windows",
Sku: "2016-DataCenter",
Version: "2016.1.4",
},
expectedPutDiskBody: `
{
"location": "westus",
"properties": {
"osType": "Linux",
"hyperVGeneration": "V1",
"creationData": {
"createOption":"FromImage",
"imageReference": {
"id":"/subscriptions/SubscriptionID/providers/Microsoft.Compute/locations/westus/publishers/Microsoft/artifacttypes/vmimage/offers/Windows/skus/2016-DataCenter/versions/2016.1.4"
}
}
},
"sku": {
"name": "Standard_LRS"
}
}`,
},
want: multistep.ActionContinue,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := StepCreateNewDisk{
ResourceID: tt.fields.ResourceID,
DiskSizeGB: tt.fields.DiskSizeGB,
DiskStorageAccountType: tt.fields.DiskStorageAccountType,
HyperVGeneration: tt.fields.HyperVGeneration,
Location: tt.fields.Location,
PlatformImage: tt.fields.PlatformImage,
SourceDiskResourceID: tt.fields.SourceDiskResourceID,
}
expectedPutDiskBody := regexp.MustCompile(`[\s\n]`).ReplaceAllString(tt.fields.expectedPutDiskBody, "")
m := compute.NewDisksClient("subscriptionId")
m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
if r.Method != "PUT" {
t.Fatal("Expected only a PUT disk call")
}
b, _ := ioutil.ReadAll(r.Body)
if string(b) != expectedPutDiskBody {
t.Fatalf("expected body to be %q, but got %q", expectedPutDiskBody, string(b))
}
return &http.Response{
Request: r,
StatusCode: 200,
}, nil
})
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{
DisksClientMock: m,
})
state.Put("ui", packer.TestUi(t))
if got := s.Run(context.TODO(), state); !reflect.DeepEqual(got, tt.want) {
t.Errorf("StepCreateNewDisk.Run() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -0,0 +1,239 @@
package chroot
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
"github.com/Azure/go-autorest/autorest/to"
)
var _ multistep.Step = &StepCreateNewDiskset{}
type StepCreateNewDiskset struct {
OSDiskID string // Disk ID
OSDiskSizeGB int32 // optional, ignored if 0
OSDiskStorageAccountType string // from compute.DiskStorageAccountTypes
DataDiskStorageAccountType string // from compute.DiskStorageAccountTypes
DataDiskIDPrefix string
disks Diskset
HyperVGeneration string // For OS disk
// Copy another disk
SourceOSDiskResourceID string
// Extract from platform image
SourcePlatformImage *client.PlatformImage
// Extract from shared image
SourceImageResourceID string
// Location is needed for platform and shared images
Location string
SkipCleanup bool
}
func (s *StepCreateNewDiskset) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
s.disks = make(Diskset)
errorMessage := func(format string, params ...interface{}) multistep.StepAction {
err := fmt.Errorf("StepCreateNewDiskset.Run: error: "+format, params...)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// we always have an OS disk
osDisk, err := client.ParseResourceID(s.OSDiskID)
if err != nil {
return errorMessage("error parsing resource id '%s': %v", s.OSDiskID, err)
}
if !strings.EqualFold(osDisk.Provider, "Microsoft.Compute") ||
!strings.EqualFold(osDisk.ResourceType.String(), "disks") {
return errorMessage("Resource %q is not of type Microsoft.Compute/disks", s.OSDiskID)
}
// transform step config to disk model
disk := s.getOSDiskDefinition(azcli.SubscriptionID())
// Initiate disk creation
f, err := azcli.DisksClient().CreateOrUpdate(ctx, osDisk.ResourceGroup, osDisk.ResourceName.String(), disk)
if err != nil {
return errorMessage("Failed to initiate resource creation: %q", osDisk)
}
s.disks[-1] = osDisk // save the resoure we just create in our disk set
state.Put(stateBagKey_Diskset, s.disks) // update the statebag
ui.Say(fmt.Sprintf("Creating disk %q", s.OSDiskID))
type Future struct {
client.Resource
compute.DisksCreateOrUpdateFuture
}
futures := []Future{{osDisk, f}}
if s.SourceImageResourceID != "" {
// retrieve image to see if there are any datadisks
imageID, err := client.ParseResourceID(s.SourceImageResourceID)
if err != nil {
return errorMessage("could not parse source image id %q: %v", s.SourceImageResourceID, err)
}
if !strings.EqualFold(imageID.Provider+"/"+imageID.ResourceType.String(),
"Microsoft.Compute/galleries/images/versions") {
return errorMessage("source image id is not a shared image version %q, expected type 'Microsoft.Compute/galleries/images/versions'", imageID)
}
image, err := azcli.GalleryImageVersionsClient().Get(ctx,
imageID.ResourceGroup,
imageID.ResourceName[0], imageID.ResourceName[1], imageID.ResourceName[2], "")
if err != nil {
return errorMessage("error retrieving source image %q: %v", imageID, err)
}
if image.GalleryImageVersionProperties != nil &&
image.GalleryImageVersionProperties.StorageProfile != nil &&
image.GalleryImageVersionProperties.StorageProfile.DataDiskImages != nil {
for i, ddi := range *image.GalleryImageVersionProperties.StorageProfile.DataDiskImages {
if ddi.Lun == nil {
return errorMessage("unexpected: lun is null for data disk # %d", i)
}
datadiskID, err := client.ParseResourceID(fmt.Sprintf("%s%d", s.DataDiskIDPrefix, *ddi.Lun))
if err != nil {
return errorMessage("unable to construct resource id for datadisk: %v", err)
}
disk := s.getDatadiskDefinitionFromImage(*ddi.Lun)
// Initiate disk creation
f, err := azcli.DisksClient().CreateOrUpdate(ctx, datadiskID.ResourceGroup, datadiskID.ResourceName.String(), disk)
if err != nil {
return errorMessage("Failed to initiate resource creation: %q", datadiskID)
}
s.disks[*ddi.Lun] = datadiskID // save the resoure we just create in our disk set
state.Put(stateBagKey_Diskset, s.disks) // update the statebag
ui.Say(fmt.Sprintf("Creating disk %q", datadiskID))
futures = append(futures, Future{datadiskID, f})
}
}
}
ui.Say("Waiting for disks to be created.")
// Wait for completion
for _, f := range futures {
cli := azcli.PollClient() // quick polling for quick operations
cli.PollingDelay = time.Second
err = f.WaitForCompletionRef(ctx, cli)
if err != nil {
return errorMessage(
"error creating new disk '%s': %v", f.Resource, err)
}
ui.Say(fmt.Sprintf("Disk %q created", f.Resource))
}
return multistep.ActionContinue
}
func (s StepCreateNewDiskset) getOSDiskDefinition(subscriptionID string) compute.Disk {
disk := compute.Disk{
Location: to.StringPtr(s.Location),
DiskProperties: &compute.DiskProperties{
OsType: "Linux",
CreationData: &compute.CreationData{},
},
}
if s.OSDiskStorageAccountType != "" {
disk.Sku = &compute.DiskSku{
Name: compute.DiskStorageAccountTypes(s.OSDiskStorageAccountType),
}
}
if s.HyperVGeneration != "" {
disk.DiskProperties.HyperVGeneration = compute.HyperVGeneration(s.HyperVGeneration)
}
if s.OSDiskSizeGB > 0 {
disk.DiskProperties.DiskSizeGB = to.Int32Ptr(s.OSDiskSizeGB)
}
switch {
case s.SourcePlatformImage != nil:
disk.CreationData.CreateOption = compute.FromImage
disk.CreationData.ImageReference = &compute.ImageDiskReference{
ID: to.StringPtr(fmt.Sprintf(
"/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions/%s",
subscriptionID, s.Location,
s.SourcePlatformImage.Publisher, s.SourcePlatformImage.Offer, s.SourcePlatformImage.Sku, s.SourcePlatformImage.Version)),
}
case s.SourceOSDiskResourceID != "":
disk.CreationData.CreateOption = compute.Copy
disk.CreationData.SourceResourceID = to.StringPtr(s.SourceOSDiskResourceID)
case s.SourceImageResourceID != "":
disk.CreationData.CreateOption = compute.FromImage
disk.CreationData.GalleryImageReference = &compute.ImageDiskReference{
ID: to.StringPtr(s.SourceImageResourceID),
}
default:
disk.CreationData.CreateOption = compute.Empty
}
return disk
}
func (s StepCreateNewDiskset) getDatadiskDefinitionFromImage(lun int32) compute.Disk {
disk := compute.Disk{
Location: to.StringPtr(s.Location),
DiskProperties: &compute.DiskProperties{
CreationData: &compute.CreationData{},
},
}
disk.CreationData.CreateOption = compute.FromImage
disk.CreationData.GalleryImageReference = &compute.ImageDiskReference{
ID: to.StringPtr(s.SourceImageResourceID),
Lun: to.Int32Ptr(lun),
}
if s.DataDiskStorageAccountType != "" {
disk.Sku = &compute.DiskSku{
Name: compute.DiskStorageAccountTypes(s.DataDiskStorageAccountType),
}
}
return disk
}
func (s *StepCreateNewDiskset) Cleanup(state multistep.StateBag) {
if !s.SkipCleanup {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
for _, d := range s.disks {
ui.Say(fmt.Sprintf("Waiting for disk %q detach to complete", d))
err := NewDiskAttacher(azcli).WaitForDetach(context.Background(), d.String())
if err != nil {
ui.Error(fmt.Sprintf("error detaching disk %q: %s", d, err))
}
ui.Say(fmt.Sprintf("Deleting disk %q", d))
f, err := azcli.DisksClient().Delete(context.TODO(), d.ResourceGroup, d.ResourceName.String())
if err == nil {
err = f.WaitForCompletionRef(context.TODO(), azcli.PollClient())
}
if err != nil {
log.Printf("StepCreateNewDiskset.Cleanup: error: %+v", err)
ui.Error(fmt.Sprintf("error deleting disk '%s': %v.", d, err))
}
}
}
}

View File

@ -0,0 +1,247 @@
package chroot
import (
"context"
"io/ioutil"
"net/http"
"reflect"
"regexp"
"strings"
"testing"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
"github.com/Azure/go-autorest/autorest"
)
func TestStepCreateNewDisk_Run(t *testing.T) {
tests := []struct {
name string
fields StepCreateNewDiskset
expectedPutDiskBodies []string
want multistep.StepAction
verifyDiskset *Diskset
}{
{
name: "from disk",
fields: StepCreateNewDiskset{
OSDiskID: "/subscriptions/SubscriptionID/resourcegroups/ResourceGroupName/providers/Microsoft.Compute/disks/TemporaryOSDiskName",
OSDiskSizeGB: 42,
OSDiskStorageAccountType: string(compute.PremiumLRS),
HyperVGeneration: string(compute.V1),
Location: "westus",
SourceOSDiskResourceID: "SourceDisk",
},
expectedPutDiskBodies: []string{`
{
"location": "westus",
"properties": {
"osType": "Linux",
"hyperVGeneration": "V1",
"creationData": {
"createOption": "Copy",
"sourceResourceId": "SourceDisk"
},
"diskSizeGB": 42
},
"sku": {
"name": "Premium_LRS"
}
}`},
want: multistep.ActionContinue,
verifyDiskset: &Diskset{-1: resource("/subscriptions/SubscriptionID/resourceGroups/ResourceGroupName/providers/Microsoft.Compute/disks/TemporaryOSDiskName")},
},
{
name: "from platform image",
fields: StepCreateNewDiskset{
OSDiskID: "/subscriptions/SubscriptionID/resourcegroups/ResourceGroupName/providers/Microsoft.Compute/disks/TemporaryOSDiskName",
OSDiskStorageAccountType: string(compute.StandardLRS),
HyperVGeneration: string(compute.V1),
Location: "westus",
SourcePlatformImage: &client.PlatformImage{
Publisher: "Microsoft",
Offer: "Windows",
Sku: "2016-DataCenter",
Version: "2016.1.4",
},
},
expectedPutDiskBodies: []string{`
{
"location": "westus",
"properties": {
"osType": "Linux",
"hyperVGeneration": "V1",
"creationData": {
"createOption":"FromImage",
"imageReference": {
"id":"/subscriptions/SubscriptionID/providers/Microsoft.Compute/locations/westus/publishers/Microsoft/artifacttypes/vmimage/offers/Windows/skus/2016-DataCenter/versions/2016.1.4"
}
}
},
"sku": {
"name": "Standard_LRS"
}
}`},
want: multistep.ActionContinue,
verifyDiskset: &Diskset{-1: resource("/subscriptions/SubscriptionID/resourceGroups/ResourceGroupName/providers/Microsoft.Compute/disks/TemporaryOSDiskName")},
},
{
name: "from shared image",
fields: StepCreateNewDiskset{
OSDiskID: "/subscriptions/SubscriptionID/resourcegroups/ResourceGroupName/providers/Microsoft.Compute/disks/TemporaryOSDiskName",
OSDiskStorageAccountType: string(compute.StandardLRS),
DataDiskStorageAccountType: string(compute.PremiumLRS),
DataDiskIDPrefix: "/subscriptions/SubscriptionID/resourcegroups/ResourceGroupName/providers/Microsoft.Compute/disks/TemporaryDataDisk-",
HyperVGeneration: string(compute.V1),
Location: "westus",
SourceImageResourceID: "/subscriptions/SubscriptionID/resourcegroups/imagegroup/providers/Microsoft.Compute/galleries/MyGallery/images/MyImage/versions/1.2.3",
},
expectedPutDiskBodies: []string{`
{
"location": "westus",
"properties": {
"osType": "Linux",
"hyperVGeneration": "V1",
"creationData": {
"createOption":"FromImage",
"galleryImageReference": {
"id":"/subscriptions/SubscriptionID/resourcegroups/imagegroup/providers/Microsoft.Compute/galleries/MyGallery/images/MyImage/versions/1.2.3"
}
}
},
"sku": {
"name": "Standard_LRS"
}
}`, `
{
"location": "westus",
"properties": {
"creationData": {
"createOption":"FromImage",
"galleryImageReference": {
"id": "/subscriptions/SubscriptionID/resourcegroups/imagegroup/providers/Microsoft.Compute/galleries/MyGallery/images/MyImage/versions/1.2.3",
"lun": 5
}
}
},
"sku": {
"name": "Premium_LRS"
}
}`, `
{
"location": "westus",
"properties": {
"creationData": {
"createOption":"FromImage",
"galleryImageReference": {
"id": "/subscriptions/SubscriptionID/resourcegroups/imagegroup/providers/Microsoft.Compute/galleries/MyGallery/images/MyImage/versions/1.2.3",
"lun": 9
}
}
},
"sku": {
"name": "Premium_LRS"
}
}`, `
{
"location": "westus",
"properties": {
"creationData": {
"createOption":"FromImage",
"galleryImageReference": {
"id": "/subscriptions/SubscriptionID/resourcegroups/imagegroup/providers/Microsoft.Compute/galleries/MyGallery/images/MyImage/versions/1.2.3",
"lun": 3
}
}
},
"sku": {
"name": "Premium_LRS"
}
}`},
want: multistep.ActionContinue,
verifyDiskset: &Diskset{
-1: resource("/subscriptions/SubscriptionID/resourceGroups/ResourceGroupName/providers/Microsoft.Compute/disks/TemporaryOSDiskName"),
3: resource("/subscriptions/SubscriptionID/resourceGroups/ResourceGroupName/providers/Microsoft.Compute/disks/TemporaryDataDisk-3"),
5: resource("/subscriptions/SubscriptionID/resourceGroups/ResourceGroupName/providers/Microsoft.Compute/disks/TemporaryDataDisk-5"),
9: resource("/subscriptions/SubscriptionID/resourceGroups/ResourceGroupName/providers/Microsoft.Compute/disks/TemporaryDataDisk-9"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := tt.fields
bodyCount := 0
m := compute.NewDisksClient("SubscriptionID")
m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
if r.Method != "PUT" {
t.Fatal("Expected only a PUT disk call")
}
b, _ := ioutil.ReadAll(r.Body)
expectedPutDiskBody := regexp.MustCompile(`[\s\n]`).ReplaceAllString(tt.expectedPutDiskBodies[bodyCount], "")
bodyCount++
if string(b) != expectedPutDiskBody {
t.Fatalf("expected body #%d to be %q, but got %q", bodyCount, expectedPutDiskBody, string(b))
}
return &http.Response{
Request: r,
StatusCode: 200,
}, nil
})
giv := compute.NewGalleryImageVersionsClient("SubscriptionID")
giv.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
if r.Method == "GET" &&
regexp.MustCompile(`(?i)/versions/1\.2\.3$`).MatchString(r.URL.Path) {
return &http.Response{
Request: r,
Body: ioutil.NopCloser(strings.NewReader(`{
"properties": { "storageProfile": {
"dataDiskImages":[
{ "lun": 5 },
{ "lun": 9 },
{ "lun": 3 }
]
} }
}`)),
StatusCode: 200,
}, nil
}
return &http.Response{
Request: r,
Status: "Unexpected request",
StatusCode: 500,
}, nil
})
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{
SubscriptionIDMock: "SubscriptionID",
DisksClientMock: m,
GalleryImageVersionsClientMock: giv,
})
state.Put("ui", packer.TestUi(t))
if got := s.Run(context.TODO(), state); !reflect.DeepEqual(got, tt.want) {
t.Errorf("StepCreateNewDisk.Run() = %v, want %v", got, tt.want)
}
ds := state.Get(stateBagKey_Diskset)
if tt.verifyDiskset != nil && !reflect.DeepEqual(*tt.verifyDiskset, ds) {
t.Errorf("Error verifying diskset after Run(), got %v, want %v", ds, *tt.verifyDiskset)
}
})
}
}
func resource(id string) client.Resource {
v, err := client.ParseResourceID(id)
if err != nil {
panic(err)
}
return v
}

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log"
"sort"
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
@ -14,19 +15,20 @@ import (
)
type StepCreateSharedImageVersion struct {
Destination SharedImageGalleryDestination
OSDiskCacheType string
Location string
Destination SharedImageGalleryDestination
OSDiskCacheType string
DataDiskCacheType string
Location string
}
func (s *StepCreateSharedImageVersion) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
osDiskSnapshotResourceID := state.Get(stateBagKey_OSDiskSnapshotResourceID).(string)
snapshotset := state.Get(stateBagKey_Snapshotset).(Diskset)
ui.Say(fmt.Sprintf("Creating image version %s\n using %s for os disk.",
ui.Say(fmt.Sprintf("Creating image version %s\n using %q for os disk.",
s.Destination.ResourceID(azcli.SubscriptionID()),
osDiskSnapshotResourceID))
snapshotset.OS()))
var targetRegions []compute.TargetRegion
// transform target regions to API objects
@ -44,7 +46,7 @@ func (s *StepCreateSharedImageVersion) Run(ctx context.Context, state multistep.
GalleryImageVersionProperties: &compute.GalleryImageVersionProperties{
StorageProfile: &compute.GalleryImageVersionStorageProfile{
OsDiskImage: &compute.GalleryOSDiskImage{
Source: &compute.GalleryArtifactVersionSource{ID: &osDiskSnapshotResourceID},
Source: &compute.GalleryArtifactVersionSource{ID: to.StringPtr(snapshotset.OS().String())},
HostCaching: compute.HostCaching(s.OSDiskCacheType),
},
},
@ -55,6 +57,26 @@ func (s *StepCreateSharedImageVersion) Run(ctx context.Context, state multistep.
},
}
var datadisks []compute.GalleryDataDiskImage
for lun, resource := range snapshotset {
if lun != -1 {
ui.Say(fmt.Sprintf(" using %q for data disk (lun %d).", resource, lun))
datadisks = append(datadisks, compute.GalleryDataDiskImage{
Lun: to.Int32Ptr(lun),
Source: &compute.GalleryArtifactVersionSource{ID: to.StringPtr(resource.String())},
HostCaching: compute.HostCaching(s.DataDiskCacheType),
})
}
}
if datadisks != nil {
// sort by lun
sort.Slice(datadisks, func(i, j int) bool {
return *datadisks[i].Lun < *datadisks[j].Lun
})
imageVersion.GalleryImageVersionProperties.StorageProfile.DataDiskImages = &datadisks
}
f, err := azcli.GalleryImageVersionsClient().CreateOrUpdate(
ctx,
s.Destination.ResourceGroup,

View File

@ -18,13 +18,15 @@ import (
func TestStepCreateSharedImageVersion_Run(t *testing.T) {
type fields struct {
Destination SharedImageGalleryDestination
OSDiskCacheType string
Location string
Destination SharedImageGalleryDestination
OSDiskCacheType string
DataDiskCacheType string
Location string
}
tests := []struct {
name string
fields fields
snapshotset Diskset
want multistep.StepAction
expectedPutBody string
}{
@ -37,7 +39,7 @@ func TestStepCreateSharedImageVersion_Run(t *testing.T) {
ImageName: "ImageName",
ImageVersion: "0.1.2",
TargetRegions: []TargetRegion{
TargetRegion{
{
Name: "region1",
ReplicaCount: 5,
StorageAccountType: "Standard_ZRS",
@ -45,8 +47,15 @@ func TestStepCreateSharedImageVersion_Run(t *testing.T) {
},
ExcludeFromLatest: true,
},
Location: "region2",
OSDiskCacheType: "ReadWrite",
DataDiskCacheType: "None",
Location: "region2",
},
snapshotset: diskset(
"/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/snapshots/osdisksnapshot",
"/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/snapshots/datadisksnapshot0",
"/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/snapshots/datadisksnapshot1",
"/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/snapshots/datadisksnapshot2"),
expectedPutBody: `{
"location": "region2",
"properties": {
@ -62,10 +71,34 @@ func TestStepCreateSharedImageVersion_Run(t *testing.T) {
},
"storageProfile": {
"osDiskImage": {
"hostCaching": "ReadWrite",
"source": {
"id": "osdisksnapshotresourceid"
"id": "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/snapshots/osdisksnapshot"
}
}
},
"dataDiskImages": [
{
"lun": 0,
"hostCaching": "None",
"source": {
"id": "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/snapshots/datadisksnapshot0"
}
},
{
"lun": 1,
"hostCaching": "None",
"source": {
"id": "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/snapshots/datadisksnapshot1"
}
},
{
"lun": 2,
"hostCaching": "None",
"source": {
"id": "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/snapshots/datadisksnapshot2"
}
}
]
}
}
}`,
@ -94,13 +127,14 @@ func TestStepCreateSharedImageVersion_Run(t *testing.T) {
GalleryImageVersionsClientMock: m,
})
state.Put("ui", packer.TestUi(t))
state.Put(stateBagKey_OSDiskSnapshotResourceID, "osdisksnapshotresourceid")
state.Put(stateBagKey_Snapshotset, tt.snapshotset)
t.Run(tt.name, func(t *testing.T) {
s := &StepCreateSharedImageVersion{
Destination: tt.fields.Destination,
OSDiskCacheType: tt.fields.OSDiskCacheType,
Location: tt.fields.Location,
Destination: tt.fields.Destination,
OSDiskCacheType: tt.fields.OSDiskCacheType,
DataDiskCacheType: tt.fields.DataDiskCacheType,
Location: tt.fields.Location,
}
if got := s.Run(context.TODO(), state); !reflect.DeepEqual(got, tt.want) {
t.Errorf("StepCreateSharedImageVersion.Run() = %v, want %v", got, tt.want)

View File

@ -1,125 +0,0 @@
package chroot
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
)
var _ multistep.Step = &StepCreateSnapshot{}
type StepCreateSnapshot struct {
ResourceID string
Location string
SkipCleanup bool
subscriptionID, resourceGroup, snapshotName string
}
func parseSnapshotResourceID(resourceID string) (subscriptionID, resourceGroup, snapshotName string, err error) {
r, err := azure.ParseResourceID(resourceID)
if err != nil {
return "", "", "", err
}
if !strings.EqualFold(r.Provider, "Microsoft.Compute") ||
!strings.EqualFold(r.ResourceType, "snapshots") {
return "", "", "", fmt.Errorf("Resource %q is not of type Microsoft.Compute/snapshots", resourceID)
}
return r.SubscriptionID, r.ResourceGroup, r.ResourceName, nil
}
func (s *StepCreateSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
osDiskResourceID := state.Get(stateBagKey_OSDiskResourceID).(string)
state.Put(stateBagKey_OSDiskSnapshotResourceID, s.ResourceID)
ui.Say(fmt.Sprintf("Creating snapshot '%s'", s.ResourceID))
var err error
s.subscriptionID, s.resourceGroup, s.snapshotName, err = parseSnapshotResourceID(s.ResourceID)
if err != nil {
log.Printf("StepCreateSnapshot.Run: error: %+v", err)
err := fmt.Errorf(
"error parsing resource id '%s': %v", s.ResourceID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
snapshot := compute.Snapshot{
Location: to.StringPtr(s.Location),
SnapshotProperties: &compute.SnapshotProperties{
CreationData: &compute.CreationData{
CreateOption: compute.Copy,
SourceResourceID: to.StringPtr(osDiskResourceID),
},
Incremental: to.BoolPtr(false),
},
}
f, err := azcli.SnapshotsClient().CreateOrUpdate(ctx, s.resourceGroup, s.snapshotName, snapshot)
if err == nil {
pollClient := azcli.PollClient()
pollClient.PollingDelay = 2 * time.Second
ctx, cancel := context.WithTimeout(ctx, time.Hour*12)
defer cancel()
err = f.WaitForCompletionRef(ctx, pollClient)
}
if err != nil {
log.Printf("StepCreateSnapshot.Run: error: %+v", err)
err := fmt.Errorf(
"error creating snapshot '%s': %v", s.ResourceID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepCreateSnapshot) Cleanup(state multistep.StateBag) {
if !s.SkipCleanup {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Removing any active SAS for snapshot %q", s.ResourceID))
{
f, err := azcli.SnapshotsClient().RevokeAccess(context.TODO(), s.resourceGroup, s.snapshotName)
if err == nil {
log.Printf("StepCreateSnapshot.Cleanup: removing SAS...")
err = f.WaitForCompletionRef(context.TODO(), azcli.PollClient())
}
if err != nil {
log.Printf("StepCreateSnapshot.Cleanup: error: %+v", err)
ui.Error(fmt.Sprintf("error deleting snapshot '%s': %v.", s.ResourceID, err))
}
}
ui.Say(fmt.Sprintf("Deleting snapshot %q", s.ResourceID))
{
f, err := azcli.SnapshotsClient().Delete(context.TODO(), s.resourceGroup, s.snapshotName)
if err == nil {
log.Printf("StepCreateSnapshot.Cleanup: deleting snapshot...")
err = f.WaitForCompletionRef(context.TODO(), azcli.PollClient())
}
if err != nil {
log.Printf("StepCreateSnapshot.Cleanup: error: %+v", err)
ui.Error(fmt.Sprintf("error deleting snapshot '%s': %v.", s.ResourceID, err))
}
}
}
}

View File

@ -1,216 +0,0 @@
package chroot
import (
"context"
"io/ioutil"
"net/http"
"reflect"
"regexp"
"strings"
"testing"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
"github.com/Azure/go-autorest/autorest"
)
func Test_parseSnapshotResourceID(t *testing.T) {
tests := []struct {
name string
resourceID string
wantSubscriptionID string
wantResourceGroup string
wantSnapshotName string
wantErr bool
}{
{
name: "happy path",
resourceID: "/subscriptions/1234/resourceGroups/rg/providers/microsoft.compute/snapshots/disksnapshot1",
wantErr: false,
wantSubscriptionID: "1234",
wantResourceGroup: "rg",
wantSnapshotName: "disksnapshot1",
},
{
name: "error: nonsense",
resourceID: "nonsense",
wantErr: true,
},
{
name: "error: other resource type",
resourceID: "/subscriptions/1234/resourceGroups/rg/providers/microsoft.compute/disks/disksnapshot1",
wantErr: true,
},
{
name: "error: no name",
resourceID: "/subscriptions/1234/resourceGroups/rg/providers/microsoft.compute/snapshots",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotSubscriptionID, gotResourceGroup, gotSnapshotName, err := parseSnapshotResourceID(tt.resourceID)
if (err != nil) != tt.wantErr {
t.Errorf("parseSnapshotResourceID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotSubscriptionID != tt.wantSubscriptionID {
t.Errorf("parseSnapshotResourceID() gotSubscriptionID = %v, want %v", gotSubscriptionID, tt.wantSubscriptionID)
}
if gotResourceGroup != tt.wantResourceGroup {
t.Errorf("parseSnapshotResourceID() gotResourceGroup = %v, want %v", gotResourceGroup, tt.wantResourceGroup)
}
if gotSnapshotName != tt.wantSnapshotName {
t.Errorf("parseSnapshotResourceID() gotSnapshotName = %v, want %v", gotSnapshotName, tt.wantSnapshotName)
}
})
}
}
func TestStepCreateSnapshot_Run(t *testing.T) {
type fields struct {
ResourceID string
Location string
}
tests := []struct {
name string
fields fields
want multistep.StepAction
wantSnapshotID string
expectedPutBody string
}{
{
name: "happy path",
fields: fields{
ResourceID: "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/snap1",
Location: "region1",
},
expectedPutBody: `{
"location": "region1",
"properties": {
"creationData": {
"createOption": "Copy",
"sourceResourceId": "osdiskresourceid"
},
"incremental": false
}
}`,
},
{
name: "invalid ResourceID",
fields: fields{
ResourceID: "notaresourceid",
Location: "region1",
},
want: multistep.ActionHalt,
},
}
for _, tt := range tests {
expectedPutBody := regexp.MustCompile(`[\s\n]`).ReplaceAllString(tt.expectedPutBody, "")
m := compute.NewSnapshotsClient("subscriptionId")
m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
if r.Method != "PUT" {
t.Fatal("Expected only a PUT call")
}
b, _ := ioutil.ReadAll(r.Body)
if string(b) != expectedPutBody {
t.Errorf("expected body to be %v, but got %v", expectedPutBody, string(b))
}
return &http.Response{
Request: r,
StatusCode: 200,
}, nil
})
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{
SnapshotsClientMock: m,
})
state.Put("ui", packer.TestUi(t))
state.Put(stateBagKey_OSDiskResourceID, "osdiskresourceid")
t.Run(tt.name, func(t *testing.T) {
s := &StepCreateSnapshot{
ResourceID: tt.fields.ResourceID,
Location: tt.fields.Location,
}
if got := s.Run(context.TODO(), state); !reflect.DeepEqual(got, tt.want) {
t.Errorf("StepCreateSnapshot.Run() = %v, want %v", got, tt.want)
}
if tt.wantSnapshotID != "" {
got := state.Get(stateBagKey_OSDiskSnapshotResourceID).(string)
if !strings.EqualFold(got, tt.wantSnapshotID) {
t.Errorf("OSDiskSnapshotResourceID = %v, want %v", got, tt.wantSnapshotID)
}
}
})
}
}
func TestStepCreateSnapshot_Cleanup_skipped(t *testing.T) {
m := compute.NewSnapshotsClient("subscriptionId")
m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
t.Fatalf("clean up should be skipped, did not expect HTTP calls")
return nil, nil
})
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{
SnapshotsClientMock: m,
})
state.Put("ui", packer.TestUi(t))
s := &StepCreateSnapshot{
SkipCleanup: true,
}
s.Cleanup(state)
}
func TestStepCreateSnapshot_Cleanup(t *testing.T) {
m := compute.NewSnapshotsClient("subscriptionId")
{
expectedCalls := []string{
"POST /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/snap1/endGetAccess",
"DELETE /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/snap1",
}
i := 0
m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
want := expectedCalls[i]
got := r.Method + " " + r.URL.Path
if want != got {
t.Errorf("unexpected HTTP call: %v, wanted %v", got, want)
return &http.Response{
Request: r,
StatusCode: 599, // 500 is retried
}, nil
}
i++
return &http.Response{
Request: r,
StatusCode: 200,
}, nil
})
}
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{
SnapshotsClientMock: m,
})
state.Put("ui", packer.TestUi(t))
s := &StepCreateSnapshot{
ResourceID: "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/snap1",
Location: "region1",
SkipCleanup: false,
resourceGroup: "rg",
snapshotName: "snap1",
subscriptionID: "1234",
}
s.Cleanup(state)
}

View File

@ -0,0 +1,126 @@
package chroot
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
"github.com/Azure/go-autorest/autorest/to"
)
var _ multistep.Step = &StepCreateSnapshotset{}
type StepCreateSnapshotset struct {
OSDiskSnapshotID string
DataDiskSnapshotIDPrefix string
Location string
SkipCleanup bool
snapshots Diskset
}
func (s *StepCreateSnapshotset) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
diskset := state.Get(stateBagKey_Diskset).(Diskset)
s.snapshots = make(Diskset)
errorMessage := func(format string, params ...interface{}) multistep.StepAction {
err := fmt.Errorf("StepCreateSnapshotset.Run: error: "+format, params...)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for lun, resource := range diskset {
snapshotID := fmt.Sprintf("%s%d", s.DataDiskSnapshotIDPrefix, lun)
if lun == -1 {
snapshotID = s.OSDiskSnapshotID
}
ssr, err := client.ParseResourceID(snapshotID)
if err != nil {
errorMessage("Could not create a valid resource id, tried %q: %v", snapshotID, err)
}
if !strings.EqualFold(ssr.Provider, "Microsoft.Compute") ||
!strings.EqualFold(ssr.ResourceType.String(), "snapshots") {
return errorMessage("Resource %q is not of type Microsoft.Compute/snapshots", snapshotID)
}
s.snapshots[lun] = ssr
state.Put(stateBagKey_Snapshotset, s.snapshots)
ui.Say(fmt.Sprintf("Creating snapshot %q", ssr))
snapshot := compute.Snapshot{
Location: to.StringPtr(s.Location),
SnapshotProperties: &compute.SnapshotProperties{
CreationData: &compute.CreationData{
CreateOption: compute.Copy,
SourceResourceID: to.StringPtr(resource.String()),
},
Incremental: to.BoolPtr(false),
},
}
f, err := azcli.SnapshotsClient().CreateOrUpdate(ctx, ssr.ResourceGroup, ssr.ResourceName.String(), snapshot)
if err != nil {
return errorMessage("error initiating snapshot %q: %v", ssr, err)
}
pollClient := azcli.PollClient()
pollClient.PollingDelay = 2 * time.Second
ctx, cancel := context.WithTimeout(ctx, time.Hour*12)
defer cancel()
err = f.WaitForCompletionRef(ctx, pollClient)
if err != nil {
return errorMessage("error creating snapshot '%s': %v", s.OSDiskSnapshotID, err)
}
}
return multistep.ActionContinue
}
func (s *StepCreateSnapshotset) Cleanup(state multistep.StateBag) {
if !s.SkipCleanup {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
for _, resource := range s.snapshots {
ui.Say(fmt.Sprintf("Removing any active SAS for snapshot %q", resource))
{
f, err := azcli.SnapshotsClient().RevokeAccess(context.TODO(), resource.ResourceGroup, resource.ResourceName.String())
if err == nil {
log.Printf("StepCreateSnapshotset.Cleanup: removing SAS...")
err = f.WaitForCompletionRef(context.TODO(), azcli.PollClient())
}
if err != nil {
log.Printf("StepCreateSnapshotset.Cleanup: error: %+v", err)
ui.Error(fmt.Sprintf("error deleting snapshot %q: %v.", resource, err))
}
}
ui.Say(fmt.Sprintf("Deleting snapshot %q", resource))
{
f, err := azcli.SnapshotsClient().Delete(context.TODO(), resource.ResourceGroup, resource.ResourceName.String())
if err == nil {
log.Printf("StepCreateSnapshotset.Cleanup: deleting snapshot...")
err = f.WaitForCompletionRef(context.TODO(), azcli.PollClient())
}
if err != nil {
log.Printf("StepCreateSnapshotset.Cleanup: error: %+v", err)
ui.Error(fmt.Sprintf("error deleting snapshot %q: %v.", resource, err))
}
}
}
}
}

View File

@ -0,0 +1,230 @@
package chroot
import (
"context"
"io/ioutil"
"net/http"
"reflect"
"regexp"
"testing"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
"github.com/Azure/go-autorest/autorest"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func TestStepCreateSnapshot_Run(t *testing.T) {
type fields struct {
OSDiskSnapshotID string
DataDiskSnapshotIDPrefix string
Location string
}
tests := []struct {
name string
fields fields
diskset Diskset
want multistep.StepAction
wantSnapshotset Diskset
expectedPutBody string
}{
{
name: "happy path",
fields: fields{
OSDiskSnapshotID: "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/osdisk-snap",
Location: "region1",
},
diskset: diskset("/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/disk1"),
expectedPutBody: `{
"location": "region1",
"properties": {
"creationData": {
"createOption": "Copy",
"sourceResourceId": "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/disk1"
},
"incremental": false
}
}`,
wantSnapshotset: diskset("/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/osdisk-snap"),
},
{
name: "multi disk",
fields: fields{
OSDiskSnapshotID: "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/osdisk-snap",
DataDiskSnapshotIDPrefix: "/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datadisk-snap",
Location: "region1",
},
diskset: diskset(
"/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/osdisk",
"/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/datadisk1",
"/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/datadisk2",
"/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/datadisk3"),
wantSnapshotset: diskset(
"/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/osdisk-snap",
"/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datadisk-snap0",
"/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datadisk-snap1",
"/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datadisk-snap2",
),
},
{
name: "invalid ResourceID",
fields: fields{
OSDiskSnapshotID: "notaresourceid",
Location: "region1",
},
diskset: diskset("/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/disk1"),
want: multistep.ActionHalt,
},
}
for _, tt := range tests {
expectedPutBody := regexp.MustCompile(`[\s\n]`).ReplaceAllString(tt.expectedPutBody, "")
m := compute.NewSnapshotsClient("subscriptionId")
m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
if r.Method != "PUT" {
t.Fatal("Expected only a PUT call")
}
if expectedPutBody != "" {
b, _ := ioutil.ReadAll(r.Body)
if string(b) != expectedPutBody {
t.Errorf("expected body to be %v, but got %v", expectedPutBody, string(b))
}
}
return &http.Response{
Request: r,
StatusCode: 200,
}, nil
})
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{
SnapshotsClientMock: m,
})
state.Put("ui", packer.TestUi(t))
state.Put(stateBagKey_Diskset, tt.diskset)
t.Run(tt.name, func(t *testing.T) {
s := &StepCreateSnapshotset{
OSDiskSnapshotID: tt.fields.OSDiskSnapshotID,
DataDiskSnapshotIDPrefix: tt.fields.DataDiskSnapshotIDPrefix,
Location: tt.fields.Location,
}
if got := s.Run(context.TODO(), state); !reflect.DeepEqual(got, tt.want) {
t.Errorf("StepCreateSnapshot.Run() = %v, want %v", got, tt.want)
}
if len(tt.wantSnapshotset) > 0 {
got := state.Get(stateBagKey_Snapshotset).(Diskset)
if !reflect.DeepEqual(got, tt.wantSnapshotset) {
t.Errorf("Snapshotset = %v, want %v", got, tt.wantSnapshotset)
}
}
})
}
}
func TestStepCreateSnapshot_Cleanup_skipped(t *testing.T) {
m := compute.NewSnapshotsClient("subscriptionId")
m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
t.Fatalf("clean up should be skipped, did not expect HTTP calls")
return nil, nil
})
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{
SnapshotsClientMock: m,
})
state.Put("ui", packer.TestUi(t))
s := &StepCreateSnapshotset{
SkipCleanup: true,
}
s.Cleanup(state)
}
func TestStepCreateSnapshot_Cleanup(t *testing.T) {
m := compute.NewSnapshotsClient("subscriptionId")
{
expectedCalls := []string{
"POST /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/ossnap/endGetAccess",
"DELETE /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/ossnap",
"POST /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datasnap1/endGetAccess",
"DELETE /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datasnap1",
"POST /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datasnap2/endGetAccess",
"DELETE /subscriptions/subscriptionId/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datasnap2",
}
m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
got := r.Method + " " + r.URL.Path
found := false
for i, call := range expectedCalls {
if call == got {
// swap i with last and drop last
expectedCalls[i] = expectedCalls[len(expectedCalls)-1]
expectedCalls = expectedCalls[:len(expectedCalls)-1]
found = true
break
}
}
if !found {
t.Errorf("unexpected HTTP call: %v, wanted one of %q", got, expectedCalls)
return &http.Response{
Request: r,
StatusCode: 599, // 500 is retried
}, nil
}
return &http.Response{
Request: r,
StatusCode: 200,
}, nil
})
}
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{
SnapshotsClientMock: m,
})
state.Put("ui", packer.TestUi(t))
s := &StepCreateSnapshotset{
SkipCleanup: false,
snapshots: diskset(
"/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/ossnap",
"/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datasnap1",
"/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Compute/snapshots/datasnap2"),
}
s.Cleanup(state)
}
func TestStepCreateSnapshotset_Cleanup(t *testing.T) {
type fields struct {
OSDiskSnapshotID string
DataDiskSnapshotIDPrefix string
Location string
SkipCleanup bool
snapshots Diskset
}
type args struct {
state multistep.StateBag
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &StepCreateSnapshotset{
OSDiskSnapshotID: tt.fields.OSDiskSnapshotID,
DataDiskSnapshotIDPrefix: tt.fields.DataDiskSnapshotIDPrefix,
Location: tt.fields.Location,
SkipCleanup: tt.fields.SkipCleanup,
snapshots: tt.fields.snapshots,
}
s.Cleanup(tt.args.state)
})
}
}

View File

@ -40,12 +40,22 @@
- `os_disk_cache_type` (string) - The [cache type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#cachingtypes)
specified in the resulting image and for attaching it to the Packer VM. Defaults to `ReadOnly`
- `data_disk_storage_account_type` (string) - The [storage SKU](https://docs.microsoft.com/en-us/rest/api/compute/disks/createorupdate#diskstorageaccounttypes)
to use for datadisks. Defaults to `Standard_LRS`.
- `data_disk_cache_type` (string) - The [cache type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#cachingtypes)
specified in the resulting image and for attaching it to the Packer VM. Defaults to `ReadOnly`
- `image_hyperv_generation` (string) - The [Hyper-V generation type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#hypervgenerationtypes) for Managed Image output.
Defaults to `V1`.
- `temporary_os_disk_id` (string) - The id of the temporary disk that will be created. Will be generated if not set.
- `temporary_os_disk_id` (string) - The id of the temporary OS disk that will be created. Will be generated if not set.
- `temporary_os_disk_snapshot_id` (string) - The id of the temporary snapshot that will be created. Will be generated if not set.
- `temporary_os_disk_snapshot_id` (string) - The id of the temporary OS disk snapshot that will be created. Will be generated if not set.
- `temporary_data_disk_id_prefix` (string) - The prefix for the resource ids of the temporary data disks that will be created. The disks will be suffixed with a number. Will be generated if not set.
- `temporary_data_disk_snapshot_id` (string) - The prefix for the resource ids of the temporary data disk snapshots that will be created. The snapshots will be suffixed with a number. Will be generated if not set.
- `skip_cleanup` (bool) - If set to `true`, leaves the temporary disks and snapshots behind in the Packer VM resource group. Defaults to `false`