mirror of https://github.com/ansible/ansible.git
Rename azure_deploy to azure_rm_deployment. Refactor to use azure_common.
parent
12ca251c86
commit
1d7a109867
@ -1,620 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# 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/>.
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: azure_deployment
|
||||
short_description: Create or destroy Azure Resource Manager template deployments
|
||||
version_added: "2.1"
|
||||
description:
|
||||
- Create or destroy Azure Resource Manager template deployments via the Azure SDK for Python.
|
||||
You can find some quick start templates in GitHub here https://github.com/azure/azure-quickstart-templates.
|
||||
If you would like to find out more information about Azure Resource Manager templates, see https://azure.microsoft.com/en-us/documentation/articles/resource-group-template-deploy/.
|
||||
options:
|
||||
subscription_id:
|
||||
description:
|
||||
- The Azure subscription to deploy the template into.
|
||||
required: true
|
||||
resource_group_name:
|
||||
description:
|
||||
- The resource group name to use or create to host the deployed template
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- If state is "present", template will be created. If state is "present" and if deployment exists, it will be updated.
|
||||
If state is "absent", stack will be removed.
|
||||
required: true
|
||||
template:
|
||||
description:
|
||||
- A hash containg the templates inline. This parameter is mutually exclusive with 'template_link'.
|
||||
Either one of them is required if "state" parameter is "present".
|
||||
required: false
|
||||
default: None
|
||||
template_link:
|
||||
description:
|
||||
- Uri of file containing the template body. This parameter is mutually exclusive with 'template'. Either one
|
||||
of them is required if "state" parameter is "present".
|
||||
required: false
|
||||
default: None
|
||||
parameters:
|
||||
description:
|
||||
- A hash of all the required template variables for the deployment template. This parameter is mutually exclusive with 'parameters_link'.
|
||||
Either one of them is required if "state" parameter is "present".
|
||||
required: false
|
||||
default: None
|
||||
parameters_link:
|
||||
description:
|
||||
- Uri of file containing the parameters body. This parameter is mutually exclusive with 'parameters'. Either
|
||||
one of them is required if "state" parameter is "present".
|
||||
required: false
|
||||
default: None
|
||||
location:
|
||||
description:
|
||||
- The geo-locations in which the resource group will be located.
|
||||
require: false
|
||||
default: West US
|
||||
|
||||
author: "David Justice (@devigned) / Laurent Mazuel (@lmazuel) / Andre Price (@obsoleted)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Destroy a template deployment
|
||||
- name: Destroy Azure Deploy
|
||||
azure_deploy:
|
||||
state: absent
|
||||
subscription_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
resource_group_name: dev-ops-cle
|
||||
|
||||
# Create or update a template deployment based on uris to paramters and a template
|
||||
- name: Create Azure Deploy
|
||||
azure_deploy:
|
||||
state: present
|
||||
subscription_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
resource_group_name: dev-ops-cle
|
||||
parameters_link: 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-simple-linux-vm/azuredeploy.parameters.json'
|
||||
template_link: 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-simple-linux-vm/azuredeploy.json'
|
||||
|
||||
# Create or update a template deployment based on a uri to the template and parameters specified inline.
|
||||
# This deploys a VM with SSH support for a given public key, then stores the result in 'azure_vms'. The result is then used
|
||||
# to create a new host group. This host group is then used to wait for each instance to respond to the public IP SSH.
|
||||
---
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
- name: Destroy Azure Deploy
|
||||
azure_deployment:
|
||||
state: absent
|
||||
subscription_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
resource_group_name: dev-ops-cle
|
||||
|
||||
- name: Create Azure Deploy
|
||||
azure_deployment:
|
||||
state: present
|
||||
subscription_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
resource_group_name: dev-ops-cle
|
||||
parameters:
|
||||
newStorageAccountName:
|
||||
value: devopsclestorage1
|
||||
adminUsername:
|
||||
value: devopscle
|
||||
dnsNameForPublicIP:
|
||||
value: devopscleazure
|
||||
location:
|
||||
value: West US
|
||||
vmSize:
|
||||
value: Standard_A2
|
||||
vmName:
|
||||
value: ansibleSshVm
|
||||
sshKeyData:
|
||||
value: YOUR_SSH_PUBLIC_KEY
|
||||
template_link: 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-vm-sshkey/azuredeploy.json'
|
||||
register: azure
|
||||
- name: Add new instance to host group
|
||||
add_host: hostname={{ item['ips'][0].public_ip }} groupname=azure_vms
|
||||
with_items: azure.instances
|
||||
|
||||
- hosts: azure_vms
|
||||
user: devopscle
|
||||
tasks:
|
||||
- name: Wait for SSH to come up
|
||||
wait_for: port=22 timeout=2000 state=started
|
||||
- name: echo the hostname of the vm
|
||||
shell: hostname
|
||||
|
||||
# Deploy an Azure WebApp running a hello world'ish node app
|
||||
- name: Create Azure WebApp Deployment at http://devopscleweb.azurewebsites.net/hello.js
|
||||
azure_deployment:
|
||||
state: present
|
||||
subscription_id: cbbdaed0-fea9-4693-bf0c-d446ac93c030
|
||||
resource_group_name: dev-ops-cle-webapp
|
||||
parameters:
|
||||
repoURL:
|
||||
value: 'https://github.com/devigned/az-roadshow-oss.git'
|
||||
siteName:
|
||||
value: devopscleweb
|
||||
hostingPlanName:
|
||||
value: someplan
|
||||
siteLocation:
|
||||
value: westus
|
||||
sku:
|
||||
value: Standard
|
||||
template_link: 'https://raw.githubusercontent.com/azure/azure-quickstart-templates/master/201-web-app-github-deploy/azuredeploy.json'
|
||||
|
||||
# Create or update a template deployment based on an inline template and parameters
|
||||
- name: Create Azure Deploy
|
||||
azure_deploy:
|
||||
state: present
|
||||
subscription_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
resource_group_name: dev-ops-cle
|
||||
|
||||
template:
|
||||
$schema: "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#"
|
||||
contentVersion: "1.0.0.0"
|
||||
parameters:
|
||||
newStorageAccountName:
|
||||
type: "string"
|
||||
metadata:
|
||||
description: "Unique DNS Name for the Storage Account where the Virtual Machine's disks will be placed."
|
||||
adminUsername:
|
||||
type: "string"
|
||||
metadata:
|
||||
description: "User name for the Virtual Machine."
|
||||
adminPassword:
|
||||
type: "securestring"
|
||||
metadata:
|
||||
description: "Password for the Virtual Machine."
|
||||
dnsNameForPublicIP:
|
||||
type: "string"
|
||||
metadata:
|
||||
description: "Unique DNS Name for the Public IP used to access the Virtual Machine."
|
||||
ubuntuOSVersion:
|
||||
type: "string"
|
||||
defaultValue: "14.04.2-LTS"
|
||||
allowedValues:
|
||||
- "12.04.5-LTS"
|
||||
- "14.04.2-LTS"
|
||||
- "15.04"
|
||||
metadata:
|
||||
description: "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version. Allowed values: 12.04.5-LTS, 14.04.2-LTS, 15.04."
|
||||
variables:
|
||||
location: "West US"
|
||||
imagePublisher: "Canonical"
|
||||
imageOffer: "UbuntuServer"
|
||||
OSDiskName: "osdiskforlinuxsimple"
|
||||
nicName: "myVMNic"
|
||||
addressPrefix: "10.0.0.0/16"
|
||||
subnetName: "Subnet"
|
||||
subnetPrefix: "10.0.0.0/24"
|
||||
storageAccountType: "Standard_LRS"
|
||||
publicIPAddressName: "myPublicIP"
|
||||
publicIPAddressType: "Dynamic"
|
||||
vmStorageAccountContainerName: "vhds"
|
||||
vmName: "MyUbuntuVM"
|
||||
vmSize: "Standard_D1"
|
||||
virtualNetworkName: "MyVNET"
|
||||
vnetID: "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]"
|
||||
subnetRef: "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]"
|
||||
resources:
|
||||
-
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
name: "[parameters('newStorageAccountName')]"
|
||||
apiVersion: "2015-05-01-preview"
|
||||
location: "[variables('location')]"
|
||||
properties:
|
||||
accountType: "[variables('storageAccountType')]"
|
||||
-
|
||||
apiVersion: "2015-05-01-preview"
|
||||
type: "Microsoft.Network/publicIPAddresses"
|
||||
name: "[variables('publicIPAddressName')]"
|
||||
location: "[variables('location')]"
|
||||
properties:
|
||||
publicIPAllocationMethod: "[variables('publicIPAddressType')]"
|
||||
dnsSettings:
|
||||
domainNameLabel: "[parameters('dnsNameForPublicIP')]"
|
||||
-
|
||||
type: "Microsoft.Network/virtualNetworks"
|
||||
apiVersion: "2015-05-01-preview"
|
||||
name: "[variables('virtualNetworkName')]"
|
||||
location: "[variables('location')]"
|
||||
properties:
|
||||
addressSpace:
|
||||
addressPrefixes:
|
||||
- "[variables('addressPrefix')]"
|
||||
subnets:
|
||||
-
|
||||
name: "[variables('subnetName')]"
|
||||
properties:
|
||||
addressPrefix: "[variables('subnetPrefix')]"
|
||||
-
|
||||
type: "Microsoft.Network/networkInterfaces"
|
||||
apiVersion: "2015-05-01-preview"
|
||||
name: "[variables('nicName')]"
|
||||
location: "[variables('location')]"
|
||||
dependsOn:
|
||||
- "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]"
|
||||
- "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
properties:
|
||||
ipConfigurations:
|
||||
-
|
||||
name: "ipconfig1"
|
||||
properties:
|
||||
privateIPAllocationMethod: "Dynamic"
|
||||
publicIPAddress:
|
||||
id: "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
|
||||
subnet:
|
||||
id: "[variables('subnetRef')]"
|
||||
-
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
apiVersion: "2015-06-15"
|
||||
name: "[variables('vmName')]"
|
||||
location: "[variables('location')]"
|
||||
dependsOn:
|
||||
- "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]"
|
||||
- "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
|
||||
properties:
|
||||
hardwareProfile:
|
||||
vmSize: "[variables('vmSize')]"
|
||||
osProfile:
|
||||
computername: "[variables('vmName')]"
|
||||
adminUsername: "[parameters('adminUsername')]"
|
||||
adminPassword: "[parameters('adminPassword')]"
|
||||
storageProfile:
|
||||
imageReference:
|
||||
publisher: "[variables('imagePublisher')]"
|
||||
offer: "[variables('imageOffer')]"
|
||||
sku: "[parameters('ubuntuOSVersion')]"
|
||||
version: "latest"
|
||||
osDisk:
|
||||
name: "osdisk"
|
||||
vhd:
|
||||
uri: "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]"
|
||||
caching: "ReadWrite"
|
||||
createOption: "FromImage"
|
||||
networkProfile:
|
||||
networkInterfaces:
|
||||
-
|
||||
id: "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
|
||||
diagnosticsProfile:
|
||||
bootDiagnostics:
|
||||
enabled: "true"
|
||||
storageUri: "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net')]"
|
||||
parameters:
|
||||
newStorageAccountName:
|
||||
value: devopsclestorage
|
||||
adminUsername:
|
||||
value: devopscle
|
||||
adminPassword:
|
||||
value: Password1!
|
||||
dnsNameForPublicIP:
|
||||
value: devopscleazure
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
try:
|
||||
import time
|
||||
import yaml
|
||||
from itertools import chain
|
||||
from azure.common.credentials import ServicePrincipalCredentials
|
||||
from azure.common.exceptions import CloudError
|
||||
from azure.mgmt.resource.resources.models import (
|
||||
DeploymentProperties,
|
||||
ParametersLink,
|
||||
TemplateLink,
|
||||
Deployment,
|
||||
ResourceGroup,
|
||||
Dependency
|
||||
)
|
||||
from azure.mgmt.resource.resources import ResourceManagementClient, ResourceManagementClientConfiguration
|
||||
from azure.mgmt.network import NetworkManagementClient, NetworkManagementClientConfiguration
|
||||
|
||||
HAS_DEPS = True
|
||||
except ImportError:
|
||||
HAS_DEPS = False
|
||||
|
||||
AZURE_URL = "https://management.azure.com"
|
||||
|
||||
|
||||
def get_azure_connection_info(module):
|
||||
azure_url = module.params.get('azure_url')
|
||||
tenant_id = module.params.get('tenant_id')
|
||||
client_id = module.params.get('client_id')
|
||||
client_secret = module.params.get('client_secret')
|
||||
resource_group_name = module.params.get('resource_group_name')
|
||||
subscription_id = module.params.get('subscription_id')
|
||||
|
||||
if not azure_url:
|
||||
if 'AZURE_URL' in os.environ:
|
||||
azure_url = os.environ['AZURE_URL']
|
||||
else:
|
||||
azure_url = None
|
||||
|
||||
if not subscription_id:
|
||||
if 'AZURE_SUBSCRIPTION_ID' in os.environ:
|
||||
subscription_id = os.environ['AZURE_SUBSCRIPTION_ID']
|
||||
else:
|
||||
subscription_id = None
|
||||
|
||||
if not resource_group_name:
|
||||
if 'AZURE_RESOURCE_GROUP_NAME' in os.environ:
|
||||
resource_group_name = os.environ['AZURE_RESOURCE_GROUP_NAME']
|
||||
else:
|
||||
resource_group_name = None
|
||||
|
||||
if not tenant_id:
|
||||
if 'AZURE_TENANT_ID' in os.environ:
|
||||
tenant_id = os.environ['AZURE_TENANT_ID']
|
||||
elif 'AZURE_DOMAIN' in os.environ:
|
||||
tenant_id = os.environ['AZURE_DOMAIN']
|
||||
else:
|
||||
tenant_id = None
|
||||
|
||||
if not client_id:
|
||||
if 'AZURE_CLIENT_ID' in os.environ:
|
||||
client_id = os.environ['AZURE_CLIENT_ID']
|
||||
else:
|
||||
client_id = None
|
||||
|
||||
if not client_secret:
|
||||
if 'AZURE_CLIENT_SECRET' in os.environ:
|
||||
client_secret = os.environ['AZURE_CLIENT_SECRET']
|
||||
else:
|
||||
client_secret = None
|
||||
|
||||
return dict(azure_url=azure_url,
|
||||
tenant_id=tenant_id,
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
resource_group_name=resource_group_name,
|
||||
subscription_id=subscription_id)
|
||||
|
||||
|
||||
def build_deployment_body(module):
|
||||
"""
|
||||
Build the deployment body from the module parameters
|
||||
:param module: Ansible module containing the validated configuration for the deployment template
|
||||
:return: body as dict
|
||||
"""
|
||||
properties = dict(mode='Incremental')
|
||||
properties['templateLink'] = \
|
||||
dict(uri=module.params.get('template_link'),
|
||||
contentVersion=module.params.get('content_version'))
|
||||
|
||||
properties['parametersLink'] = \
|
||||
dict(uri=module.params.get('parameters_link'),
|
||||
contentVersion=module.params.get('content_version'))
|
||||
|
||||
return dict(properties=properties)
|
||||
|
||||
def get_failed_nested_operations(client, resource_group, current_operations):
|
||||
new_operations = []
|
||||
for operation in current_operations:
|
||||
if operation.properties.provisioning_state == 'Failed':
|
||||
new_operations.append(operation)
|
||||
if operation.properties.target_resource and 'Microsoft.Resources/deployments' in operation.properties.target_resource.id:
|
||||
nested_deployment = operation.properties.target_resource.resource_name
|
||||
nested_operations = client.deployment_operations.list(resource_group, nested_deployment)
|
||||
new_nested_operations = get_failed_nested_operations(client, resource_group, nested_operations)
|
||||
new_operations += new_nested_operations
|
||||
|
||||
return new_operations
|
||||
|
||||
def get_failed_deployment_operations(module, client, resource_group, deployment_name):
|
||||
operations = client.deployment_operations.list(resource_group, deployment_name)
|
||||
return [
|
||||
dict(
|
||||
id=op.id,
|
||||
operation_id=op.operation_id,
|
||||
status_code=op.properties.status_code,
|
||||
status_message=op.properties.status_message,
|
||||
target_resource = dict(
|
||||
id=op.properties.target_resource.id,
|
||||
resource_name=op.properties.target_resource.resource_name,
|
||||
resource_type=op.properties.target_resource.resource_type
|
||||
) if op.properties.target_resource else None,
|
||||
provisioning_state=op.properties.provisioning_state,
|
||||
)
|
||||
for op in get_failed_nested_operations(client, resource_group, operations)
|
||||
]
|
||||
|
||||
def deploy_template(module, client, conn_info):
|
||||
"""
|
||||
Deploy the targeted template and parameters
|
||||
:param module: Ansible module containing the validated configuration for the deployment template
|
||||
:param client: resource management client for azure
|
||||
:param conn_info: connection info needed
|
||||
:return:
|
||||
"""
|
||||
|
||||
deployment_name = conn_info["deployment_name"]
|
||||
group_name = conn_info["resource_group_name"]
|
||||
|
||||
deploy_parameter = DeploymentProperties()
|
||||
deploy_parameter.mode = module.params.get('deployment_mode')
|
||||
|
||||
if module.params.get('parameters_link') is None:
|
||||
deploy_parameter.parameters = module.params.get('parameters')
|
||||
else:
|
||||
parameters_link = ParametersLink(
|
||||
uri = module.params.get('parameters_link')
|
||||
)
|
||||
deploy_parameter.parameters_link = parameters_link
|
||||
|
||||
if module.params.get('template_link') is None:
|
||||
deploy_parameter.template = module.params.get('template')
|
||||
else:
|
||||
template_link = TemplateLink(
|
||||
uri = module.params.get('template_link')
|
||||
)
|
||||
deploy_parameter.template_link = template_link
|
||||
|
||||
params = ResourceGroup(location=module.params.get('location'), tags=module.params.get('tags'))
|
||||
try:
|
||||
client.resource_groups.create_or_update(group_name, params)
|
||||
result = client.deployments.create_or_update(group_name, deployment_name, deploy_parameter)
|
||||
deployment_result = result.result() # Blocking wait, return the Deployment object
|
||||
if module.params.get('wait_for_deployment_completion'):
|
||||
while not deployment_result.properties.provisioning_state in ['Canceled', 'Failed', 'Deleted', 'Succeeded']:
|
||||
deployment_result = client.deployments.get(group_name, deployment_name)
|
||||
time.sleep(module.params.get('wait_for_deployment_polling_period'))
|
||||
|
||||
if deployment_result.properties.provisioning_state == 'Succeeded':
|
||||
return deployment_result
|
||||
|
||||
failed_deployment_operations = get_failed_deployment_operations(module, client, group_name, deployment_name)
|
||||
module.fail_json(msg='Deployment failed. Deployment id: %s' % (deployment_result.id), failed_deployment_operations=failed_deployment_operations)
|
||||
except CloudError as e:
|
||||
failed_deployment_operations = get_failed_deployment_operations(module, client, group_name, deployment_name)
|
||||
module.fail_json(msg='Deploy create failed with status code: %s and message: "%s"' % (e.status_code, e.message),failed_deployment_operations=failed_deployment_operations)
|
||||
|
||||
def destroy_resource_group(module, client, conn_info):
|
||||
"""
|
||||
Destroy the targeted resource group
|
||||
:param module: ansible module
|
||||
:param client: resource management client for azure
|
||||
:param conn_info: connection info needed
|
||||
:return: if the result caused a change in the deployment
|
||||
"""
|
||||
|
||||
try:
|
||||
result = client.resource_groups.delete(conn_info['resource_group_name'])
|
||||
result.wait() # Blocking wait till the delete is finished
|
||||
except CloudError as e:
|
||||
if e.status_code == 404 or e.status_code == 204:
|
||||
return True
|
||||
else:
|
||||
module.fail_json(
|
||||
msg='Delete resource group and deploy failed with status code: %s and message: %s' % (e.status_code, e.message))
|
||||
|
||||
|
||||
def get_dependencies(dep_tree, resource_type):
|
||||
matches = [value for value in dep_tree.values() if value['dep'].resource_type == resource_type]
|
||||
for child_tree in [value['children'] for value in dep_tree.values()]:
|
||||
matches += get_dependencies(child_tree, resource_type)
|
||||
return matches
|
||||
|
||||
|
||||
def build_hierarchy(dependencies, tree=None):
|
||||
tree = dict(top=True) if tree is None else tree
|
||||
for dep in dependencies:
|
||||
if dep.resource_name not in tree:
|
||||
tree[dep.resource_name] = dict(dep=dep, children=dict())
|
||||
if isinstance(dep, Dependency) and dep.depends_on is not None and len(dep.depends_on) > 0:
|
||||
build_hierarchy(dep.depends_on, tree[dep.resource_name]['children'])
|
||||
|
||||
if 'top' in tree:
|
||||
tree.pop('top', None)
|
||||
keys = list(tree.keys())
|
||||
for key1 in keys:
|
||||
for key2 in keys:
|
||||
if key2 in tree and key1 in tree[key2]['children'] and key1 in tree:
|
||||
tree[key2]['children'][key1] = tree[key1]
|
||||
tree.pop(key1)
|
||||
return tree
|
||||
|
||||
|
||||
def get_ip_dict(ip):
|
||||
ip_dict = dict(name=ip.name,
|
||||
id=ip.id,
|
||||
public_ip=ip.ip_address,
|
||||
public_ip_allocation_method=str(ip.public_ip_allocation_method))
|
||||
|
||||
if ip.dns_settings:
|
||||
ip_dict['dns_settings'] = {
|
||||
'domain_name_label':ip.dns_settings.domain_name_label,
|
||||
'fqdn':ip.dns_settings.fqdn
|
||||
}
|
||||
|
||||
return ip_dict
|
||||
|
||||
|
||||
def nic_to_public_ips_instance(client, group, nics):
|
||||
return [client.public_ip_addresses.get(group, public_ip_id.split('/')[-1])
|
||||
for nic_obj in [client.network_interfaces.get(group, nic['dep'].resource_name) for nic in nics]
|
||||
for public_ip_id in [ip_conf_instance.public_ip_address.id for ip_conf_instance in nic_obj.ip_configurations if ip_conf_instance.public_ip_address]]
|
||||
|
||||
|
||||
def get_instances(client, group, deployment):
|
||||
dep_tree = build_hierarchy(deployment.properties.dependencies)
|
||||
vms = get_dependencies(dep_tree, resource_type="Microsoft.Compute/virtualMachines")
|
||||
|
||||
vms_and_nics = [(vm, get_dependencies(vm['children'], "Microsoft.Network/networkInterfaces")) for vm in vms]
|
||||
vms_and_ips = [(vm['dep'], nic_to_public_ips_instance(client, group, nics)) for vm, nics in vms_and_nics]
|
||||
|
||||
return [dict(vm_name=vm.resource_name, ips=[get_ip_dict(ip) for ip in ips]) for vm, ips in vms_and_ips if len(ips) > 0]
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
azure_url=dict(default=AZURE_URL),
|
||||
subscription_id=dict(),
|
||||
client_secret=dict(no_log=True),
|
||||
client_id=dict(required=True),
|
||||
tenant_id=dict(required=True),
|
||||
resource_group_name=dict(required=True),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
template=dict(default=None, type='dict'),
|
||||
parameters=dict(default=None, type='dict'),
|
||||
template_link=dict(default=None),
|
||||
parameters_link=dict(default=None),
|
||||
location=dict(default="West US"),
|
||||
deployment_mode=dict(default='Complete', choices=['Complete', 'Incremental']),
|
||||
deployment_name=dict(default="ansible-arm"),
|
||||
wait_for_deployment_completion=dict(default=True),
|
||||
wait_for_deployment_polling_period=dict(default=30)
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
mutually_exclusive=[['template_link', 'template'], ['parameters_link', 'parameters']],
|
||||
)
|
||||
|
||||
if not HAS_DEPS:
|
||||
module.fail_json(msg='requests and azure are required for this module')
|
||||
|
||||
conn_info = get_azure_connection_info(module)
|
||||
|
||||
credentials = ServicePrincipalCredentials(client_id=conn_info['client_id'],
|
||||
secret=conn_info['client_secret'],
|
||||
tenant=conn_info['tenant_id'])
|
||||
|
||||
subscription_id = conn_info['subscription_id']
|
||||
resource_configuration = ResourceManagementClientConfiguration(credentials, subscription_id)
|
||||
resource_configuration.add_user_agent('Ansible-Deploy')
|
||||
resource_client = ResourceManagementClient(resource_configuration)
|
||||
network_configuration = NetworkManagementClientConfiguration(credentials, subscription_id)
|
||||
network_configuration.add_user_agent('Ansible-Deploy')
|
||||
network_client = NetworkManagementClient(network_configuration)
|
||||
conn_info['deployment_name'] = module.params.get('deployment_name')
|
||||
|
||||
if module.params.get('state') == 'present':
|
||||
deployment = deploy_template(module, resource_client, conn_info)
|
||||
data = dict(name=deployment.name,
|
||||
group_name=conn_info['resource_group_name'],
|
||||
id=deployment.id,
|
||||
outputs=deployment.properties.outputs,
|
||||
instances=get_instances(network_client, conn_info['resource_group_name'], deployment),
|
||||
changed=True,
|
||||
msg='deployment created')
|
||||
module.exit_json(**data)
|
||||
else:
|
||||
destroy_resource_group(module, resource_client, conn_info)
|
||||
module.exit_json(changed=True, msg='deployment deleted')
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,646 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# 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/>.
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: azure_rm_deployment
|
||||
|
||||
short_description: Create or destroy Azure Resource Manager template deployments
|
||||
|
||||
version_added: "2.1"
|
||||
|
||||
description:
|
||||
- "Create or destroy Azure Resource Manager template deployments via the Azure SDK for Python.
|
||||
You can find some quick start templates in GitHub here https://github.com/azure/azure-quickstart-templates.
|
||||
For more information on Azue resource manager templates see https://azure.microsoft.com/en-us/documentation/articles/resource-group-template-deploy/."
|
||||
|
||||
options:
|
||||
resource_group_name:
|
||||
description:
|
||||
- The resource group name to use or create to host the deployed template
|
||||
required: true
|
||||
location:
|
||||
description:
|
||||
- The geo-locations in which the resource group will be located.
|
||||
required: false
|
||||
default: westus
|
||||
state:
|
||||
description:
|
||||
- If state is "present", template will be created. If state is "present" and if deployment exists, it will be
|
||||
updated. If state is "absent", stack will be removed.
|
||||
default: present
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
template:
|
||||
description:
|
||||
- A hash containing the templates inline. This parameter is mutually exclusive with 'template_link'.
|
||||
Either one of them is required if "state" parameter is "present".
|
||||
required: false
|
||||
default: None
|
||||
template_link:
|
||||
description:
|
||||
- Uri of file containing the template body. This parameter is mutually exclusive with 'template'. Either one
|
||||
of them is required if "state" parameter is "present".
|
||||
required: false
|
||||
default: None
|
||||
parameters:
|
||||
description:
|
||||
- A hash of all the required template variables for the deployment template. This parameter is mutually exclusive
|
||||
with 'parameters_link'. Either one of them is required if "state" parameter is "present".
|
||||
required: false
|
||||
default: None
|
||||
parameters_link:
|
||||
description:
|
||||
- Uri of file containing the parameters body. This parameter is mutually exclusive with 'parameters'. Either
|
||||
one of them is required if "state" parameter is "present".
|
||||
required: false
|
||||
default: None
|
||||
|
||||
extends_documentation_fragment:
|
||||
- azure
|
||||
|
||||
author:
|
||||
- David Justice (@devigned)
|
||||
- Laurent Mazuel (@lmazuel)
|
||||
- Andre Price (@obsoleted)
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Destroy a template deployment
|
||||
- name: Destroy Azure Deploy
|
||||
azure_rm_deployment:
|
||||
state: absent
|
||||
subscription_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
resource_group_name: dev-ops-cle
|
||||
|
||||
# Create or update a template deployment based on uris using parameter and template links
|
||||
- name: Create Azure Deploy
|
||||
azure_rm_deployment:
|
||||
state: present
|
||||
resource_group_name: dev-ops-cle
|
||||
template_link: 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-vm-simple-linux/azuredeploy.json'
|
||||
parameters_link: 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-vm-simple-linux/azuredeploy.parameters.json'
|
||||
|
||||
# Create or update a template deployment based on a uri to the template and parameters specified inline.
|
||||
# This deploys a VM with SSH support for a given public key, then stores the result in 'azure_vms'. The result is then
|
||||
# used to create a new host group. This host group is then used to wait for each instance to respond to the public IP SSH.
|
||||
---
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Destroy Azure Deploy
|
||||
azure_rm_deployment:
|
||||
state: absent
|
||||
subscription_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
resource_group_name: dev-ops-cle
|
||||
|
||||
- name: Create Azure Deploy
|
||||
azure_rm_deployment:
|
||||
state: present
|
||||
subscription_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
resource_group_name: dev-ops-cle
|
||||
parameters:
|
||||
newStorageAccountName:
|
||||
value: devopsclestorage1
|
||||
adminUsername:
|
||||
value: devopscle
|
||||
dnsNameForPublicIP:
|
||||
value: devopscleazure
|
||||
location:
|
||||
value: West US
|
||||
vmSize:
|
||||
value: Standard_A2
|
||||
vmName:
|
||||
value: ansibleSshVm
|
||||
sshKeyData:
|
||||
value: YOUR_SSH_PUBLIC_KEY
|
||||
template_link: 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-vm-sshkey/azuredeploy.json'
|
||||
register: azure
|
||||
|
||||
- name: Add new instance to host group
|
||||
add_host: hostname={{ item['ips'][0].public_ip }} groupname=azure_vms
|
||||
with_items: azure.deployment.instances
|
||||
|
||||
- hosts: azure_vms
|
||||
user: devopscle
|
||||
tasks:
|
||||
- name: Wait for SSH to come up
|
||||
wait_for: port=22 timeout=2000 state=started
|
||||
- name: echo the hostname of the vm
|
||||
shell: hostname
|
||||
|
||||
# Deploy an Azure WebApp running a hello world'ish node app
|
||||
- name: Create Azure WebApp Deployment at http://devopscleweb.azurewebsites.net/hello.js
|
||||
azure_rm_deployment:
|
||||
state: present
|
||||
subscription_id: cbbdaed0-fea9-4693-bf0c-d446ac93c030
|
||||
resource_group_name: dev-ops-cle-webapp
|
||||
parameters:
|
||||
repoURL:
|
||||
value: 'https://github.com/devigned/az-roadshow-oss.git'
|
||||
siteName:
|
||||
value: devopscleweb
|
||||
hostingPlanName:
|
||||
value: someplan
|
||||
siteLocation:
|
||||
value: westus
|
||||
sku:
|
||||
value: Standard
|
||||
template_link: 'https://raw.githubusercontent.com/azure/azure-quickstart-templates/master/201-web-app-github-deploy/azuredeploy.json'
|
||||
|
||||
# Create or update a template deployment based on an inline template and parameters
|
||||
- name: Create Azure Deploy
|
||||
azure_rm_deploy:
|
||||
state: present
|
||||
subscription_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
resource_group_name: dev-ops-cle
|
||||
|
||||
template:
|
||||
$schema: "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#"
|
||||
contentVersion: "1.0.0.0"
|
||||
parameters:
|
||||
newStorageAccountName:
|
||||
type: "string"
|
||||
metadata:
|
||||
description: "Unique DNS Name for the Storage Account where the Virtual Machine's disks will be placed."
|
||||
adminUsername:
|
||||
type: "string"
|
||||
metadata:
|
||||
description: "User name for the Virtual Machine."
|
||||
adminPassword:
|
||||
type: "securestring"
|
||||
metadata:
|
||||
description: "Password for the Virtual Machine."
|
||||
dnsNameForPublicIP:
|
||||
type: "string"
|
||||
metadata:
|
||||
description: "Unique DNS Name for the Public IP used to access the Virtual Machine."
|
||||
ubuntuOSVersion:
|
||||
type: "string"
|
||||
defaultValue: "14.04.2-LTS"
|
||||
allowedValues:
|
||||
- "12.04.5-LTS"
|
||||
- "14.04.2-LTS"
|
||||
- "15.04"
|
||||
metadata:
|
||||
description: "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version. Allowed values: 12.04.5-LTS, 14.04.2-LTS, 15.04."
|
||||
variables:
|
||||
location: "West US"
|
||||
imagePublisher: "Canonical"
|
||||
imageOffer: "UbuntuServer"
|
||||
OSDiskName: "osdiskforlinuxsimple"
|
||||
nicName: "myVMNic"
|
||||
addressPrefix: "10.0.0.0/16"
|
||||
subnetName: "Subnet"
|
||||
subnetPrefix: "10.0.0.0/24"
|
||||
storageAccountType: "Standard_LRS"
|
||||
publicIPAddressName: "myPublicIP"
|
||||
publicIPAddressType: "Dynamic"
|
||||
vmStorageAccountContainerName: "vhds"
|
||||
vmName: "MyUbuntuVM"
|
||||
vmSize: "Standard_D1"
|
||||
virtualNetworkName: "MyVNET"
|
||||
vnetID: "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]"
|
||||
subnetRef: "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]"
|
||||
resources:
|
||||
-
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
name: "[parameters('newStorageAccountName')]"
|
||||
apiVersion: "2015-05-01-preview"
|
||||
location: "[variables('location')]"
|
||||
properties:
|
||||
accountType: "[variables('storageAccountType')]"
|
||||
-
|
||||
apiVersion: "2015-05-01-preview"
|
||||
type: "Microsoft.Network/publicIPAddresses"
|
||||
name: "[variables('publicIPAddressName')]"
|
||||
location: "[variables('location')]"
|
||||
properties:
|
||||
publicIPAllocationMethod: "[variables('publicIPAddressType')]"
|
||||
dnsSettings:
|
||||
domainNameLabel: "[parameters('dnsNameForPublicIP')]"
|
||||
-
|
||||
type: "Microsoft.Network/virtualNetworks"
|
||||
apiVersion: "2015-05-01-preview"
|
||||
name: "[variables('virtualNetworkName')]"
|
||||
location: "[variables('location')]"
|
||||
properties:
|
||||
addressSpace:
|
||||
addressPrefixes:
|
||||
- "[variables('addressPrefix')]"
|
||||
subnets:
|
||||
-
|
||||
name: "[variables('subnetName')]"
|
||||
properties:
|
||||
addressPrefix: "[variables('subnetPrefix')]"
|
||||
-
|
||||
type: "Microsoft.Network/networkInterfaces"
|
||||
apiVersion: "2015-05-01-preview"
|
||||
name: "[variables('nicName')]"
|
||||
location: "[variables('location')]"
|
||||
dependsOn:
|
||||
- "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]"
|
||||
- "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
|
||||
properties:
|
||||
ipConfigurations:
|
||||
-
|
||||
name: "ipconfig1"
|
||||
properties:
|
||||
privateIPAllocationMethod: "Dynamic"
|
||||
publicIPAddress:
|
||||
id: "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
|
||||
subnet:
|
||||
id: "[variables('subnetRef')]"
|
||||
-
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
apiVersion: "2015-06-15"
|
||||
name: "[variables('vmName')]"
|
||||
location: "[variables('location')]"
|
||||
dependsOn:
|
||||
- "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]"
|
||||
- "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
|
||||
properties:
|
||||
hardwareProfile:
|
||||
vmSize: "[variables('vmSize')]"
|
||||
osProfile:
|
||||
computername: "[variables('vmName')]"
|
||||
adminUsername: "[parameters('adminUsername')]"
|
||||
adminPassword: "[parameters('adminPassword')]"
|
||||
storageProfile:
|
||||
imageReference:
|
||||
publisher: "[variables('imagePublisher')]"
|
||||
offer: "[variables('imageOffer')]"
|
||||
sku: "[parameters('ubuntuOSVersion')]"
|
||||
version: "latest"
|
||||
osDisk:
|
||||
name: "osdisk"
|
||||
vhd:
|
||||
uri: "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]"
|
||||
caching: "ReadWrite"
|
||||
createOption: "FromImage"
|
||||
networkProfile:
|
||||
networkInterfaces:
|
||||
-
|
||||
id: "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
|
||||
diagnosticsProfile:
|
||||
bootDiagnostics:
|
||||
enabled: "true"
|
||||
storageUri: "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net')]"
|
||||
parameters:
|
||||
newStorageAccountName:
|
||||
value: devopsclestorage
|
||||
adminUsername:
|
||||
value: devopscle
|
||||
adminPassword:
|
||||
value: Password1!
|
||||
dnsNameForPublicIP:
|
||||
value: devopscleazure
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
msg:
|
||||
description: String indicating if the deployment was created or deleted
|
||||
returned: always
|
||||
type: string
|
||||
sample: "deployment created"
|
||||
deployment:
|
||||
description: Deployment details
|
||||
type: dict
|
||||
returned: always
|
||||
sample:{
|
||||
"group_name": "Test_Deployment",
|
||||
"id": "/subscriptions/3f7e29ba-24e0-42f6-8d9c-5149a14bda37/resourceGroups/Test_Deployment/providers/Microsoft.Resources/deployments/ansible-arm",
|
||||
"instances": [
|
||||
{
|
||||
"ips": [
|
||||
{
|
||||
"dns_settings": {
|
||||
"domain_name_label": "testvm9910001",
|
||||
"fqdn": "testvm9910001.westus.cloudapp.azure.com"
|
||||
},
|
||||
"id": "/subscriptions/3f7e29ba-24e0-42f6-8d9c-5149a14bda37/resourceGroups/Test_Deployment/providers/Microsoft.Network/publicIPAddresses/myPublicIP",
|
||||
"name": "myPublicIP",
|
||||
"public_ip": "13.91.99.232",
|
||||
"public_ip_allocation_method": "IPAllocationMethod.dynamic"
|
||||
}
|
||||
],
|
||||
"vm_name": "MyUbuntuVM"
|
||||
}
|
||||
],
|
||||
"name": "ansible-arm",
|
||||
"outputs": {
|
||||
"hostname": {
|
||||
"type": "String",
|
||||
"value": "testvm9910001.westus.cloudapp.azure.com"
|
||||
},
|
||||
"sshCommand": {
|
||||
"type": "String",
|
||||
"value": "ssh chouseknecht@testvm9910001.westus.cloudapp.azure.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
import time
|
||||
import yaml
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.azure_rm_common import *
|
||||
|
||||
try:
|
||||
from itertools import chain
|
||||
from azure.common.credentials import ServicePrincipalCredentials
|
||||
from azure.common.exceptions import CloudError
|
||||
from azure.mgmt.resource.resources.models import (DeploymentProperties,
|
||||
ParametersLink,
|
||||
TemplateLink,
|
||||
Deployment,
|
||||
ResourceGroup,
|
||||
Dependency)
|
||||
from azure.mgmt.resource.resources import ResourceManagementClient, ResourceManagementClientConfiguration
|
||||
from azure.mgmt.network import NetworkManagementClient, NetworkManagementClientConfiguration
|
||||
|
||||
except ImportError:
|
||||
# This is handled in azure_rm_common
|
||||
pass
|
||||
|
||||
|
||||
class AzureRMDeploymentManager(AzureRMModuleBase):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.module_arg_spec = dict(
|
||||
resource_group_name=dict(type='str', required=True, aliases=['resource_group']),
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
template=dict(type='dict', default=None),
|
||||
parameters=dict(type='dict', default=None),
|
||||
template_link=dict(type='str', default=None),
|
||||
parameters_link=dict(type='str', default=None),
|
||||
location=dict(type='str', default="westus"),
|
||||
deployment_mode=dict(type='str', default='complete', choices=['complete', 'incremental']),
|
||||
deployment_name=dict(type='str', default="ansible-arm"),
|
||||
wait_for_deployment_completion=dict(type='bool', default=True),
|
||||
wait_for_deployment_polling_period=dict(type='int', default=30)
|
||||
)
|
||||
|
||||
mutually_exclusive = [('template', 'template_link'),
|
||||
('parameters', 'parameters_link')]
|
||||
|
||||
self.resource_group_name = None
|
||||
self.state = None
|
||||
self.template = None
|
||||
self.parameters = None
|
||||
self.template_link = None
|
||||
self.parameters_link = None
|
||||
self.location = None
|
||||
self.deployment_mode = None
|
||||
self.deployment_name = None
|
||||
self.wait_for_deployment_completion = None
|
||||
self.wait_for_deployment_polling_period = None
|
||||
self.tags = None
|
||||
|
||||
self.results = dict(
|
||||
deployment=dict(),
|
||||
changed=False,
|
||||
msg=""
|
||||
)
|
||||
|
||||
super(AzureRMDeploymentManager, self).__init__(derived_arg_spec=self.module_arg_spec,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=False)
|
||||
|
||||
def exec_module(self, **kwargs):
|
||||
|
||||
for key in self.module_arg_spec.keys() + ['tags']:
|
||||
setattr(self, key, kwargs[key])
|
||||
|
||||
if self.state == 'present':
|
||||
deployment = self.deploy_template()
|
||||
self.results['deployment'] = dict(
|
||||
name=deployment.name,
|
||||
group_name=self.resource_group_name,
|
||||
id=deployment.id,
|
||||
outputs=deployment.properties.outputs,
|
||||
instances=self._get_instances(deployment)
|
||||
)
|
||||
self.results['changed'] = True
|
||||
self.results['msg'] = 'deployment created'
|
||||
else:
|
||||
if self.resource_group_exists(self.resource_group_name):
|
||||
self.destroy_resource_group()
|
||||
self.results['changed'] = True
|
||||
self.results['msg'] = "deployment deleted"
|
||||
|
||||
return self.results
|
||||
|
||||
def deploy_template(self):
|
||||
"""
|
||||
Deploy the targeted template and parameters
|
||||
:param module: Ansible module containing the validated configuration for the deployment template
|
||||
:param client: resource management client for azure
|
||||
:param conn_info: connection info needed
|
||||
:return:
|
||||
"""
|
||||
|
||||
deploy_parameter = DeploymentProperties()
|
||||
deploy_parameter.mode = self.deployment_mode
|
||||
if not self.parameters_link:
|
||||
deploy_parameter.parameters = self.parameters
|
||||
else:
|
||||
deploy_parameter.parameters_link = ParametersLink(
|
||||
uri=self.parameters_link
|
||||
)
|
||||
if not self.template_link:
|
||||
deploy_parameter.template = self.template
|
||||
else:
|
||||
deploy_parameter.template_link = TemplateLink(
|
||||
uri=self.template_link
|
||||
)
|
||||
|
||||
params = ResourceGroup(location=self.location, tags=self.tags)
|
||||
|
||||
try:
|
||||
self.rm_client.resource_groups.create_or_update(self.resource_group_name, params)
|
||||
except CloudError as exc:
|
||||
self.fail("Resource group create_or_update failed with status code: %s and message: %s" %
|
||||
(exc.status_code, exc.message))
|
||||
try:
|
||||
result = self.rm_client.deployments.create_or_update(self.resource_group_name,
|
||||
self.deployment_name,
|
||||
deploy_parameter)
|
||||
|
||||
deployment_result = self.get_poller_result(result)
|
||||
if self.wait_for_deployment_completion:
|
||||
while deployment_result.properties.provisioning_state not in ['Canceled', 'Failed', 'Deleted',
|
||||
'Succeeded']:
|
||||
time.sleep(self.wait_for_deployment_polling_period)
|
||||
deployment_result = self.rm_client.deployments.get(self.resource_group_name, self.deployment_name)
|
||||
except CloudError as exc:
|
||||
failed_deployment_operations = self._get_failed_deployment_operations(self.deployment_name)
|
||||
self.log("Deployment failed %s: %s" % (exc.status_code, exc.message))
|
||||
self.fail("Deployment failed with status code: %s and message: %s" % (exc.status_code, exc.message),
|
||||
failed_deployment_operations=failed_deployment_operations)
|
||||
|
||||
if self.wait_for_deployment_completion and deployment_result.properties.provisioning_state != 'Succeeded':
|
||||
self.log("provisioning state: %s" % deployment_result.properties.provisioning_state)
|
||||
failed_deployment_operations = self._get_failed_deployment_operations(self.deployment_name)
|
||||
self.fail('Deployment failed. Deployment id: %s' % deployment_result.id,
|
||||
failed_deployment_operations=failed_deployment_operations)
|
||||
|
||||
return deployment_result
|
||||
|
||||
def destroy_resource_group(self):
|
||||
"""
|
||||
Destroy the targeted resource group
|
||||
"""
|
||||
try:
|
||||
result = self.rm_client.resource_groups.delete(self.resource_group_name)
|
||||
result.wait() # Blocking wait till the delete is finished
|
||||
except CloudError as e:
|
||||
if e.status_code == 404 or e.status_code == 204:
|
||||
return
|
||||
else:
|
||||
self.fail("Delete resource group and deploy failed with status code: %s and message: %s" %
|
||||
(e.status_code, e.message))
|
||||
|
||||
def resource_group_exists(self, resource_group):
|
||||
'''
|
||||
Return True/False based on existence of requested resource group.
|
||||
|
||||
:param resource_group: string. Name of a resource group.
|
||||
:return: boolean
|
||||
'''
|
||||
try:
|
||||
self.rm_client.resource_groups.get(resource_group)
|
||||
except CloudError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _get_failed_nested_operations(self, current_operations):
|
||||
new_operations = []
|
||||
for operation in current_operations:
|
||||
if operation.properties.provisioning_state == 'Failed':
|
||||
new_operations.append(operation)
|
||||
if operation.properties.target_resource and \
|
||||
'Microsoft.Resources/deployments' in operation.properties.target_resource.id:
|
||||
nested_deployment = operation.properties.target_resource.resource_name
|
||||
try:
|
||||
nested_operations = self.rm_client.deployment_operations.list(self.resource_group_name,
|
||||
nested_deployment)
|
||||
except CloudError as exc:
|
||||
self.fail("List nested deployment operations failed with status code: %s and message: %s" %
|
||||
(e.status_code, e.message))
|
||||
new_nested_operations = self._get_failed_nested_operations(nested_operations)
|
||||
new_operations += new_nested_operations
|
||||
return new_operations
|
||||
|
||||
def _get_failed_deployment_operations(self, deployment_name):
|
||||
results = []
|
||||
# time.sleep(15) # there is a race condition between when we ask for deployment status and when the
|
||||
# # status is available.
|
||||
|
||||
try:
|
||||
operations = self.rm_client.deployment_operations.list(self.resource_group_name, deployment_name)
|
||||
except CloudError as exc:
|
||||
self.fail("Get deployment failed with status code: %s and message: %s" %
|
||||
(exc.status_code, exc.message))
|
||||
try:
|
||||
results = [
|
||||
dict(
|
||||
id=op.id,
|
||||
operation_id=op.operation_id,
|
||||
status_code=op.properties.status_code,
|
||||
status_message=op.properties.status_message,
|
||||
target_resource=dict(
|
||||
id=op.properties.target_resource.id,
|
||||
resource_name=op.properties.target_resource.resource_name,
|
||||
resource_type=op.properties.target_resource.resource_type
|
||||
) if op.properties.target_resource else None,
|
||||
provisioning_state=op.properties.provisioning_state,
|
||||
)
|
||||
for op in self._get_failed_nested_operations(operations)
|
||||
]
|
||||
except:
|
||||
# If we fail here, the original error gets lost and user receives wrong error message/stacktrace
|
||||
pass
|
||||
self.log(dict(failed_deployment_operations=results), pretty_print=True)
|
||||
return results
|
||||
|
||||
def _get_instances(self, deployment):
|
||||
dep_tree = self._build_hierarchy(deployment.properties.dependencies)
|
||||
vms = self._get_dependencies(dep_tree, resource_type="Microsoft.Compute/virtualMachines")
|
||||
vms_and_nics = [(vm, self._get_dependencies(vm['children'], "Microsoft.Network/networkInterfaces"))
|
||||
for vm in vms]
|
||||
vms_and_ips = [(vm['dep'], self._nic_to_public_ips_instance(nics))
|
||||
for vm, nics in vms_and_nics]
|
||||
return [dict(vm_name=vm.resource_name, ips=[self._get_ip_dict(ip)
|
||||
for ip in ips]) for vm, ips in vms_and_ips if len(ips) > 0]
|
||||
|
||||
def _get_dependencies(self, dep_tree, resource_type):
|
||||
matches = [value for value in dep_tree.values() if value['dep'].resource_type == resource_type]
|
||||
for child_tree in [value['children'] for value in dep_tree.values()]:
|
||||
matches += self._get_dependencies(child_tree, resource_type)
|
||||
return matches
|
||||
|
||||
def _build_hierarchy(self, dependencies, tree=None):
|
||||
tree = dict(top=True) if tree is None else tree
|
||||
for dep in dependencies:
|
||||
if dep.resource_name not in tree:
|
||||
tree[dep.resource_name] = dict(dep=dep, children=dict())
|
||||
if isinstance(dep, Dependency) and dep.depends_on is not None and len(dep.depends_on) > 0:
|
||||
self._build_hierarchy(dep.depends_on, tree[dep.resource_name]['children'])
|
||||
|
||||
if 'top' in tree:
|
||||
tree.pop('top', None)
|
||||
keys = list(tree.keys())
|
||||
for key1 in keys:
|
||||
for key2 in keys:
|
||||
if key2 in tree and key1 in tree[key2]['children'] and key1 in tree:
|
||||
tree[key2]['children'][key1] = tree[key1]
|
||||
tree.pop(key1)
|
||||
return tree
|
||||
|
||||
def _get_ip_dict(self, ip):
|
||||
ip_dict = dict(name=ip.name,
|
||||
id=ip.id,
|
||||
public_ip=ip.ip_address,
|
||||
public_ip_allocation_method=str(ip.public_ip_allocation_method)
|
||||
)
|
||||
if ip.dns_settings:
|
||||
ip_dict['dns_settings'] = {
|
||||
'domain_name_label':ip.dns_settings.domain_name_label,
|
||||
'fqdn':ip.dns_settings.fqdn
|
||||
}
|
||||
return ip_dict
|
||||
|
||||
def _nic_to_public_ips_instance(self, nics):
|
||||
return [self.network_client.public_ip_addresses.get(self.resource_group_name, public_ip_id.split('/')[-1])
|
||||
for nic_obj in [self.network_client.network_interfaces.get(self.resource_group_name,
|
||||
nic['dep'].resource_name) for nic in nics]
|
||||
for public_ip_id in [ip_conf_instance.public_ip_address.id
|
||||
for ip_conf_instance in nic_obj.ip_configurations
|
||||
if ip_conf_instance.public_ip_address]]
|
||||
|
||||
|
||||
def main():
|
||||
AzureRMDeploymentManager()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue