Merge pull request #425 from ryanpetrello/release_3.2.0

remove support for azure classic
This commit is contained in:
Ryan Petrello
2017-09-18 11:26:55 -04:00
committed by GitHub
19 changed files with 63 additions and 475 deletions

View File

@@ -89,7 +89,7 @@ class Metadata(metadata.SimpleMetadata):
# Special handling of inventory source_region choices that vary based on # Special handling of inventory source_region choices that vary based on
# selected inventory source. # selected inventory source.
if field.field_name == 'source_regions': if field.field_name == 'source_regions':
for cp in ('azure', 'ec2', 'gce'): for cp in ('azure_rm', 'ec2', 'gce'):
get_regions = getattr(InventorySource, 'get_%s_region_choices' % cp) get_regions = getattr(InventorySource, 'get_%s_region_choices' % cp)
field_info['%s_region_choices' % cp] = get_regions() field_info['%s_region_choices' % cp] = get_regions()

View File

@@ -5,7 +5,7 @@ import re
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
CLOUD_PROVIDERS = ('azure', 'azure_rm', 'ec2', 'gce', 'rax', 'vmware', 'openstack', 'satellite6', 'cloudforms') CLOUD_PROVIDERS = ('azure_rm', 'ec2', 'gce', 'vmware', 'openstack', 'satellite6', 'cloudforms')
SCHEDULEABLE_PROVIDERS = CLOUD_PROVIDERS + ('custom', 'scm',) SCHEDULEABLE_PROVIDERS = CLOUD_PROVIDERS + ('custom', 'scm',)
PRIVILEGE_ESCALATION_METHODS = [ ('sudo', _('Sudo')), ('su', _('Su')), ('pbrun', _('Pbrun')), ('pfexec', _('Pfexec')), ('dzdo', _('DZDO')), ('pmrun', _('Pmrun')), ('runas', _('Runas'))] PRIVILEGE_ESCALATION_METHODS = [ ('sudo', _('Sudo')), ('su', _('Su')), ('pbrun', _('Pbrun')), ('pfexec', _('Pfexec')), ('dzdo', _('DZDO')), ('pmrun', _('Pmrun')), ('runas', _('Runas'))]
ANSI_SGR_PATTERN = re.compile(r'\x1b\[[0-9;]*m') ANSI_SGR_PATTERN = re.compile(r'\x1b\[[0-9;]*m')

View File

