From 270081f645cf03bcb4ae82a8778e45339bc155f2 Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Thu, 21 Jun 2018 17:28:09 -0700 Subject: [PATCH 01/14] Add userAssigned identity support for VM and VMSS --- azurerm/resource_arm_virtual_machine.go | 28 ++++++++++++++++++- .../resource_arm_virtual_machine_scale_set.go | 28 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/azurerm/resource_arm_virtual_machine.go b/azurerm/resource_arm_virtual_machine.go index 51eb75daa852..c1de6dd62058 100644 --- a/azurerm/resource_arm_virtual_machine.go +++ b/azurerm/resource_arm_virtual_machine.go @@ -85,12 +85,20 @@ func resourceArmVirtualMachine() *schema.Resource { DiffSuppressFunc: ignoreCaseDiffSuppressFunc, ValidateFunc: validation.StringInSlice([]string{ "SystemAssigned", + "UserAssigned", }, true), }, "principal_id": { Type: schema.TypeString, Computed: true, }, + "identity_ids": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, }, }, }, @@ -933,6 +941,12 @@ func flattenAzureRmVirtualMachineIdentity(identity *compute.VirtualMachineIdenti result["principal_id"] = *identity.PrincipalID } + identity_ids := make([]string, 0) + for _, id := range *identity.IdentityIds { + identity_ids = append(identity_ids, id) + } + result["identity_ids"] = identity_ids + return []interface{}{result} } @@ -1156,9 +1170,21 @@ func expandAzureRmVirtualMachineIdentity(d *schema.ResourceData) *compute.Virtua identities := v.([]interface{}) identity := identities[0].(map[string]interface{}) identityType := identity["type"].(string) - return &compute.VirtualMachineIdentity{ + + identityIds := []string{} + for _, id := range identity["identity_ids"].([]interface{}) { + identityIds = append(identityIds, id.(string)) + } + + vmIdentity := compute.VirtualMachineIdentity{ Type: compute.ResourceIdentityType(identityType), } + + if vmIdentity.Type == compute.ResourceIdentityTypeUserAssigned { + vmIdentity.IdentityIds = &identityIds + } + + return &vmIdentity } func expandAzureRmVirtualMachineOsProfile(d *schema.ResourceData) (*compute.OSProfile, error) { diff --git a/azurerm/resource_arm_virtual_machine_scale_set.go b/azurerm/resource_arm_virtual_machine_scale_set.go index d095b7357423..729bddc90058 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set.go +++ b/azurerm/resource_arm_virtual_machine_scale_set.go @@ -50,8 +50,16 @@ func resourceArmVirtualMachineScaleSet() *schema.Resource { DiffSuppressFunc: ignoreCaseDiffSuppressFunc, ValidateFunc: validation.StringInSlice([]string{ "SystemAssigned", + "UserAssigned", }, true), }, + "identity_ids": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, "principal_id": { Type: schema.TypeString, Computed: true, @@ -932,6 +940,12 @@ func flattenAzureRmVirtualMachineScaleSetIdentity(identity *compute.VirtualMachi result["principal_id"] = *identity.PrincipalID } + identity_ids := make([]string, 0) + for _, id := range *identity.IdentityIds { + identity_ids = append(identity_ids, id) + } + result["identity_ids"] = identity_ids + return []interface{}{result} } @@ -1636,9 +1650,21 @@ func expandAzureRmVirtualMachineScaleSetIdentity(d *schema.ResourceData) *comput identities := v.([]interface{}) identity := identities[0].(map[string]interface{}) identityType := identity["type"].(string) - return &compute.VirtualMachineScaleSetIdentity{ + + identityIds := []string{} + for _, id := range identity["identity_ids"].([]interface{}) { + identityIds = append(identityIds, id.(string)) + } + + vmssIdentity := compute.VirtualMachineScaleSetIdentity{ Type: compute.ResourceIdentityType(identityType), } + + if vmssIdentity.Type == compute.ResourceIdentityTypeUserAssigned { + vmssIdentity.IdentityIds = &identityIds + } + + return &vmssIdentity } func expandAzureRMVirtualMachineScaleSetsStorageProfileOsDisk(d *schema.ResourceData) (*compute.VirtualMachineScaleSetOSDisk, error) { From 9d5c108c21773fbee7abddcd94696c0e11bcd79d Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Fri, 22 Jun 2018 16:25:44 -0700 Subject: [PATCH 02/14] Update VM and VMSS Identity tests --- azurerm/resource_arm_virtual_machine.go | 10 +- .../resource_arm_virtual_machine_scale_set.go | 10 +- ...urce_arm_virtual_machine_scale_set_test.go | 15 +- azurerm/resource_arm_virtual_machine_test.go | 138 ++++++++++++++++++ 4 files changed, 159 insertions(+), 14 deletions(-) diff --git a/azurerm/resource_arm_virtual_machine.go b/azurerm/resource_arm_virtual_machine.go index c1de6dd62058..82d4d3fe437f 100644 --- a/azurerm/resource_arm_virtual_machine.go +++ b/azurerm/resource_arm_virtual_machine.go @@ -941,11 +941,13 @@ func flattenAzureRmVirtualMachineIdentity(identity *compute.VirtualMachineIdenti result["principal_id"] = *identity.PrincipalID } - identity_ids := make([]string, 0) - for _, id := range *identity.IdentityIds { - identity_ids = append(identity_ids, id) + if identity.IdentityIds != nil { + identity_ids := make([]string, 0) + for _, id := range *identity.IdentityIds { + identity_ids = append(identity_ids, id) + } + result["identity_ids"] = identity_ids } - result["identity_ids"] = identity_ids return []interface{}{result} } diff --git a/azurerm/resource_arm_virtual_machine_scale_set.go b/azurerm/resource_arm_virtual_machine_scale_set.go index 729bddc90058..7771115cbe2f 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set.go +++ b/azurerm/resource_arm_virtual_machine_scale_set.go @@ -940,11 +940,13 @@ func flattenAzureRmVirtualMachineScaleSetIdentity(identity *compute.VirtualMachi result["principal_id"] = *identity.PrincipalID } - identity_ids := make([]string, 0) - for _, id := range *identity.IdentityIds { - identity_ids = append(identity_ids, id) + if identity.IdentityIds != nil { + identity_ids := make([]string, 0) + for _, id := range *identity.IdentityIds { + identity_ids = append(identity_ids, id) + } + result["identity_ids"] = identity_ids } - result["identity_ids"] = identity_ids return []interface{}{result} } diff --git a/azurerm/resource_arm_virtual_machine_scale_set_test.go b/azurerm/resource_arm_virtual_machine_scale_set_test.go index 3406a3406e0a..765fdbd075bb 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set_test.go +++ b/azurerm/resource_arm_virtual_machine_scale_set_test.go @@ -517,10 +517,10 @@ func TestAccAzureRMVirtualMachineScaleSet_priority(t *testing.T) { }) } -func TestAccAzureRMVirtualMachineScaleSet_MSI(t *testing.T) { +func TestAccAzureRMVirtualMachineScaleSet_SystemAssignedMSI(t *testing.T) { resourceName := "azurerm_virtual_machine_scale_set.test" ri := acctest.RandInt() - config := testAccAzureRMVirtualMachineScaleSetMSITemplate(ri, testLocation()) + config := testAccAzureRMVirtualMachineScaleSetSystemAssignedMSITemplate(ri, testLocation()) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -528,7 +528,10 @@ func TestAccAzureRMVirtualMachineScaleSet_MSI(t *testing.T) { Steps: []resource.TestStep{ { Config: config, - Check: resource.TestCheckResourceAttrSet(resourceName, "identity.0.principal_id"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualMachineScaleSetSystemAssignedMSI(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "identity.0.principal_id"), + ), }, }, }) @@ -911,7 +914,7 @@ func testCheckAzureRMVirtualMachineScaleSetSinglePlacementGroup(name string, exp } } -func testCheckAzureRMVirtualMachineScaleSetMSI(name string) resource.TestCheckFunc { +func testCheckAzureRMVirtualMachineScaleSetSystemAssignedMSI(name string) resource.TestCheckFunc { return func(s *terraform.State) error { resp, err := testGetAzureRMVirtualMachineScaleSet(s, name) if err != nil { @@ -919,7 +922,7 @@ func testCheckAzureRMVirtualMachineScaleSetMSI(name string) resource.TestCheckFu } identityType := resp.Identity.Type - if identityType != "systemAssigned" { + if identityType != compute.ResourceIdentityTypeSystemAssigned { return fmt.Errorf("Bad: Identity Type is not systemAssigned for scale set %v", name) } @@ -2876,7 +2879,7 @@ resource "azurerm_virtual_machine_scale_set" "test" { `, rInt, location) } -func testAccAzureRMVirtualMachineScaleSetMSITemplate(rInt int, location string) string { +func testAccAzureRMVirtualMachineScaleSetSystemAssignedMSITemplate(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestrg-%[1]d" diff --git a/azurerm/resource_arm_virtual_machine_test.go b/azurerm/resource_arm_virtual_machine_test.go index c38b656ef482..2781eabc93c3 100644 --- a/azurerm/resource_arm_virtual_machine_test.go +++ b/azurerm/resource_arm_virtual_machine_test.go @@ -3,12 +3,37 @@ package azurerm import ( "fmt" "net/http" + "testing" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2017-12-01/compute" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) +func TestAccAzureRMVirtualMachine_SystemAssignedIdentity(t *testing.T) { + var vm compute.VirtualMachine + + resourceName := "azurerm_virtual_machine.test" + ri := acctest.RandInt() + config := testAccAzureRMVirtualMachineSystemAssignedIdentityTemplate(ri, testLocation()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualMachineScaleSetDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualMachineExists(resourceName, &vm), + testCheckAzureRMVirtualMachineScaleSetIdentity(resourceName, &vm), + resource.TestCheckResourceAttrSet(resourceName, "identity.0.principal_id"), + ), + }, + }, + }) +} + func testCheckAzureRMVirtualMachineExists(name string, vm *compute.VirtualMachine) resource.TestCheckFunc { return func(s *terraform.State) error { // Ensure we have enough information in state to look up in API @@ -68,3 +93,116 @@ func testCheckAzureRMVirtualMachineDestroy(s *terraform.State) error { return nil } + +func testCheckAzureRMVirtualMachineScaleSetIdentity(name string, vm *compute.VirtualMachine) resource.TestCheckFunc { + return func(s *terraform.State) error { + + identityType := vm.Identity.Type + if identityType != compute.ResourceIdentityTypeSystemAssigned { + return fmt.Errorf("Bad: Identity Type is not systemAssigned for vm %s.", name) + } + + principalID := *vm.Identity.PrincipalID + if len(principalID) == 0 { + return fmt.Errorf("Bad: Could not get principal_id for vm %s.", name) + } + + return nil + } +} + +func testAccAzureRMVirtualMachineSystemAssignedIdentityTemplate(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctvn-%d" + address_space = ["10.0.0.0/16"] + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_subnet" "test" { + name = "acctsub-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.2.0/24" +} + +resource "azurerm_network_interface" "test" { + name = "acctni-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + ip_configuration { + name = "testconfiguration1" + subnet_id = "${azurerm_subnet.test.id}" + private_ip_address_allocation = "dynamic" + } +} + +resource "azurerm_storage_account" "test" { + name = "accsa%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_tier = "Standard" + account_replication_type = "LRS" + + tags { + environment = "staging" + } +} + +resource "azurerm_storage_container" "test" { + name = "vhds" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + container_access_type = "private" +} + +resource "azurerm_virtual_machine" "test" { + name = "acctvm-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + network_interface_ids = ["${azurerm_network_interface.test.id}"] + vm_size = "Standard_D1_v2" + + storage_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + + storage_os_disk { + name = "myosdisk1" + vhd_uri = "${azurerm_storage_account.test.primary_blob_endpoint}${azurerm_storage_container.test.name}/myosdisk1.vhd" + caching = "ReadWrite" + create_option = "FromImage" + disk_size_gb = "45" + } + + os_profile { + computer_name = "hn%d" + admin_username = "testadmin" + admin_password = "Password1234!" + } + + os_profile_linux_config { + disable_password_authentication = false + } + + tags { + environment = "Production" + cost-center = "Ops" + } + + identity { + type = "systemAssigned" + } +} +`, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt) +} From a5200d82b9b7f3a15b93a62b98f43f679d584561 Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Fri, 22 Jun 2018 17:33:17 -0700 Subject: [PATCH 03/14] Add msi user assigned identity package --- .../msi/mgmt/2015-08-31-preview/msi/client.go | 51 ++ .../msi/mgmt/2015-08-31-preview/msi/models.go | 403 ++++++++++++++ .../mgmt/2015-08-31-preview/msi/operations.go | 126 +++++ .../msi/userassignedidentities.go | 496 ++++++++++++++++++ .../mgmt/2015-08-31-preview/msi/version.go | 30 ++ vendor/vendor.json | 8 + 6 files changed, 1114 insertions(+) create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/client.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/models.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/operations.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/userassignedidentities.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/version.go diff --git a/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/client.go b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/client.go new file mode 100644 index 000000000000..a4e7c71d55b9 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/client.go @@ -0,0 +1,51 @@ +// Package msi implements the Azure ARM Msi service API version 2015-08-31-preview. +// +// The Managed Service Identity Client. +package msi + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +import ( + "github.com/Azure/go-autorest/autorest" +) + +const ( + // DefaultBaseURI is the default URI used for the service Msi + DefaultBaseURI = "https://management.azure.com" +) + +// BaseClient is the base client for Msi. +type BaseClient struct { + autorest.Client + BaseURI string + SubscriptionID string +} + +// New creates an instance of the BaseClient client. +func New(subscriptionID string) BaseClient { + return NewWithBaseURI(DefaultBaseURI, subscriptionID) +} + +// NewWithBaseURI creates an instance of the BaseClient client. +func NewWithBaseURI(baseURI string, subscriptionID string) BaseClient { + return BaseClient{ + Client: autorest.NewClientWithUserAgent(UserAgent()), + BaseURI: baseURI, + SubscriptionID: subscriptionID, + } +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/models.go b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/models.go new file mode 100644 index 000000000000..c72acd231b01 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/models.go @@ -0,0 +1,403 @@ +package msi + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +import ( + "encoding/json" + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/to" + "github.com/satori/go.uuid" + "net/http" +) + +// UserAssignedIdentities enumerates the values for user assigned identities. +type UserAssignedIdentities string + +const ( + // MicrosoftManagedIdentityuserAssignedIdentities ... + MicrosoftManagedIdentityuserAssignedIdentities UserAssignedIdentities = "Microsoft.ManagedIdentity/userAssignedIdentities" +) + +// PossibleUserAssignedIdentitiesValues returns an array of possible values for the UserAssignedIdentities const type. +func PossibleUserAssignedIdentitiesValues() []UserAssignedIdentities { + return []UserAssignedIdentities{MicrosoftManagedIdentityuserAssignedIdentities} +} + +// CloudError an error response from the ManagedServiceIdentity service. +type CloudError struct { + // Error - A list of additional details about the error. + Error *CloudErrorBody `json:"error,omitempty"` +} + +// CloudErrorBody an error response from the ManagedServiceIdentity service. +type CloudErrorBody struct { + // Code - An identifier for the error. + Code *string `json:"code,omitempty"` + // Message - A message describing the error, intended to be suitable for display in a user interface. + Message *string `json:"message,omitempty"` + // Target - The target of the particular error. For example, the name of the property in error. + Target *string `json:"target,omitempty"` + // Details - A list of additional details about the error. + Details *[]CloudErrorBody `json:"details,omitempty"` +} + +// Identity describes an identity resource. +type Identity struct { + autorest.Response `json:"-"` + // ID - The id of the created identity. + ID *string `json:"id,omitempty"` + // Name - The name of the created identity. + Name *string `json:"name,omitempty"` + // Location - The Azure region where the identity lives. + Location *string `json:"location,omitempty"` + // Tags - Resource tags + Tags map[string]*string `json:"tags"` + // IdentityProperties - The properties associated with the identity. + *IdentityProperties `json:"properties,omitempty"` + // Type - The type of resource i.e. Microsoft.ManagedIdentity/userAssignedIdentities. Possible values include: 'MicrosoftManagedIdentityuserAssignedIdentities' + Type UserAssignedIdentities `json:"type,omitempty"` +} + +// MarshalJSON is the custom marshaler for Identity. +func (i Identity) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]interface{}) + if i.ID != nil { + objectMap["id"] = i.ID + } + if i.Name != nil { + objectMap["name"] = i.Name + } + if i.Location != nil { + objectMap["location"] = i.Location + } + if i.Tags != nil { + objectMap["tags"] = i.Tags + } + if i.IdentityProperties != nil { + objectMap["properties"] = i.IdentityProperties + } + if i.Type != "" { + objectMap["type"] = i.Type + } + return json.Marshal(objectMap) +} + +// UnmarshalJSON is the custom unmarshaler for Identity struct. +func (i *Identity) UnmarshalJSON(body []byte) error { + var m map[string]*json.RawMessage + err := json.Unmarshal(body, &m) + if err != nil { + return err + } + for k, v := range m { + switch k { + case "id": + if v != nil { + var ID string + err = json.Unmarshal(*v, &ID) + if err != nil { + return err + } + i.ID = &ID + } + case "name": + if v != nil { + var name string + err = json.Unmarshal(*v, &name) + if err != nil { + return err + } + i.Name = &name + } + case "location": + if v != nil { + var location string + err = json.Unmarshal(*v, &location) + if err != nil { + return err + } + i.Location = &location + } + case "tags": + if v != nil { + var tags map[string]*string + err = json.Unmarshal(*v, &tags) + if err != nil { + return err + } + i.Tags = tags + } + case "properties": + if v != nil { + var identityProperties IdentityProperties + err = json.Unmarshal(*v, &identityProperties) + if err != nil { + return err + } + i.IdentityProperties = &identityProperties + } + case "type": + if v != nil { + var typeVar UserAssignedIdentities + err = json.Unmarshal(*v, &typeVar) + if err != nil { + return err + } + i.Type = typeVar + } + } + } + + return nil +} + +// IdentityProperties the properties associated with the identity. +type IdentityProperties struct { + // TenantID - The id of the tenant which the identity belongs to. + TenantID *uuid.UUID `json:"tenantId,omitempty"` + // PrincipalID - The id of the service principal object associated with the created identity. + PrincipalID *uuid.UUID `json:"principalId,omitempty"` + // ClientID - The id of the app associated with the identity. This is a random generated UUID by MSI. + ClientID *uuid.UUID `json:"clientId,omitempty"` + // ClientSecretURL - The ManagedServiceIdentity DataPlane URL that can be queried to obtain the identity credentials. + ClientSecretURL *string `json:"clientSecretUrl,omitempty"` +} + +// Operation operation supported by the Microsoft.ManagedIdentity REST API. +type Operation struct { + // Name - The name of the REST Operation. This is of the format {provider}/{resource}/{operation}. + Name *string `json:"name,omitempty"` + // Display - The object that describes the operation. + Display *OperationDisplay `json:"display,omitempty"` +} + +// OperationDisplay the object that describes the operation. +type OperationDisplay struct { + // Provider - Friendly name of the resource provider. + Provider *string `json:"provider,omitempty"` + // Operation - The type of operation. For example: read, write, delete. + Operation *string `json:"operation,omitempty"` + // Resource - The resource type on which the operation is performed. + Resource *string `json:"resource,omitempty"` + // Description - A description of the operation. + Description *string `json:"description,omitempty"` +} + +// OperationListResult a list of operations supported by Microsoft.ManagedIdentity Resource Provider. +type OperationListResult struct { + autorest.Response `json:"-"` + // Value - A list of operations supported by Microsoft.ManagedIdentity Resource Provider. + Value *[]Operation `json:"value,omitempty"` + // NextLink - The url to get the next page of results, if any. + NextLink *string `json:"nextLink,omitempty"` +} + +// OperationListResultIterator provides access to a complete listing of Operation values. +type OperationListResultIterator struct { + i int + page OperationListResultPage +} + +// Next advances to the next value. If there was an error making +// the request the iterator does not advance and the error is returned. +func (iter *OperationListResultIterator) Next() error { + iter.i++ + if iter.i < len(iter.page.Values()) { + return nil + } + err := iter.page.Next() + if err != nil { + iter.i-- + return err + } + iter.i = 0 + return nil +} + +// NotDone returns true if the enumeration should be started or is not yet complete. +func (iter OperationListResultIterator) NotDone() bool { + return iter.page.NotDone() && iter.i < len(iter.page.Values()) +} + +// Response returns the raw server response from the last page request. +func (iter OperationListResultIterator) Response() OperationListResult { + return iter.page.Response() +} + +// Value returns the current value or a zero-initialized value if the +// iterator has advanced beyond the end of the collection. +func (iter OperationListResultIterator) Value() Operation { + if !iter.page.NotDone() { + return Operation{} + } + return iter.page.Values()[iter.i] +} + +// IsEmpty returns true if the ListResult contains no values. +func (olr OperationListResult) IsEmpty() bool { + return olr.Value == nil || len(*olr.Value) == 0 +} + +// operationListResultPreparer prepares a request to retrieve the next set of results. +// It returns nil if no more results exist. +func (olr OperationListResult) operationListResultPreparer() (*http.Request, error) { + if olr.NextLink == nil || len(to.String(olr.NextLink)) < 1 { + return nil, nil + } + return autorest.Prepare(&http.Request{}, + autorest.AsJSON(), + autorest.AsGet(), + autorest.WithBaseURL(to.String(olr.NextLink))) +} + +// OperationListResultPage contains a page of Operation values. +type OperationListResultPage struct { + fn func(OperationListResult) (OperationListResult, error) + olr OperationListResult +} + +// Next advances to the next page of values. If there was an error making +// the request the page does not advance and the error is returned. +func (page *OperationListResultPage) Next() error { + next, err := page.fn(page.olr) + if err != nil { + return err + } + page.olr = next + return nil +} + +// NotDone returns true if the page enumeration should be started or is not yet complete. +func (page OperationListResultPage) NotDone() bool { + return !page.olr.IsEmpty() +} + +// Response returns the raw server response from the last page request. +func (page OperationListResultPage) Response() OperationListResult { + return page.olr +} + +// Values returns the slice of values for the current page or nil if there are no values. +func (page OperationListResultPage) Values() []Operation { + if page.olr.IsEmpty() { + return nil + } + return *page.olr.Value +} + +// UserAssignedIdentitiesListResult values returned by the List operation. +type UserAssignedIdentitiesListResult struct { + autorest.Response `json:"-"` + // Value - The collection of userAssignedIdentities returned by the listing operation. + Value *[]Identity `json:"value,omitempty"` + // NextLink - The url to get the next page of results, if any. + NextLink *string `json:"nextLink,omitempty"` +} + +// UserAssignedIdentitiesListResultIterator provides access to a complete listing of Identity values. +type UserAssignedIdentitiesListResultIterator struct { + i int + page UserAssignedIdentitiesListResultPage +} + +// Next advances to the next value. If there was an error making +// the request the iterator does not advance and the error is returned. +func (iter *UserAssignedIdentitiesListResultIterator) Next() error { + iter.i++ + if iter.i < len(iter.page.Values()) { + return nil + } + err := iter.page.Next() + if err != nil { + iter.i-- + return err + } + iter.i = 0 + return nil +} + +// NotDone returns true if the enumeration should be started or is not yet complete. +func (iter UserAssignedIdentitiesListResultIterator) NotDone() bool { + return iter.page.NotDone() && iter.i < len(iter.page.Values()) +} + +// Response returns the raw server response from the last page request. +func (iter UserAssignedIdentitiesListResultIterator) Response() UserAssignedIdentitiesListResult { + return iter.page.Response() +} + +// Value returns the current value or a zero-initialized value if the +// iterator has advanced beyond the end of the collection. +func (iter UserAssignedIdentitiesListResultIterator) Value() Identity { + if !iter.page.NotDone() { + return Identity{} + } + return iter.page.Values()[iter.i] +} + +// IsEmpty returns true if the ListResult contains no values. +func (uailr UserAssignedIdentitiesListResult) IsEmpty() bool { + return uailr.Value == nil || len(*uailr.Value) == 0 +} + +// userAssignedIdentitiesListResultPreparer prepares a request to retrieve the next set of results. +// It returns nil if no more results exist. +func (uailr UserAssignedIdentitiesListResult) userAssignedIdentitiesListResultPreparer() (*http.Request, error) { + if uailr.NextLink == nil || len(to.String(uailr.NextLink)) < 1 { + return nil, nil + } + return autorest.Prepare(&http.Request{}, + autorest.AsJSON(), + autorest.AsGet(), + autorest.WithBaseURL(to.String(uailr.NextLink))) +} + +// UserAssignedIdentitiesListResultPage contains a page of Identity values. +type UserAssignedIdentitiesListResultPage struct { + fn func(UserAssignedIdentitiesListResult) (UserAssignedIdentitiesListResult, error) + uailr UserAssignedIdentitiesListResult +} + +// Next advances to the next page of values. If there was an error making +// the request the page does not advance and the error is returned. +func (page *UserAssignedIdentitiesListResultPage) Next() error { + next, err := page.fn(page.uailr) + if err != nil { + return err + } + page.uailr = next + return nil +} + +// NotDone returns true if the page enumeration should be started or is not yet complete. +func (page UserAssignedIdentitiesListResultPage) NotDone() bool { + return !page.uailr.IsEmpty() +} + +// Response returns the raw server response from the last page request. +func (page UserAssignedIdentitiesListResultPage) Response() UserAssignedIdentitiesListResult { + return page.uailr +} + +// Values returns the slice of values for the current page or nil if there are no values. +func (page UserAssignedIdentitiesListResultPage) Values() []Identity { + if page.uailr.IsEmpty() { + return nil + } + return *page.uailr.Value +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/operations.go b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/operations.go new file mode 100644 index 000000000000..aff009073620 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/operations.go @@ -0,0 +1,126 @@ +package msi + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +import ( + "context" + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "net/http" +) + +// OperationsClient is the the Managed Service Identity Client. +type OperationsClient struct { + BaseClient +} + +// NewOperationsClient creates an instance of the OperationsClient client. +func NewOperationsClient(subscriptionID string) OperationsClient { + return NewOperationsClientWithBaseURI(DefaultBaseURI, subscriptionID) +} + +// NewOperationsClientWithBaseURI creates an instance of the OperationsClient client. +func NewOperationsClientWithBaseURI(baseURI string, subscriptionID string) OperationsClient { + return OperationsClient{NewWithBaseURI(baseURI, subscriptionID)} +} + +// List lists available operations for the Microsoft.ManagedIdentity provider +func (client OperationsClient) List(ctx context.Context) (result OperationListResultPage, err error) { + result.fn = client.listNextResults + req, err := client.ListPreparer(ctx) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.OperationsClient", "List", nil, "Failure preparing request") + return + } + + resp, err := client.ListSender(req) + if err != nil { + result.olr.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "msi.OperationsClient", "List", resp, "Failure sending request") + return + } + + result.olr, err = client.ListResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.OperationsClient", "List", resp, "Failure responding to request") + } + + return +} + +// ListPreparer prepares the List request. +func (client OperationsClient) ListPreparer(ctx context.Context) (*http.Request, error) { + const APIVersion = "2015-08-31-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPath("/providers/Microsoft.ManagedIdentity/operations"), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// ListSender sends the List request. The method will close the +// http.Response Body if it receives an error. +func (client OperationsClient) ListSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...)) +} + +// ListResponder handles the response to the List request. The method always +// closes the http.Response Body. +func (client OperationsClient) ListResponder(resp *http.Response) (result OperationListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// listNextResults retrieves the next set of results, if any. +func (client OperationsClient) listNextResults(lastResults OperationListResult) (result OperationListResult, err error) { + req, err := lastResults.operationListResultPreparer() + if err != nil { + return result, autorest.NewErrorWithError(err, "msi.OperationsClient", "listNextResults", nil, "Failure preparing next results request") + } + if req == nil { + return + } + resp, err := client.ListSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "msi.OperationsClient", "listNextResults", resp, "Failure sending next results request") + } + result, err = client.ListResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.OperationsClient", "listNextResults", resp, "Failure responding to next results request") + } + return +} + +// ListComplete enumerates all values, automatically crossing page boundaries as required. +func (client OperationsClient) ListComplete(ctx context.Context) (result OperationListResultIterator, err error) { + result.page, err = client.List(ctx) + return +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/userassignedidentities.go b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/userassignedidentities.go new file mode 100644 index 000000000000..06c7a49452d2 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/userassignedidentities.go @@ -0,0 +1,496 @@ +package msi + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +import ( + "context" + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "net/http" +) + +// UserAssignedIdentitiesClient is the the Managed Service Identity Client. +type UserAssignedIdentitiesClient struct { + BaseClient +} + +// NewUserAssignedIdentitiesClient creates an instance of the UserAssignedIdentitiesClient client. +func NewUserAssignedIdentitiesClient(subscriptionID string) UserAssignedIdentitiesClient { + return NewUserAssignedIdentitiesClientWithBaseURI(DefaultBaseURI, subscriptionID) +} + +// NewUserAssignedIdentitiesClientWithBaseURI creates an instance of the UserAssignedIdentitiesClient client. +func NewUserAssignedIdentitiesClientWithBaseURI(baseURI string, subscriptionID string) UserAssignedIdentitiesClient { + return UserAssignedIdentitiesClient{NewWithBaseURI(baseURI, subscriptionID)} +} + +// CreateOrUpdate create or update an identity in the specified subscription and resource group. +// Parameters: +// resourceGroupName - the name of the Resource Group to which the identity belongs. +// resourceName - the name of the identity resource. +// parameters - parameters to create or update the identity +func (client UserAssignedIdentitiesClient) CreateOrUpdate(ctx context.Context, resourceGroupName string, resourceName string, parameters Identity) (result Identity, err error) { + req, err := client.CreateOrUpdatePreparer(ctx, resourceGroupName, resourceName, parameters) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "CreateOrUpdate", nil, "Failure preparing request") + return + } + + resp, err := client.CreateOrUpdateSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "CreateOrUpdate", resp, "Failure sending request") + return + } + + result, err = client.CreateOrUpdateResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "CreateOrUpdate", resp, "Failure responding to request") + } + + return +} + +// CreateOrUpdatePreparer prepares the CreateOrUpdate request. +func (client UserAssignedIdentitiesClient) CreateOrUpdatePreparer(ctx context.Context, resourceGroupName string, resourceName string, parameters Identity) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "resourceName": autorest.Encode("path", resourceName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + const APIVersion = "2015-08-31-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsPut(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{resourceName}", pathParameters), + autorest.WithJSON(parameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// CreateOrUpdateSender sends the CreateOrUpdate request. The method will close the +// http.Response Body if it receives an error. +func (client UserAssignedIdentitiesClient) CreateOrUpdateSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// CreateOrUpdateResponder handles the response to the CreateOrUpdate request. The method always +// closes the http.Response Body. +func (client UserAssignedIdentitiesClient) CreateOrUpdateResponder(resp *http.Response) (result Identity, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// Delete deletes the identity. +// Parameters: +// resourceGroupName - the name of the Resource Group to which the identity belongs. +// resourceName - the name of the identity resource. +func (client UserAssignedIdentitiesClient) Delete(ctx context.Context, resourceGroupName string, resourceName string) (result autorest.Response, err error) { + req, err := client.DeletePreparer(ctx, resourceGroupName, resourceName) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "Delete", nil, "Failure preparing request") + return + } + + resp, err := client.DeleteSender(req) + if err != nil { + result.Response = resp + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "Delete", resp, "Failure sending request") + return + } + + result, err = client.DeleteResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "Delete", resp, "Failure responding to request") + } + + return +} + +// DeletePreparer prepares the Delete request. +func (client UserAssignedIdentitiesClient) DeletePreparer(ctx context.Context, resourceGroupName string, resourceName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "resourceName": autorest.Encode("path", resourceName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + const APIVersion = "2015-08-31-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsDelete(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{resourceName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// DeleteSender sends the Delete request. The method will close the +// http.Response Body if it receives an error. +func (client UserAssignedIdentitiesClient) DeleteSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// DeleteResponder handles the response to the Delete request. The method always +// closes the http.Response Body. +func (client UserAssignedIdentitiesClient) DeleteResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusNoContent), + autorest.ByClosing()) + result.Response = resp + return +} + +// Get gets the identity. +// Parameters: +// resourceGroupName - the name of the Resource Group to which the identity belongs. +// resourceName - the name of the identity resource. +func (client UserAssignedIdentitiesClient) Get(ctx context.Context, resourceGroupName string, resourceName string) (result Identity, err error) { + req, err := client.GetPreparer(ctx, resourceGroupName, resourceName) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "Get", nil, "Failure preparing request") + return + } + + resp, err := client.GetSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "Get", resp, "Failure sending request") + return + } + + result, err = client.GetResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "Get", resp, "Failure responding to request") + } + + return +} + +// GetPreparer prepares the Get request. +func (client UserAssignedIdentitiesClient) GetPreparer(ctx context.Context, resourceGroupName string, resourceName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "resourceName": autorest.Encode("path", resourceName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + const APIVersion = "2015-08-31-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{resourceName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// GetSender sends the Get request. The method will close the +// http.Response Body if it receives an error. +func (client UserAssignedIdentitiesClient) GetSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// GetResponder handles the response to the Get request. The method always +// closes the http.Response Body. +func (client UserAssignedIdentitiesClient) GetResponder(resp *http.Response) (result Identity, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListByResourceGroup lists all the userAssignedIdentities available under the specified ResourceGroup. +// Parameters: +// resourceGroupName - the name of the Resource Group to which the identity belongs. +func (client UserAssignedIdentitiesClient) ListByResourceGroup(ctx context.Context, resourceGroupName string) (result UserAssignedIdentitiesListResultPage, err error) { + result.fn = client.listByResourceGroupNextResults + req, err := client.ListByResourceGroupPreparer(ctx, resourceGroupName) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "ListByResourceGroup", nil, "Failure preparing request") + return + } + + resp, err := client.ListByResourceGroupSender(req) + if err != nil { + result.uailr.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "ListByResourceGroup", resp, "Failure sending request") + return + } + + result.uailr, err = client.ListByResourceGroupResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "ListByResourceGroup", resp, "Failure responding to request") + } + + return +} + +// ListByResourceGroupPreparer prepares the ListByResourceGroup request. +func (client UserAssignedIdentitiesClient) ListByResourceGroupPreparer(ctx context.Context, resourceGroupName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + const APIVersion = "2015-08-31-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// ListByResourceGroupSender sends the ListByResourceGroup request. The method will close the +// http.Response Body if it receives an error. +func (client UserAssignedIdentitiesClient) ListByResourceGroupSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// ListByResourceGroupResponder handles the response to the ListByResourceGroup request. The method always +// closes the http.Response Body. +func (client UserAssignedIdentitiesClient) ListByResourceGroupResponder(resp *http.Response) (result UserAssignedIdentitiesListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// listByResourceGroupNextResults retrieves the next set of results, if any. +func (client UserAssignedIdentitiesClient) listByResourceGroupNextResults(lastResults UserAssignedIdentitiesListResult) (result UserAssignedIdentitiesListResult, err error) { + req, err := lastResults.userAssignedIdentitiesListResultPreparer() + if err != nil { + return result, autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "listByResourceGroupNextResults", nil, "Failure preparing next results request") + } + if req == nil { + return + } + resp, err := client.ListByResourceGroupSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "listByResourceGroupNextResults", resp, "Failure sending next results request") + } + result, err = client.ListByResourceGroupResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "listByResourceGroupNextResults", resp, "Failure responding to next results request") + } + return +} + +// ListByResourceGroupComplete enumerates all values, automatically crossing page boundaries as required. +func (client UserAssignedIdentitiesClient) ListByResourceGroupComplete(ctx context.Context, resourceGroupName string) (result UserAssignedIdentitiesListResultIterator, err error) { + result.page, err = client.ListByResourceGroup(ctx, resourceGroupName) + return +} + +// ListBySubscription lists all the userAssignedIdentities available under the specified subscription. +func (client UserAssignedIdentitiesClient) ListBySubscription(ctx context.Context) (result UserAssignedIdentitiesListResultPage, err error) { + result.fn = client.listBySubscriptionNextResults + req, err := client.ListBySubscriptionPreparer(ctx) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "ListBySubscription", nil, "Failure preparing request") + return + } + + resp, err := client.ListBySubscriptionSender(req) + if err != nil { + result.uailr.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "ListBySubscription", resp, "Failure sending request") + return + } + + result.uailr, err = client.ListBySubscriptionResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "ListBySubscription", resp, "Failure responding to request") + } + + return +} + +// ListBySubscriptionPreparer prepares the ListBySubscription request. +func (client UserAssignedIdentitiesClient) ListBySubscriptionPreparer(ctx context.Context) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + const APIVersion = "2015-08-31-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/Microsoft.ManagedIdentity/userAssignedIdentities", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// ListBySubscriptionSender sends the ListBySubscription request. The method will close the +// http.Response Body if it receives an error. +func (client UserAssignedIdentitiesClient) ListBySubscriptionSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// ListBySubscriptionResponder handles the response to the ListBySubscription request. The method always +// closes the http.Response Body. +func (client UserAssignedIdentitiesClient) ListBySubscriptionResponder(resp *http.Response) (result UserAssignedIdentitiesListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// listBySubscriptionNextResults retrieves the next set of results, if any. +func (client UserAssignedIdentitiesClient) listBySubscriptionNextResults(lastResults UserAssignedIdentitiesListResult) (result UserAssignedIdentitiesListResult, err error) { + req, err := lastResults.userAssignedIdentitiesListResultPreparer() + if err != nil { + return result, autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "listBySubscriptionNextResults", nil, "Failure preparing next results request") + } + if req == nil { + return + } + resp, err := client.ListBySubscriptionSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "listBySubscriptionNextResults", resp, "Failure sending next results request") + } + result, err = client.ListBySubscriptionResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "listBySubscriptionNextResults", resp, "Failure responding to next results request") + } + return +} + +// ListBySubscriptionComplete enumerates all values, automatically crossing page boundaries as required. +func (client UserAssignedIdentitiesClient) ListBySubscriptionComplete(ctx context.Context) (result UserAssignedIdentitiesListResultIterator, err error) { + result.page, err = client.ListBySubscription(ctx) + return +} + +// Update update an identity in the specified subscription and resource group. +// Parameters: +// resourceGroupName - the name of the Resource Group to which the identity belongs. +// resourceName - the name of the identity resource. +// parameters - parameters to update the identity +func (client UserAssignedIdentitiesClient) Update(ctx context.Context, resourceGroupName string, resourceName string, parameters Identity) (result Identity, err error) { + req, err := client.UpdatePreparer(ctx, resourceGroupName, resourceName, parameters) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "Update", nil, "Failure preparing request") + return + } + + resp, err := client.UpdateSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "Update", resp, "Failure sending request") + return + } + + result, err = client.UpdateResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "msi.UserAssignedIdentitiesClient", "Update", resp, "Failure responding to request") + } + + return +} + +// UpdatePreparer prepares the Update request. +func (client UserAssignedIdentitiesClient) UpdatePreparer(ctx context.Context, resourceGroupName string, resourceName string, parameters Identity) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "resourceName": autorest.Encode("path", resourceName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + const APIVersion = "2015-08-31-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsPatch(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{resourceName}", pathParameters), + autorest.WithJSON(parameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// UpdateSender sends the Update request. The method will close the +// http.Response Body if it receives an error. +func (client UserAssignedIdentitiesClient) UpdateSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// UpdateResponder handles the response to the Update request. The method always +// closes the http.Response Body. +func (client UserAssignedIdentitiesClient) UpdateResponder(resp *http.Response) (result Identity, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/version.go b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/version.go new file mode 100644 index 000000000000..7d3f9ab50203 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi/version.go @@ -0,0 +1,30 @@ +package msi + +import "github.com/Azure/azure-sdk-for-go/version" + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +// UserAgent returns the UserAgent string to use when sending http.Requests. +func UserAgent() string { + return "Azure-SDK-For-Go/" + version.Number + " msi/2015-08-31-preview" +} + +// Version returns the semantic version (see http://semver.org) of the client. +func Version() string { + return version.Number +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 9b03f265db4b..5306c16b66a7 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -170,6 +170,14 @@ "version": "v16.2.1", "versionExact": "v16.2.1" }, + { + "checksumSHA1": "QT7LrDyti+qDCjhxerYb8PwLfew=", + "path": "github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi", + "revision": "4650843026a7fdec254a8d9cf893693a254edd0b", + "revisionTime": "2018-05-04T19:14:26Z", + "version": "v16.2.1", + "versionExact": "v16.2.1" + }, { "checksumSHA1": "EoE/UnsEm8MDYl+ox/+aIvs1HSY=", "path": "github.com/Azure/azure-sdk-for-go/services/preview/operationalinsights/mgmt/2015-11-01-preview/operationalinsights", From b51366302c761350154cee98a04b226e9241fd81 Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Fri, 22 Jun 2018 19:08:47 -0700 Subject: [PATCH 04/14] Add User Assigned Identity resource --- azurerm/config.go | 8 ++ azurerm/provider.go | 1 + .../resource_arm_user_assigned_identity.go | 111 ++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 azurerm/resource_arm_user_assigned_identity.go diff --git a/azurerm/config.go b/azurerm/config.go index fd23c447d24f..311b44a8f569 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -31,6 +31,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2017-09-01/network" "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-01-01-preview/authorization" + "github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi" "github.com/Azure/azure-sdk-for-go/services/preview/operationalinsights/mgmt/2015-11-01-preview/operationalinsights" "github.com/Azure/azure-sdk-for-go/services/preview/operationsmanagement/mgmt/2015-11-01-preview/operationsmanagement" "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql" @@ -149,6 +150,9 @@ type ArmClient struct { // Monitor monitorAlertRulesClient insights.AlertRulesClient + // MSI + userAssignedIdentitiesClient msi.UserAssignedIdentitiesClient + // Networking applicationGatewayClient network.ApplicationGatewaysClient applicationSecurityGroupsClient network.ApplicationSecurityGroupsClient @@ -793,6 +797,10 @@ func (c *ArmClient) registerNetworkingClients(endpoint, subscriptionId string, a c.configureClient(&subnetsClient.Client, auth) c.subnetClient = subnetsClient + userAssignedIdentitiesClient := msi.NewUserAssignedIdentitiesClientWithBaseURI(endpoint, subscriptionId) + c.configureClient(&userAssignedIdentitiesClient.Client, auth) + c.userAssignedIdentitiesClient = userAssignedIdentitiesClient + watchersClient := network.NewWatchersClientWithBaseURI(endpoint, subscriptionId) c.configureClient(&watchersClient.Client, auth) c.watcherClient = watchersClient diff --git a/azurerm/provider.go b/azurerm/provider.go index d53daabbaf5a..0150e8b157eb 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -223,6 +223,7 @@ func Provider() terraform.ResourceProvider { "azurerm_template_deployment": resourceArmTemplateDeployment(), "azurerm_traffic_manager_endpoint": resourceArmTrafficManagerEndpoint(), "azurerm_traffic_manager_profile": resourceArmTrafficManagerProfile(), + "azurerm_user_assigned_identity": resourceArmUserAssignedIdentity(), "azurerm_virtual_machine_extension": resourceArmVirtualMachineExtensions(), "azurerm_virtual_machine": resourceArmVirtualMachine(), "azurerm_virtual_machine_scale_set": resourceArmVirtualMachineScaleSet(), diff --git a/azurerm/resource_arm_user_assigned_identity.go b/azurerm/resource_arm_user_assigned_identity.go new file mode 100644 index 000000000000..a4ddba19f24e --- /dev/null +++ b/azurerm/resource_arm_user_assigned_identity.go @@ -0,0 +1,111 @@ +package azurerm + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi" + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmUserAssignedIdentity() *schema.Resource { + return &schema.Resource{ + Create: resourceArmUserAssignedIdentityCreate, + Read: resourceArmUserAssignedIdentityRead, + Update: resourceArmUserAssignedIdentityCreate, + Delete: resourceArmUserAssignedIdentityDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "resource_group_name": resourceGroupNameSchema(), + "location": locationSchema(), + "tags": tagsSchema(), + }, + } +} + +func resourceArmUserAssignedIdentityCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).userAssignedIdentitiesClient + ctx := meta.(*ArmClient).StopContext + + log.Printf("[INFO] preparing arguments for Azure ARM user identity creation.") + + resourceName := d.Get("name").(string) + location := d.Get("location").(string) + resGroup := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + identity := msi.Identity{ + Name: &resourceName, + Location: &location, + Tags: expandTags(tags), + } + + identity, err := client.CreateOrUpdate(ctx, resGroup, resourceName, identity) + if err != nil { + return fmt.Errorf("Error Creating/Updating User Assigned Identity %q (Resource Group %q): %+v", resourceName, resGroup, err) + } + + if identity.ID == nil { + return fmt.Errorf("Cannot read User Assigned Identity %q ID (resource group %q) ID", resourceName, resGroup) + } + + d.SetId(*identity.ID) + + return resourceArmUserAssignedIdentityRead(d, meta) +} + +func resourceArmUserAssignedIdentityRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).userAssignedIdentitiesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + resourceName := id.Path["userAssignedIdentities"] + + resp, err := client.Get(ctx, resGroup, resourceName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on User Assigned Identity %q (Resource Group %q): %+v", resourceName, resGroup, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) + d.Set("location", resp.Location) + + flattenAndSetTags(d, resp.Tags) + + return nil +} + +func resourceArmUserAssignedIdentityDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).userAssignedIdentitiesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + resourceName := id.Path["userAssignedIdentities"] + + _, err = client.Delete(ctx, resGroup, resourceName) + if err != nil { + return fmt.Errorf("Error deleting User Assigned Identity %q (Resource Group %q): %+v", resourceName, resGroup, err) + } + + return nil +} From 537e9ff0478b86131b6eb585bff0cf4c55ecd162 Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Mon, 25 Jun 2018 15:22:45 -0700 Subject: [PATCH 05/14] Add User Assigned Identity tests --- ...esource_arm_user_assigned_identity_test.go | 42 +++++ azurerm/resource_arm_virtual_machine.go | 12 +- azurerm/resource_arm_virtual_machine_test.go | 143 ++++++++++++++++-- 3 files changed, 179 insertions(+), 18 deletions(-) create mode 100644 azurerm/resource_arm_user_assigned_identity_test.go diff --git a/azurerm/resource_arm_user_assigned_identity_test.go b/azurerm/resource_arm_user_assigned_identity_test.go new file mode 100644 index 000000000000..64c367e106cf --- /dev/null +++ b/azurerm/resource_arm_user_assigned_identity_test.go @@ -0,0 +1,42 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAzureRMUserAssignedIdentity_create(t *testing.T) { + resourceName := "azurerm_user_assigned_identity.test" + ri := acctest.RandInt() + config := testAccAzureRMUserAssignedIdentityCreateTemplate(ri, testLocation()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualMachineScaleSetDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.TestCheckResourceAttrSet(resourceName, "id"), + }, + }, + }) +} + +func testAccAzureRMUserAssignedIdentityCreateTemplate(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_user_assigned_identity" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + name = "test" +} +`, rInt, location) +} diff --git a/azurerm/resource_arm_virtual_machine.go b/azurerm/resource_arm_virtual_machine.go index 82d4d3fe437f..833e85b06a96 100644 --- a/azurerm/resource_arm_virtual_machine.go +++ b/azurerm/resource_arm_virtual_machine.go @@ -1168,10 +1168,18 @@ func expandAzureRmVirtualMachinePlan(d *schema.ResourceData) (*compute.Plan, err } func expandAzureRmVirtualMachineIdentity(d *schema.ResourceData) *compute.VirtualMachineIdentity { + var identityType compute.ResourceIdentityType + v := d.Get("identity") identities := v.([]interface{}) identity := identities[0].(map[string]interface{}) - identityType := identity["type"].(string) + + switch strings.ToLower(identity["type"].(string)) { + case strings.ToLower(string(compute.ResourceIdentityTypeUserAssigned)): + identityType = compute.ResourceIdentityTypeUserAssigned + case strings.ToLower(string(compute.ResourceIdentityTypeSystemAssigned)): + identityType = compute.ResourceIdentityTypeSystemAssigned + } identityIds := []string{} for _, id := range identity["identity_ids"].([]interface{}) { @@ -1179,7 +1187,7 @@ func expandAzureRmVirtualMachineIdentity(d *schema.ResourceData) *compute.Virtua } vmIdentity := compute.VirtualMachineIdentity{ - Type: compute.ResourceIdentityType(identityType), + Type: identityType, } if vmIdentity.Type == compute.ResourceIdentityTypeUserAssigned { diff --git a/azurerm/resource_arm_virtual_machine_test.go b/azurerm/resource_arm_virtual_machine_test.go index 2781eabc93c3..86479368d80d 100644 --- a/azurerm/resource_arm_virtual_machine_test.go +++ b/azurerm/resource_arm_virtual_machine_test.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "testing" + "regexp" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2017-12-01/compute" "github.com/hashicorp/terraform/helper/acctest" @@ -13,7 +14,6 @@ import ( func TestAccAzureRMVirtualMachine_SystemAssignedIdentity(t *testing.T) { var vm compute.VirtualMachine - resourceName := "azurerm_virtual_machine.test" ri := acctest.RandInt() config := testAccAzureRMVirtualMachineSystemAssignedIdentityTemplate(ri, testLocation()) @@ -26,8 +26,32 @@ func TestAccAzureRMVirtualMachine_SystemAssignedIdentity(t *testing.T) { Config: config, Check: resource.ComposeTestCheckFunc( testCheckAzureRMVirtualMachineExists(resourceName, &vm), - testCheckAzureRMVirtualMachineScaleSetIdentity(resourceName, &vm), - resource.TestCheckResourceAttrSet(resourceName, "identity.0.principal_id"), + resource.TestCheckResourceAttr(resourceName, "identity.0.type", "SystemAssigned"), + resource.TestCheckResourceAttr(resourceName, "identity.0.identity_ids.#", "0"), + resource.TestMatchResourceAttr(resourceName, "identity.0.principal_id", regexp.MustCompile(".+")), + ), + }, + }, + }) +} + +func TestAccAzureRMVirtualMachine_UserAssignedIdentity(t *testing.T) { + var vm compute.VirtualMachine + resourceName := "azurerm_virtual_machine.test" + ri := acctest.RandInt() + config := testAccAzureRMVirtualMachineUserAssignedIdentityTemplate(ri, testLocation()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualMachineScaleSetDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualMachineExists(resourceName, &vm), + resource.TestCheckResourceAttr(resourceName, "identity.0.type", "UserAssigned"), + resource.TestCheckResourceAttr(resourceName, "identity.0.identity_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "identity.0.principal_id", ""), ), }, }, @@ -94,24 +118,103 @@ func testCheckAzureRMVirtualMachineDestroy(s *terraform.State) error { return nil } -func testCheckAzureRMVirtualMachineScaleSetIdentity(name string, vm *compute.VirtualMachine) resource.TestCheckFunc { - return func(s *terraform.State) error { +func testAccAzureRMVirtualMachineSystemAssignedIdentityTemplate(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} - identityType := vm.Identity.Type - if identityType != compute.ResourceIdentityTypeSystemAssigned { - return fmt.Errorf("Bad: Identity Type is not systemAssigned for vm %s.", name) - } +resource "azurerm_virtual_network" "test" { + name = "acctvn-%d" + address_space = ["10.0.0.0/16"] + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} - principalID := *vm.Identity.PrincipalID - if len(principalID) == 0 { - return fmt.Errorf("Bad: Could not get principal_id for vm %s.", name) - } +resource "azurerm_subnet" "test" { + name = "acctsub-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.2.0/24" +} - return nil +resource "azurerm_network_interface" "test" { + name = "acctni-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + ip_configuration { + name = "testconfiguration1" + subnet_id = "${azurerm_subnet.test.id}" + private_ip_address_allocation = "dynamic" } } -func testAccAzureRMVirtualMachineSystemAssignedIdentityTemplate(rInt int, location string) string { +resource "azurerm_storage_account" "test" { + name = "accsa%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_tier = "Standard" + account_replication_type = "LRS" + + tags { + environment = "staging" + } +} + +resource "azurerm_storage_container" "test" { + name = "vhds" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + container_access_type = "private" +} + +resource "azurerm_virtual_machine" "test" { + name = "acctvm-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + network_interface_ids = ["${azurerm_network_interface.test.id}"] + vm_size = "Standard_D1_v2" + + storage_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + + storage_os_disk { + name = "myosdisk1" + vhd_uri = "${azurerm_storage_account.test.primary_blob_endpoint}${azurerm_storage_container.test.name}/myosdisk1.vhd" + caching = "ReadWrite" + create_option = "FromImage" + disk_size_gb = "45" + } + + os_profile { + computer_name = "hn%d" + admin_username = "testadmin" + admin_password = "Password1234!" + } + + os_profile_linux_config { + disable_password_authentication = false + } + + tags { + environment = "Production" + cost-center = "Ops" + } + + identity { + type = "systemAssigned" + } +} +`, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt) +} + +func testAccAzureRMVirtualMachineUserAssignedIdentityTemplate(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" @@ -163,6 +266,13 @@ resource "azurerm_storage_container" "test" { container_access_type = "private" } +resource "azurerm_user_assigned_identity" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + name = "test" +} + resource "azurerm_virtual_machine" "test" { name = "acctvm-%d" location = "${azurerm_resource_group.test.location}" @@ -201,7 +311,8 @@ resource "azurerm_virtual_machine" "test" { } identity { - type = "systemAssigned" + type = "userAssigned" + identity_ids = ["${azurerm_user_assigned_identity.test.id}"] } } `, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt) From add9e0582cd3431361fccdeb7b150f5e1e9d8bda Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Mon, 25 Jun 2018 15:46:31 -0700 Subject: [PATCH 06/14] User assigned identity test for VMSS --- .../resource_arm_virtual_machine_scale_set.go | 12 +- ...urce_arm_virtual_machine_scale_set_test.go | 152 +++++++++++++++--- 2 files changed, 139 insertions(+), 25 deletions(-) diff --git a/azurerm/resource_arm_virtual_machine_scale_set.go b/azurerm/resource_arm_virtual_machine_scale_set.go index 7771115cbe2f..aab8db458588 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set.go +++ b/azurerm/resource_arm_virtual_machine_scale_set.go @@ -1648,10 +1648,18 @@ func expandAzureRMVirtualMachineScaleSetsDiagnosticProfile(d *schema.ResourceDat } func expandAzureRmVirtualMachineScaleSetIdentity(d *schema.ResourceData) *compute.VirtualMachineScaleSetIdentity { + var identityType compute.ResourceIdentityType + v := d.Get("identity") identities := v.([]interface{}) identity := identities[0].(map[string]interface{}) - identityType := identity["type"].(string) + + switch strings.ToLower(identity["type"].(string)) { + case strings.ToLower(string(compute.ResourceIdentityTypeUserAssigned)): + identityType = compute.ResourceIdentityTypeUserAssigned + case strings.ToLower(string(compute.ResourceIdentityTypeSystemAssigned)): + identityType = compute.ResourceIdentityTypeSystemAssigned + } identityIds := []string{} for _, id := range identity["identity_ids"].([]interface{}) { @@ -1659,7 +1667,7 @@ func expandAzureRmVirtualMachineScaleSetIdentity(d *schema.ResourceData) *comput } vmssIdentity := compute.VirtualMachineScaleSetIdentity{ - Type: compute.ResourceIdentityType(identityType), + Type: identityType, } if vmssIdentity.Type == compute.ResourceIdentityTypeUserAssigned { diff --git a/azurerm/resource_arm_virtual_machine_scale_set_test.go b/azurerm/resource_arm_virtual_machine_scale_set_test.go index 765fdbd075bb..572b294b0dcb 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set_test.go +++ b/azurerm/resource_arm_virtual_machine_scale_set_test.go @@ -529,8 +529,32 @@ func TestAccAzureRMVirtualMachineScaleSet_SystemAssignedMSI(t *testing.T) { { Config: config, Check: resource.ComposeTestCheckFunc( - testCheckAzureRMVirtualMachineScaleSetSystemAssignedMSI(resourceName), - resource.TestCheckResourceAttrSet(resourceName, "identity.0.principal_id"), + testCheckAzureRMVirtualMachineScaleSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "identity.0.type", "SystemAssigned"), + resource.TestCheckResourceAttr(resourceName, "identity.0.identity_ids.#", "0"), + resource.TestMatchResourceAttr(resourceName, "identity.0.principal_id", regexp.MustCompile(".+")), + ), + }, + }, + }) +} + +func TestAccAzureRMVirtualMachineScaleSet_UserAssignedMSI(t *testing.T) { + resourceName := "azurerm_virtual_machine_scale_set.test" + ri := acctest.RandInt() + config := testAccAzureRMVirtualMachineScaleSetUserAssignedMSITemplate(ri, testLocation()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualMachineScaleSetDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualMachineScaleSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "identity.0.type", "UserAssigned"), + resource.TestCheckResourceAttr(resourceName, "identity.0.identity_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "identity.0.principal_id", ""), ), }, }, @@ -914,27 +938,6 @@ func testCheckAzureRMVirtualMachineScaleSetSinglePlacementGroup(name string, exp } } -func testCheckAzureRMVirtualMachineScaleSetSystemAssignedMSI(name string) resource.TestCheckFunc { - return func(s *terraform.State) error { - resp, err := testGetAzureRMVirtualMachineScaleSet(s, name) - if err != nil { - return err - } - - identityType := resp.Identity.Type - if identityType != compute.ResourceIdentityTypeSystemAssigned { - return fmt.Errorf("Bad: Identity Type is not systemAssigned for scale set %v", name) - } - - principalID := *resp.Identity.PrincipalID - if len(principalID) == 0 { - return fmt.Errorf("Bad: Could not get principal_id for scale set %v", name) - } - - return nil - } -} - func testCheckAzureRMVirtualMachineScaleSetExtension(name string) resource.TestCheckFunc { return func(s *terraform.State) error { resp, err := testGetAzureRMVirtualMachineScaleSet(s, name) @@ -2974,6 +2977,109 @@ resource "azurerm_virtual_machine_scale_set" "test" { `, rInt, location) } +func testAccAzureRMVirtualMachineScaleSetUserAssignedMSITemplate(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestrg-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctvn-%[1]d" + address_space = ["10.0.0.0/16"] + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_subnet" "test" { + name = "acctsub-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.2.0/24" +} + +resource "azurerm_storage_account" "test" { + name = "accsa%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "test" { + name = "vhds" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + container_access_type = "private" +} + +resource "azurerm_user_assigned_identity" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + name = "test" +} + +resource "azurerm_virtual_machine_scale_set" "test" { + name = "acctvmss-%[1]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + upgrade_policy_mode = "Manual" + overprovision = false + + sku { + name = "Standard_D1_v2" + tier = "Standard" + capacity = 1 + } + + identity { + type = "userAssigned" + identity_ids = ["${azurerm_user_assigned_identity.test.id}"] + } + + extension { + name = "MSILinuxExtension" + publisher = "Microsoft.ManagedIdentity" + type = "ManagedIdentityExtensionForLinux" + type_handler_version = "1.0" + settings = "{\"port\": 50342}" + } + + os_profile { + computer_name_prefix = "testvm-%[1]d" + admin_username = "myadmin" + admin_password = "Passwword1234" + } + + network_profile { + name = "TestNetworkProfile" + primary = true + + ip_configuration { + name = "TestIPConfiguration" + subnet_id = "${azurerm_subnet.test.id}" + } + } + + storage_profile_os_disk { + name = "os-disk" + caching = "ReadWrite" + create_option = "FromImage" + vhd_containers = ["${azurerm_storage_account.test.primary_blob_endpoint}${azurerm_storage_container.test.name}"] + } + + storage_profile_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } +} + +`, rInt, location) +} + func testAccAzureRMVirtualMachineScaleSetExtensionTemplate(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { From 35dcf6e7e4036a79c392f70d38a9d3b57a89474e Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Mon, 25 Jun 2018 16:59:04 -0700 Subject: [PATCH 07/14] Add principal_id for user assigned identity --- .../resource_arm_user_assigned_identity.go | 19 +++++++++++++++---- ...esource_arm_user_assigned_identity_test.go | 8 +++++++- azurerm/resource_arm_virtual_machine_test.go | 2 +- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/azurerm/resource_arm_user_assigned_identity.go b/azurerm/resource_arm_user_assigned_identity.go index a4ddba19f24e..ea941a48bf4b 100644 --- a/azurerm/resource_arm_user_assigned_identity.go +++ b/azurerm/resource_arm_user_assigned_identity.go @@ -20,14 +20,20 @@ func resourceArmUserAssignedIdentity() *schema.Resource { }, Schema: map[string]*schema.Schema{ + "resource_group_name": resourceGroupNameSchema(), + "location": locationSchema(), + "name": { Type: schema.TypeString, Required: true, }, - "resource_group_name": resourceGroupNameSchema(), - "location": locationSchema(), - "tags": tagsSchema(), + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsSchema(), }, } } @@ -82,9 +88,14 @@ func resourceArmUserAssignedIdentityRead(d *schema.ResourceData, meta interface{ return fmt.Errorf("Error making Read request on User Assigned Identity %q (Resource Group %q): %+v", resourceName, resGroup, err) } - d.Set("name", resp.Name) + if resp.IdentityProperties == nil || resp.IdentityProperties.PrincipalID == nil { + return fmt.Errorf("Error PrincipalID can't be null for User Assigned Identity %q (Resource Group %q): %+v", resourceName, resGroup, err) + } + d.Set("resource_group_name", resGroup) d.Set("location", resp.Location) + d.Set("name", resp.Name) + d.Set("principal_id", resp.IdentityProperties.PrincipalID.String()) flattenAndSetTags(d, resp.Tags) diff --git a/azurerm/resource_arm_user_assigned_identity_test.go b/azurerm/resource_arm_user_assigned_identity_test.go index 64c367e106cf..245036a9e9db 100644 --- a/azurerm/resource_arm_user_assigned_identity_test.go +++ b/azurerm/resource_arm_user_assigned_identity_test.go @@ -2,6 +2,7 @@ package azurerm import ( "fmt" + "regexp" "testing" "github.com/hashicorp/terraform/helper/acctest" @@ -9,6 +10,8 @@ import ( ) func TestAccAzureRMUserAssignedIdentity_create(t *testing.T) { + resourceIdRegex := "/subscriptions/.+/resourcegroups/.+/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test" + principalIdRegex := "^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$" resourceName := "azurerm_user_assigned_identity.test" ri := acctest.RandInt() config := testAccAzureRMUserAssignedIdentityCreateTemplate(ri, testLocation()) @@ -19,7 +22,10 @@ func TestAccAzureRMUserAssignedIdentity_create(t *testing.T) { Steps: []resource.TestStep{ { Config: config, - Check: resource.TestCheckResourceAttrSet(resourceName, "id"), + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr(resourceName, "id", regexp.MustCompile(resourceIdRegex)), + resource.TestMatchResourceAttr(resourceName, "principal_id", regexp.MustCompile(principalIdRegex)), + ), }, }, }) diff --git a/azurerm/resource_arm_virtual_machine_test.go b/azurerm/resource_arm_virtual_machine_test.go index 86479368d80d..f7a08203849f 100644 --- a/azurerm/resource_arm_virtual_machine_test.go +++ b/azurerm/resource_arm_virtual_machine_test.go @@ -3,8 +3,8 @@ package azurerm import ( "fmt" "net/http" - "testing" "regexp" + "testing" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2017-12-01/compute" "github.com/hashicorp/terraform/helper/acctest" From 9bcaf990205b8f58c80fbd1188caf7de241e3a9f Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Mon, 25 Jun 2018 18:25:01 -0700 Subject: [PATCH 08/14] Update docs --- .../docs/d/user_assigned_identity.markdown | 64 +++++++++++++++++++ website/docs/r/virtual_machine.html.markdown | 4 +- .../r/virtual_machine_scale_set.html.markdown | 4 +- 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 website/docs/d/user_assigned_identity.markdown diff --git a/website/docs/d/user_assigned_identity.markdown b/website/docs/d/user_assigned_identity.markdown new file mode 100644 index 000000000000..5305cdccb59e --- /dev/null +++ b/website/docs/d/user_assigned_identity.markdown @@ -0,0 +1,64 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azure_user_assigned_identity" +sidebar_current: "docs-azurerm-resource-user-assigned-identity" +description: |- + Manages a new user assigned identity. +--- + +# azurerm_user_assigned_identity + +Manages a user assigned identity. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "test" { + name = "acceptanceTestResourceGroup1" + location = "eastus" +} + +resource "azurerm_user_assigned_identity" "testIdentity" { + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + name = "${var.name}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the user assigned identity. Changing this forces a + new identity to be created. + +* `resource_group_name` - (Required) The name of the resource group in which to + create the user assigned identity. + +* `location` - (Required) The location/region where the user assigned identity is + created. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The user assigned identity ID. + +* `name` - The name of the user assigned identity. + +* `resource_group_name` - The name of the resource group in which user assigned identity created. + +* `location` - The location/region where the user assigned identity created. + +* `principal_id` - Service Principal ID associated with the user assigned identity. + +## Import + +User Assigned Identitites can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_user_assigned_identity.testIdentity /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/acceptanceTestResourceGroup1/providers/Microsoft.ManagedIdentity/userAssignedIdentities/testIdentity +``` diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index 2a636ecc1d53..64b8ae41f864 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -441,7 +441,9 @@ resource "azurerm_virtual_machine" "test" { `identity` supports the following: -* `type` - (Required) Specifies the identity type of the virtual machine. The only allowable value is `SystemAssigned`. To enable Managed Service Identity the virtual machine extension "ManagedIdentityExtensionForWindows" or "ManagedIdentityExtensionForLinux" must also be added to the virtual machine. The Principal ID can be retrieved after the virtual machine has been created, e.g. +* `type` - (Required) Specifies the identity type of the virtual machine. Allowable values are `SystemAssigned` and `UserAssigned`. To enable Managed Service Identity the virtual machine extension "ManagedIdentityExtensionForWindows" or "ManagedIdentityExtensionForLinux" must also be added to the virtual machine. For the `SystemAssigned` identity the Principal ID can be retrieved after the virtual machine has been created. + +* `identities_ids` - (Optional) Specifies a list of user managed identity ids to be assigned to the VM. Required if `type` is `UserAssigned`. ```hcl resource "azurerm_virtual_machine" "test" { diff --git a/website/docs/r/virtual_machine_scale_set.html.markdown b/website/docs/r/virtual_machine_scale_set.html.markdown index 25a541c86046..d87cab947dfb 100644 --- a/website/docs/r/virtual_machine_scale_set.html.markdown +++ b/website/docs/r/virtual_machine_scale_set.html.markdown @@ -274,7 +274,9 @@ The following arguments are supported: `identity` supports the following: -* `type` - (Required) Specifies the identity type to be assigned to the scale set. The only allowable value is `SystemAssigned`. To enable Managed Service Identity (MSI) on all machines in the scale set, an extension with the type "ManagedIdentityExtensionForWindows" or "ManagedIdentityExtensionForLinux" must also be added. The scale set's Service Principal ID (SPN) can be retrieved after the scale set has been created. +* `type` - (Required) Specifies the identity type to be assigned to the scale set. Allowable values are `SystemAssigned` and `UserAssigned`. To enable Managed Service Identity (MSI) on all machines in the scale set, an extension with the type "ManagedIdentityExtensionForWindows" or "ManagedIdentityExtensionForLinux" must also be added. For the `SystemAssigned` identity the scale set's Service Principal ID (SPN) can be retrieved after the scale set has been created. + +* `identities_ids` - (Optional) Specifies a list of user managed identity ids to be assigned to the VMSS. Required if `type` is `UserAssigned`. ```hcl resource "azurerm_virtual_machine_scale_set" "test" { From fb8fb21507c79c227a5c60390838489ebfdf7c2e Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Mon, 25 Jun 2018 18:30:16 -0700 Subject: [PATCH 09/14] Fix docs typos & move docs to the right place --- website/docs/{d => r}/user_assigned_identity.markdown | 0 website/docs/r/virtual_machine.html.markdown | 2 +- website/docs/r/virtual_machine_scale_set.html.markdown | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename website/docs/{d => r}/user_assigned_identity.markdown (100%) diff --git a/website/docs/d/user_assigned_identity.markdown b/website/docs/r/user_assigned_identity.markdown similarity index 100% rename from website/docs/d/user_assigned_identity.markdown rename to website/docs/r/user_assigned_identity.markdown diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index 64b8ae41f864..661f89eae99f 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -443,7 +443,7 @@ resource "azurerm_virtual_machine" "test" { * `type` - (Required) Specifies the identity type of the virtual machine. Allowable values are `SystemAssigned` and `UserAssigned`. To enable Managed Service Identity the virtual machine extension "ManagedIdentityExtensionForWindows" or "ManagedIdentityExtensionForLinux" must also be added to the virtual machine. For the `SystemAssigned` identity the Principal ID can be retrieved after the virtual machine has been created. -* `identities_ids` - (Optional) Specifies a list of user managed identity ids to be assigned to the VM. Required if `type` is `UserAssigned`. +* `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned to the VM. Required if `type` is `UserAssigned`. ```hcl resource "azurerm_virtual_machine" "test" { diff --git a/website/docs/r/virtual_machine_scale_set.html.markdown b/website/docs/r/virtual_machine_scale_set.html.markdown index d87cab947dfb..311860462766 100644 --- a/website/docs/r/virtual_machine_scale_set.html.markdown +++ b/website/docs/r/virtual_machine_scale_set.html.markdown @@ -276,7 +276,7 @@ The following arguments are supported: * `type` - (Required) Specifies the identity type to be assigned to the scale set. Allowable values are `SystemAssigned` and `UserAssigned`. To enable Managed Service Identity (MSI) on all machines in the scale set, an extension with the type "ManagedIdentityExtensionForWindows" or "ManagedIdentityExtensionForLinux" must also be added. For the `SystemAssigned` identity the scale set's Service Principal ID (SPN) can be retrieved after the scale set has been created. -* `identities_ids` - (Optional) Specifies a list of user managed identity ids to be assigned to the VMSS. Required if `type` is `UserAssigned`. +* `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned to the VMSS. Required if `type` is `UserAssigned`. ```hcl resource "azurerm_virtual_machine_scale_set" "test" { From edb0bdef151804612630db103be94cd2765919dd Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Tue, 26 Jun 2018 13:22:05 -0700 Subject: [PATCH 10/14] Add link to ms docs --- website/docs/r/virtual_machine.html.markdown | 2 +- website/docs/r/virtual_machine_scale_set.html.markdown | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index 661f89eae99f..b5dedc3d2b6e 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -441,7 +441,7 @@ resource "azurerm_virtual_machine" "test" { `identity` supports the following: -* `type` - (Required) Specifies the identity type of the virtual machine. Allowable values are `SystemAssigned` and `UserAssigned`. To enable Managed Service Identity the virtual machine extension "ManagedIdentityExtensionForWindows" or "ManagedIdentityExtensionForLinux" must also be added to the virtual machine. For the `SystemAssigned` identity the Principal ID can be retrieved after the virtual machine has been created. +* `type` - (Required) Specifies the identity type of the virtual machine. Allowable values are `SystemAssigned` and `UserAssigned`. To enable Managed Service Identity the virtual machine extension "ManagedIdentityExtensionForWindows" or "ManagedIdentityExtensionForLinux" must also be added to the virtual machine. For the `SystemAssigned` identity the Principal ID can be retrieved after the virtual machine has been created. See [documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview) for more information. * `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned to the VM. Required if `type` is `UserAssigned`. diff --git a/website/docs/r/virtual_machine_scale_set.html.markdown b/website/docs/r/virtual_machine_scale_set.html.markdown index 311860462766..55e925192f1a 100644 --- a/website/docs/r/virtual_machine_scale_set.html.markdown +++ b/website/docs/r/virtual_machine_scale_set.html.markdown @@ -274,7 +274,7 @@ The following arguments are supported: `identity` supports the following: -* `type` - (Required) Specifies the identity type to be assigned to the scale set. Allowable values are `SystemAssigned` and `UserAssigned`. To enable Managed Service Identity (MSI) on all machines in the scale set, an extension with the type "ManagedIdentityExtensionForWindows" or "ManagedIdentityExtensionForLinux" must also be added. For the `SystemAssigned` identity the scale set's Service Principal ID (SPN) can be retrieved after the scale set has been created. +* `type` - (Required) Specifies the identity type to be assigned to the scale set. Allowable values are `SystemAssigned` and `UserAssigned`. To enable Managed Service Identity (MSI) on all machines in the scale set, an extension with the type "ManagedIdentityExtensionForWindows" or "ManagedIdentityExtensionForLinux" must also be added. For the `SystemAssigned` identity the scale set's Service Principal ID (SPN) can be retrieved after the scale set has been created. See [documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview) for more information. * `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned to the VMSS. Required if `type` is `UserAssigned`. From 81fa080df1f54fc2ed9602e976996b8fe1d7ed84 Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Fri, 29 Jun 2018 12:07:46 -0700 Subject: [PATCH 11/14] Address review feedback --- azurerm/provider.go | 1 + azurerm/resource_arm_user_assigned_identity.go | 12 +++++++++--- .../resource_arm_user_assigned_identity_test.go | 16 ++++++---------- azurerm/resource_arm_virtual_machine.go | 4 ++-- .../resource_arm_virtual_machine_scale_set.go | 4 ++-- ...esource_arm_virtual_machine_scale_set_test.go | 10 +++++----- azurerm/resource_arm_virtual_machine_test.go | 12 ++++++------ website/docs/r/user_assigned_identity.markdown | 8 +------- 8 files changed, 32 insertions(+), 35 deletions(-) diff --git a/azurerm/provider.go b/azurerm/provider.go index 0150e8b157eb..1fe0b4825106 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -347,6 +347,7 @@ func determineAzureResourceProvidersToRegister(providerList []resources.Provider "Microsoft.EventHub": {}, "Microsoft.KeyVault": {}, "microsoft.insights": {}, + "Microsoft.ManagedIdentity": {}, "Microsoft.Network": {}, "Microsoft.OperationalInsights": {}, "Microsoft.Relay": {}, diff --git a/azurerm/resource_arm_user_assigned_identity.go b/azurerm/resource_arm_user_assigned_identity.go index ea941a48bf4b..e16cf3e90ffc 100644 --- a/azurerm/resource_arm_user_assigned_identity.go +++ b/azurerm/resource_arm_user_assigned_identity.go @@ -26,6 +26,7 @@ func resourceArmUserAssignedIdentity() *schema.Resource { "name": { Type: schema.TypeString, Required: true, + ForceNew: true, }, "principal_id": { @@ -54,16 +55,21 @@ func resourceArmUserAssignedIdentityCreate(d *schema.ResourceData, meta interfac Tags: expandTags(tags), } - identity, err := client.CreateOrUpdate(ctx, resGroup, resourceName, identity) + _, err := client.CreateOrUpdate(ctx, resGroup, resourceName, identity) if err != nil { return fmt.Errorf("Error Creating/Updating User Assigned Identity %q (Resource Group %q): %+v", resourceName, resGroup, err) } - if identity.ID == nil { + read, err := client.Get(ctx, resGroup, resourceName) + if err != nil { + return err + } + + if read.ID == nil { return fmt.Errorf("Cannot read User Assigned Identity %q ID (resource group %q) ID", resourceName, resGroup) } - d.SetId(*identity.ID) + d.SetId(*read.ID) return resourceArmUserAssignedIdentityRead(d, meta) } diff --git a/azurerm/resource_arm_user_assigned_identity_test.go b/azurerm/resource_arm_user_assigned_identity_test.go index 245036a9e9db..91f03ed16392 100644 --- a/azurerm/resource_arm_user_assigned_identity_test.go +++ b/azurerm/resource_arm_user_assigned_identity_test.go @@ -10,11 +10,10 @@ import ( ) func TestAccAzureRMUserAssignedIdentity_create(t *testing.T) { - resourceIdRegex := "/subscriptions/.+/resourcegroups/.+/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test" principalIdRegex := "^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$" resourceName := "azurerm_user_assigned_identity.test" ri := acctest.RandInt() - config := testAccAzureRMUserAssignedIdentityCreateTemplate(ri, testLocation()) + config := testAccAzureRMUserAssignedIdentityCreate(ri, testLocation()) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -22,27 +21,24 @@ func TestAccAzureRMUserAssignedIdentity_create(t *testing.T) { Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestMatchResourceAttr(resourceName, "id", regexp.MustCompile(resourceIdRegex)), - resource.TestMatchResourceAttr(resourceName, "principal_id", regexp.MustCompile(principalIdRegex)), - ), + Check: resource.TestMatchResourceAttr(resourceName, "principal_id", regexp.MustCompile(principalIdRegex)), }, }, }) } -func testAccAzureRMUserAssignedIdentityCreateTemplate(rInt int, location string) string { +func testAccAzureRMUserAssignedIdentityCreate(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" + name = "acctestRG-acctest%[1]d" + location = "%[2]s" } resource "azurerm_user_assigned_identity" "test" { resource_group_name = "${azurerm_resource_group.test.name}" location = "${azurerm_resource_group.test.location}" - name = "test" + name = "acctest%[1]d" } `, rInt, location) } diff --git a/azurerm/resource_arm_virtual_machine.go b/azurerm/resource_arm_virtual_machine.go index 269423a0dd9e..429a1bd2f42d 100644 --- a/azurerm/resource_arm_virtual_machine.go +++ b/azurerm/resource_arm_virtual_machine.go @@ -948,13 +948,13 @@ func flattenAzureRmVirtualMachineIdentity(identity *compute.VirtualMachineIdenti result["principal_id"] = *identity.PrincipalID } + identity_ids := make([]string, 0) if identity.IdentityIds != nil { - identity_ids := make([]string, 0) for _, id := range *identity.IdentityIds { identity_ids = append(identity_ids, id) } - result["identity_ids"] = identity_ids } + result["identity_ids"] = identity_ids return []interface{}{result} } diff --git a/azurerm/resource_arm_virtual_machine_scale_set.go b/azurerm/resource_arm_virtual_machine_scale_set.go index aab8db458588..2c1d62673b97 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set.go +++ b/azurerm/resource_arm_virtual_machine_scale_set.go @@ -940,13 +940,13 @@ func flattenAzureRmVirtualMachineScaleSetIdentity(identity *compute.VirtualMachi result["principal_id"] = *identity.PrincipalID } + identity_ids := make([]string, 0) if identity.IdentityIds != nil { - identity_ids := make([]string, 0) for _, id := range *identity.IdentityIds { identity_ids = append(identity_ids, id) } - result["identity_ids"] = identity_ids } + result["identity_ids"] = identity_ids return []interface{}{result} } diff --git a/azurerm/resource_arm_virtual_machine_scale_set_test.go b/azurerm/resource_arm_virtual_machine_scale_set_test.go index aa4cc69c62e8..fa4b85b620df 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set_test.go +++ b/azurerm/resource_arm_virtual_machine_scale_set_test.go @@ -520,7 +520,7 @@ func TestAccAzureRMVirtualMachineScaleSet_priority(t *testing.T) { func TestAccAzureRMVirtualMachineScaleSet_SystemAssignedMSI(t *testing.T) { resourceName := "azurerm_virtual_machine_scale_set.test" ri := acctest.RandInt() - config := testAccAzureRMVirtualMachineScaleSetSystemAssignedMSITemplate(ri, testLocation()) + config := testAccAzureRMVirtualMachineScaleSetSystemAssignedMSI(ri, testLocation()) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -542,7 +542,7 @@ func TestAccAzureRMVirtualMachineScaleSet_SystemAssignedMSI(t *testing.T) { func TestAccAzureRMVirtualMachineScaleSet_UserAssignedMSI(t *testing.T) { resourceName := "azurerm_virtual_machine_scale_set.test" ri := acctest.RandInt() - config := testAccAzureRMVirtualMachineScaleSetUserAssignedMSITemplate(ri, testLocation()) + config := testAccAzureRMVirtualMachineScaleSetUserAssignedMSI(ri, testLocation()) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -2882,7 +2882,7 @@ resource "azurerm_virtual_machine_scale_set" "test" { `, rInt, location) } -func testAccAzureRMVirtualMachineScaleSetSystemAssignedMSITemplate(rInt int, location string) string { +func testAccAzureRMVirtualMachineScaleSetSystemAssignedMSI(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%[1]d" @@ -2977,7 +2977,7 @@ resource "azurerm_virtual_machine_scale_set" "test" { `, rInt, location) } -func testAccAzureRMVirtualMachineScaleSetUserAssignedMSITemplate(rInt int, location string) string { +func testAccAzureRMVirtualMachineScaleSetUserAssignedMSI(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestrg-%[1]d" @@ -3017,7 +3017,7 @@ resource "azurerm_user_assigned_identity" "test" { resource_group_name = "${azurerm_resource_group.test.name}" location = "${azurerm_resource_group.test.location}" - name = "test" + name = "acctest%[1]d" } resource "azurerm_virtual_machine_scale_set" "test" { diff --git a/azurerm/resource_arm_virtual_machine_test.go b/azurerm/resource_arm_virtual_machine_test.go index 268a245a0e51..3cea33bd5f0a 100644 --- a/azurerm/resource_arm_virtual_machine_test.go +++ b/azurerm/resource_arm_virtual_machine_test.go @@ -37,7 +37,7 @@ func TestAccAzureRMVirtualMachine_SystemAssignedIdentity(t *testing.T) { var vm compute.VirtualMachine resourceName := "azurerm_virtual_machine.test" ri := acctest.RandInt() - config := testAccAzureRMVirtualMachineSystemAssignedIdentityTemplate(ri, testLocation()) + config := testAccAzureRMVirtualMachineSystemAssignedIdentity(ri, testLocation()) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -60,7 +60,7 @@ func TestAccAzureRMVirtualMachine_UserAssignedIdentity(t *testing.T) { var vm compute.VirtualMachine resourceName := "azurerm_virtual_machine.test" ri := acctest.RandInt() - config := testAccAzureRMVirtualMachineUserAssignedIdentityTemplate(ri, testLocation()) + config := testAccAzureRMVirtualMachineUserAssignedIdentity(ri, testLocation()) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -221,7 +221,7 @@ resource "azurerm_virtual_machine" "test" { `, rInt, location, rInt, rInt, rInt, rInt, rInt) } -func testAccAzureRMVirtualMachineSystemAssignedIdentityTemplate(rInt int, location string) string { +func testAccAzureRMVirtualMachineSystemAssignedIdentity(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" @@ -317,7 +317,7 @@ resource "azurerm_virtual_machine" "test" { `, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt) } -func testAccAzureRMVirtualMachineUserAssignedIdentityTemplate(rInt int, location string) string { +func testAccAzureRMVirtualMachineUserAssignedIdentity(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" @@ -373,7 +373,7 @@ resource "azurerm_user_assigned_identity" "test" { resource_group_name = "${azurerm_resource_group.test.name}" location = "${azurerm_resource_group.test.location}" - name = "test" + name = "acctest%d" } resource "azurerm_virtual_machine" "test" { @@ -418,5 +418,5 @@ resource "azurerm_virtual_machine" "test" { identity_ids = ["${azurerm_user_assigned_identity.test.id}"] } } -`, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt) +`, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt, rInt) } diff --git a/website/docs/r/user_assigned_identity.markdown b/website/docs/r/user_assigned_identity.markdown index 5305cdccb59e..9358cbac4530 100644 --- a/website/docs/r/user_assigned_identity.markdown +++ b/website/docs/r/user_assigned_identity.markdown @@ -22,7 +22,7 @@ resource "azurerm_user_assigned_identity" "testIdentity" { resource_group_name = "${azurerm_resource_group.test.name}" location = "${azurerm_resource_group.test.location}" - name = "${var.name}" + name = "search-api" } ``` @@ -47,12 +47,6 @@ The following attributes are exported: * `id` - The user assigned identity ID. -* `name` - The name of the user assigned identity. - -* `resource_group_name` - The name of the resource group in which user assigned identity created. - -* `location` - The location/region where the user assigned identity created. - * `principal_id` - Service Principal ID associated with the user assigned identity. ## Import From d3c8a5f02e5380a0573d42f07fc5f074727cdb7f Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Fri, 29 Jun 2018 13:14:26 -0700 Subject: [PATCH 12/14] Update tests to limit UAI name to 24 symbols --- azurerm/resource_arm_user_assigned_identity.go | 8 +++++--- azurerm/resource_arm_virtual_machine_scale_set_test.go | 9 +++++---- azurerm/resource_arm_virtual_machine_test.go | 9 +++++---- run_tests.sh | 7 +++++++ 4 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 run_tests.sh diff --git a/azurerm/resource_arm_user_assigned_identity.go b/azurerm/resource_arm_user_assigned_identity.go index e16cf3e90ffc..e1e26ce9346d 100644 --- a/azurerm/resource_arm_user_assigned_identity.go +++ b/azurerm/resource_arm_user_assigned_identity.go @@ -6,6 +6,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -24,9 +25,10 @@ func resourceArmUserAssignedIdentity() *schema.Resource { "location": locationSchema(), "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 24), }, "principal_id": { diff --git a/azurerm/resource_arm_virtual_machine_scale_set_test.go b/azurerm/resource_arm_virtual_machine_scale_set_test.go index fa4b85b620df..2b22f37ddb04 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set_test.go +++ b/azurerm/resource_arm_virtual_machine_scale_set_test.go @@ -542,7 +542,8 @@ func TestAccAzureRMVirtualMachineScaleSet_SystemAssignedMSI(t *testing.T) { func TestAccAzureRMVirtualMachineScaleSet_UserAssignedMSI(t *testing.T) { resourceName := "azurerm_virtual_machine_scale_set.test" ri := acctest.RandInt() - config := testAccAzureRMVirtualMachineScaleSetUserAssignedMSI(ri, testLocation()) + rs := acctest.RandString(14) + config := testAccAzureRMVirtualMachineScaleSetUserAssignedMSI(ri, testLocation(), rs) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -2977,7 +2978,7 @@ resource "azurerm_virtual_machine_scale_set" "test" { `, rInt, location) } -func testAccAzureRMVirtualMachineScaleSetUserAssignedMSI(rInt int, location string) string { +func testAccAzureRMVirtualMachineScaleSetUserAssignedMSI(rInt int, location string, rString string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestrg-%[1]d" @@ -3017,7 +3018,7 @@ resource "azurerm_user_assigned_identity" "test" { resource_group_name = "${azurerm_resource_group.test.name}" location = "${azurerm_resource_group.test.location}" - name = "acctest%[1]d" + name = "acctest%[3]s" } resource "azurerm_virtual_machine_scale_set" "test" { @@ -3077,7 +3078,7 @@ resource "azurerm_virtual_machine_scale_set" "test" { } } -`, rInt, location) +`, rInt, location, rString) } func testAccAzureRMVirtualMachineScaleSetExtensionTemplate(rInt int, location string) string { diff --git a/azurerm/resource_arm_virtual_machine_test.go b/azurerm/resource_arm_virtual_machine_test.go index 3cea33bd5f0a..fbdc159be73d 100644 --- a/azurerm/resource_arm_virtual_machine_test.go +++ b/azurerm/resource_arm_virtual_machine_test.go @@ -60,7 +60,8 @@ func TestAccAzureRMVirtualMachine_UserAssignedIdentity(t *testing.T) { var vm compute.VirtualMachine resourceName := "azurerm_virtual_machine.test" ri := acctest.RandInt() - config := testAccAzureRMVirtualMachineUserAssignedIdentity(ri, testLocation()) + rs := acctest.RandString(15) + config := testAccAzureRMVirtualMachineUserAssignedIdentity(ri, testLocation(), rs) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -317,7 +318,7 @@ resource "azurerm_virtual_machine" "test" { `, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt) } -func testAccAzureRMVirtualMachineUserAssignedIdentity(rInt int, location string) string { +func testAccAzureRMVirtualMachineUserAssignedIdentity(rInt int, location string, rString string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" @@ -373,7 +374,7 @@ resource "azurerm_user_assigned_identity" "test" { resource_group_name = "${azurerm_resource_group.test.name}" location = "${azurerm_resource_group.test.location}" - name = "acctest%d" + name = "acctest%s" } resource "azurerm_virtual_machine" "test" { @@ -418,5 +419,5 @@ resource "azurerm_virtual_machine" "test" { identity_ids = ["${azurerm_user_assigned_identity.test.id}"] } } -`, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt, rInt) +`, rInt, location, rInt, rInt, rInt, rInt, rString, rInt, rInt) } diff --git a/run_tests.sh b/run_tests.sh new file mode 100644 index 000000000000..9717b753af1a --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,7 @@ +TF_ACC=1 go test github.com/terraform-providers/terraform-provider-azurerm/azurerm -timeout 180m -v -run TestAccAzureRMVirtualMachineScaleSet_UserAssignedMSI & +TF_ACC=1 go test github.com/terraform-providers/terraform-provider-azurerm/azurerm -timeout 180m -v -run TestAccAzureRMVirtualMachineScaleSet_SystemAssignedMSI & +TF_ACC=1 go test github.com/terraform-providers/terraform-provider-azurerm/azurerm -timeout 180m -v -run TestAccAzureRMVirtualMachine_UserAssignedIdentity & +TF_ACC=1 go test github.com/terraform-providers/terraform-provider-azurerm/azurerm -timeout 180m -v -run TestAccAzureRMVirtualMachine_SystemAssignedIdentity & +TF_ACC=1 go test github.com/terraform-providers/terraform-provider-azurerm/azurerm -timeout 180m -v -run TestAccAzureRMUserAssignedIdentity_create & +wait + From 7540fd8b194490c75c1ca9bdd4af90ab86306425 Mon Sep 17 00:00:00 2001 From: Kirill Logachev Date: Fri, 29 Jun 2018 14:13:44 -0700 Subject: [PATCH 13/14] More review feedback --- .../resource_arm_user_assigned_identity_test.go | 9 +++++---- azurerm/resource_arm_virtual_machine.go | 16 ++++------------ .../resource_arm_virtual_machine_scale_set.go | 16 ++++------------ ...esource_arm_virtual_machine_scale_set_test.go | 4 ++-- azurerm/resource_arm_virtual_machine_test.go | 6 +++--- run_tests.sh | 7 ------- 6 files changed, 18 insertions(+), 40 deletions(-) delete mode 100644 run_tests.sh diff --git a/azurerm/resource_arm_user_assigned_identity_test.go b/azurerm/resource_arm_user_assigned_identity_test.go index 91f03ed16392..3265c9d1b8fb 100644 --- a/azurerm/resource_arm_user_assigned_identity_test.go +++ b/azurerm/resource_arm_user_assigned_identity_test.go @@ -13,7 +13,8 @@ func TestAccAzureRMUserAssignedIdentity_create(t *testing.T) { principalIdRegex := "^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$" resourceName := "azurerm_user_assigned_identity.test" ri := acctest.RandInt() - config := testAccAzureRMUserAssignedIdentityCreate(ri, testLocation()) + rs := acctest.RandString(14) + config := testAccAzureRMUserAssignedIdentityCreate(ri, testLocation(), rs) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -27,7 +28,7 @@ func TestAccAzureRMUserAssignedIdentity_create(t *testing.T) { }) } -func testAccAzureRMUserAssignedIdentityCreate(rInt int, location string) string { +func testAccAzureRMUserAssignedIdentityCreate(rInt int, location string, rString string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-acctest%[1]d" @@ -38,7 +39,7 @@ resource "azurerm_user_assigned_identity" "test" { resource_group_name = "${azurerm_resource_group.test.name}" location = "${azurerm_resource_group.test.location}" - name = "acctest%[1]d" + name = "acctest%[3]s" } -`, rInt, location) +`, rInt, location, rString) } diff --git a/azurerm/resource_arm_virtual_machine.go b/azurerm/resource_arm_virtual_machine.go index ed8f8b87f178..4a4dde26b0c0 100644 --- a/azurerm/resource_arm_virtual_machine.go +++ b/azurerm/resource_arm_virtual_machine.go @@ -86,9 +86,9 @@ func resourceArmVirtualMachine() *schema.Resource { Required: true, DiffSuppressFunc: ignoreCaseDiffSuppressFunc, ValidateFunc: validation.StringInSlice([]string{ - "SystemAssigned", - "UserAssigned", - }, true), + string(compute.ResourceIdentityTypeSystemAssigned), + string(compute.ResourceIdentityTypeUserAssigned), + }, false), }, "principal_id": { Type: schema.TypeString, @@ -1194,18 +1194,10 @@ func expandAzureRmVirtualMachinePlan(d *schema.ResourceData) (*compute.Plan, err } func expandAzureRmVirtualMachineIdentity(d *schema.ResourceData) *compute.VirtualMachineIdentity { - var identityType compute.ResourceIdentityType - v := d.Get("identity") identities := v.([]interface{}) identity := identities[0].(map[string]interface{}) - - switch strings.ToLower(identity["type"].(string)) { - case strings.ToLower(string(compute.ResourceIdentityTypeUserAssigned)): - identityType = compute.ResourceIdentityTypeUserAssigned - case strings.ToLower(string(compute.ResourceIdentityTypeSystemAssigned)): - identityType = compute.ResourceIdentityTypeSystemAssigned - } + identityType := compute.ResourceIdentityType(identity["type"].(string)) identityIds := []string{} for _, id := range identity["identity_ids"].([]interface{}) { diff --git a/azurerm/resource_arm_virtual_machine_scale_set.go b/azurerm/resource_arm_virtual_machine_scale_set.go index 2c1d62673b97..d277736520af 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set.go +++ b/azurerm/resource_arm_virtual_machine_scale_set.go @@ -49,9 +49,9 @@ func resourceArmVirtualMachineScaleSet() *schema.Resource { Required: true, DiffSuppressFunc: ignoreCaseDiffSuppressFunc, ValidateFunc: validation.StringInSlice([]string{ - "SystemAssigned", - "UserAssigned", - }, true), + string(compute.ResourceIdentityTypeSystemAssigned), + string(compute.ResourceIdentityTypeUserAssigned), + }, false), }, "identity_ids": { Type: schema.TypeList, @@ -1648,18 +1648,10 @@ func expandAzureRMVirtualMachineScaleSetsDiagnosticProfile(d *schema.ResourceDat } func expandAzureRmVirtualMachineScaleSetIdentity(d *schema.ResourceData) *compute.VirtualMachineScaleSetIdentity { - var identityType compute.ResourceIdentityType - v := d.Get("identity") identities := v.([]interface{}) identity := identities[0].(map[string]interface{}) - - switch strings.ToLower(identity["type"].(string)) { - case strings.ToLower(string(compute.ResourceIdentityTypeUserAssigned)): - identityType = compute.ResourceIdentityTypeUserAssigned - case strings.ToLower(string(compute.ResourceIdentityTypeSystemAssigned)): - identityType = compute.ResourceIdentityTypeSystemAssigned - } + identityType := compute.ResourceIdentityType(identity["type"].(string)) identityIds := []string{} for _, id := range identity["identity_ids"].([]interface{}) { diff --git a/azurerm/resource_arm_virtual_machine_scale_set_test.go b/azurerm/resource_arm_virtual_machine_scale_set_test.go index 2b22f37ddb04..2ea5bb886c09 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set_test.go +++ b/azurerm/resource_arm_virtual_machine_scale_set_test.go @@ -2933,7 +2933,7 @@ resource "azurerm_virtual_machine_scale_set" "test" { } identity { - type = "systemAssigned" + type = "SystemAssigned" } extension { @@ -3035,7 +3035,7 @@ resource "azurerm_virtual_machine_scale_set" "test" { } identity { - type = "userAssigned" + type = "UserAssigned" identity_ids = ["${azurerm_user_assigned_identity.test.id}"] } diff --git a/azurerm/resource_arm_virtual_machine_test.go b/azurerm/resource_arm_virtual_machine_test.go index fbdc159be73d..f9f4ddae570f 100644 --- a/azurerm/resource_arm_virtual_machine_test.go +++ b/azurerm/resource_arm_virtual_machine_test.go @@ -60,7 +60,7 @@ func TestAccAzureRMVirtualMachine_UserAssignedIdentity(t *testing.T) { var vm compute.VirtualMachine resourceName := "azurerm_virtual_machine.test" ri := acctest.RandInt() - rs := acctest.RandString(15) + rs := acctest.RandString(14) config := testAccAzureRMVirtualMachineUserAssignedIdentity(ri, testLocation(), rs) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -312,7 +312,7 @@ resource "azurerm_virtual_machine" "test" { } identity { - type = "systemAssigned" + type = "SystemAssigned" } } `, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt) @@ -415,7 +415,7 @@ resource "azurerm_virtual_machine" "test" { } identity { - type = "userAssigned" + type = "UserAssigned" identity_ids = ["${azurerm_user_assigned_identity.test.id}"] } } diff --git a/run_tests.sh b/run_tests.sh deleted file mode 100644 index 9717b753af1a..000000000000 --- a/run_tests.sh +++ /dev/null @@ -1,7 +0,0 @@ -TF_ACC=1 go test github.com/terraform-providers/terraform-provider-azurerm/azurerm -timeout 180m -v -run TestAccAzureRMVirtualMachineScaleSet_UserAssignedMSI & -TF_ACC=1 go test github.com/terraform-providers/terraform-provider-azurerm/azurerm -timeout 180m -v -run TestAccAzureRMVirtualMachineScaleSet_SystemAssignedMSI & -TF_ACC=1 go test github.com/terraform-providers/terraform-provider-azurerm/azurerm -timeout 180m -v -run TestAccAzureRMVirtualMachine_UserAssignedIdentity & -TF_ACC=1 go test github.com/terraform-providers/terraform-provider-azurerm/azurerm -timeout 180m -v -run TestAccAzureRMVirtualMachine_SystemAssignedIdentity & -TF_ACC=1 go test github.com/terraform-providers/terraform-provider-azurerm/azurerm -timeout 180m -v -run TestAccAzureRMUserAssignedIdentity_create & -wait - From b5970f385bf4835d06d7a605cdf6da50905cbbed Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Mon, 2 Jul 2018 07:40:18 +0200 Subject: [PATCH 14/14] User Assigned Identities: refactoring mentioned in PR ``` acctests azurerm TestAccAzureRMUserAssignedIdentity_ === RUN TestAccAzureRMUserAssignedIdentity_importBasic --- PASS: TestAccAzureRMUserAssignedIdentity_importBasic (80.67s) === RUN TestAccAzureRMUserAssignedIdentity_basic --- PASS: TestAccAzureRMUserAssignedIdentity_basic (78.45s) PASS ok github.com/terraform-providers/terraform-provider-azurerm/azurerm 159.156s ``` --- .../import_arm_user_assigned_identity_test.go | 35 ++++++++ .../resource_arm_user_assigned_identity.go | 57 +++++++------ ...esource_arm_user_assigned_identity_test.go | 85 ++++++++++++++++--- 3 files changed, 139 insertions(+), 38 deletions(-) create mode 100644 azurerm/import_arm_user_assigned_identity_test.go diff --git a/azurerm/import_arm_user_assigned_identity_test.go b/azurerm/import_arm_user_assigned_identity_test.go new file mode 100644 index 000000000000..325ca34d86ab --- /dev/null +++ b/azurerm/import_arm_user_assigned_identity_test.go @@ -0,0 +1,35 @@ +package azurerm + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAzureRMUserAssignedIdentity_importBasic(t *testing.T) { + resourceName := "azurerm_user_assigned_identity.test" + + ri := acctest.RandInt() + location := testLocation() + rs := acctest.RandString(14) + config := testAccAzureRMUserAssignedIdentity_basic(ri, location, rs) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMUserAssignedIdentityDestroy, + Steps: []resource.TestStep{ + { + Config: config, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +// diff --git a/azurerm/resource_arm_user_assigned_identity.go b/azurerm/resource_arm_user_assigned_identity.go index e1e26ce9346d..768086bbb72f 100644 --- a/azurerm/resource_arm_user_assigned_identity.go +++ b/azurerm/resource_arm_user_assigned_identity.go @@ -12,18 +12,15 @@ import ( func resourceArmUserAssignedIdentity() *schema.Resource { return &schema.Resource{ - Create: resourceArmUserAssignedIdentityCreate, + Create: resourceArmUserAssignedIdentityCreateUpdate, Read: resourceArmUserAssignedIdentityRead, - Update: resourceArmUserAssignedIdentityCreate, + Update: resourceArmUserAssignedIdentityCreateUpdate, Delete: resourceArmUserAssignedIdentityDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ - "resource_group_name": resourceGroupNameSchema(), - "location": locationSchema(), - "name": { Type: schema.TypeString, Required: true, @@ -31,44 +28,48 @@ func resourceArmUserAssignedIdentity() *schema.Resource { ValidateFunc: validation.StringLenBetween(1, 24), }, + "resource_group_name": resourceGroupNameSchema(), + + "location": locationSchema(), + + "tags": tagsSchema(), + "principal_id": { Type: schema.TypeString, Computed: true, }, - - "tags": tagsSchema(), }, } } -func resourceArmUserAssignedIdentityCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmUserAssignedIdentityCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).userAssignedIdentitiesClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for Azure ARM user identity creation.") - resourceName := d.Get("name").(string) + name := d.Get("name").(string) location := d.Get("location").(string) resGroup := d.Get("resource_group_name").(string) tags := d.Get("tags").(map[string]interface{}) identity := msi.Identity{ - Name: &resourceName, + Name: &name, Location: &location, Tags: expandTags(tags), } - _, err := client.CreateOrUpdate(ctx, resGroup, resourceName, identity) + _, err := client.CreateOrUpdate(ctx, resGroup, name, identity) if err != nil { - return fmt.Errorf("Error Creating/Updating User Assigned Identity %q (Resource Group %q): %+v", resourceName, resGroup, err) + return fmt.Errorf("Error Creating/Updating User Assigned Identity %q (Resource Group %q): %+v", name, resGroup, err) } - read, err := client.Get(ctx, resGroup, resourceName) + read, err := client.Get(ctx, resGroup, name) if err != nil { return err } if read.ID == nil { - return fmt.Errorf("Cannot read User Assigned Identity %q ID (resource group %q) ID", resourceName, resGroup) + return fmt.Errorf("Cannot read User Assigned Identity %q ID (resource group %q) ID", name, resGroup) } d.SetId(*read.ID) @@ -84,26 +85,27 @@ func resourceArmUserAssignedIdentityRead(d *schema.ResourceData, meta interface{ if err != nil { return err } - resGroup := id.ResourceGroup - resourceName := id.Path["userAssignedIdentities"] - resp, err := client.Get(ctx, resGroup, resourceName) + resGroup := id.ResourceGroup + name := id.Path["userAssignedIdentities"] + resp, err := client.Get(ctx, resGroup, name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { d.SetId("") return nil } - return fmt.Errorf("Error making Read request on User Assigned Identity %q (Resource Group %q): %+v", resourceName, resGroup, err) - } - - if resp.IdentityProperties == nil || resp.IdentityProperties.PrincipalID == nil { - return fmt.Errorf("Error PrincipalID can't be null for User Assigned Identity %q (Resource Group %q): %+v", resourceName, resGroup, err) + return fmt.Errorf("Error making Read request on User Assigned Identity %q (Resource Group %q): %+v", name, resGroup, err) } + d.Set("name", resp.Name) d.Set("resource_group_name", resGroup) d.Set("location", resp.Location) - d.Set("name", resp.Name) - d.Set("principal_id", resp.IdentityProperties.PrincipalID.String()) + + if props := resp.IdentityProperties; props != nil { + if principalId := props.PrincipalID; principalId != nil { + d.Set("principal_id", principalId.String()) + } + } flattenAndSetTags(d, resp.Tags) @@ -118,12 +120,13 @@ func resourceArmUserAssignedIdentityDelete(d *schema.ResourceData, meta interfac if err != nil { return err } + resGroup := id.ResourceGroup - resourceName := id.Path["userAssignedIdentities"] + name := id.Path["userAssignedIdentities"] - _, err = client.Delete(ctx, resGroup, resourceName) + _, err = client.Delete(ctx, resGroup, name) if err != nil { - return fmt.Errorf("Error deleting User Assigned Identity %q (Resource Group %q): %+v", resourceName, resGroup, err) + return fmt.Errorf("Error deleting User Assigned Identity %q (Resource Group %q): %+v", name, resGroup, err) } return nil diff --git a/azurerm/resource_arm_user_assigned_identity_test.go b/azurerm/resource_arm_user_assigned_identity_test.go index 3265c9d1b8fb..6dc61cc0525d 100644 --- a/azurerm/resource_arm_user_assigned_identity_test.go +++ b/azurerm/resource_arm_user_assigned_identity_test.go @@ -5,41 +5,104 @@ import ( "regexp" "testing" + "net/http" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" ) -func TestAccAzureRMUserAssignedIdentity_create(t *testing.T) { +func TestAccAzureRMUserAssignedIdentity_basic(t *testing.T) { principalIdRegex := "^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$" resourceName := "azurerm_user_assigned_identity.test" ri := acctest.RandInt() rs := acctest.RandString(14) - config := testAccAzureRMUserAssignedIdentityCreate(ri, testLocation(), rs) + config := testAccAzureRMUserAssignedIdentity_basic(ri, testLocation(), rs) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: testCheckAzureRMVirtualMachineScaleSetDestroy, + CheckDestroy: testCheckAzureRMUserAssignedIdentityDestroy, Steps: []resource.TestStep{ { Config: config, - Check: resource.TestMatchResourceAttr(resourceName, "principal_id", regexp.MustCompile(principalIdRegex)), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMUserAssignedIdentityExists(resourceName), + resource.TestMatchResourceAttr(resourceName, "principal_id", regexp.MustCompile(principalIdRegex)), + ), }, }, }) } -func testAccAzureRMUserAssignedIdentityCreate(rInt int, location string, rString string) string { +func testCheckAzureRMUserAssignedIdentityExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for virtual machine: %s", name) + } + + client := testAccProvider.Meta().(*ArmClient).userAssignedIdentitiesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Bad: Get on userAssignedIdentitiesClient: %+v", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: User Assigned Identity %q (resource group: %q) does not exist", name, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMUserAssignedIdentityDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).userAssignedIdentitiesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_virtual_machine" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.Get(ctx, resourceGroup, name) + + if err != nil { + if resp.StatusCode == http.StatusNotFound { + return nil + } + + return err + } + + return fmt.Errorf("User Assigned Identity still exists:\n%#v", resp) + } + + return nil +} + +func testAccAzureRMUserAssignedIdentity_basic(rInt int, location string, rString string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { - name = "acctestRG-acctest%[1]d" - location = "%[2]s" + name = "acctestRG-%d" + location = "%s" } resource "azurerm_user_assigned_identity" "test" { - resource_group_name = "${azurerm_resource_group.test.name}" - location = "${azurerm_resource_group.test.location}" - - name = "acctest%[3]s" + name = "acctest%s" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" } `, rInt, location, rString) }