mirror of
https://github.com/ansible/awx.git
synced 2026-05-16 22:07:36 -02:30
Merge pull request #2410 from ryanpetrello/update-azure-inv-script
update Azure inventory script to latest from Ansible Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
This commit is contained in:
@@ -24,7 +24,7 @@ Azure External Inventory Script
|
|||||||
===============================
|
===============================
|
||||||
Generates dynamic inventory by making API requests to the Azure Resource
|
Generates dynamic inventory by making API requests to the Azure Resource
|
||||||
Manager using the Azure Python SDK. For instruction on installing the
|
Manager using the Azure Python SDK. For instruction on installing the
|
||||||
Azure Python SDK see http://azure-sdk-for-python.readthedocs.org/
|
Azure Python SDK see https://azure-sdk-for-python.readthedocs.io/
|
||||||
|
|
||||||
Authentication
|
Authentication
|
||||||
--------------
|
--------------
|
||||||
@@ -50,6 +50,7 @@ Command line arguments:
|
|||||||
- ad_user
|
- ad_user
|
||||||
- password
|
- password
|
||||||
- cloud_environment
|
- cloud_environment
|
||||||
|
- adfs_authority_url
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
- AZURE_PROFILE
|
- AZURE_PROFILE
|
||||||
@@ -60,6 +61,7 @@ Environment variables:
|
|||||||
- AZURE_AD_USER
|
- AZURE_AD_USER
|
||||||
- AZURE_PASSWORD
|
- AZURE_PASSWORD
|
||||||
- AZURE_CLOUD_ENVIRONMENT
|
- AZURE_CLOUD_ENVIRONMENT
|
||||||
|
- AZURE_ADFS_AUTHORITY_URL
|
||||||
|
|
||||||
Run for Specific Host
|
Run for Specific Host
|
||||||
-----------------------
|
-----------------------
|
||||||
@@ -200,27 +202,43 @@ except ImportError:
|
|||||||
# python3
|
# python3
|
||||||
import configparser as cp
|
import configparser as cp
|
||||||
|
|
||||||
from packaging.version import Version
|
|
||||||
|
|
||||||
from os.path import expanduser
|
from os.path import expanduser
|
||||||
import ansible.module_utils.six.moves.urllib.parse as urlparse
|
import ansible.module_utils.six.moves.urllib.parse as urlparse
|
||||||
|
|
||||||
HAS_AZURE = True
|
HAS_AZURE = True
|
||||||
HAS_AZURE_EXC = None
|
HAS_AZURE_EXC = None
|
||||||
|
HAS_AZURE_CLI_CORE = True
|
||||||
|
CLIError = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
from msrestazure.azure_active_directory import AADTokenCredentials
|
||||||
from msrestazure.azure_exceptions import CloudError
|
from msrestazure.azure_exceptions import CloudError
|
||||||
|
from msrestazure.azure_active_directory import MSIAuthentication
|
||||||
from msrestazure import azure_cloud
|
from msrestazure import azure_cloud
|
||||||
from azure.mgmt.compute import __version__ as azure_compute_version
|
from azure.mgmt.compute import __version__ as azure_compute_version
|
||||||
from azure.common import AzureMissingResourceHttpError, AzureHttpError
|
from azure.common import AzureMissingResourceHttpError, AzureHttpError
|
||||||
from azure.common.credentials import ServicePrincipalCredentials, UserPassCredentials
|
from azure.common.credentials import ServicePrincipalCredentials, UserPassCredentials
|
||||||
from azure.mgmt.network import NetworkManagementClient
|
from azure.mgmt.network import NetworkManagementClient
|
||||||
from azure.mgmt.resource.resources import ResourceManagementClient
|
from azure.mgmt.resource.resources import ResourceManagementClient
|
||||||
|
from azure.mgmt.resource.subscriptions import SubscriptionClient
|
||||||
from azure.mgmt.compute import ComputeManagementClient
|
from azure.mgmt.compute import ComputeManagementClient
|
||||||
|
from adal.authentication_context import AuthenticationContext
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
HAS_AZURE_EXC = exc
|
HAS_AZURE_EXC = exc
|
||||||
HAS_AZURE = False
|
HAS_AZURE = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
from azure.cli.core.util import CLIError
|
||||||
|
from azure.common.credentials import get_azure_cli_credentials, get_cli_profile
|
||||||
|
from azure.common.cloud import get_cli_active_cloud
|
||||||
|
except ImportError:
|
||||||
|
HAS_AZURE_CLI_CORE = False
|
||||||
|
CLIError = Exception
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ansible.release import __version__ as ansible_version
|
||||||
|
except ImportError:
|
||||||
|
ansible_version = 'unknown'
|
||||||
|
|
||||||
AZURE_CREDENTIAL_ENV_MAPPING = dict(
|
AZURE_CREDENTIAL_ENV_MAPPING = dict(
|
||||||
profile='AZURE_PROFILE',
|
profile='AZURE_PROFILE',
|
||||||
@@ -231,6 +249,7 @@ AZURE_CREDENTIAL_ENV_MAPPING = dict(
|
|||||||
ad_user='AZURE_AD_USER',
|
ad_user='AZURE_AD_USER',
|
||||||
password='AZURE_PASSWORD',
|
password='AZURE_PASSWORD',
|
||||||
cloud_environment='AZURE_CLOUD_ENVIRONMENT',
|
cloud_environment='AZURE_CLOUD_ENVIRONMENT',
|
||||||
|
adfs_authority_url='AZURE_ADFS_AUTHORITY_URL'
|
||||||
)
|
)
|
||||||
|
|
||||||
AZURE_CONFIG_SETTINGS = dict(
|
AZURE_CONFIG_SETTINGS = dict(
|
||||||
@@ -241,10 +260,13 @@ AZURE_CONFIG_SETTINGS = dict(
|
|||||||
group_by_resource_group='AZURE_GROUP_BY_RESOURCE_GROUP',
|
group_by_resource_group='AZURE_GROUP_BY_RESOURCE_GROUP',
|
||||||
group_by_location='AZURE_GROUP_BY_LOCATION',
|
group_by_location='AZURE_GROUP_BY_LOCATION',
|
||||||
group_by_security_group='AZURE_GROUP_BY_SECURITY_GROUP',
|
group_by_security_group='AZURE_GROUP_BY_SECURITY_GROUP',
|
||||||
group_by_tag='AZURE_GROUP_BY_TAG'
|
group_by_tag='AZURE_GROUP_BY_TAG',
|
||||||
|
group_by_os_family='AZURE_GROUP_BY_OS_FAMILY',
|
||||||
|
use_private_ip='AZURE_USE_PRIVATE_IP'
|
||||||
)
|
)
|
||||||
|
|
||||||
AZURE_MIN_VERSION = "2.0.0"
|
AZURE_MIN_VERSION = "2.0.0"
|
||||||
|
ANSIBLE_USER_AGENT = 'Ansible/{0}'.format(ansible_version)
|
||||||
|
|
||||||
|
|
||||||
def azure_id_to_dict(id):
|
def azure_id_to_dict(id):
|
||||||
@@ -265,6 +287,8 @@ class AzureRM(object):
|
|||||||
self._compute_client = None
|
self._compute_client = None
|
||||||
self._resource_client = None
|
self._resource_client = None
|
||||||
self._network_client = None
|
self._network_client = None
|
||||||
|
self._adfs_authority_url = None
|
||||||
|
self._resource = None
|
||||||
|
|
||||||
self.debug = False
|
self.debug = False
|
||||||
if args.debug:
|
if args.debug:
|
||||||
@@ -300,13 +324,38 @@ class AzureRM(object):
|
|||||||
self.log("setting subscription_id")
|
self.log("setting subscription_id")
|
||||||
self.subscription_id = self.credentials['subscription_id']
|
self.subscription_id = self.credentials['subscription_id']
|
||||||
|
|
||||||
if self.credentials.get('client_id') is not None and \
|
# get authentication authority
|
||||||
self.credentials.get('secret') is not None and \
|
# for adfs, user could pass in authority or not.
|
||||||
self.credentials.get('tenant') is not None:
|
# for others, use default authority from cloud environment
|
||||||
|
if self.credentials.get('adfs_authority_url'):
|
||||||
|
self._adfs_authority_url = self.credentials.get('adfs_authority_url')
|
||||||
|
else:
|
||||||
|
self._adfs_authority_url = self._cloud_environment.endpoints.active_directory
|
||||||
|
|
||||||
|
# get resource from cloud environment
|
||||||
|
self._resource = self._cloud_environment.endpoints.active_directory_resource_id
|
||||||
|
|
||||||
|
if self.credentials.get('credentials'):
|
||||||
|
self.azure_credentials = self.credentials.get('credentials')
|
||||||
|
elif self.credentials.get('client_id') and self.credentials.get('secret') and self.credentials.get('tenant'):
|
||||||
self.azure_credentials = ServicePrincipalCredentials(client_id=self.credentials['client_id'],
|
self.azure_credentials = ServicePrincipalCredentials(client_id=self.credentials['client_id'],
|
||||||
secret=self.credentials['secret'],
|
secret=self.credentials['secret'],
|
||||||
tenant=self.credentials['tenant'],
|
tenant=self.credentials['tenant'],
|
||||||
cloud_environment=self._cloud_environment)
|
cloud_environment=self._cloud_environment)
|
||||||
|
|
||||||
|
elif self.credentials.get('ad_user') is not None and \
|
||||||
|
self.credentials.get('password') is not None and \
|
||||||
|
self.credentials.get('client_id') is not None and \
|
||||||
|
self.credentials.get('tenant') is not None:
|
||||||
|
|
||||||
|
self.azure_credentials = self.acquire_token_with_username_password(
|
||||||
|
self._adfs_authority_url,
|
||||||
|
self._resource,
|
||||||
|
self.credentials['ad_user'],
|
||||||
|
self.credentials['password'],
|
||||||
|
self.credentials['client_id'],
|
||||||
|
self.credentials['tenant'])
|
||||||
|
|
||||||
elif self.credentials.get('ad_user') is not None and self.credentials.get('password') is not None:
|
elif self.credentials.get('ad_user') is not None and self.credentials.get('password') is not None:
|
||||||
tenant = self.credentials.get('tenant')
|
tenant = self.credentials.get('tenant')
|
||||||
if not tenant:
|
if not tenant:
|
||||||
@@ -315,9 +364,12 @@ class AzureRM(object):
|
|||||||
self.credentials['password'],
|
self.credentials['password'],
|
||||||
tenant=tenant,
|
tenant=tenant,
|
||||||
cloud_environment=self._cloud_environment)
|
cloud_environment=self._cloud_environment)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.fail("Failed to authenticate with provided credentials. Some attributes were missing. "
|
self.fail("Failed to authenticate with provided credentials. Some attributes were missing. "
|
||||||
"Credentials must include client_id, secret and tenant or ad_user and password.")
|
"Credentials must include client_id, secret and tenant or ad_user and password, or "
|
||||||
|
"ad_user, password, client_id, tenant and adfs_authority_url(optional) for ADFS authentication, or "
|
||||||
|
"be logged in using AzureCLI.")
|
||||||
|
|
||||||
def log(self, msg):
|
def log(self, msg):
|
||||||
if self.debug:
|
if self.debug:
|
||||||
@@ -361,6 +413,32 @@ class AzureRM(object):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _get_azure_cli_credentials(self):
|
||||||
|
credentials, subscription_id = get_azure_cli_credentials()
|
||||||
|
cloud_environment = get_cli_active_cloud()
|
||||||
|
|
||||||
|
cli_credentials = {
|
||||||
|
'credentials': credentials,
|
||||||
|
'subscription_id': subscription_id,
|
||||||
|
'cloud_environment': cloud_environment
|
||||||
|
}
|
||||||
|
return cli_credentials
|
||||||
|
|
||||||
|
def _get_msi_credentials(self, subscription_id_param=None):
|
||||||
|
credentials = MSIAuthentication()
|
||||||
|
subscription_id_param = subscription_id_param or os.environ.get(AZURE_CREDENTIAL_ENV_MAPPING['subscription_id'], None)
|
||||||
|
try:
|
||||||
|
# try to get the subscription in MSI to test whether MSI is enabled
|
||||||
|
subscription_client = SubscriptionClient(credentials)
|
||||||
|
subscription = next(subscription_client.subscriptions.list())
|
||||||
|
subscription_id = str(subscription.subscription_id)
|
||||||
|
return {
|
||||||
|
'credentials': credentials,
|
||||||
|
'subscription_id': subscription_id_param or subscription_id
|
||||||
|
}
|
||||||
|
except Exception as exc:
|
||||||
|
return None
|
||||||
|
|
||||||
def _get_credentials(self, params):
|
def _get_credentials(self, params):
|
||||||
# Get authentication credentials.
|
# Get authentication credentials.
|
||||||
# Precedence: cmd line parameters-> environment variables-> default profile in ~/.azure/credentials.
|
# Precedence: cmd line parameters-> environment variables-> default profile in ~/.azure/credentials.
|
||||||
@@ -397,8 +475,31 @@ class AzureRM(object):
|
|||||||
self.log('Retrieved default profile credentials from ~/.azure/credentials.')
|
self.log('Retrieved default profile credentials from ~/.azure/credentials.')
|
||||||
return default_credentials
|
return default_credentials
|
||||||
|
|
||||||
|
msi_credentials = self._get_msi_credentials(arg_credentials.get('subscription_id'))
|
||||||
|
if msi_credentials:
|
||||||
|
self.log('Retrieved credentials from MSI.')
|
||||||
|
return msi_credentials
|
||||||
|
|
||||||
|
try:
|
||||||
|
if HAS_AZURE_CLI_CORE:
|
||||||
|
self.log('Retrieving credentials from AzureCLI profile')
|
||||||
|
cli_credentials = self._get_azure_cli_credentials()
|
||||||
|
return cli_credentials
|
||||||
|
except CLIError as ce:
|
||||||
|
self.log('Error getting AzureCLI profile credentials - {0}'.format(ce))
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def acquire_token_with_username_password(self, authority, resource, username, password, client_id, tenant):
|
||||||
|
authority_uri = authority
|
||||||
|
|
||||||
|
if tenant is not None:
|
||||||
|
authority_uri = authority + '/' + tenant
|
||||||
|
|
||||||
|
context = AuthenticationContext(authority_uri)
|
||||||
|
token_response = context.acquire_token_with_username_password(resource, username, password, client_id)
|
||||||
|
return AADTokenCredentials(token_response)
|
||||||
|
|
||||||
def _register(self, key):
|
def _register(self, key):
|
||||||
try:
|
try:
|
||||||
# We have to perform the one-time registration here. Otherwise, we receive an error the first
|
# We have to perform the one-time registration here. Otherwise, we receive an error the first
|
||||||
@@ -412,16 +513,21 @@ class AzureRM(object):
|
|||||||
"https://docs.microsoft.com/azure/azure-resource-manager/"
|
"https://docs.microsoft.com/azure/azure-resource-manager/"
|
||||||
"resource-manager-common-deployment-errors#noregisteredproviderfound"))
|
"resource-manager-common-deployment-errors#noregisteredproviderfound"))
|
||||||
|
|
||||||
|
def get_mgmt_svc_client(self, client_type, base_url, api_version):
|
||||||
|
client = client_type(self.azure_credentials,
|
||||||
|
self.subscription_id,
|
||||||
|
base_url=base_url,
|
||||||
|
api_version=api_version)
|
||||||
|
client.config.add_user_agent(ANSIBLE_USER_AGENT)
|
||||||
|
return client
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def network_client(self):
|
def network_client(self):
|
||||||
self.log('Getting network client')
|
self.log('Getting network client')
|
||||||
if not self._network_client:
|
if not self._network_client:
|
||||||
self._network_client = NetworkManagementClient(
|
self._network_client = self.get_mgmt_svc_client(NetworkManagementClient,
|
||||||
self.azure_credentials,
|
self._cloud_environment.endpoints.resource_manager,
|
||||||
self.subscription_id,
|
'2017-06-01')
|
||||||
base_url=self._cloud_environment.endpoints.resource_manager,
|
|
||||||
api_version='2017-06-01'
|
|
||||||
)
|
|
||||||
self._register('Microsoft.Network')
|
self._register('Microsoft.Network')
|
||||||
return self._network_client
|
return self._network_client
|
||||||
|
|
||||||
@@ -429,24 +535,18 @@ class AzureRM(object):
|
|||||||
def rm_client(self):
|
def rm_client(self):
|
||||||
self.log('Getting resource manager client')
|
self.log('Getting resource manager client')
|
||||||
if not self._resource_client:
|
if not self._resource_client:
|
||||||
self._resource_client = ResourceManagementClient(
|
self._resource_client = self.get_mgmt_svc_client(ResourceManagementClient,
|
||||||
self.azure_credentials,
|
self._cloud_environment.endpoints.resource_manager,
|
||||||
self.subscription_id,
|
'2017-05-10')
|
||||||
base_url=self._cloud_environment.endpoints.resource_manager,
|
|
||||||
api_version='2017-05-10'
|
|
||||||
)
|
|
||||||
return self._resource_client
|
return self._resource_client
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def compute_client(self):
|
def compute_client(self):
|
||||||
self.log('Getting compute client')
|
self.log('Getting compute client')
|
||||||
if not self._compute_client:
|
if not self._compute_client:
|
||||||
self._compute_client = ComputeManagementClient(
|
self._compute_client = self.get_mgmt_svc_client(ComputeManagementClient,
|
||||||
self.azure_credentials,
|
self._cloud_environment.endpoints.resource_manager,
|
||||||
self.subscription_id,
|
'2017-03-30')
|
||||||
base_url=self._cloud_environment.endpoints.resource_manager,
|
|
||||||
api_version='2017-03-30'
|
|
||||||
)
|
|
||||||
self._register('Microsoft.Compute')
|
self._register('Microsoft.Compute')
|
||||||
return self._compute_client
|
return self._compute_client
|
||||||
|
|
||||||
@@ -473,9 +573,11 @@ class AzureInventory(object):
|
|||||||
self.replace_dash_in_groups = False
|
self.replace_dash_in_groups = False
|
||||||
self.group_by_resource_group = True
|
self.group_by_resource_group = True
|
||||||
self.group_by_location = True
|
self.group_by_location = True
|
||||||
|
self.group_by_os_family = True
|
||||||
self.group_by_security_group = True
|
self.group_by_security_group = True
|
||||||
self.group_by_tag = True
|
self.group_by_tag = True
|
||||||
self.include_powerstate = True
|
self.include_powerstate = True
|
||||||
|
self.use_private_ip = False
|
||||||
|
|
||||||
self._inventory = dict(
|
self._inventory = dict(
|
||||||
_meta=dict(
|
_meta=dict(
|
||||||
@@ -528,6 +630,8 @@ class AzureInventory(object):
|
|||||||
help='Active Directory User')
|
help='Active Directory User')
|
||||||
parser.add_argument('--password', action='store',
|
parser.add_argument('--password', action='store',
|
||||||
help='password')
|
help='password')
|
||||||
|
parser.add_argument('--adfs_authority_url', action='store',
|
||||||
|
help='Azure ADFS authority url')
|
||||||
parser.add_argument('--cloud_environment', action='store',
|
parser.add_argument('--cloud_environment', action='store',
|
||||||
help='Azure Cloud Environment name or metadata discovery URL')
|
help='Azure Cloud Environment name or metadata discovery URL')
|
||||||
parser.add_argument('--resource-groups', action='store',
|
parser.add_argument('--resource-groups', action='store',
|
||||||
@@ -545,7 +649,7 @@ class AzureInventory(object):
|
|||||||
# get VMs for requested resource groups
|
# get VMs for requested resource groups
|
||||||
for resource_group in self.resource_groups:
|
for resource_group in self.resource_groups:
|
||||||
try:
|
try:
|
||||||
virtual_machines = self._compute_client.virtual_machines.list(resource_group)
|
virtual_machines = self._compute_client.virtual_machines.list(resource_group.lower())
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
sys.exit("Error: fetching virtual machines for resource group {0} - {1}".format(resource_group, str(exc)))
|
sys.exit("Error: fetching virtual machines for resource group {0} - {1}".format(resource_group, str(exc)))
|
||||||
if self._args.host or self.tags:
|
if self._args.host or self.tags:
|
||||||
@@ -604,7 +708,7 @@ class AzureInventory(object):
|
|||||||
|
|
||||||
host_vars['os_disk'] = dict(
|
host_vars['os_disk'] = dict(
|
||||||
name=machine.storage_profile.os_disk.name,
|
name=machine.storage_profile.os_disk.name,
|
||||||
operating_system_type=machine.storage_profile.os_disk.os_type.value
|
operating_system_type=machine.storage_profile.os_disk.os_type.value.lower()
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.include_powerstate:
|
if self.include_powerstate:
|
||||||
@@ -651,12 +755,15 @@ class AzureInventory(object):
|
|||||||
for ip_config in network_interface.ip_configurations:
|
for ip_config in network_interface.ip_configurations:
|
||||||
host_vars['private_ip'] = ip_config.private_ip_address
|
host_vars['private_ip'] = ip_config.private_ip_address
|
||||||
host_vars['private_ip_alloc_method'] = ip_config.private_ip_allocation_method
|
host_vars['private_ip_alloc_method'] = ip_config.private_ip_allocation_method
|
||||||
|
if self.use_private_ip:
|
||||||
|
host_vars['ansible_host'] = ip_config.private_ip_address
|
||||||
if ip_config.public_ip_address:
|
if ip_config.public_ip_address:
|
||||||
public_ip_reference = self._parse_ref_id(ip_config.public_ip_address.id)
|
public_ip_reference = self._parse_ref_id(ip_config.public_ip_address.id)
|
||||||
public_ip_address = self._network_client.public_ip_addresses.get(
|
public_ip_address = self._network_client.public_ip_addresses.get(
|
||||||
public_ip_reference['resourceGroups'],
|
public_ip_reference['resourceGroups'],
|
||||||
public_ip_reference['publicIPAddresses'])
|
public_ip_reference['publicIPAddresses'])
|
||||||
host_vars['ansible_host'] = public_ip_address.ip_address
|
if not self.use_private_ip:
|
||||||
|
host_vars['ansible_host'] = public_ip_address.ip_address
|
||||||
host_vars['public_ip'] = public_ip_address.ip_address
|
host_vars['public_ip'] = public_ip_address.ip_address
|
||||||
host_vars['public_ip_name'] = public_ip_address.name
|
host_vars['public_ip_name'] = public_ip_address.name
|
||||||
host_vars['public_ip_alloc_method'] = public_ip_address.public_ip_allocation_method
|
host_vars['public_ip_alloc_method'] = public_ip_address.public_ip_allocation_method
|
||||||
@@ -706,10 +813,16 @@ class AzureInventory(object):
|
|||||||
|
|
||||||
host_name = self._to_safe(vars['name'])
|
host_name = self._to_safe(vars['name'])
|
||||||
resource_group = self._to_safe(vars['resource_group'])
|
resource_group = self._to_safe(vars['resource_group'])
|
||||||
|
operating_system_type = self._to_safe(vars['os_disk']['operating_system_type'].lower())
|
||||||
security_group = None
|
security_group = None
|
||||||
if vars.get('security_group'):
|
if vars.get('security_group'):
|
||||||
security_group = self._to_safe(vars['security_group'])
|
security_group = self._to_safe(vars['security_group'])
|
||||||
|
|
||||||
|
if self.group_by_os_family:
|
||||||
|
if not self._inventory.get(operating_system_type):
|
||||||
|
self._inventory[operating_system_type] = []
|
||||||
|
self._inventory[operating_system_type].append(host_name)
|
||||||
|
|
||||||
if self.group_by_resource_group:
|
if self.group_by_resource_group:
|
||||||
if not self._inventory.get(resource_group):
|
if not self._inventory.get(resource_group):
|
||||||
self._inventory[resource_group] = []
|
self._inventory[resource_group] = []
|
||||||
|
|||||||
Reference in New Issue
Block a user