Merge "Azure: add support for User-assigned Managed Identities"
This commit is contained in:
@@ -749,6 +749,33 @@ section of the configuration.
|
|||||||
|
|
||||||
If given, the size of the operating system disk, in GiB.
|
If given, the size of the operating system disk, in GiB.
|
||||||
|
|
||||||
|
.. attr:: user-assigned-identities
|
||||||
|
:type: dict
|
||||||
|
:default: None
|
||||||
|
|
||||||
|
`User-assigned Managed Identities`_ to assign to the VM.
|
||||||
|
Useful for giving access to services without needing any secrets.
|
||||||
|
|
||||||
|
.. attr:: name
|
||||||
|
:required:
|
||||||
|
:type: str
|
||||||
|
|
||||||
|
The name of the User-assigned Managed Identity.
|
||||||
|
|
||||||
|
.. attr:: resource-group
|
||||||
|
:type: str
|
||||||
|
:default: The provider's resource group
|
||||||
|
|
||||||
|
Overrides :attr:`providers.[azure].resource-group`.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
user-assigned-identities:
|
||||||
|
- name: myLocalIdentity
|
||||||
|
- name: myRemoteIdentity
|
||||||
|
resource-group: remote-rg
|
||||||
|
|
||||||
.. _`Azure CLI`: https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest
|
.. _`Azure CLI`: https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest
|
||||||
|
|
||||||
@@ -757,3 +784,5 @@ section of the configuration.
|
|||||||
.. _`Azure User Data`: https://docs.microsoft.com/en-us/azure/virtual-machines/user-data
|
.. _`Azure User Data`: https://docs.microsoft.com/en-us/azure/virtual-machines/user-data
|
||||||
|
|
||||||
.. _`Azure Custom Data`: https://docs.microsoft.com/en-us/azure/virtual-machines/custom-data
|
.. _`Azure Custom Data`: https://docs.microsoft.com/en-us/azure/virtual-machines/custom-data
|
||||||
|
|
||||||
|
.. _`User-assigned Managed Identities`: https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/qs-configure-rest-vm
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from nodepool.driver.utils import (
|
|||||||
RateLimiter,
|
RateLimiter,
|
||||||
ImageUploader,
|
ImageUploader,
|
||||||
)
|
)
|
||||||
|
from urllib.parse import urlparse
|
||||||
from nodepool.driver import statemachine
|
from nodepool.driver import statemachine
|
||||||
from nodepool import exceptions
|
from nodepool import exceptions
|
||||||
from . import azul
|
from . import azul
|
||||||
@@ -677,6 +678,23 @@ class AzureAdapter(statemachine.Adapter):
|
|||||||
if label.user_data:
|
if label.user_data:
|
||||||
spec['properties']['userData'] = label.user_data
|
spec['properties']['userData'] = label.user_data
|
||||||
|
|
||||||
|
# build resource id for all configured User-assigned Identities
|
||||||
|
uai_resource_ids = set()
|
||||||
|
for uai in label.user_assigned_identities:
|
||||||
|
uai_rg_name = uai.get("resource-group", self.resource_group)
|
||||||
|
uai_url = self.azul.managed_identities.url(
|
||||||
|
resourceGroupName=uai_rg_name, resourceName=uai['name'])
|
||||||
|
uai_resource_ids.add(urlparse(uai_url).path)
|
||||||
|
|
||||||
|
# adding empty userAssignedIdentities is not allowed by Azure
|
||||||
|
if uai_resource_ids:
|
||||||
|
spec['identity'] = {
|
||||||
|
'type': 'UserAssigned',
|
||||||
|
'userAssignedIdentities': {
|
||||||
|
rid: {} for rid in uai_resource_ids
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
with self.rate_limiter:
|
with self.rate_limiter:
|
||||||
self.log.debug(f"Creating VM {hostname}")
|
self.log.debug(f"Creating VM {hostname}")
|
||||||
return self.azul.virtual_machines.create(
|
return self.azul.virtual_machines.create(
|
||||||
|
|||||||
@@ -268,6 +268,11 @@ class AzureCloud:
|
|||||||
providerId='Microsoft.Compute',
|
providerId='Microsoft.Compute',
|
||||||
resource='skus',
|
resource='skus',
|
||||||
apiVersion='2019-04-01')
|
apiVersion='2019-04-01')
|
||||||
|
self.managed_identities = AzureResourceProviderCRUD(
|
||||||
|
self,
|
||||||
|
providerId='Microsoft.ManagedIdentity',
|
||||||
|
resource='userAssignedIdentities',
|
||||||
|
apiVersion='2023-01-31')
|
||||||
|
|
||||||
def get(self, url, codes=[200]):
|
def get(self, url, codes=[200]):
|
||||||
return self.request('GET', url, None, codes)
|
return self.request('GET', url, None, codes)
|
||||||
|
|||||||
@@ -193,6 +193,8 @@ class AzureLabel(ConfigValue):
|
|||||||
self.custom_data = self._encodeData(label.get('custom-data', None))
|
self.custom_data = self._encodeData(label.get('custom-data', None))
|
||||||
self.host_key_checking = self.pool.host_key_checking
|
self.host_key_checking = self.pool.host_key_checking
|
||||||
self.volume_size = label.get('volume-size')
|
self.volume_size = label.get('volume-size')
|
||||||
|
self.user_assigned_identities = label.get(
|
||||||
|
'user-assigned-identities', [])
|
||||||
|
|
||||||
def _encodeData(self, s):
|
def _encodeData(self, s):
|
||||||
if not s:
|
if not s:
|
||||||
@@ -205,6 +207,11 @@ class AzureLabel(ConfigValue):
|
|||||||
v.Required('vm-size'): str,
|
v.Required('vm-size'): str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user_assigned_identities = {
|
||||||
|
v.Required('name'): str,
|
||||||
|
'resource-group': str,
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
v.Required('name'): str,
|
v.Required('name'): str,
|
||||||
'cloud-image': str,
|
'cloud-image': str,
|
||||||
@@ -215,6 +222,7 @@ class AzureLabel(ConfigValue):
|
|||||||
'user-data': str,
|
'user-data': str,
|
||||||
'custom-data': str,
|
'custom-data': str,
|
||||||
'volume-size': int,
|
'volume-size': int,
|
||||||
|
'user-assigned-identities': [user_assigned_identities],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
4
nodepool/tests/fixtures/azure.yaml
vendored
4
nodepool/tests/fixtures/azure.yaml
vendored
@@ -93,6 +93,10 @@ providers:
|
|||||||
dynamic-tenant: "Tenant is {{request.tenant_name}}"
|
dynamic-tenant: "Tenant is {{request.tenant_name}}"
|
||||||
user-data: "This is the user data"
|
user-data: "This is the user data"
|
||||||
custom-data: "This is the custom data"
|
custom-data: "This is the custom data"
|
||||||
|
user-assigned-identities:
|
||||||
|
- name: localid
|
||||||
|
- name: otherid
|
||||||
|
resource-group: othergroup
|
||||||
- name: image-by-name
|
- name: image-by-name
|
||||||
cloud-image: image-by-name
|
cloud-image: image-by-name
|
||||||
hardware-profile:
|
hardware-profile:
|
||||||
|
|||||||
@@ -99,6 +99,22 @@ class TestDriverAzure(tests.DBTestCase):
|
|||||||
self.fake_azure.crud['Microsoft.Compute/virtualMachines'].
|
self.fake_azure.crud['Microsoft.Compute/virtualMachines'].
|
||||||
requests[0]['properties']['userData'],
|
requests[0]['properties']['userData'],
|
||||||
'VGhpcyBpcyB0aGUgdXNlciBkYXRh') # This is the user data
|
'VGhpcyBpcyB0aGUgdXNlciBkYXRh') # This is the user data
|
||||||
|
self.assertEqual(
|
||||||
|
self.fake_azure.crud['Microsoft.Compute/virtualMachines'].
|
||||||
|
requests[0]['identity'],
|
||||||
|
{
|
||||||
|
"type": "UserAssigned",
|
||||||
|
"userAssignedIdentities": {
|
||||||
|
f"/subscriptions/{self.fake_azure.subscription_id}/"
|
||||||
|
"resourceGroups/nodepool/"
|
||||||
|
"providers/Microsoft.ManagedIdentity/"
|
||||||
|
"userAssignedIdentities/localid": {},
|
||||||
|
f"/subscriptions/{self.fake_azure.subscription_id}/"
|
||||||
|
"resourceGroups/othergroup/"
|
||||||
|
"providers/Microsoft.ManagedIdentity/"
|
||||||
|
"userAssignedIdentities/otherid": {},
|
||||||
|
}
|
||||||
|
})
|
||||||
tags = (self.fake_azure.crud['Microsoft.Compute/virtualMachines'].
|
tags = (self.fake_azure.crud['Microsoft.Compute/virtualMachines'].
|
||||||
requests[0]['tags'])
|
requests[0]['tags'])
|
||||||
self.assertEqual(tags.get('team'), 'DevOps')
|
self.assertEqual(tags.get('team'), 'DevOps')
|
||||||
|
|||||||
Reference in New Issue
Block a user