Azure inventory sync!

This commit is contained in:
Luke Sneeringer 2014-07-29 13:06:27 -05:00
parent 504b810418
commit cf96240556
4 changed files with 108 additions and 38 deletions

View File

@ -425,6 +425,11 @@ def load_inventory_source(source, all_group=None, group_filter_re=None,
'''
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)
original_all_group = all_group
if not os.path.exists(source):

View File

@ -201,15 +201,39 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
return password
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')
begin_re = re.compile(r'^(-{4,})\s*BEGIN\s+([A-Z0-9]+)?\s*PRIVATE\sKEY\s*(-{4,})$')
header_re = re.compile(r'^(.+?):\s*?(.+?)(\\??)$')
end_re = re.compile(r'^(-{4,})\s*END\s+([A-Z0-9]+)?\s*PRIVATE\sKEY\s*(-{4,})$')
lines = data.strip().splitlines()
# Set up the valid private key header and footer.
begin_re = r'^(-{4,})\s*BEGIN\s+([A-Z0-9]+)?\s*PRIVATE\sKEY\s*(-{4,})$'
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:
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:
raise validation_error
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]:
raise validation_error
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 = ''
for line in lines[1:-1]:
line = line.strip()
if not line:
for segment_to_validate in (cert, data):
# If we have nothing; skip this one.
# 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
if line_continues:
line_continues = line.endswith('\\')
continue
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:
# Ensure that this segment is valid base64 data.
lines = segment_to_validate.splitlines()
for line in lines[1:-1]:
line = line.strip()
if not line:
continue
if line_continues:
line_continues = line.endswith('\\')
continue
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
except TypeError:
raise validation_error
def clean_ssh_key_data(self):
if self.pk:

View File

@ -85,7 +85,9 @@ class AzureInventory(object):
elif not self.is_cache_valid():
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)
elif self.args.list:
# Display list of nodes for inventory
@ -96,6 +98,23 @@ class AzureInventory(object):
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):
images = []
for image in self.sms.list_os_images():
@ -142,13 +161,20 @@ class AzureInventory(object):
def parse_cli_args(self):
"""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,
help='List nodes (default: True)')
help='List nodes (default: True)')
parser.add_argument('--list-images', action='store',
help='Get all available images.')
parser.add_argument('--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests to Azure (default: False - use cache files)')
help='Get all available images.')
parser.add_argument('--refresh-cache',
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()
def do_api_calls_update_cache(self):

View File

@ -443,7 +443,7 @@ GCE_INSTANCE_ID_VAR = None
# It's not possible to get zones in Azure without authenticating, so we
# provide a list here.
WA_REGION_CHOICES = [
AZURE_REGION_CHOICES = [
('Central_US', 'US Central'),
('East_US_1', 'US East'),
('East_US_2', 'US East 2'),
@ -458,19 +458,19 @@ WA_REGION_CHOICES = [
('West_Japan', 'Japan West'),
('South_Brazil', 'Brazil South'),
]
WA_REGIONS_BLACKLIST = []
AZURE_REGIONS_BLACKLIST = []
# Inventory variable name/value for determining whether a host is active
# in Google Compute Engine.
WA_ENABLED_VAR = 'status'
WA_ENABLED_VALUE = 'running'
# in Windows Azure.
AZURE_ENABLED_VAR = 'status'
AZURE_ENABLED_VALUE = 'created'
# Filter for allowed group and host names when importing inventory from
# Google Compute Engine.
WA_GROUP_FILTER = r'^.+$'
WA_HOST_FILTER = r'^.+$'
WA_EXCLUDE_EMPTY_GROUPS = True
WA_INSTANCE_ID_VAR = None
# Windows Azure.
AZURE_GROUP_FILTER = r'^.+$'
AZURE_HOST_FILTER = r'^.+$'
AZURE_EXCLUDE_EMPTY_GROUPS = True
AZURE_INSTANCE_ID_VAR = None
# ---------------------