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

provider/azurerm: Container Registry #10973

Merged
merged 12 commits into from
Jan 2, 2017
9 changes: 9 additions & 0 deletions builtin/providers/azurerm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/Azure/azure-sdk-for-go/arm/cdn"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/Azure/azure-sdk-for-go/arm/containerregistry"
"github.com/Azure/azure-sdk-for-go/arm/eventhub"
"github.com/Azure/azure-sdk-for-go/arm/keyvault"
"github.com/Azure/azure-sdk-for-go/arm/network"
Expand Down Expand Up @@ -63,6 +64,8 @@ type ArmClient struct {
cdnProfilesClient cdn.ProfilesClient
cdnEndpointsClient cdn.EndpointsClient

containerRegistryClient containerregistry.RegistriesClient

eventHubClient eventhub.EventHubsClient
eventHubConsumerGroupClient eventhub.ConsumerGroupsClient
eventHubNamespacesClient eventhub.NamespacesClient
Expand Down Expand Up @@ -221,6 +224,12 @@ func (c *Config) getArmClient() (*ArmClient, error) {
agc.Sender = autorest.CreateSender(withRequestLogging())
client.appGatewayClient = agc

crc := containerregistry.NewRegistriesClient(c.SubscriptionID)
setUserAgent(&crc.Client)
crc.Authorizer = spt
crc.Sender = autorest.CreateSender(withRequestLogging())
client.containerRegistryClient = crc

ehc := eventhub.NewEventHubsClient(c.SubscriptionID)
setUserAgent(&ehc.Client)
ehc.Authorizer = spt
Expand Down
61 changes: 61 additions & 0 deletions builtin/providers/azurerm/import_arm_container_registry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package azurerm

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)

func TestAccAzureRMContainerRegistry_importBasic(t *testing.T) {
resourceName := "azurerm_container_registry.test"

ri := acctest.RandInt()
rs := acctest.RandString(4)
config := fmt.Sprintf(testAccAzureRMContainerRegistry_basic, ri, rs, ri)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMContainerRegistryDestroy,
Steps: []resource.TestStep{
{
Config: config,
},

{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"storage_account"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we ignore the storage_account?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because the inner access_key field isn't returned through the API after the initial CreateOrUpdate. Potentially we could retrieve it via the Storage Account - but it felt like the wrong thing to do (given it's a completely unrelated object)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, that makes sense

},
},
})
}

func TestAccAzureRMContainerRegistry_importComplete(t *testing.T) {
resourceName := "azurerm_container_registry.test"

ri := acctest.RandInt()
rs := acctest.RandString(4)
config := fmt.Sprintf(testAccAzureRMContainerRegistry_complete, ri, rs, ri)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMContainerRegistryDestroy,
Steps: []resource.TestStep{
{
Config: config,
},

{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"storage_account"},
},
},
})
}
8 changes: 5 additions & 3 deletions builtin/providers/azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ func Provider() terraform.ResourceProvider {

ResourcesMap: map[string]*schema.Resource{
// These resources use the Azure ARM SDK
"azurerm_availability_set": resourceArmAvailabilitySet(),
"azurerm_cdn_endpoint": resourceArmCdnEndpoint(),
"azurerm_cdn_profile": resourceArmCdnProfile(),
"azurerm_availability_set": resourceArmAvailabilitySet(),
"azurerm_cdn_endpoint": resourceArmCdnEndpoint(),
"azurerm_cdn_profile": resourceArmCdnProfile(),
"azurerm_container_registry": resourceArmContainerRegistry(),

"azurerm_eventhub": resourceArmEventHub(),
"azurerm_eventhub_consumer_group": resourceArmEventHubConsumerGroup(),
Expand Down Expand Up @@ -209,6 +210,7 @@ func registerAzureResourceProvidersWithSubscription(client *riviera.Client) erro
// We register Microsoft.Compute during client initialization
providers := []string{
"Microsoft.Cache",
"Microsoft.ContainerRegistry",
"Microsoft.Network",
"Microsoft.Cdn",
"Microsoft.Storage",
Expand Down
236 changes: 236 additions & 0 deletions builtin/providers/azurerm/resource_arm_container_registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package azurerm

import (
"fmt"
"log"

"net/http"

"regexp"

"github.com/Azure/azure-sdk-for-go/arm/containerregistry"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
"github.com/jen20/riviera/azure"
)

func resourceArmContainerRegistry() *schema.Resource {
return &schema.Resource{
Create: resourceArmContainerRegistryCreate,
Read: resourceArmContainerRegistryRead,
Update: resourceArmContainerRegistryCreate,
Delete: resourceArmContainerRegistryDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateAzureRMContainerRegistryName,
},

"resource_group_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"location": locationSchema(),

"admin_enabled": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},

"storage_account": {
Type: schema.TypeSet,
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},

"access_key": {
Type: schema.TypeString,
Required: true,
Sensitive: true,
},
},
},
},

