Skip to content

Commit

Permalink
[HPR-1606] Add support specifying disk encryption set keys across rep…
Browse files Browse the repository at this point in the history
…licated regions (#371)

* Add support for target_region block in SharedImageGalleryDestination

As an alternative to specifying replication_regions for a Shared Image
Gallery Destination, users can not use one or more `target_region` block
attributes to define the target regions for a shared image version. The
target_region block supports attributes for setting individual disk
encryption key id to support multi-region replication of encrypted
disks using CMKs.

The `target_region` block attribute is meant to replace the now deprecated
`replication_regions` attributed. Internally, any values set for
replication_regions will be transferred into a set of target_regions to
provide the same level of functionality. The one difference being that
Packer will no longer add a default entry for the SIG build location
when using target_region blocks.
  • Loading branch information
nywilken authored Feb 16, 2024
1 parent f2b3b2e commit 66da753
Show file tree
Hide file tree
Showing 13 changed files with 453 additions and 54 deletions.
16 changes: 15 additions & 1 deletion .web-docs/components/builder/arm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,16 @@ Providing `temp_resource_group_name` or `location` in combination with
gallery_name = "GalleryName"
image_name = "ImageName"
image_version = "1.0.0"
replication_regions = ["regionA", "regionB", "regionC"]
storage_account_type = "Standard_LRS"
target_region {
name = "regionA"
}
target_region {
name = "regionB"
}
target_region {
name = "regionC"
}
}
managed_image_name = "TargetImageName"
managed_image_resource_group_name = "TargetResourceGroup"
Expand Down Expand Up @@ -643,6 +651,12 @@ The shared_image_gallery_destination block is available for publishing a new ima
- `replication_regions` ([]string) - A list of regions to replicate the image version in, by default the build location will be used as a replication region (the build location is either set in the location field, or the location of the resource group used in `build_resource_group_name` will be included.
Can not contain any region but the build region when using shallow replication

- `target_region` ([]TargetRegion) - A target region to store the image version in. The attribute supersedes `replication_regions` which is now considered deprecated.
One or more target_region blocks can be specified for storing an imager version to various regions. In addition to specifying a region,
a DiskEncryptionSetId can be specified for each target region to support multi-region disk encryption.
At a minimum their must be one target region entry for the primary build region where the image version will be stored.
Target region must only contain one entry matching the build region when using shallow replication.

- `storage_account_type` (string) - Specify a storage account type for the Shared Image Gallery Image Version.
Defaults to `Standard_LRS`. Accepted values are `Standard_LRS`, `Standard_ZRS` and `Premium_LRS`

Expand Down
56 changes: 34 additions & 22 deletions builder/azure/arm/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,33 +215,45 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
return nil, fmt.Errorf("a gallery image version for image name:version %s:%s already exists in gallery %s", b.config.SharedGalleryDestination.SigDestinationImageName, b.config.SharedGalleryDestination.SigDestinationImageVersion, b.config.SharedGalleryDestination.SigDestinationGalleryName)
}

