Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support geo-replication for azurerm_container_registry resource #2055

Merged
merged 22 commits into from
Nov 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ac6455f
wip: #2047 support for acr georeplication
jcorioland Oct 10, 2018
97dd6b5
wip: #2047 fix typo in variable name
jcorioland Oct 10, 2018
6d4cc79
wip: #2047 improve logging and set replication name correctly
jcorioland Oct 10, 2018
e040704
wip: #2047 update replication locations (add/delete)
jcorioland Oct 10, 2018
997a3be
wip: #2047 update read geo-replication
jcorioland Oct 11, 2018
40e8e7b
remove unused property in schema for container registry
jcorioland Oct 11, 2018
60b8704
#2047 add acc test for ACR geo replication
jcorioland Oct 11, 2018
02a6c67
fix unit tests
jcorioland Oct 11, 2018
24079fd
wip: refactoring + acc tests
jcorioland Oct 12, 2018
2ba19f8
fix acc tests + scenario coverage
jcorioland Oct 12, 2018
7cbbb43
#2047 - improvements after code review
jcorioland Oct 15, 2018
8a81a13
code improvements after @katbyte review
jcorioland Nov 8, 2018
ae01a69
refactor update of geolocations
jcorioland Nov 9, 2018
172d0a3
Merge branch 'master' into feat/acr-georeplication
katbyte Nov 14, 2018
4a316bb
use Set instead of List of georeplication locations
jcorioland Nov 23, 2018
1a6d8b5
add example for container-registry
jcorioland Nov 23, 2018
0dd1765
remove unecessary provider directive
jcorioland Nov 23, 2018
d7cc20b
docs update for container-registry
jcorioland Nov 23, 2018
3435f0b
fix lint warnings
jcorioland Nov 23, 2018
1cdca50
fix examples and docs typo for acr
jcorioland Nov 27, 2018
0511763
update container_registry tests
jcorioland Nov 27, 2018
62cefde
fix sku validation
jcorioland Nov 27, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions azurerm/common_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ func zonesSchemaComputed() *schema.Schema {
func expandZones(v []interface{}) *[]string {
return azure.ExpandZones(v)
}

func azureRMHashLocation(location interface{}) int {
return azure.HashAzureLocation(location)
}
14 changes: 10 additions & 4 deletions azurerm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,11 @@ type ArmClient struct {
dnsClient dns.RecordSetsClient
zonesClient dns.ZonesClient

containerRegistryClient containerregistry.RegistriesClient
containerServicesClient containerservice.ContainerServicesClient
kubernetesClustersClient containerservice.ManagedClustersClient
containerGroupsClient containerinstance.ContainerGroupsClient
containerRegistryClient containerregistry.RegistriesClient
containerRegistryReplicationsClient containerregistry.ReplicationsClient
containerServicesClient containerservice.ContainerServicesClient
kubernetesClustersClient containerservice.ManagedClustersClient
containerGroupsClient containerinstance.ContainerGroupsClient

eventGridTopicsClient eventgrid.TopicsClient
eventHubClient eventhub.EventHubsClient
Expand Down Expand Up @@ -607,6 +608,11 @@ func (c *ArmClient) registerContainerRegistryClients(endpoint, subscriptionId st
crc := containerregistry.NewRegistriesClientWithBaseURI(endpoint, subscriptionId)
c.configureClient(&crc.Client, auth)
c.containerRegistryClient = crc

// container registry replicalication client
crrc := containerregistry.NewReplicationsClientWithBaseURI(endpoint, subscriptionId)
c.configureClient(&crrc.Client, auth)
c.containerRegistryReplicationsClient = crrc
}

func (c *ArmClient) registerContainerServicesClients(endpoint, subscriptionId string, auth autorest.Authorizer) {
Expand Down
5 changes: 5 additions & 0 deletions azurerm/helpers/azure/location.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package azure
import (
"strings"

"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
)

Expand Down Expand Up @@ -46,3 +47,7 @@ func NormalizeLocation(location interface{}) string {
func SuppressLocationDiff(k, old, new string, d *schema.ResourceData) bool {
return NormalizeLocation(old) == NormalizeLocation(new)
}

func HashAzureLocation(location interface{}) int {
return hashcode.String(NormalizeLocation(location.(string)))
}
165 changes: 159 additions & 6 deletions azurerm/resource_arm_container_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ func resourceArmContainerRegistry() *schema.Resource {
Default: false,
},

"georeplication_locations": {
Type: schema.TypeSet,
MinItems: 1,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.NoZeroValues,
},
katbyte marked this conversation as resolved.
Show resolved Hide resolved
Set: azureRMHashLocation,
},

"storage_account_id": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -101,6 +112,17 @@ func resourceArmContainerRegistry() *schema.Resource {

"tags": tagsSchema(),
},

CustomizeDiff: func(d *schema.ResourceDiff, v interface{}) error {
sku := d.Get("sku").(string)
geoReplicationLocations := d.Get("georeplication_locations").(*schema.Set)
// if locations have been specified for geo-replication then, the SKU has to be Premium
if geoReplicationLocations != nil && geoReplicationLocations.Len() > 0 && !strings.EqualFold(sku, string(containerregistry.Premium)) {
return fmt.Errorf("ACR geo-replication can only be applied when using the Premium Sku.")
}

return nil
},
}
}

Expand All @@ -115,6 +137,7 @@ func resourceArmContainerRegistryCreate(d *schema.ResourceData, meta interface{}
sku := d.Get("sku").(string)
adminUserEnabled := d.Get("admin_enabled").(bool)
tags := d.Get("tags").(map[string]interface{})
geoReplicationLocations := d.Get("georeplication_locations").(*schema.Set)

parameters := containerregistry.Registry{
Location: &location,
Expand Down Expand Up @@ -152,6 +175,16 @@ func resourceArmContainerRegistryCreate(d *schema.ResourceData, meta interface{}
return fmt.Errorf("Error waiting for creation of Container Registry %q (Resource Group %q): %+v", name, resourceGroup, err)
}

// locations have been specified for geo-replication
if geoReplicationLocations != nil && geoReplicationLocations.Len() > 0 {
// the ACR is being created so no previous geo-replication locations
oldGeoReplicationLocations := []interface{}{}
err = applyGeoReplicationLocations(meta, resourceGroup, name, oldGeoReplicationLocations, geoReplicationLocations.List())
if err != nil {
return fmt.Errorf("Error applying geo replications for Container Registry %q (Resource Group %q): %+v", name, resourceGroup, err)
}
}

read, err := client.Get(ctx, resourceGroup, name)
if err != nil {
return fmt.Errorf("Error retrieving Container Registry %q (Resource Group %q): %+v", name, resourceGroup, err)
Expand All @@ -178,6 +211,11 @@ func resourceArmContainerRegistryUpdate(d *schema.ResourceData, meta interface{}
adminUserEnabled := d.Get("admin_enabled").(bool)
tags := d.Get("tags").(map[string]interface{})

old, new := d.GetChange("georeplication_locations")
hasGeoReplicationChanges := d.HasChange("georeplication_locations")
oldGeoReplicationLocations := old.(*schema.Set)
newGeoReplicationLocations := new.(*schema.Set)

parameters := containerregistry.RegistryUpdateParameters{
RegistryPropertiesUpdateParameters: &containerregistry.RegistryPropertiesUpdateParameters{
AdminUserEnabled: utils.Bool(adminUserEnabled),
Expand All @@ -190,19 +228,32 @@ func resourceArmContainerRegistryUpdate(d *schema.ResourceData, meta interface{}
}

if v, ok := d.GetOk("storage_account_id"); ok {
if strings.ToLower(sku) != strings.ToLower(string(containerregistry.Classic)) {
if !strings.EqualFold(sku, string(containerregistry.Classic)) {
return fmt.Errorf("`storage_account_id` can only be specified for a Classic (unmanaged) Sku.")
}

parameters.StorageAccount = &containerregistry.StorageAccountProperties{
ID: utils.String(v.(string)),
}
} else {
if strings.ToLower(sku) == strings.ToLower(string(containerregistry.Classic)) {
if strings.EqualFold(sku, string(containerregistry.Classic)) {
return fmt.Errorf("`storage_account_id` must be specified for a Classic (unmanaged) Sku.")
}
}

// geo replication is only supported by Premium Sku
if hasGeoReplicationChanges && newGeoReplicationLocations.Len() > 0 && !strings.EqualFold(sku, string(containerregistry.Premium)) {
return fmt.Errorf("ACR geo-replication can only be applied when using the Premium Sku.")
}

// if the registry had replications and is updated to another Sku than premium - remove old locations
if !strings.EqualFold(sku, string(containerregistry.Premium)) && oldGeoReplicationLocations != nil && oldGeoReplicationLocations.Len() > 0 {
err := applyGeoReplicationLocations(meta, resourceGroup, name, oldGeoReplicationLocations.List(), newGeoReplicationLocations.List())
if err != nil {
return fmt.Errorf("Error applying geo replications for Container Registry %q (Resource Group %q): %+v", name, resourceGroup, err)
}
}
jcorioland marked this conversation as resolved.
Show resolved Hide resolved

future, err := client.Update(ctx, resourceGroup, name, parameters)
if err != nil {
return fmt.Errorf("Error updating Container Registry %q (Resource Group %q): %+v", name, resourceGroup, err)
Expand All @@ -213,6 +264,13 @@ func resourceArmContainerRegistryUpdate(d *schema.ResourceData, meta interface{}
return fmt.Errorf("Error waiting for update of Container Registry %q (Resource Group %q): %+v", name, resourceGroup, err)
}

if strings.EqualFold(sku, string(containerregistry.Premium)) && hasGeoReplicationChanges {
err = applyGeoReplicationLocations(meta, resourceGroup, name, oldGeoReplicationLocations.List(), newGeoReplicationLocations.List())
if err != nil {
return fmt.Errorf("Error applying geo replications for Container Registry %q (Resource Group %q): %+v", name, resourceGroup, err)
}
}

read, err := client.Get(ctx, resourceGroup, name)
if err != nil {
return fmt.Errorf("Error retrieving Container Registry %q (Resource Group %q): %+v", name, resourceGroup, err)
Expand All @@ -227,8 +285,78 @@ func resourceArmContainerRegistryUpdate(d *schema.ResourceData, meta interface{}
return resourceArmContainerRegistryRead(d, meta)
}

func applyGeoReplicationLocations(meta interface{}, resourceGroup string, name string, oldGeoReplicationLocations []interface{}, newGeoReplicationLocations []interface{}) error {
replicationClient := meta.(*ArmClient).containerRegistryReplicationsClient
ctx := meta.(*ArmClient).StopContext
log.Printf("[INFO] preparing to apply geo-replications for AzureRM Container Registry.")

createLocations := make(map[string]bool)

// loop on the new location values
for _, nl := range newGeoReplicationLocations {
newLocation := azureRMNormalizeLocation(nl)
createLocations[newLocation] = true // the location needs to be created
}

// loop on the old location values
for _, ol := range oldGeoReplicationLocations {
// oldLocation was created from a previous deployment
oldLocation := azureRMNormalizeLocation(ol)

// if the list of locations to create already contains the location
if _, ok := createLocations[oldLocation]; ok {
createLocations[oldLocation] = false // the location do not need to be created, it already exists
}
}

// create new geo-replication locations
for locationToCreate := range createLocations {
// if false, the location does not need to be created, continue
if !createLocations[locationToCreate] {
continue
}

// create the new replication location
replication := containerregistry.Replication{
Location: &locationToCreate,
Name: &locationToCreate,
katbyte marked this conversation as resolved.
Show resolved Hide resolved
}

future, err := replicationClient.Create(ctx, resourceGroup, name, locationToCreate, replication)
if err != nil {
return fmt.Errorf("Error creating Container Registry Replication %q (Resource Group %q, Location %q): %+v", name, resourceGroup, locationToCreate, err)
}

if err = future.WaitForCompletionRef(ctx, replicationClient.Client); err != nil {
return fmt.Errorf("Error waiting for creation of Container Registry Replication %q (Resource Group %q, Location %q): %+v", name, resourceGroup, locationToCreate, err)
}
}

// loop on the list of previously deployed locations
for _, ol := range oldGeoReplicationLocations {
oldLocation := azureRMNormalizeLocation(ol)
// if the old location is still in the list of locations, then continue
if _, ok := createLocations[oldLocation]; ok {
continue
}

// the old location is not in the list of locations, delete it
future, err := replicationClient.Delete(ctx, resourceGroup, name, oldLocation)
if err != nil {
jcorioland marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("Error deleting Container Registry Replication %q (Resource Group %q, Location %q): %+v", name, resourceGroup, oldLocation, err)
}

if err = future.WaitForCompletionRef(ctx, replicationClient.Client); err != nil {
return fmt.Errorf("Error waiting for deletion of Container Registry Replication %q (Resource Group %q, Location %q): %+v", name, resourceGroup, oldLocation, err)
}
}

return nil
}

func resourceArmContainerRegistryRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).containerRegistryClient
replicationClient := meta.(*ArmClient).containerRegistryReplicationsClient
ctx := meta.(*ArmClient).StopContext

id, err := parseAzureResourceID(d.Id())
Expand All @@ -251,7 +379,9 @@ func resourceArmContainerRegistryRead(d *schema.ResourceData, meta interface{})

d.Set("name", resp.Name)
d.Set("resource_group_name", resourceGroup)
if location := resp.Location; location != nil {

location := resp.Location
if location != nil {
jcorioland marked this conversation as resolved.
Show resolved Hide resolved
d.Set("location", azureRMNormalizeLocation(*location))
}
d.Set("admin_enabled", resp.AdminUserEnabled)
Expand All @@ -266,9 +396,9 @@ func resourceArmContainerRegistryRead(d *schema.ResourceData, meta interface{})
}

if *resp.AdminUserEnabled {
credsResp, err := client.ListCredentials(ctx, resourceGroup, name)
if err != nil {
return fmt.Errorf("Error making Read request on Azure Container Registry %s for Credentials: %s", name, err)
credsResp, errList := client.ListCredentials(ctx, resourceGroup, name)
if errList != nil {
return fmt.Errorf("Error making Read request on Azure Container Registry %s for Credentials: %s", name, errList)
}

d.Set("admin_username", credsResp.Username)
Expand All @@ -283,6 +413,29 @@ func resourceArmContainerRegistryRead(d *schema.ResourceData, meta interface{})

flattenAndSetTags(d, resp.Tags)

replications, err := replicationClient.List(ctx, resourceGroup, name)
if err != nil {
return fmt.Errorf("Error making Read request on Azure Container Registry %s for replications: %s", name, err)
}

replicationValues := replications.Values()

// if there is more than one location (the main one and the replicas)
if replicationValues != nil || len(replicationValues) > 1 {
georeplication_locations := &schema.Set{F: schema.HashString}

for _, value := range replicationValues {
if value.Location != nil {
valueLocation := azureRMNormalizeLocation(*value.Location)
if location != nil && valueLocation != azureRMNormalizeLocation(*location) {
georeplication_locations.Add(valueLocation)
}
}
}

d.Set("georeplication_locations", georeplication_locations)
}

return nil
}

Expand Down
Loading