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:
softwarefactory-project-zuul[bot]
2018-10-11 16:15:15 +00:00
committed by GitHub

View File

@@ -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] = []