// SIG requires that replication regions include the region in which the created image version resides
buildLocation := normalizeAzureRegion(b.stateBag.Get(constants.ArmLocation).(string))
foundMandatoryReplicationRegion := false
var normalizedReplicationRegions []string
for _, region := range b.config.SharedGalleryDestination.SigDestinationReplicationRegions {
// change region to lower-case and strip spaces
normalizedRegion := normalizeAzureRegion(region)
normalizedReplicationRegions = append(normalizedReplicationRegions, normalizedRegion)
if strings.EqualFold(normalizedRegion, buildLocation) {
foundMandatoryReplicationRegion = true
continue
if len(b.config.SharedGalleryDestination.SigDestinationTargetRegions) > 0 {
normalizedRegions := make([]TargetRegion, 0, len(b.config.SharedGalleryDestination.SigDestinationTargetRegions))
for _, tr := range b.config.SharedGalleryDestination.SigDestinationTargetRegions {
tr.Name = normalizeAzureRegion(tr.Name)
normalizedRegions = append(normalizedRegions, tr)
}
b.config.SharedGalleryDestination.SigDestinationTargetRegions = normalizedRegions
}
if foundMandatoryReplicationRegion == false {
b.config.SharedGalleryDestination.SigDestinationReplicationRegions = append(normalizedReplicationRegions, buildLocation)
}
// TODO It would be better if validation could be handled in a central location
// Currently we rely on the build Resource Group being queried if used to get the build location
// So we have to do this validation afterwards
// We should remove this logic builder and handle this logic via the `Step` pattern
if b.config.SharedGalleryDestination.SigDestinationUseShallowReplicationMode {
if len(b.config.SharedGalleryDestination.SigDestinationReplicationRegions) != 1 {
return nil, fmt.Errorf("when `use_shallow_replication` is enabled the value of `replicated_regions` must match the build region specified by `location` or match the region of `build_resource_group_name`.")

// Convert deprecated replication_regions to []TargetRegion
if len(b.config.SharedGalleryDestination.SigDestinationReplicationRegions) > 0 {
var foundMandatoryReplicationRegion bool
buildLocation := normalizeAzureRegion(b.stateBag.Get(constants.ArmLocation).(string))
normalizedRegions := make([]TargetRegion, 0, len(b.config.SharedGalleryDestination.SigDestinationReplicationRegions))
for _, region := range b.config.SharedGalleryDestination.SigDestinationReplicationRegions {
region := normalizeAzureRegion(region)
if strings.EqualFold(region, buildLocation) {
// backwards compatibility DiskEncryptionSetId was set on the global config not on the target region.
// Users using target_region blocks are responsible for setting the DES within the block
normalizedRegions = append(normalizedRegions, TargetRegion{Name: region, DiskEncryptionSetId: b.config.DiskEncryptionSetId})
foundMandatoryReplicationRegion = true
continue
}
normalizedRegions = append(normalizedRegions, TargetRegion{Name: region})
}
// SIG requires that replication regions include the region in which the created image version resides
if foundMandatoryReplicationRegion == false {
normalizedRegions = append(normalizedRegions, TargetRegion{Name: buildLocation, DiskEncryptionSetId: b.config.DiskEncryptionSetId})
}
b.config.SharedGalleryDestination.SigDestinationTargetRegions = normalizedRegions
}
b.stateBag.Put(constants.ArmManagedImageSharedGalleryReplicationRegions, b.config.SharedGalleryDestination.SigDestinationReplicationRegions)

if len(b.config.SharedGalleryDestination.SigDestinationTargetRegions) == 0 {
return nil, errors.New("no target destination region specified for the Shared Image Gallery; use target_region to specify at least the primary destination region for storing the image version.")
}

b.stateBag.Put(constants.ArmSharedImageGalleryDestinationTargetRegions, b.config.SharedGalleryDestination.SigDestinationTargetRegions)
}

sourceImageSpecialized := false
if b.config.SharedGallery.GalleryName != "" {
client := azureClient.GalleryImagesClient
Expand Down
51 changes: 49 additions & 2 deletions builder/azure/arm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: MPL-2.0

//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation,Spot
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation,Spot,TargetRegion

package arm

Expand All @@ -13,6 +13,7 @@ import (
"crypto/x509/pkix"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"math/big"
"net"
Expand Down Expand Up @@ -106,6 +107,12 @@ type SharedImageGalleryDestination struct {
// A list of regions to replicate the image version in, by default the build location will be used as a replication region (the build location is either set in the location field, or the location of the resource group used in `build_resource_group_name` will be included.
// Can not contain any region but the build region when using shallow replication
SigDestinationReplicationRegions []string `mapstructure:"replication_regions"`
// A target region to store the image version in. The attribute supersedes `replication_regions` which is now considered deprecated.
// One or more target_region blocks can be specified for storing an imager version to various regions. In addition to specifying a region,
// a DiskEncryptionSetId can be specified for each target region to support multi-region disk encryption.
// At a minimum their must be one target region entry for the primary build region where the image version will be stored.
// Target region must only contain one entry matching the build region when using shallow replication.
SigDestinationTargetRegions []TargetRegion `mapstructure:"target_region"`
// Specify a storage account type for the Shared Image Gallery Image Version.
// Defaults to `Standard_LRS`. Accepted values are `Standard_LRS`, `Standard_ZRS` and `Premium_LRS`
SigDestinationStorageAccountType string `mapstructure:"storage_account_type"`
Expand All @@ -118,6 +125,28 @@ type SharedImageGalleryDestination struct {
SigDestinationUseShallowReplicationMode bool `mapstructure:"use_shallow_replication" required:"false"`
}

func (d SharedImageGalleryDestination) ValidateShallowReplicationRegion() error {

n := len(d.SigDestinationTargetRegions) | len(d.SigDestinationReplicationRegions)
if n == 0 {
return errors.New("when `use_shallow_replication` is set there must be one destination region must match the build location of the Shared Image Gallery.")
}
if n > 1 {
return errors.New("when `use_shallow_replication` there can only be one destination region must match the build location of the Shared Image Gallery.")
}
return nil
}

// TargetRegion describes a destination region for storing the image version of a Shard Image Gallery.
type TargetRegion struct {
// Name of the Azure region
Name string `mapstructure:"name" required:"true"`
// DiskEncryptionSetId for Disk Encryption Set in Region. Needed for supporting
// the replication of encrypted disks across regions. CMKs must
// already exist within the target regions.
DiskEncryptionSetId string `mapstructure:"disk_encryption_set_id"`
}

type Spot struct {
// Specify eviction policy for spot instance: "Deallocate" or "Delete". If this is set, a spot instance will be used.
EvictionPolicy virtualmachines.VirtualMachineEvictionPolicyTypes `mapstructure:"eviction_policy"`
Expand Down Expand Up @@ -202,8 +231,16 @@ type Config struct {
// gallery_name = "GalleryName"
// image_name = "ImageName"
// image_version = "1.0.0"
// replication_regions = ["regionA", "regionB", "regionC"]
// storage_account_type = "Standard_LRS"
// target_region {
// name = "regionA"
// }
// target_region {
// name = "regionB"
// }
// target_region {
// name = "regionC"
// }
// }
// managed_image_name = "TargetImageName"
// managed_image_resource_group_name = "TargetResourceGroup"
Expand Down Expand Up @@ -1268,14 +1305,24 @@ func assertRequiredParametersSet(c *Config, errs *packersdk.MultiError) {
if c.SharedGalleryDestination.SigDestinationSubscription == "" {
c.SharedGalleryDestination.SigDestinationSubscription = c.ClientConfig.SubscriptionID
}
// Validate target region settings; it can be the deprecated replicated_regions attribute or multiple target_region blocks
if (len(c.SharedGalleryDestination.SigDestinationReplicationRegions) > 0) && (len(c.SharedGalleryDestination.SigDestinationTargetRegions) > 0) {
errs = packersdk.MultiErrorAppend(errs, errors.New("`replicated_regions` can not be defined alongside `target_region`; you can defined a target_region for each destination region you wish to replicate to."))
}

if c.SharedGalleryDestination.SigDestinationUseShallowReplicationMode {
if err := c.SharedGalleryDestination.ValidateShallowReplicationRegion(); err != nil {
errs = packersdk.MultiErrorAppend(errs, err)
}

if c.SharedGalleryImageVersionReplicaCount == 0 {
c.SharedGalleryImageVersionReplicaCount = 1
}

if c.SharedGalleryImageVersionReplicaCount != 1 {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("When using shallow replication the replica count can only be 1, leaving this value unset will default to 1"))
}

}
}
if c.SharedGalleryTimeout == 0 {
Expand Down
45 changes: 36 additions & 9 deletions builder/azure/arm/config.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 66da753

Please sign in to comment.