From f31031f6b1cface81c247eb772bba28f5ac7918f Mon Sep 17 00:00:00 2001 From: Manuel Riezebosch Date: Thu, 17 Jan 2019 09:01:42 +0100 Subject: [PATCH 1/2] feat(arm-builder): zone resilient from config --- builder/azure/arm/config.go | 4 ++ builder/azure/arm/config_test.go | 45 ++++++++++++++++++++++ website/source/docs/builders/azure.html.md | 3 ++ 3 files changed, 52 insertions(+) diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index 862226245..340e9b46d 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -107,6 +107,7 @@ type Config struct { ManagedImageOSDiskSnapshotName string `mapstructure:"managed_image_os_disk_snapshot_name"` ManagedImageDataDiskSnapshotPrefix string `mapstructure:"managed_image_data_disk_snapshot_prefix"` manageImageLocation string + ManagedImageZoneResilient bool `mapstructure:"managed_image_zone_resilient"` // Deployment AzureTags map[string]*string `mapstructure:"azure_tags"` @@ -196,6 +197,9 @@ func (c *Config) toImageParameters() *compute.Image { SourceVirtualMachine: &compute.SubResource{ ID: to.StringPtr(c.toVMID()), }, + StorageProfile: &compute.ImageStorageProfile{ + ZoneResilient: to.BoolPtr(c.ManagedImageZoneResilient), + }, }, Location: to.StringPtr(c.Location), Tags: c.AzureTags, diff --git a/builder/azure/arm/config_test.go b/builder/azure/arm/config_test.go index bf1bd5023..64d59e4eb 100644 --- a/builder/azure/arm/config_test.go +++ b/builder/azure/arm/config_test.go @@ -794,6 +794,51 @@ func TestConfigShouldRejectExcessiveTagValueLength(t *testing.T) { } } +func TestConfigZoneResilientShouldDefaultToFalse(t *testing.T) { + config := map[string]interface{}{ + "managed_image_name": "ignore", + "managed_image_resource_group_name": "ignore", + "build_resource_group_name": "ignore", + "image_publisher": "igore", + "image_offer": "ignore", + "image_sku": "ignore", + "os_type": "linux", + } + + c, _, err := newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + + p := c.toImageParameters() + if *p.ImageProperties.StorageProfile.ZoneResilient { + t.Fatal("expected zone resilient default to be false") + } +} + +func TestConfigZoneResilientSetFromConfig(t *testing.T) { + config := map[string]interface{}{ + "managed_image_name": "ignore", + "managed_image_resource_group_name": "ignore", + "build_resource_group_name": "ignore", + "image_publisher": "igore", + "image_offer": "ignore", + "image_sku": "ignore", + "os_type": "linux", + "managed_image_zone_resilient": true, + } + + c, _, err := newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + + p := c.toImageParameters() + if *p.ImageProperties.StorageProfile.ZoneResilient == false { + t.Fatal("expected managed image zone resilient to be true from config") + } +} + func TestConfigShouldRejectMissingCustomDataFile(t *testing.T) { config := map[string]interface{}{ "capture_name_prefix": "ignore", diff --git a/website/source/docs/builders/azure.html.md b/website/source/docs/builders/azure.html.md index 8caf44848..c218e6d43 100644 --- a/website/source/docs/builders/azure.html.md +++ b/website/source/docs/builders/azure.html.md @@ -333,6 +333,9 @@ Providing `temp_resource_group_name` or `location` in combination with is set, snapshot of the data disk(s) is created with the same prefix as this value before the VM is captured. +- `managed_image_zone_resilient` (bool) Store the image in zone-resilient storage. You need to create it + in a region that supports [availability zones](https://docs.microsoft.com/en-us/azure/availability-zones/az-overview). + ## Basic Example Here is a basic example for Azure. From d79b54e46aa3a5b712e9e02d7e8662ddc7c06832 Mon Sep 17 00:00:00 2001 From: Manuel Riezebosch Date: Fri, 15 Feb 2019 16:24:19 +0100 Subject: [PATCH 2/2] feat(arm-builder): print warning on zone resiliency for currently not supported locations --- builder/azure/arm/builder.go | 2 ++ builder/azure/arm/config.go | 20 ++++++++++++++++++ builder/azure/arm/config_test.go | 35 ++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/builder/azure/arm/builder.go b/builder/azure/arm/builder.go index 48c19263e..041f4f80c 100644 --- a/builder/azure/arm/builder.go +++ b/builder/azure/arm/builder.go @@ -139,6 +139,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe b.config.Location = *group.Location } + b.config.validateLocationZoneResiliency(ui.Say) + if b.config.StorageAccount != "" { account, err := b.getBlobAccount(ctx, azureClient, b.config.ResourceGroupName, b.config.StorageAccount) if err != nil { diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index 340e9b46d..df0b7a4f6 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -725,3 +725,23 @@ func isValidAzureName(re *regexp.Regexp, rgn string) bool { !strings.HasSuffix(rgn, ".") && !strings.HasSuffix(rgn, "-") } + +func (c *Config) validateLocationZoneResiliency(say func(s string)) { + // Docs on regions that support Availibility Zones: + // https://docs.microsoft.com/en-us/azure/availability-zones/az-overview#regions-that-support-availability-zones + // Query technical names for locations: + // az account list-locations --query '[].name' -o tsv + + var zones = make(map[string]struct{}) + zones["westeurope"] = struct{}{} + zones["centralus"] = struct{}{} + zones["eastus2"] = struct{}{} + zones["francecentral"] = struct{}{} + zones["northeurope"] = struct{}{} + zones["southeastasia"] = struct{}{} + zones["westus2"] = struct{}{} + + if _, ok := zones[c.Location]; !ok { + say(fmt.Sprintf("WARNING: Zone resiliency may not be supported in %s, checkout the docs at https://docs.microsoft.com/en-us/azure/availability-zones/", c.Location)) + } +} diff --git a/builder/azure/arm/config_test.go b/builder/azure/arm/config_test.go index 64d59e4eb..ef5c9fb93 100644 --- a/builder/azure/arm/config_test.go +++ b/builder/azure/arm/config_test.go @@ -1681,7 +1681,42 @@ func TestConfigShouldRejectSharedImageGalleryWithVhdTarget(t *testing.T) { if err != nil { t.Log("expected an error if Shared Image Gallery source is used with VHD target", err) } +} +func Test_GivenZoneNotSupportingResiliency_ConfigValidate_ShouldWarn(t *testing.T) { + builderValues := getArmBuilderConfiguration() + builderValues["managed_image_zone_resilient"] = "true" + builderValues["location"] = "ukwest" + + c, _, err := newConfig(builderValues, getPackerConfiguration()) + if err != nil { + t.Errorf("newConfig failed with %q", err) + } + + var m = "" + c.validateLocationZoneResiliency(func(s string) { m = s }) + + if m != "WARNING: Zone resiliency may not be supported in ukwest, checkout the docs at https://docs.microsoft.com/en-us/azure/availability-zones/" { + t.Errorf("warning message not as expected: %s", m) + } +} + +func Test_GivenZoneSupportingResiliency_ConfigValidate_ShouldNotWarn(t *testing.T) { + builderValues := getArmBuilderConfiguration() + builderValues["managed_image_zone_resilient"] = "true" + builderValues["location"] = "westeurope" + + c, _, err := newConfig(builderValues, getPackerConfiguration()) + if err != nil { + t.Errorf("newConfig failed with %q", err) + } + + var m = "" + c.validateLocationZoneResiliency(func(s string) { m = s }) + + if m != "" { + t.Errorf("warning message not as expected: %s", m) + } } func getArmBuilderConfiguration() map[string]string {