@@ -194,7 +194,6 @@ def load_inventory_source(source, group_filter_re=None,
''' '''
# Sanity check: We sanitize these module names for our API but Ansible proper doesn't follow # Sanity check: We sanitize these module names for our API but Ansible proper doesn't follow
# good naming conventions # good naming conventions
source = source.replace('azure.py', 'windows_azure.py')
source = source.replace('satellite6.py', 'foreman.py') source = source.replace('satellite6.py', 'foreman.py')
source = source.replace('vmware.py', 'vmware_inventory.py') source = source.replace('vmware.py', 'vmware_inventory.py')
if not os.path.exists(source): if not os.path.exists(source):

View File

@@ -145,12 +145,12 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='inventorysource', model_name='inventorysource',
name='source', name='source',
field=models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'File, Directory or Script'), (b'scm', 'Sourced from a Project'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure', 'Microsoft Azure Classic (deprecated)'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'vmware', 'VMware vCenter'), (b'satellite6', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'openstack', 'OpenStack'), (b'custom', 'Custom Script')]), field=models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'File, Directory or Script'), (b'scm', 'Sourced from a Project'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'vmware', 'VMware vCenter'), (b'satellite6', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'openstack', 'OpenStack'), (b'custom', 'Custom Script')]),
), ),
migrations.AlterField( migrations.AlterField(
model_name='inventoryupdate', model_name='inventoryupdate',
name='source', name='source',
field=models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'File, Directory or Script'), (b'scm', 'Sourced from a Project'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure', 'Microsoft Azure Classic (deprecated)'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'vmware', 'VMware vCenter'), (b'satellite6', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'openstack', 'OpenStack'), (b'custom', 'Custom Script')]), field=models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'File, Directory or Script'), (b'scm', 'Sourced from a Project'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'vmware', 'VMware vCenter'), (b'satellite6', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'openstack', 'OpenStack'), (b'custom', 'Custom Script')]),
), ),
migrations.AlterField( migrations.AlterField(
model_name='inventorysource', model_name='inventorysource',

View File

@@ -11,6 +11,7 @@ from awx.main.migrations import _migration_utils as migration_utils
from awx.main.migrations import _reencrypt as reencrypt from awx.main.migrations import _reencrypt as reencrypt
from awx.main.migrations import _scan_jobs as scan_jobs from awx.main.migrations import _scan_jobs as scan_jobs
from awx.main.migrations import _credentialtypes as credentialtypes from awx.main.migrations import _credentialtypes as credentialtypes
from awx.main.migrations import _azure_credentials as azurecreds
import awx.main.fields import awx.main.fields
@@ -24,6 +25,8 @@ class Migration(migrations.Migration):
# Inventory Refresh # Inventory Refresh
migrations.RunPython(migration_utils.set_current_apps_for_migrations), migrations.RunPython(migration_utils.set_current_apps_for_migrations),
migrations.RunPython(invsrc.remove_rax_inventory_sources), migrations.RunPython(invsrc.remove_rax_inventory_sources),
migrations.RunPython(azurecreds.remove_azure_credentials),
migrations.RunPython(invsrc.remove_azure_inventory_sources),
migrations.RunPython(invsrc.remove_inventory_source_with_no_inventory_link), migrations.RunPython(invsrc.remove_inventory_source_with_no_inventory_link),
migrations.RunPython(invsrc.rename_inventory_sources), migrations.RunPython(invsrc.rename_inventory_sources),
migrations.RunPython(reencrypt.replace_aesecb_fernet), migrations.RunPython(reencrypt.replace_aesecb_fernet),

View File

@@ -0,0 +1,15 @@
import logging
from django.db.models import Q
logger = logging.getLogger('awx.main.migrations')
def remove_azure_credentials(apps, schema_editor):
'''Azure is not supported as of 3.2 and greater. Instead, azure_rm is
supported.
'''
Credential = apps.get_model('main', 'Credential')
logger.debug("Removing all Azure Credentials from database.")
Credential.objects.filter(kind='azure').delete()

View File

@@ -51,3 +51,12 @@ def remove_inventory_source_with_no_inventory_link(apps, schema_editor):
InventorySource = apps.get_model('main', 'InventorySource') InventorySource = apps.get_model('main', 'InventorySource')
logger.debug("Removing all InventorySource that have no link to an Inventory from database.") logger.debug("Removing all InventorySource that have no link to an Inventory from database.")
InventorySource.objects.filter(Q(inventory__organization=None) & Q(deprecated_group__inventory=None)).delete() InventorySource.objects.filter(Q(inventory__organization=None) & Q(deprecated_group__inventory=None)).delete()
def remove_azure_inventory_sources(apps, schema_editor):
'''Azure inventory sources are not supported since 3.2, remove them.
'''
InventorySource = apps.get_model('main', 'InventorySource')
logger.debug("Removing all Azure InventorySource from database.")
InventorySource.objects.filter(source='azure').delete()

View File

@@ -52,7 +52,7 @@ PROJECT_UPDATE_JOB_TYPE_CHOICES = [
(PERM_INVENTORY_CHECK, _('Check')), (PERM_INVENTORY_CHECK, _('Check')),
] ]
CLOUD_INVENTORY_SOURCES = ['ec2', 'rax', 'vmware', 'gce', 'azure', 'azure_rm', 'openstack', 'custom', 'satellite6', 'cloudforms', 'scm',] CLOUD_INVENTORY_SOURCES = ['ec2', 'vmware', 'gce', 'azure_rm', 'openstack', 'custom', 'satellite6', 'cloudforms', 'scm',]
VERBOSITY_CHOICES = [ VERBOSITY_CHOICES = [
(0, '0 (Normal)'), (0, '0 (Normal)'),

View File

@@ -57,7 +57,6 @@ class V1Credential(object):
('satellite6', 'Red Hat Satellite 6'), ('satellite6', 'Red Hat Satellite 6'),
('cloudforms', 'Red Hat CloudForms'), ('cloudforms', 'Red Hat CloudForms'),
('gce', 'Google Compute Engine'), ('gce', 'Google Compute Engine'),
('azure', 'Microsoft Azure Classic (deprecated)'),
('azure_rm', 'Microsoft Azure Resource Manager'), ('azure_rm', 'Microsoft Azure Resource Manager'),
('openstack', 'OpenStack'), ('openstack', 'OpenStack'),
('insights', 'Insights'), ('insights', 'Insights'),
@@ -934,35 +933,6 @@ def gce(cls):
) )
@CredentialType.default
def azure(cls):
return cls(
kind='cloud',
name='Microsoft Azure Classic (deprecated)',
managed_by_tower=True,
inputs={
'fields': [{
'id': 'username',
'label': 'Subscription ID',
'type': 'string',
'help_text': ('Subscription ID is an Azure construct, which is '
'mapped to a username.')
}, {
'id': 'ssh_key_data',
'label': 'Management Certificate',
'type': 'string',
'format': 'ssh_private_key',
'secret': True,
'multiline': True,
'help_text': ('Paste the contents of the PEM file that corresponds '
'to the certificate you uploaded in the Microsoft '
'Azure console.')
}],
'required': ['username', 'ssh_key_data'],
}
)
@CredentialType.default @CredentialType.default
def azure_rm(cls): def azure_rm(cls):
return cls( return cls(

View File

@@ -867,7 +867,6 @@ class InventorySourceOptions(BaseModel):
('scm', _('Sourced from a Project')), ('scm', _('Sourced from a Project')),
('ec2', _('Amazon EC2')), ('ec2', _('Amazon EC2')),
('gce', _('Google Compute Engine')), ('gce', _('Google Compute Engine')),
('azure', _('Microsoft Azure Classic (deprecated)')),
('azure_rm', _('Microsoft Azure Resource Manager')), ('azure_rm', _('Microsoft Azure Resource Manager')),
('vmware', _('VMware vCenter')), ('vmware', _('VMware vCenter')),
('satellite6', _('Red Hat Satellite 6')), ('satellite6', _('Red Hat Satellite 6')),
@@ -1087,7 +1086,7 @@ class InventorySourceOptions(BaseModel):
return regions return regions
@classmethod @classmethod
def get_azure_region_choices(self): def get_azure_rm_region_choices(self):
"""Return a complete list of regions in Microsoft Azure, as a list of """Return a complete list of regions in Microsoft Azure, as a list of
two-tuples. two-tuples.
""" """
@@ -1095,14 +1094,10 @@ class InventorySourceOptions(BaseModel):
# authenticating first (someone reading these might think there's # authenticating first (someone reading these might think there's
# a pattern here!). Therefore, you guessed it, use a list from # a pattern here!). Therefore, you guessed it, use a list from
# settings. # settings.
regions = list(getattr(settings, 'AZURE_REGION_CHOICES', [])) regions = list(getattr(settings, 'AZURE_RM_REGION_CHOICES', []))
regions.insert(0, ('all', 'All')) regions.insert(0, ('all', 'All'))
return regions return regions
@classmethod
def get_azure_rm_region_choices(self):
return InventorySourceOptions.get_azure_region_choices()
@classmethod @classmethod
def get_vmware_region_choices(self): def get_vmware_region_choices(self):
"""Return a complete list of regions in VMware, as a list of two-tuples """Return a complete list of regions in VMware, as a list of two-tuples

View File

@@ -1060,9 +1060,6 @@ class RunJob(BaseTask):
env['GCE_EMAIL'] = cloud_cred.username env['GCE_EMAIL'] = cloud_cred.username
env['GCE_PROJECT'] = cloud_cred.project env['GCE_PROJECT'] = cloud_cred.project
env['GCE_PEM_FILE_PATH'] = cred_files.get(cloud_cred, '') env['GCE_PEM_FILE_PATH'] = cred_files.get(cloud_cred, '')
elif cloud_cred and cloud_cred.kind == 'azure':
env['AZURE_SUBSCRIPTION_ID'] = cloud_cred.username
env['AZURE_CERT_PATH'] = cred_files.get(cloud_cred, '')
elif cloud_cred and cloud_cred.kind == 'azure_rm': elif cloud_cred and cloud_cred.kind == 'azure_rm':
if len(cloud_cred.client) and len(cloud_cred.tenant): if len(cloud_cred.client) and len(cloud_cred.tenant):
env['AZURE_CLIENT_ID'] = cloud_cred.client env['AZURE_CLIENT_ID'] = cloud_cred.client
@@ -1633,8 +1630,8 @@ class RunInventoryUpdate(BaseTask):
If no private data is needed, return None. If no private data is needed, return None.
""" """
private_data = {'credentials': {}} private_data = {'credentials': {}}
# If this is Microsoft Azure or GCE, return the RSA key # If this is GCE, return the RSA key
if inventory_update.source in ('azure', 'gce'): if inventory_update.source == 'gce':
credential = inventory_update.credential credential = inventory_update.credential
private_data['credentials'][credential] = decrypt_field(credential, 'ssh_key_data') private_data['credentials'][credential] = decrypt_field(credential, 'ssh_key_data')
return private_data return private_data
@@ -1864,9 +1861,6 @@ class RunInventoryUpdate(BaseTask):
env['EC2_INI_PATH'] = cloud_credential env['EC2_INI_PATH'] = cloud_credential
elif inventory_update.source == 'vmware': elif inventory_update.source == 'vmware':
env['VMWARE_INI_PATH'] = cloud_credential env['VMWARE_INI_PATH'] = cloud_credential
elif inventory_update.source == 'azure':
env['AZURE_SUBSCRIPTION_ID'] = passwords.get('source_username', '')
env['AZURE_CERT_PATH'] = cloud_credential
elif inventory_update.source == 'azure_rm': elif inventory_update.source == 'azure_rm':
if len(passwords.get('source_client', '')) and \ if len(passwords.get('source_client', '')) and \
len(passwords.get('source_tenant', '')): len(passwords.get('source_tenant', '')):

View File

@@ -1071,43 +1071,6 @@ def test_gce_create_ok(post, organization, admin, version, params):
assert decrypt_field(cred, 'ssh_key_data') == EXAMPLE_PRIVATE_KEY assert decrypt_field(cred, 'ssh_key_data') == EXAMPLE_PRIVATE_KEY
#
# Azure Classic
#
@pytest.mark.django_db
@pytest.mark.parametrize('version, params', [
['v1', {
'kind': 'azure',
'name': 'Best credential ever',
'username': 'some_username',
'ssh_key_data': EXAMPLE_PRIVATE_KEY
}],
['v2', {
'credential_type': 1,
'name': 'Best credential ever',
'inputs': {
'username': 'some_username',
'ssh_key_data': EXAMPLE_PRIVATE_KEY
}
}]
])
def test_azure_create_ok(post, organization, admin, version, params):
azure = CredentialType.defaults['azure']()
azure.save()
params['organization'] = organization.id
response = post(
reverse('api:credential_list', kwargs={'version': version}),
params,
admin
)
assert response.status_code == 201
assert Credential.objects.count() == 1
cred = Credential.objects.all()[:1].get()
assert cred.inputs['username'] == 'some_username'
assert decrypt_field(cred, 'ssh_key_data') == EXAMPLE_PRIVATE_KEY
# #
# Azure Resource Manager # Azure Resource Manager
# #

View File

@@ -19,7 +19,6 @@ EXAMPLE_ENCRYPTED_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nProc-Type: 4,ENCRY
def test_default_cred_types(): def test_default_cred_types():
assert sorted(CredentialType.defaults.keys()) == [ assert sorted(CredentialType.defaults.keys()) == [
'aws', 'aws',
'azure',
'azure_rm', 'azure_rm',
'cloudforms', 'cloudforms',
'gce', 'gce',

View File

@@ -269,22 +269,6 @@ def test_gce_migration():
assert Credential.objects.count() == 1 assert Credential.objects.count() == 1
@pytest.mark.django_db
def test_azure_classic_migration():
cred = Credential(name='My Credential')
with migrate(cred, 'azure'):
cred.__dict__.update({
'username': 'bob',
'ssh_key_data': EXAMPLE_PRIVATE_KEY
})
assert cred.credential_type.name == 'Microsoft Azure Classic (deprecated)'
assert cred.inputs['username'] == 'bob'
assert cred.inputs['ssh_key_data'].startswith('$encrypted$')
assert decrypt_field(cred, 'ssh_key_data') == EXAMPLE_PRIVATE_KEY
assert Credential.objects.count() == 1
@pytest.mark.django_db @pytest.mark.django_db
def test_azure_rm_migration(): def test_azure_rm_migration():
cred = Credential(name='My Credential') cred = Credential(name='My Credential')

View File

@@ -35,3 +35,13 @@ def test_inv_src_rename(inventory_source_factory):
inv_src01.refresh_from_db() inv_src01.refresh_from_db()
# inv-is-t1 is generated in the inventory_source_factory # inv-is-t1 is generated in the inventory_source_factory
assert inv_src01.name == 't1 - inv-is-t1 - 0' assert inv_src01.name == 't1 - inv-is-t1 - 0'
@pytest.mark.django_db
def test_azure_inv_src_removal(inventory_source):
inventory_source.source = 'azure'
inventory_source.save()
assert InventorySource.objects.filter(pk=inventory_source.pk).exists()
invsrc.remove_azure_inventory_sources(apps, None)
assert not InventorySource.objects.filter(pk=inventory_source.pk).exists()

View File

@@ -544,29 +544,6 @@ class TestJobCredentials(TestJobExecution):
self.run_pexpect.side_effect = run_pexpect_side_effect self.run_pexpect.side_effect = run_pexpect_side_effect
self.task.run(self.pk) self.task.run(self.pk)
def test_azure_credentials(self):
azure = CredentialType.defaults['azure']()
credential = Credential(
pk=1,
credential_type=azure,
inputs = {
'username': 'bob',
'ssh_key_data': self.EXAMPLE_PRIVATE_KEY
}
)
credential.inputs['ssh_key_data'] = encrypt_field(credential, 'ssh_key_data')
self.instance.extra_credentials.add(credential)
def run_pexpect_side_effect(*args, **kwargs):
args, cwd, env, stdout = args
assert env['AZURE_SUBSCRIPTION_ID'] == 'bob'
ssh_key_data = env['AZURE_CERT_PATH']
assert open(ssh_key_data, 'rb').read() == self.EXAMPLE_PRIVATE_KEY
return ['successful', 0]
self.run_pexpect.side_effect = run_pexpect_side_effect
self.task.run(self.pk)
def test_azure_rm_with_tenant(self): def test_azure_rm_with_tenant(self):
azure = CredentialType.defaults['azure_rm']() azure = CredentialType.defaults['azure_rm']()
credential = Credential( credential = Credential(
@@ -1038,29 +1015,25 @@ class TestJobCredentials(TestJobExecution):
gce_credential.inputs['ssh_key_data'] = encrypt_field(gce_credential, 'ssh_key_data') gce_credential.inputs['ssh_key_data'] = encrypt_field(gce_credential, 'ssh_key_data')
self.instance.extra_credentials.add(gce_credential) self.instance.extra_credentials.add(gce_credential)
azure = CredentialType.defaults['azure']() azure_rm = CredentialType.defaults['azure_rm']()
azure_credential = Credential( azure_rm_credential = Credential(
pk=2, pk=2,
credential_type=azure, credential_type=azure_rm,
inputs = { inputs = {
'username': 'joe', 'subscription': 'some-subscription',
'ssh_key_data': 'AZURE: %s' % self.EXAMPLE_PRIVATE_KEY 'username': 'bob',
'password': 'secret'
} }
) )
azure_credential.inputs['ssh_key_data'] = encrypt_field(azure_credential, 'ssh_key_data') azure_rm_credential.inputs['secret'] = encrypt_field(azure_rm_credential, 'secret')
self.instance.extra_credentials.add(azure_credential) self.instance.extra_credentials.add(azure_rm_credential)
def run_pexpect_side_effect(*args, **kwargs): def run_pexpect_side_effect(*args, **kwargs):
args, cwd, env, stdout = args args, cwd, env, stdout = args
assert env['GCE_EMAIL'] == 'bob' assert env['AZURE_SUBSCRIPTION_ID'] == 'some-subscription'
assert env['GCE_PROJECT'] == 'some-project' assert env['AZURE_AD_USER'] == 'bob'
ssh_key_data = env['GCE_PEM_FILE_PATH'] assert env['AZURE_PASSWORD'] == 'secret'
assert open(ssh_key_data, 'rb').read() == 'GCE: %s' % self.EXAMPLE_PRIVATE_KEY
assert env['AZURE_SUBSCRIPTION_ID'] == 'joe'
ssh_key_data = env['AZURE_CERT_PATH']
assert open(ssh_key_data, 'rb').read() == 'AZURE: %s' % self.EXAMPLE_PRIVATE_KEY
return ['successful', 0] return ['successful', 0]
@@ -1278,31 +1251,6 @@ class TestInventoryUpdateCredentials(TestJobExecution):
self.run_pexpect.side_effect = run_pexpect_side_effect self.run_pexpect.side_effect = run_pexpect_side_effect
self.task.run(self.pk) self.task.run(self.pk)
def test_azure_source(self):
azure = CredentialType.defaults['azure']()
self.instance.source = 'azure'
self.instance.credential = Credential(
pk=1,
credential_type=azure,
inputs = {
'username': 'bob',
'ssh_key_data': self.EXAMPLE_PRIVATE_KEY
}
)
self.instance.credential.inputs['ssh_key_data'] = encrypt_field(
self.instance.credential, 'ssh_key_data'
)
def run_pexpect_side_effect(*args, **kwargs):
args, cwd, env, stdout = args
assert env['AZURE_SUBSCRIPTION_ID'] == 'bob'
ssh_key_data = env['AZURE_CERT_PATH']
assert open(ssh_key_data, 'rb').read() == self.EXAMPLE_PRIVATE_KEY
return ['successful', 0]
self.run_pexpect.side_effect = run_pexpect_side_effect
self.task.run(self.pk)
def test_gce_source(self): def test_gce_source(self):
gce = CredentialType.defaults['gce']() gce = CredentialType.defaults['gce']()
self.instance.source = 'gce' self.instance.source = 'gce'

View File

@@ -1,284 +0,0 @@
#!/usr/bin/env python
'''
Windows Azure external inventory script
=======================================
Generates inventory that Ansible can understand by making API request to
Windows Azure using the azure python library.
NOTE: This script assumes Ansible is being executed where azure is already
installed.
pip install azure
Adapted from the ansible Linode plugin by Dan Slimmon.
'''
# (c) 2013, John Whitbeck
#
# This file is part of Ansible,
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
######################################################################
# Standard imports
import re
import sys
import argparse
import os
from urlparse import urlparse
from time import time
try:
import json
except ImportError:
import simplejson as json
try:
from azure.servicemanagement import ServiceManagementService
except ImportError as e:
sys.exit("ImportError: {0}".format(str(e)))
# Imports for ansible
import ConfigParser
class AzureInventory(object):
def __init__(self):
"""Main execution path."""
# Inventory grouped by display group
self.inventory = {}
# Index of deployment name -> host
self.index = {}
self.host_metadata = {}
# Cache setting defaults.
# These can be overridden in settings (see `read_settings`).
cache_dir = os.path.expanduser('~')
self.cache_path_cache = os.path.join(cache_dir, '.ansible-azure.cache')
self.cache_path_index = os.path.join(cache_dir, '.ansible-azure.index')
self.cache_max_age = 0
# Read settings and parse CLI arguments
self.read_settings()
self.read_environment()
self.parse_cli_args()
# Initialize Azure ServiceManagementService
self.sms = ServiceManagementService(self.subscription_id, self.cert_path)
# Cache
if self.args.refresh_cache:
self.do_api_calls_update_cache()
elif not self.is_cache_valid():
self.do_api_calls_update_cache()
if self.args.list_images:
data_to_print = self.json_format_dict(self.get_images(), True)
elif self.args.list or self.args.host:
# Display list of nodes for inventory
if len(self.inventory) == 0:
data = json.loads(self.get_inventory_from_cache())
else:
data = self.inventory
if self.args.host:
data_to_print = self.get_host(self.args.host)
else:
# Add the `['_meta']['hostvars']` information.
hostvars = {}
if len(data) > 0:
for host in set([h for hosts in data.values() for h in hosts if h]):
hostvars[host] = self.get_host(host, jsonify=False)
data['_meta'] = {'hostvars': hostvars}
# JSONify the data.
data_to_print = self.json_format_dict(data, pretty=True)
print(data_to_print)
def get_host(self, hostname, jsonify=True):
"""Return information about the given hostname, based on what
the Windows Azure API provides.
"""
if hostname not in self.host_metadata:
return "No host found: %s" % json.dumps(self.host_metadata)
if jsonify:
return json.dumps(self.host_metadata[hostname])
return self.host_metadata[hostname]
def get_images(self):
images = []
for image in self.sms.list_os_images():
if str(image.label).lower().find(self.args.list_images.lower()) >= 0:
images.append(vars(image))
return json.loads(json.dumps(images, default=lambda o: o.__dict__))
def is_cache_valid(self):
"""Determines if the cache file has expired, or if it is still valid."""
if os.path.isfile(self.cache_path_cache):
mod_time = os.path.getmtime(self.cache_path_cache)
current_time = time()
if (mod_time + self.cache_max_age) > current_time:
if os.path.isfile(self.cache_path_index):
return True
return False
def read_settings(self):
"""Reads the settings from the .ini file."""
config = ConfigParser.SafeConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__)) + '/windows_azure.ini')
# Credentials related
if config.has_option('azure', 'subscription_id'):
self.subscription_id = config.get('azure', 'subscription_id')
if config.has_option('azure', 'cert_path'):
self.cert_path = config.get('azure', 'cert_path')
# Cache related
if config.has_option('azure', 'cache_path'):
cache_path = os.path.expandvars(os.path.expanduser(config.get('azure', 'cache_path')))
self.cache_path_cache = os.path.join(cache_path, 'ansible-azure.cache')
self.cache_path_index = os.path.join(cache_path, 'ansible-azure.index')
if config.has_option('azure', 'cache_max_age'):
self.cache_max_age = config.getint('azure', 'cache_max_age')
def read_environment(self):
''' Reads the settings from environment variables '''
# Credentials
if os.getenv("AZURE_SUBSCRIPTION_ID"):
self.subscription_id = os.getenv("AZURE_SUBSCRIPTION_ID")
if os.getenv("AZURE_CERT_PATH"):
self.cert_path = os.getenv("AZURE_CERT_PATH")
def parse_cli_args(self):
"""Command line argument processing"""
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)')
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 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):
"""Do API calls, and save data in cache files."""
self.add_cloud_services()
self.write_to_cache(self.inventory, self.cache_path_cache)
self.write_to_cache(self.index, self.cache_path_index)
def add_cloud_services(self):
"""Makes an Azure API call to get the list of cloud services."""
try:
for cloud_service in self.sms.list_hosted_services():
self.add_deployments(cloud_service)
except Exception as e:
sys.exit("Error: Failed to access cloud services - {0}".format(str(e)))
def add_deployments(self, cloud_service):
"""Makes an Azure API call to get the list of virtual machines
associated with a cloud service.
"""
try:
for deployment in self.sms.get_hosted_service_properties(cloud_service.service_name,embed_detail=True).deployments.deployments:
self.add_deployment(cloud_service, deployment)
except Exception as e:
sys.exit("Error: Failed to access deployments - {0}".format(str(e)))
def add_deployment(self, cloud_service, deployment):
"""Adds a deployment to the inventory and index"""
for role in deployment.role_instance_list.role_instances:
try:
# Default port 22 unless port found with name 'SSH'
port = '22'
for ie in role.instance_endpoints.instance_endpoints:
if ie.name == 'SSH':
port = ie.public_port
break
except AttributeError as e:
pass
finally:
self.add_instance(role.instance_name, deployment, port, cloud_service, role.instance_status)
def add_instance(self, hostname, deployment, ssh_port, cloud_service, status):
"""Adds an instance to the inventory and index"""
dest = urlparse(deployment.url).hostname
# Add to index
self.index[hostname] = deployment.name
self.host_metadata[hostname] = dict(ansible_ssh_host=dest,
ansible_ssh_port=int(ssh_port),
instance_status=status,
private_id=deployment.private_id)
# List of all azure deployments
self.push(self.inventory, "azure", hostname)
# Inventory: Group by service name
self.push(self.inventory, self.to_safe(cloud_service.service_name), hostname)
if int(ssh_port) == 22:
self.push(self.inventory, "Cloud_services", hostname)
# Inventory: Group by region
self.push(self.inventory, self.to_safe(cloud_service.hosted_service_properties.location), hostname)
def push(self, my_dict, key, element):
"""Pushed an element onto an array that may not have been defined in the dict."""
if key in my_dict:
my_dict[key].append(element)
else:
my_dict[key] = [element]
def get_inventory_from_cache(self):
"""Reads the inventory from the cache file and returns it as a JSON object."""
cache = open(self.cache_path_cache, 'r')
json_inventory = cache.read()
return json_inventory
def load_index_from_cache(self):
"""Reads the index from the cache file and sets self.index."""
cache = open(self.cache_path_index, 'r')
json_index = cache.read()
self.index = json.loads(json_index)
def write_to_cache(self, data, filename):
"""Writes data in JSON format to a file."""
json_data = self.json_format_dict(data, True)
cache = open(filename, 'w')
cache.write(json_data)
cache.close()
def to_safe(self, word):
"""Escapes any characters that would be invalid in an ansible group name."""
return re.sub("[^A-Za-z0-9\-]", "_", word)
def json_format_dict(self, data, pretty=False):
"""Converts a dict to a JSON object and dumps it as a formatted string."""
if pretty:
return json.dumps(data, sort_keys=True, indent=2)
else:
return json.dumps(data)
AzureInventory()

View File

@@ -776,14 +776,12 @@ GCE_HOST_FILTER = r'^.+$'
GCE_EXCLUDE_EMPTY_GROUPS = True GCE_EXCLUDE_EMPTY_GROUPS = True
GCE_INSTANCE_ID_VAR = None GCE_INSTANCE_ID_VAR = None
# --------------------------------------
# ------------------- # -- Microsoft Azure Resource Manager --
# -- Microsoft Azure -- # --------------------------------------
# -------------------
# 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.
AZURE_REGION_CHOICES = [ AZURE_RM_REGION_CHOICES = [
('eastus', _('US East')), ('eastus', _('US East')),
('eastus2', _('US East 2')), ('eastus2', _('US East 2')),
('centralus', _('US Central')), ('centralus', _('US Central')),
@@ -810,23 +808,8 @@ AZURE_REGION_CHOICES = [
('koreacentral', _('Korea Central')), ('koreacentral', _('Korea Central')),
('koreasouth', _('Korea South')), ('koreasouth', _('Korea South')),
] ]
AZURE_REGIONS_BLACKLIST = [] AZURE_RM_REGIONS_BLACKLIST = []
# Inventory variable name/value for determining whether a host is active
# in Microsoft Azure.
AZURE_ENABLED_VAR = 'instance_status'
AZURE_ENABLED_VALUE = 'ReadyRole'
# Filter for allowed group and host names when importing inventory from
# Microsoft Azure.
AZURE_GROUP_FILTER = r'^.+$'
AZURE_HOST_FILTER = r'^.+$'
AZURE_EXCLUDE_EMPTY_GROUPS = True
AZURE_INSTANCE_ID_VAR = 'private_id'
# --------------------------------------
# -- Microsoft Azure Resource Manager --
# --------------------------------------
AZURE_RM_GROUP_FILTER = r'^.+$' AZURE_RM_GROUP_FILTER = r'^.+$'
AZURE_RM_HOST_FILTER = r'^.+$' AZURE_RM_HOST_FILTER = r'^.+$'
AZURE_RM_ENABLED_VAR = 'powerstate' AZURE_RM_ENABLED_VAR = 'powerstate'

View File

@@ -14,7 +14,7 @@
# W391 - Blank line at end of file # W391 - Blank line at end of file
# W293 - Blank line contains whitespace # W293 - Blank line contains whitespace
ignore=E201,E203,E221,E225,E231,E241,E251,E261,E265,E303,E501,W291,W391,W293 ignore=E201,E203,E221,E225,E231,E241,E251,E261,E265,E303,E501,W291,W391,W293
exclude=.tox,venv,awx/lib/site-packages,awx/plugins/inventory/ec2.py,awx/plugins/inventory/gce.py,awx/plugins/inventory/vmware.py,awx/plugins/inventory/windows_azure.py,awx/plugins/inventory/openstack.py,awx/ui,awx/api/urls.py,awx/main/migrations,awx/main/south_migrations,awx/main/tests/data,installer/openshift/settings.py exclude=.tox,venv,awx/lib/site-packages,awx/plugins/inventory/ec2.py,awx/plugins/inventory/gce.py,awx/plugins/inventory/vmware.py,awx/plugins/inventory/openstack.py,awx/ui,awx/api/urls.py,awx/main/migrations,awx/main/south_migrations,awx/main/tests/data,installer/openshift/settings.py
[flake8] [flake8]
ignore=E201,E203,E221,E225,E231,E241,E251,E261,E265,E303,E501,W291,W391,W293,E731,F405 ignore=E201,E203,E221,E225,E231,E241,E251,E261,E265,E303,E501,W291,W391,W293,E731,F405