mirror of
https://github.com/ansible/awx.git
synced 2026-05-09 02:17:37 -02:30
Azure inventory sync!
This commit is contained in:
@@ -425,6 +425,11 @@ def load_inventory_source(source, all_group=None, group_filter_re=None,
|
|||||||
'''
|
'''
|
||||||
Load inventory from given source directory or file.
|
Load inventory from given source directory or file.
|
||||||
'''
|
'''
|
||||||
|
# Sanity check: We need the "azure" module to be titled "windows_azure.py",
|
||||||
|
# because it depends on the "azure" package from PyPI, and naming the
|
||||||
|
# module the same way makes the importer sad.
|
||||||
|
source = source.replace('azure', 'windows_azure')
|
||||||
|
|
||||||
logger.debug('Analyzing type of source: %s', source)
|
logger.debug('Analyzing type of source: %s', source)
|
||||||
original_all_group = all_group
|
original_all_group = all_group
|
||||||
if not os.path.exists(source):
|
if not os.path.exists(source):
|
||||||
|
|||||||
@@ -201,15 +201,39 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
|
|||||||
return password
|
return password
|
||||||
|
|
||||||
def _validate_ssh_private_key(self, data):
|
def _validate_ssh_private_key(self, data):
|
||||||
|
"""Validate that the given SSH private key or certificate is,
|
||||||
|
in fact, valid.
|
||||||
|
"""
|
||||||
|
cert = ''
|
||||||
|
data = data.strip()
|
||||||
validation_error = ValidationError('Invalid SSH private key')
|
validation_error = ValidationError('Invalid SSH private key')
|
||||||
begin_re = re.compile(r'^(-{4,})\s*BEGIN\s+([A-Z0-9]+)?\s*PRIVATE\sKEY\s*(-{4,})$')
|
|
||||||
header_re = re.compile(r'^(.+?):\s*?(.+?)(\\??)$')
|
# Set up the valid private key header and footer.
|
||||||
end_re = re.compile(r'^(-{4,})\s*END\s+([A-Z0-9]+)?\s*PRIVATE\sKEY\s*(-{4,})$')
|
begin_re = r'^(-{4,})\s*BEGIN\s+([A-Z0-9]+)?\s*PRIVATE\sKEY\s*(-{4,})$'
|
||||||
lines = data.strip().splitlines()
|
end_re = r'^(-{4,})\s*END\s+([A-Z0-9]+)?\s*PRIVATE\sKEY\s*(-{4,})$'
|
||||||
|
|
||||||
|
# Sanity check: We may potentially receive a full PEM certificate,
|
||||||
|
# and we want to accept these.
|
||||||
|
cert_re = r'^(-{4,})\s*BEGIN\s+CERTIFICATE\s*(-{4,})'
|
||||||
|
cert_match = re.search(cert_re, data)
|
||||||
|
if cert_match:
|
||||||
|
private_key_begin = re.search(begin_re[1:-1], data)
|
||||||
|
if not private_key_begin:
|
||||||
|
raise validation_error
|
||||||
|
boundary = private_key_begin.start()
|
||||||
|
cert = data[:boundary].strip()
|
||||||
|
data = data[boundary:].strip()
|
||||||
|
|
||||||
|
# Split the SSH key into individual lines.
|
||||||
|
# If we have no content at all, then this is not a valid SSH key.
|
||||||
|
lines = data.splitlines()
|
||||||
if not lines:
|
if not lines:
|
||||||
raise validation_error
|
raise validation_error
|
||||||
begin_match = begin_re.match(lines[0])
|
|
||||||
end_match = end_re.match(lines[-1])
|
# Match the beginning and ending against what we expect, and also
|
||||||
|
# ensure that they match one another.
|
||||||
|
begin_match = re.match(begin_re, lines[0])
|
||||||
|
end_match = re.match(end_re, lines[-1])
|
||||||
if not begin_match or not end_match:
|
if not begin_match or not end_match:
|
||||||
raise validation_error
|
raise validation_error
|
||||||
dashes = set([begin_match.groups()[0], begin_match.groups()[2],
|
dashes = set([begin_match.groups()[0], begin_match.groups()[2],
|
||||||
@@ -219,25 +243,40 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
|
|||||||
if begin_match.groups()[1] != end_match.groups()[1]:
|
if begin_match.groups()[1] != end_match.groups()[1]:
|
||||||
raise validation_error
|
raise validation_error
|
||||||
line_continues = False
|
line_continues = False
|
||||||
|
|
||||||
|
# Establish that we are able to base64 decode the private key;
|
||||||
|
# if we can't, then it's not a valid key.
|
||||||
|
#
|
||||||
|
# If we got a certificate, validate that also, in the same way.
|
||||||
|
header_re = re.compile(r'^(.+?):\s*?(.+?)(\\??)$')
|
||||||
base64_data = ''
|
base64_data = ''
|
||||||
for line in lines[1:-1]:
|
for segment_to_validate in (cert, data):
|
||||||
line = line.strip()
|
# If we have nothing; skip this one.
|
||||||
if not line:
|
# We've already validated that we have a private key above,
|
||||||
|
# so we don't need to do it again.
|
||||||
|
if not segment_to_validate:
|
||||||
continue
|
continue
|
||||||
if line_continues:
|
|
||||||
line_continues = line.endswith('\\')
|
# Ensure that this segment is valid base64 data.
|
||||||
continue
|
lines = segment_to_validate.splitlines()
|
||||||
line_match = header_re.match(line)
|
for line in lines[1:-1]:
|
||||||
if line_match:
|
line = line.strip()
|
||||||
line_continues = line.endswith('\\')
|
if not line:
|
||||||
continue
|
continue
|
||||||
base64_data += line
|
if line_continues:
|
||||||
try:
|
line_continues = line.endswith('\\')
|
||||||
decoded_data = base64.b64decode(base64_data)
|
continue
|
||||||
if not decoded_data:
|
line_match = header_re.match(line)
|
||||||
|
if line_match:
|
||||||
|
line_continues = line.endswith('\\')
|
||||||
|
continue
|
||||||
|
base64_data += line
|
||||||
|
try:
|
||||||
|
decoded_data = base64.b64decode(base64_data)
|
||||||
|
if not decoded_data:
|
||||||
|
raise validation_error
|
||||||
|
except TypeError:
|
||||||
raise validation_error
|
raise validation_error
|
||||||
except TypeError:
|
|
||||||
raise validation_error
|
|
||||||
|
|
||||||
def clean_ssh_key_data(self):
|
def clean_ssh_key_data(self):
|
||||||
if self.pk:
|
if self.pk:
|
||||||
|
|||||||
@@ -85,7 +85,9 @@ class AzureInventory(object):
|
|||||||
elif not self.is_cache_valid():
|
elif not self.is_cache_valid():
|
||||||
self.do_api_calls_update_cache()
|
self.do_api_calls_update_cache()
|
||||||
|
|
||||||
if self.args.list_images:
|
if self.args.host:
|
||||||
|
data_to_print = self.get_host(self.args.host)
|
||||||
|
elif self.args.list_images:
|
||||||
data_to_print = self.json_format_dict(self.get_images(), True)
|
data_to_print = self.json_format_dict(self.get_images(), True)
|
||||||
elif self.args.list:
|
elif self.args.list:
|
||||||
# Display list of nodes for inventory
|
# Display list of nodes for inventory
|
||||||
@@ -96,6 +98,23 @@ class AzureInventory(object):
|
|||||||
|
|
||||||
print data_to_print
|
print data_to_print
|
||||||
|
|
||||||
|
def get_host(self, hostname):
|
||||||
|
"""Return information about the given hostname, based on what
|
||||||
|
the Windows Azure API provides.
|
||||||
|
"""
|
||||||
|
# Strip ".cloudapp.net" off of the end of the hostname if
|
||||||
|
# it is present.
|
||||||
|
if hostname.endswith('.cloudapp.net'):
|
||||||
|
hostname = hostname.replace('.cloudapp.net', '')
|
||||||
|
|
||||||
|
# Retrieve information about the host.
|
||||||
|
host = self.sms.get_hosted_service_properties(hostname)
|
||||||
|
hsp = host.hosted_service_properties # Because reasons.
|
||||||
|
return json.dumps({
|
||||||
|
'label': hsp.label,
|
||||||
|
'status': hsp.status.lower(),
|
||||||
|
})
|
||||||
|
|
||||||
def get_images(self):
|
def get_images(self):
|
||||||
images = []
|
images = []
|
||||||
for image in self.sms.list_os_images():
|
for image in self.sms.list_os_images():
|
||||||
@@ -142,13 +161,20 @@ class AzureInventory(object):
|
|||||||
|
|
||||||
def parse_cli_args(self):
|
def parse_cli_args(self):
|
||||||
"""Command line argument processing"""
|
"""Command line argument processing"""
|
||||||
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Azure')
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Produce an Ansible Inventory file based on Azure',
|
||||||
|
)
|
||||||
parser.add_argument('--list', action='store_true', default=True,
|
parser.add_argument('--list', action='store_true', default=True,
|
||||||
help='List nodes (default: True)')
|
help='List nodes (default: True)')
|
||||||
parser.add_argument('--list-images', action='store',
|
parser.add_argument('--list-images', action='store',
|
||||||
help='Get all available images.')
|
help='Get all available images.')
|
||||||
parser.add_argument('--refresh-cache', action='store_true', default=False,
|
parser.add_argument('--refresh-cache',
|
||||||
help='Force refresh of cache by making API requests to Azure (default: False - use cache files)')
|
action='store_true', default=False,
|
||||||
|
help='Force refresh of thecache by making API requests to Azure '
|
||||||
|
'(default: False - use cache files)',
|
||||||
|
)
|
||||||
|
parser.add_argument('--host', action='store',
|
||||||
|
help='Get all information about an instance.')
|
||||||
self.args = parser.parse_args()
|
self.args = parser.parse_args()
|
||||||
|
|
||||||
def do_api_calls_update_cache(self):
|
def do_api_calls_update_cache(self):
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ GCE_INSTANCE_ID_VAR = None
|
|||||||
|
|
||||||
# It's not possible to get zones in Azure without authenticating, so we
|
# It's not possible to get zones in Azure without authenticating, so we
|
||||||
# provide a list here.
|
# provide a list here.
|
||||||
WA_REGION_CHOICES = [
|
AZURE_REGION_CHOICES = [
|
||||||
('Central_US', 'US Central'),
|
('Central_US', 'US Central'),
|
||||||
('East_US_1', 'US East'),
|
('East_US_1', 'US East'),
|
||||||
('East_US_2', 'US East 2'),
|
('East_US_2', 'US East 2'),
|
||||||
@@ -458,19 +458,19 @@ WA_REGION_CHOICES = [
|
|||||||
('West_Japan', 'Japan West'),
|
('West_Japan', 'Japan West'),
|
||||||
('South_Brazil', 'Brazil South'),
|
('South_Brazil', 'Brazil South'),
|
||||||
]
|
]
|
||||||
WA_REGIONS_BLACKLIST = []
|
AZURE_REGIONS_BLACKLIST = []
|
||||||
|
|
||||||
# Inventory variable name/value for determining whether a host is active
|
# Inventory variable name/value for determining whether a host is active
|
||||||
# in Google Compute Engine.
|
# in Windows Azure.
|
||||||
WA_ENABLED_VAR = 'status'
|
AZURE_ENABLED_VAR = 'status'
|
||||||
WA_ENABLED_VALUE = 'running'
|
AZURE_ENABLED_VALUE = 'created'
|
||||||
|
|
||||||
# Filter for allowed group and host names when importing inventory from
|
# Filter for allowed group and host names when importing inventory from
|
||||||
# Google Compute Engine.
|
# Windows Azure.
|
||||||
WA_GROUP_FILTER = r'^.+$'
|
AZURE_GROUP_FILTER = r'^.+$'
|
||||||
WA_HOST_FILTER = r'^.+$'
|
AZURE_HOST_FILTER = r'^.+$'
|
||||||
WA_EXCLUDE_EMPTY_GROUPS = True
|
AZURE_EXCLUDE_EMPTY_GROUPS = True
|
||||||
WA_INSTANCE_ID_VAR = None
|
AZURE_INSTANCE_ID_VAR = None
|
||||||
|
|
||||||
|
|
||||||
# ---------------------
|
# ---------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user