initial cutover to API profiles (#35538)

* hardcoded API profiles in azure_rm_common
* changed azure_rm_securitygroup module to use api_profiles, dynamic models, kwargs on all SDK methods
* changed azure_rm_containerinstance module to use api_profiles, dynamic models, kwargs on all SDK methods
* fixed polling performance issue in azure_rm_securitygroup (default poll interval was 30s)
This commit is contained in:
Matt Davis 2018-02-09 14:26:42 -08:00 committed by GitHub
parent fd33dc6cd1
commit 26f551d1c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 171 additions and 91 deletions

View file

@ -1,25 +1,11 @@
# Copyright (c) 2016 Matt Davis, <mdavis@ansible.com>
# Chris Houseknecht, <house@redhat.com>
#
# 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/>.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
import json
import os
import re
import sys
import types
import copy
import inspect
import traceback
@ -47,7 +33,8 @@ AZURE_COMMON_ARGS = dict(
ad_user=dict(type='str', no_log=True),
password=dict(type='str', no_log=True),
cloud_environment=dict(type='str'),
cert_validation_mode=dict(type='str', choices=['validate', 'ignore'])
cert_validation_mode=dict(type='str', choices=['validate', 'ignore']),
api_profile=dict(type='str', default='latest')
# debug=dict(type='bool', default=False),
)
@ -63,6 +50,31 @@ AZURE_CREDENTIAL_ENV_MAPPING = dict(
cert_validation_mode='AZURE_CERT_VALIDATION_MODE',
)
# FUTURE: this should come from the SDK or an external location.
# For now, we have to copy from azure-cli
AZURE_API_PROFILES = {
'latest': {
'ContainerInstanceManagementClient': '2018-02-01-preview',
'ComputeManagementClient': dict(
default_api_version='2017-12-01',
resource_skus='2017-09-01',
disks='2017-03-30',
snapshots='2017-03-30',
virtual_machine_run_commands='2017-03-30'
),
'NetworkManagementClient': '2017-11-01',
'ResourceManagementClient': '2017-05-10',
'StorageManagementClient': '2017-10-01'
},
'2017-03-09-profile': {
'ComputeManagementClient': '2016-03-30',
'NetworkManagementClient': '2015-06-15',
'ResourceManagementClient': '2016-02-01',
'StorageManagementClient': '2016-01-01'
}
}
AZURE_TAG_ARGS = dict(
tags=dict(type='dict'),
append_tags=dict(type='bool', default=True),
@ -162,36 +174,36 @@ def format_resource_id(val, subscription_id, namespace, types, resource_group):
type=types,
subscription=subscription_id) if not is_valid_resource_id(val) else val
# FUTURE: either get this from the requirements file (if we can be sure it's always available at runtime)
# or generate the requirements files from this so we only have one source of truth to maintain...
AZURE_PKG_VERSIONS = {
StorageManagementClient.__name__: {
'StorageManagementClient': {
'package_name': 'storage',
'expected_version': '1.5.0',
'installed_version': storage_client_version
'expected_version': '1.5.0'
},
ComputeManagementClient.__name__: {
'ComputeManagementClient': {
'package_name': 'compute',
'expected_version': '2.0.0',
'installed_version': compute_client_version
'expected_version': '2.0.0'
},
NetworkManagementClient.__name__: {
'ContainerInstanceManagementClient': {
'package_name': 'containerinstance',
'expected_version': '0.3.1'
},
'NetworkManagementClient': {
'package_name': 'network',
'expected_version': '1.3.0',
'installed_version': network_client_version
'expected_version': '1.3.0'
},
ResourceManagementClient.__name__: {
'ResourceManagementClient': {
'package_name': 'resource',
'expected_version': '1.1.0',
'installed_version': resource_client_version
'expected_version': '1.1.0'
},
DnsManagementClient.__name__: {
'DnsManagementClient': {
'package_name': 'dns',
'expected_version': '1.0.1',
'installed_version': dns_client_version
'expected_version': '1.0.1'
},
WebSiteManagementClient.__name__: {
'WebSiteManagementClient': {
'package_name': 'web',
'expected_version': '0.32.0',
'installed_version': web_client_version
'expected_version': '0.32.0'
},
} if HAS_AZURE else {}
@ -250,6 +262,7 @@ class AzureRMModuleBase(object):
self._containerservice_client = None
self.check_mode = self.module.check_mode
self.api_profile = self.module.params.get('api_profile')
self.facts_module = facts_module
# self.debug = self.module.params.get('debug')
@ -337,10 +350,15 @@ class AzureRMModuleBase(object):
package_version = AZURE_PKG_VERSIONS.get(client_type.__name__, None)
if package_version is not None:
client_name = package_version.get('package_name')
client_version = package_version.get('installed_version')
try:
client_module = importlib.import_module(client_type.__module__)
client_version = client_module.VERSION
except RuntimeError:
# can't get at the module version for some reason, just fail silently...
return
expected_version = package_version.get('expected_version')
if Version(client_version) < Version(expected_version):
self.fail("Installed {0} client version is {1}. The supported version is {2}. Try "
self.fail("Installed azure-mgmt-{0} client version is {1}. The supported version is {2}. Try "
"`pip install ansible[azure]`".format(client_name, client_version, expected_version))
def exec_module(self, **kwargs):
@ -767,18 +785,65 @@ class AzureRMModuleBase(object):
def _validation_ignore_callback(session, global_config, local_config, **kwargs):
session.verify = False
def get_api_profile(self, client_type_name, api_profile_name):
profile_all_clients = AZURE_API_PROFILES.get(api_profile_name)
if not profile_all_clients:
raise KeyError("unknown Azure API profile: {0}".format(api_profile_name))
profile_raw = profile_all_clients.get(client_type_name, None)
if not profile_raw:
self.module.warn("Azure API profile {0} does not define an entry for {1}".format(api_profile_name, client_type_name))
if isinstance(profile_raw, dict):
if not profile_raw.get('default_api_version'):
raise KeyError("Azure API profile {0} does not define 'default_api_version'".format(api_profile_name))
return profile_raw
# wrap basic strings in a dict that just defines the default
return dict(default_api_version=profile_raw)
def get_mgmt_svc_client(self, client_type, base_url=None, api_version=None):
self.log('Getting management service client {0}'.format(client_type.__name__))
self.check_client_version(client_type)
if api_version:
client = client_type(self.azure_credentials,
self.subscription_id,
api_version=api_version,
base_url=base_url)
else:
client = client_type(self.azure_credentials,
self.subscription_id,
base_url=base_url)
client_argspec = inspect.getargspec(client_type.__init__)
client_kwargs = dict(credentials=self.azure_credentials, subscription_id=self.subscription_id, base_url=base_url)
api_profile_dict = {}
if self.api_profile:
api_profile_dict = self.get_api_profile(client_type.__name__, self.api_profile)
if not base_url:
# most things are resource_manager, don't make everyone specify
base_url = self._cloud_environment.endpoints.resource_manager
# unversioned clients won't accept profile; only send it if necessary
# clients without a version specified in the profile will use the default
if api_profile_dict and 'profile' in client_argspec.args:
client_kwargs['profile'] = api_profile_dict
# If the client doesn't accept api_version, it's unversioned.
# If it does, favor explicitly-specified api_version, fall back to api_profile
if 'api_version' in client_argspec.args:
profile_default_version = api_profile_dict.get('default_api_version', None)
if api_version or profile_default_version:
client_kwargs['api_version'] = api_version or profile_default_version
client = client_type(**client_kwargs)
# FUTURE: remove this once everything exposes models directly (eg, containerinstance)
try:
getattr(client, "models")
except AttributeError:
def _ansible_get_models(self, *arg, **kwarg):
return self._ansible_models
setattr(client, '_ansible_models', importlib.import_module(client_type.__module__).models)
client.models = types.MethodType(_ansible_get_models, client)
# Add user agent for Ansible
client.config.add_user_agent(ANSIBLE_USER_AGENT)