"login_server": {
Type: schema.TypeString,
Computed: true,
},

"admin_username": {
Type: schema.TypeString,
Computed: true,
},

"admin_password": {
Type: schema.TypeString,
Computed: true,
},

"tags": tagsSchema(),
},
}
}

func resourceArmContainerRegistryCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).containerRegistryClient
log.Printf("[INFO] preparing arguments for AzureRM Container Registry creation.")

resourceGroup := d.Get("resource_group_name").(string)
name := d.Get("name").(string)
location := d.Get("location").(string)

adminUserEnabled := d.Get("admin_enabled").(bool)
tags := d.Get("tags").(map[string]interface{})

parameters := containerregistry.Registry{
Location: &location,
RegistryProperties: &containerregistry.RegistryProperties{
AdminUserEnabled: &adminUserEnabled,
},
Tags: expandTags(tags),
}

accounts := d.Get("storage_account").(*schema.Set).List()
account := accounts[0].(map[string]interface{})
storageAccountName := account["name"].(string)
storageAccountAccessKey := account["access_key"].(string)
parameters.RegistryProperties.StorageAccount = &containerregistry.StorageAccountProperties{
Name: azure.String(storageAccountName),
AccessKey: azure.String(storageAccountAccessKey),
}

_, err := client.CreateOrUpdate(resourceGroup, name, parameters)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this handle the polling or do we need to do that in our code using a staterefreshfunc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the SDK says that it's only possible to get a 200 OK back (i.e. that it's not a long running operation) - which I've confirmed in all of the testing I've done with this, where the Registry's created so quickly I've not seen it block.

The Documentation on the other hand specifies that it's possible to get a 202 Accepted back - however I've noticed a couple of issues in the Documentation anyway so this may well be a bug in the docs.

Given they're both generated from the same place I'll file an issue - but as yet I've not seen a 202 in real world usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if err != nil {
return err
}

read, err := client.GetProperties(resourceGroup, name)
if err != nil {
return err
}

if read.ID == nil {
return fmt.Errorf("Cannot read Container Registry %s (resource group %s) ID", name, resourceGroup)
}

d.SetId(*read.ID)

return resourceArmContainerRegistryRead(d, meta)
}

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

id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
resourceGroup := id.ResourceGroup
name := id.Path["registries"]

resp, err := client.GetProperties(resourceGroup, name)
if err != nil {
return fmt.Errorf("Error making Read request on Azure Container Registry %s: %s", name, err)
}
if resp.StatusCode == http.StatusNotFound {
d.SetId("")
return nil
}

d.Set("name", resp.Name)
d.Set("resource_group_name", resourceGroup)
d.Set("location", azureRMNormalizeLocation(*resp.Location))
d.Set("admin_enabled", resp.AdminUserEnabled)
d.Set("login_server", resp.LoginServer)

if resp.StorageAccount != nil {
flattenArmContainerRegistryStorageAccount(d, resp.StorageAccount)
}

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

d.Set("admin_username", credsResp.Username)
d.Set("admin_password", credsResp.Password)
} else {
d.Set("admin_username", "")
d.Set("admin_password", "")
}

flattenAndSetTags(d, resp.Tags)

return nil
}

func resourceArmContainerRegistryDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).containerRegistryClient

id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
resourceGroup := id.ResourceGroup
name := id.Path["registries"]

resp, err := client.Delete(resourceGroup, name)

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Error issuing Azure ARM delete request of Container Registry '%s': %s", name, err)
}

return nil
}

func flattenArmContainerRegistryStorageAccount(d *schema.ResourceData, properties *containerregistry.StorageAccountProperties) {
storageAccounts := schema.Set{
F: resourceAzureRMContainerRegistryStorageAccountHash,
}

storageAccount := map[string]interface{}{}
storageAccount["name"] = properties.Name
storageAccounts.Add(storageAccount)

d.Set("storage_account", &storageAccounts)
}

func resourceAzureRMContainerRegistryStorageAccountHash(v interface{}) int {
m := v.(map[string]interface{})
name := m["name"].(*string)
return hashcode.String(*name)
}

func validateAzureRMContainerRegistryName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"alpha numeric characters only are allowed in %q: %q", k, value))
}

if 5 > len(value) {
errors = append(errors, fmt.Errorf("%q cannot be less than 5 characters: %q", k, value))
}

if len(value) >= 50 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 50 characters: %q %d", k, value, len(value)))
}

return
}
Loading