mirror of
https://github.com/saltstack/salt.git
synced 2025-04-16 09:40:20 +00:00
removing azure code from repo
This commit is contained in:
parent
b897734f4a
commit
15849a5911
38 changed files with 0 additions and 20106 deletions
|
@ -1,5 +0,0 @@
|
||||||
salt.cloud.clouds.azurearm
|
|
||||||
==========================
|
|
||||||
|
|
||||||
.. automodule:: salt.cloud.clouds.azurearm
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
salt.cloud.clouds.msazure
|
|
||||||
=========================
|
|
||||||
|
|
||||||
.. automodule:: salt.cloud.clouds.msazure
|
|
||||||
:members:
|
|
|
@ -1,4 +0,0 @@
|
||||||
salt.fileserver.azurefs
|
|
||||||
=======================
|
|
||||||
|
|
||||||
.. automodule:: salt.fileserver.azurefs
|
|
|
@ -1,5 +0,0 @@
|
||||||
salt.grains.metadata_azure
|
|
||||||
==========================
|
|
||||||
|
|
||||||
.. automodule:: salt.grains.metadata_azure
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
salt.modules.azurearm_compute
|
|
||||||
=============================
|
|
||||||
|
|
||||||
.. automodule:: salt.modules.azurearm_compute
|
|
||||||
:members:
|
|
|
@ -1,6 +0,0 @@
|
||||||
salt.modules.azurearm_dns
|
|
||||||
=========================
|
|
||||||
|
|
||||||
.. automodule:: salt.modules.azurearm_dns
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
salt.modules.azurearm_network
|
|
||||||
=============================
|
|
||||||
|
|
||||||
.. automodule:: salt.modules.azurearm_network
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
salt.modules.azurearm_resource
|
|
||||||
==============================
|
|
||||||
|
|
||||||
.. automodule:: salt.modules.azurearm_resource
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
salt.pillar.azureblob
|
|
||||||
=====================
|
|
||||||
|
|
||||||
.. automodule:: salt.pillar.azureblob
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
salt.states.azurearm_compute
|
|
||||||
============================
|
|
||||||
|
|
||||||
.. automodule:: salt.states.azurearm_compute
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
salt.states.azurearm_dns
|
|
||||||
========================
|
|
||||||
|
|
||||||
.. automodule:: salt.states.azurearm_dns
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
salt.states.azurearm_network
|
|
||||||
============================
|
|
||||||
|
|
||||||
.. automodule:: salt.states.azurearm_network
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
salt.states.azurearm_resource
|
|
||||||
=============================
|
|
||||||
|
|
||||||
.. automodule:: salt.states.azurearm_resource
|
|
||||||
:members:
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,486 +0,0 @@
|
||||||
==============================
|
|
||||||
Getting Started With Azure ARM
|
|
||||||
==============================
|
|
||||||
|
|
||||||
.. versionadded:: 2016.11.0
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This cloud provider will be removed from Salt in version 3007 in favor of
|
|
||||||
the `saltext.azurerm Salt Extension
|
|
||||||
<https://github.com/salt-extensions/saltext-azurerm>`_
|
|
||||||
|
|
||||||
Azure is a cloud service by Microsoft providing virtual machines, SQL services,
|
|
||||||
media services, and more. Azure ARM (aka, the Azure Resource Manager) is a next
|
|
||||||
generation version of the Azure portal and API. This document describes how to
|
|
||||||
use Salt Cloud to create a virtual machine on Azure ARM, with Salt installed.
|
|
||||||
|
|
||||||
More information about Azure is located at `http://www.windowsazure.com/
|
|
||||||
<http://www.windowsazure.com/>`_.
|
|
||||||
|
|
||||||
|
|
||||||
Dependencies
|
|
||||||
============
|
|
||||||
* `azure <https://pypi.org/project/azure>`_ >= 2.0.0rc6
|
|
||||||
* `azure-common <https://pypi.org/project/azure-common>`_ >= 1.1.4
|
|
||||||
* `azure-mgmt <https://pypi.org/project/azure-mgmt>`_ >= 0.30.0rc6
|
|
||||||
* `azure-mgmt-compute <https://pypi.org/project/azure-mgmt-compute>`_ >= 0.33.0
|
|
||||||
* `azure-mgmt-network <https://pypi.org/project/azure-mgmt-network>`_ >= 0.30.0rc6
|
|
||||||
* `azure-mgmt-resource <https://pypi.org/project/azure-mgmt-resource>`_ >= 0.30.0
|
|
||||||
* `azure-mgmt-storage <https://pypi.org/project/azure-mgmt-storage>`_ >= 0.30.0rc6
|
|
||||||
* `azure-mgmt-web <https://pypi.org/project/azure-mgmt-web>`_ >= 0.30.0rc6
|
|
||||||
* `azure-storage <https://pypi.org/project/azure-storage>`_ >= 0.32.0
|
|
||||||
* `msrestazure <https://pypi.org/project/msrestazure/>`_ >= 0.4.21
|
|
||||||
* A Microsoft Azure account
|
|
||||||
* `Salt <https://github.com/saltstack/salt>`_
|
|
||||||
|
|
||||||
|
|
||||||
Installation Tips
|
|
||||||
=================
|
|
||||||
Because the ``azure`` library requires the ``cryptography`` library, which is
|
|
||||||
compiled on-the-fly by ``pip``, you may need to install the development tools
|
|
||||||
for your operating system.
|
|
||||||
|
|
||||||
Before you install ``azure`` with ``pip``, you should make sure that the
|
|
||||||
required libraries are installed.
|
|
||||||
|
|
||||||
Debian
|
|
||||||
------
|
|
||||||
For Debian and Ubuntu, the following command will ensure that the required
|
|
||||||
dependencies are installed:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
sudo apt-get install build-essential libssl-dev libffi-dev python-dev
|
|
||||||
|
|
||||||
Red Hat
|
|
||||||
-------
|
|
||||||
For Fedora and RHEL-derivatives, the following command will ensure that the
|
|
||||||
required dependencies are installed:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
sudo yum install gcc libffi-devel python-devel openssl-devel
|
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
=============
|
|
||||||
|
|
||||||
Set up the provider config at ``/etc/salt/cloud.providers.d/azurearm.conf``:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
# Note: This example is for /etc/salt/cloud.providers.d/azurearm.conf
|
|
||||||
|
|
||||||
my-azurearm-config:
|
|
||||||
driver: azurearm
|
|
||||||
master: salt.example.com
|
|
||||||
subscription_id: 01234567-890a-bcde-f012-34567890abdc
|
|
||||||
|
|
||||||
# https://apps.dev.microsoft.com/#/appList
|
|
||||||
username: <username>@<subdomain>.onmicrosoft.com
|
|
||||||
password: verybadpass
|
|
||||||
location: westus
|
|
||||||
resource_group: my_rg
|
|
||||||
|
|
||||||
# Optional
|
|
||||||
network_resource_group: my_net_rg
|
|
||||||
cleanup_disks: True
|
|
||||||
cleanup_vhds: True
|
|
||||||
cleanup_data_disks: True
|
|
||||||
cleanup_interfaces: True
|
|
||||||
custom_data: 'This is custom data'
|
|
||||||
expire_publisher_cache: 604800 # 7 days
|
|
||||||
expire_offer_cache: 518400 # 6 days
|
|
||||||
expire_sku_cache: 432000 # 5 days
|
|
||||||
expire_version_cache: 345600 # 4 days
|
|
||||||
expire_group_cache: 14400 # 4 hours
|
|
||||||
expire_interface_cache: 3600 # 1 hour
|
|
||||||
expire_network_cache: 3600 # 1 hour
|
|
||||||
|
|
||||||
Cloud Profiles
|
|
||||||
==============
|
|
||||||
Set up an initial profile at ``/etc/salt/cloud.profiles``:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
azure-ubuntu-pass:
|
|
||||||
provider: my-azure-config
|
|
||||||
image: Canonical|UbuntuServer|14.04.5-LTS|14.04.201612050
|
|
||||||
size: Standard_D1_v2
|
|
||||||
location: eastus
|
|
||||||
ssh_username: azureuser
|
|
||||||
ssh_password: verybadpass
|
|
||||||
|
|
||||||
azure-ubuntu-key:
|
|
||||||
provider: my-azure-config
|
|
||||||
image: Canonical|UbuntuServer|14.04.5-LTS|14.04.201612050
|
|
||||||
size: Standard_D1_v2
|
|
||||||
location: eastus
|
|
||||||
ssh_username: azureuser
|
|
||||||
ssh_publickeyfile: /path/to/ssh_public_key.pub
|
|
||||||
|
|
||||||
azure-win2012:
|
|
||||||
provider: my-azure-config
|
|
||||||
image: MicrosoftWindowsServer|WindowsServer|2012-R2-Datacenter|latest
|
|
||||||
size: Standard_D1_v2
|
|
||||||
location: westus
|
|
||||||
win_username: azureuser
|
|
||||||
win_password: verybadpass
|
|
||||||
|
|
||||||
These options are described in more detail below. Once configured, the profile
|
|
||||||
can be realized with a salt command:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-cloud -p azure-ubuntu newinstance
|
|
||||||
|
|
||||||
This will create an salt minion instance named ``newinstance`` in Azure. If
|
|
||||||
the command was executed on the salt-master, its Salt key will automatically
|
|
||||||
be signed on the master.
|
|
||||||
|
|
||||||
Once the instance has been created with salt-minion installed, connectivity to
|
|
||||||
it can be verified with Salt:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt newinstance test.version
|
|
||||||
|
|
||||||
|
|
||||||
Profile Options
|
|
||||||
===============
|
|
||||||
The following options are currently available for Azure ARM.
|
|
||||||
|
|
||||||
provider
|
|
||||||
--------
|
|
||||||
The name of the provider as configured in
|
|
||||||
`/etc/salt/cloud.providers.d/azure.conf`.
|
|
||||||
|
|
||||||
image
|
|
||||||
-----
|
|
||||||
Required. The name of the image to use to create a VM. Available images can be
|
|
||||||
viewed using the following command:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-cloud --list-images my-azure-config
|
|
||||||
|
|
||||||
As you will see in ``--list-images``, image names are comprised of the following
|
|
||||||
fields, separated by the pipe (``|``) character:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
publisher: For example, Canonical or MicrosoftWindowsServer
|
|
||||||
offer: For example, UbuntuServer or WindowsServer
|
|
||||||
sku: Such as 14.04.5-LTS or 2012-R2-Datacenter
|
|
||||||
version: Such as 14.04.201612050 or latest
|
|
||||||
|
|
||||||
It is possible to specify the URL or resource ID path of a custom image that you
|
|
||||||
have access to, such as:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
https://<mystorage>.blob.core.windows.net/system/Microsoft.Compute/Images/<mystorage>/template-osDisk.01234567-890a-bcdef0123-4567890abcde.vhd
|
|
||||||
|
|
||||||
or:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/myRG/providers/Microsoft.Compute/images/myImage
|
|
||||||
|
|
||||||
size
|
|
||||||
----
|
|
||||||
Required. The name of the size to use to create a VM. Available sizes can be
|
|
||||||
viewed using the following command:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-cloud --list-sizes my-azure-config
|
|
||||||
|
|
||||||
location
|
|
||||||
--------
|
|
||||||
Required. The name of the location to create a VM in. Available locations can
|
|
||||||
be viewed using the following command:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-cloud --list-locations my-azure-config
|
|
||||||
|
|
||||||
ssh_username
|
|
||||||
------------
|
|
||||||
Required for Linux. The admin user to add on the instance. It is also used to log
|
|
||||||
into the newly-created VM to install Salt.
|
|
||||||
|
|
||||||
ssh_keyfile
|
|
||||||
-----------
|
|
||||||
Required if using SSH key authentication. The path on the Salt master to the SSH private
|
|
||||||
key used during the minion bootstrap process.
|
|
||||||
|
|
||||||
ssh_publickeyfile
|
|
||||||
-----------------
|
|
||||||
Use either ``ssh_publickeyfile`` or ``ssh_password``. The path on the Salt master to the
|
|
||||||
SSH public key which will be pushed to the Linux VM.
|
|
||||||
|
|
||||||
ssh_password
|
|
||||||
------------
|
|
||||||
Use either ``ssh_publickeyfile`` or ``ssh_password``. The password for the admin user on
|
|
||||||
the newly-created Linux virtual machine.
|
|
||||||
|
|
||||||
win_username
|
|
||||||
------------
|
|
||||||
Required for Windows. The user to use to log into the newly-created Windows VM
|
|
||||||
to install Salt.
|
|
||||||
|
|
||||||
win_password
|
|
||||||
------------
|
|
||||||
Required for Windows. The password to use to log into the newly-created Windows
|
|
||||||
VM to install Salt.
|
|
||||||
|
|
||||||
win_installer
|
|
||||||
-------------
|
|
||||||
Required for Windows. The path to the Salt installer to be uploaded.
|
|
||||||
|
|
||||||
resource_group
|
|
||||||
--------------
|
|
||||||
Required. The resource group that all VM resources (VM, network interfaces,
|
|
||||||
etc) will be created in.
|
|
||||||
|
|
||||||
network_resource_group
|
|
||||||
----------------------
|
|
||||||
Optional. If specified, then the VM will be connected to the virtual network
|
|
||||||
in this resource group, rather than the parent resource group of the instance.
|
|
||||||
The VM interfaces and IPs will remain in the configured ``resource_group`` with
|
|
||||||
the VM.
|
|
||||||
|
|
||||||
network
|
|
||||||
-------
|
|
||||||
Required. The virtual network that the VM will be spun up in.
|
|
||||||
|
|
||||||
subnet
|
|
||||||
------
|
|
||||||
Optional. The subnet inside the virtual network that the VM will be spun up in.
|
|
||||||
Default is ``default``.
|
|
||||||
|
|
||||||
allocate_public_ip
|
|
||||||
------------------
|
|
||||||
Optional. Default is ``False``. If set to ``True``, a public IP will
|
|
||||||
be created and assigned to the VM.
|
|
||||||
|
|
||||||
load_balancer
|
|
||||||
-------------
|
|
||||||
Optional. The load-balancer for the VM's network interface to join. If
|
|
||||||
specified the backend_pool option need to be set.
|
|
||||||
|
|
||||||
backend_pool
|
|
||||||
------------
|
|
||||||
Optional. Required if the load_balancer option is set. The load-balancer's
|
|
||||||
Backend Pool the VM's network interface will join.
|
|
||||||
|
|
||||||
iface_name
|
|
||||||
----------
|
|
||||||
Optional. The name to apply to the VM's network interface. If not supplied, the
|
|
||||||
value will be set to ``<VM name>-iface0``.
|
|
||||||
|
|
||||||
dns_servers
|
|
||||||
-----------
|
|
||||||
Optional. A **list** of the DNS servers to configure for the network interface
|
|
||||||
(will be set on the VM by the DHCP of the VNET).
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
my-azurearm-profile:
|
|
||||||
provider: azurearm-provider
|
|
||||||
network: mynetwork
|
|
||||||
dns_servers:
|
|
||||||
- 10.1.1.4
|
|
||||||
- 10.1.1.5
|
|
||||||
|
|
||||||
availability_set
|
|
||||||
----------------
|
|
||||||
Optional. If set, the VM will be added to the specified availability set.
|
|
||||||
|
|
||||||
volumes
|
|
||||||
-------
|
|
||||||
|
|
||||||
Optional. A list of dictionaries describing data disks to attach to the
|
|
||||||
instance can be specified using this setting. The data disk dictionaries are
|
|
||||||
passed entirely to the `Azure DataDisk object
|
|
||||||
<https://docs.microsoft.com/en-us/python/api/azure.mgmt.compute.v2017_12_01.models.datadisk?view=azure-python>`_,
|
|
||||||
so ad-hoc options can be handled as long as they are valid properties of the
|
|
||||||
object.
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- disk_size_gb: 50
|
|
||||||
caching: ReadWrite
|
|
||||||
- disk_size_gb: 100
|
|
||||||
caching: ReadWrite
|
|
||||||
managed_disk:
|
|
||||||
storage_account_type: Standard_LRS
|
|
||||||
|
|
||||||
cleanup_disks
|
|
||||||
-------------
|
|
||||||
Optional. Default is ``False``. If set to ``True``, disks will be cleaned up
|
|
||||||
when the VM that they belong to is deleted.
|
|
||||||
|
|
||||||
cleanup_vhds
|
|
||||||
------------
|
|
||||||
Optional. Default is ``False``. If set to ``True``, VHDs will be cleaned up
|
|
||||||
when the VM and disk that they belong to are deleted. Requires ``cleanup_disks``
|
|
||||||
to be set to ``True``.
|
|
||||||
|
|
||||||
cleanup_data_disks
|
|
||||||
------------------
|
|
||||||
Optional. Default is ``False``. If set to ``True``, data disks (non-root
|
|
||||||
volumes) will be cleaned up whtn the VM that they are attached to is deleted.
|
|
||||||
Requires ``cleanup_disks`` to be set to ``True``.
|
|
||||||
|
|
||||||
cleanup_interfaces
|
|
||||||
------------------
|
|
||||||
Optional. Default is ``False``. Normally when a VM is deleted, its associated
|
|
||||||
interfaces and IPs are retained. This is useful if you expect the deleted VM
|
|
||||||
to be recreated with the same name and network settings. If you would like
|
|
||||||
interfaces and IPs to be deleted when their associated VM is deleted, set this
|
|
||||||
to ``True``.
|
|
||||||
|
|
||||||
userdata
|
|
||||||
--------
|
|
||||||
Optional. Any custom cloud data that needs to be specified. How this data is
|
|
||||||
used depends on the operating system and image that is used. For instance,
|
|
||||||
Linux images that use ``cloud-init`` will import this data for use with that
|
|
||||||
program. Some Windows images will create a file with a copy of this data, and
|
|
||||||
others will ignore it. If a Windows image creates a file, then the location
|
|
||||||
will depend upon the version of Windows. This will be ignored if the
|
|
||||||
``userdata_file`` is specified.
|
|
||||||
|
|
||||||
userdata_file
|
|
||||||
-------------
|
|
||||||
Optional. The path to a file to be read and submitted to Azure as user data.
|
|
||||||
How this is used depends on the operating system that is being deployed. If
|
|
||||||
used, any ``userdata`` setting will be ignored.
|
|
||||||
|
|
||||||
userdata_sendkeys
|
|
||||||
-----------------
|
|
||||||
Optional. Set to ``True`` in order to generate salt minion keys and provide
|
|
||||||
them as variables to the userdata script when running it through the template
|
|
||||||
renderer. The keys can be referenced as ``{{opts['priv_key']}}`` and
|
|
||||||
``{{opts['pub_key']}}``.
|
|
||||||
|
|
||||||
userdata_template
|
|
||||||
-----------------
|
|
||||||
Optional. Enter the renderer, such as ``jinja``, to be used for the userdata
|
|
||||||
script template.
|
|
||||||
|
|
||||||
wait_for_ip_timeout
|
|
||||||
-------------------
|
|
||||||
Optional. Default is ``600``. When waiting for a VM to be created, Salt Cloud
|
|
||||||
will attempt to connect to the VM's IP address until it starts responding. This
|
|
||||||
setting specifies the maximum time to wait for a response.
|
|
||||||
|
|
||||||
wait_for_ip_interval
|
|
||||||
--------------------
|
|
||||||
Optional. Default is ``10``. How long to wait between attempts to connect to
|
|
||||||
the VM's IP.
|
|
||||||
|
|
||||||
wait_for_ip_interval_multiplier
|
|
||||||
-------------------------------
|
|
||||||
Optional. Default is ``1``. Increase the interval by this multiplier after
|
|
||||||
each request; helps with throttling.
|
|
||||||
|
|
||||||
expire_publisher_cache
|
|
||||||
----------------------
|
|
||||||
Optional. Default is ``604800``. When fetching image data using
|
|
||||||
``--list-images``, a number of web calls need to be made to the Azure ARM API.
|
|
||||||
This is normally very fast when performed using a VM that exists inside Azure
|
|
||||||
itself, but can be very slow when made from an external connection.
|
|
||||||
|
|
||||||
By default, the publisher data will be cached, and only updated every ``604800``
|
|
||||||
seconds (7 days). If you need the publisher cache to be updated at a different
|
|
||||||
frequency, change this setting. Setting it to ``0`` will turn off the publisher
|
|
||||||
cache.
|
|
||||||
|
|
||||||
expire_offer_cache
|
|
||||||
------------------
|
|
||||||
Optional. Default is ``518400``. See ``expire_publisher_cache`` for details on
|
|
||||||
why this exists.
|
|
||||||
|
|
||||||
By default, the offer data will be cached, and only updated every ``518400``
|
|
||||||
seconds (6 days). If you need the offer cache to be updated at a different
|
|
||||||
frequency, change this setting. Setting it to ``0`` will turn off the publiser
|
|
||||||
cache.
|
|
||||||
|
|
||||||
expire_sku_cache
|
|
||||||
----------------
|
|
||||||
Optional. Default is ``432000``. See ``expire_publisher_cache`` for details on
|
|
||||||
why this exists.
|
|
||||||
|
|
||||||
By default, the sku data will be cached, and only updated every ``432000``
|
|
||||||
seconds (5 days). If you need the sku cache to be updated at a different
|
|
||||||
frequency, change this setting. Setting it to ``0`` will turn off the sku
|
|
||||||
cache.
|
|
||||||
|
|
||||||
expire_version_cache
|
|
||||||
--------------------
|
|
||||||
Optional. Default is ``345600``. See ``expire_publisher_cache`` for details on
|
|
||||||
why this exists.
|
|
||||||
|
|
||||||
By default, the version data will be cached, and only updated every ``345600``
|
|
||||||
seconds (4 days). If you need the version cache to be updated at a different
|
|
||||||
frequency, change this setting. Setting it to ``0`` will turn off the version
|
|
||||||
cache.
|
|
||||||
|
|
||||||
expire_group_cache
|
|
||||||
------------------
|
|
||||||
Optional. Default is ``14400``. See ``expire_publisher_cache`` for details on
|
|
||||||
why this exists.
|
|
||||||
|
|
||||||
By default, the resource group data will be cached, and only updated every
|
|
||||||
``14400`` seconds (4 hours). If you need the resource group cache to be updated
|
|
||||||
at a different frequency, change this setting. Setting it to ``0`` will turn
|
|
||||||
off the resource group cache.
|
|
||||||
|
|
||||||
expire_interface_cache
|
|
||||||
----------------------
|
|
||||||
Optional. Default is ``3600``. See ``expire_publisher_cache`` for details on
|
|
||||||
why this exists.
|
|
||||||
|
|
||||||
By default, the interface data will be cached, and only updated every ``3600``
|
|
||||||
seconds (1 hour). If you need the interface cache to be updated at a different
|
|
||||||
frequency, change this setting. Setting it to ``0`` will turn off the interface
|
|
||||||
cache.
|
|
||||||
|
|
||||||
expire_network_cache
|
|
||||||
--------------------
|
|
||||||
Optional. Default is ``3600``. See ``expire_publisher_cache`` for details on
|
|
||||||
why this exists.
|
|
||||||
|
|
||||||
By default, the network data will be cached, and only updated every ``3600``
|
|
||||||
seconds (1 hour). If you need the network cache to be updated at a different
|
|
||||||
frequency, change this setting. Setting it to ``0`` will turn off the network
|
|
||||||
cache.
|
|
||||||
|
|
||||||
|
|
||||||
Other Options
|
|
||||||
=============
|
|
||||||
Other options relevant to Azure ARM.
|
|
||||||
|
|
||||||
storage_account
|
|
||||||
---------------
|
|
||||||
Required for actions involving an Azure storage account.
|
|
||||||
|
|
||||||
storage_key
|
|
||||||
-----------
|
|
||||||
Required for actions involving an Azure storage account.
|
|
||||||
|
|
||||||
|
|
||||||
Show Instance
|
|
||||||
=============
|
|
||||||
This action is a thin wrapper around ``--full-query``, which displays details on
|
|
||||||
a single instance only. In an environment with several machines, this will save
|
|
||||||
a user from having to sort through all instance data, just to examine a single
|
|
||||||
instance.
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-cloud -a show_instance myinstance
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,396 +0,0 @@
|
||||||
"""
|
|
||||||
The backend for serving files from the Azure blob storage service.
|
|
||||||
|
|
||||||
.. versionadded:: 2015.8.0
|
|
||||||
|
|
||||||
To enable, add ``azurefs`` to the :conf_master:`fileserver_backend` option in
|
|
||||||
the Master config file.
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
fileserver_backend:
|
|
||||||
- azurefs
|
|
||||||
|
|
||||||
Starting in Salt 2018.3.0, this fileserver requires the standalone Azure
|
|
||||||
Storage SDK for Python. Theoretically any version >= v0.20.0 should work, but
|
|
||||||
it was developed against the v0.33.0 version.
|
|
||||||
|
|
||||||
Each storage container will be mapped to an environment. By default, containers
|
|
||||||
will be mapped to the ``base`` environment. You can override this behavior with
|
|
||||||
the ``saltenv`` configuration option. You can have an unlimited number of
|
|
||||||
storage containers, and can have a storage container serve multiple
|
|
||||||
environments, or have multiple storage containers mapped to the same
|
|
||||||
environment. Normal first-found rules apply, and storage containers are
|
|
||||||
searched in the order they are defined.
|
|
||||||
|
|
||||||
You must have either an account_key or a sas_token defined for each container,
|
|
||||||
if it is private. If you use a sas_token, it must have READ and LIST
|
|
||||||
permissions.
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
azurefs:
|
|
||||||
- account_name: my_storage
|
|
||||||
account_key: 'fNH9cRp0+qVIVYZ+5rnZAhHc9ycOUcJnHtzpfOr0W0sxrtL2KVLuMe1xDfLwmfed+JJInZaEdWVCPHD4d/oqeA=='
|
|
||||||
container_name: my_container
|
|
||||||
- account_name: my_storage
|
|
||||||
sas_token: 'ss=b&sp=&sv=2015-07-08&sig=cohxXabx8FQdXsSEHyUXMjsSfNH2tZ2OB97Ou44pkRE%3D&srt=co&se=2017-04-18T21%3A38%3A01Z'
|
|
||||||
container_name: my_dev_container
|
|
||||||
saltenv: dev
|
|
||||||
- account_name: my_storage
|
|
||||||
container_name: my_public_container
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Do not include the leading ? for sas_token if generated from the web
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
import salt.fileserver
|
|
||||||
import salt.utils.files
|
|
||||||
import salt.utils.gzip_util
|
|
||||||
import salt.utils.hashutils
|
|
||||||
import salt.utils.json
|
|
||||||
import salt.utils.path
|
|
||||||
import salt.utils.stringutils
|
|
||||||
from salt.utils.versions import Version
|
|
||||||
|
|
||||||
try:
|
|
||||||
import azure.storage
|
|
||||||
|
|
||||||
if Version(azure.storage.__version__) < Version("0.20.0"):
|
|
||||||
raise ImportError("azure.storage.__version__ must be >= 0.20.0")
|
|
||||||
HAS_AZURE = True
|
|
||||||
except (ImportError, AttributeError):
|
|
||||||
HAS_AZURE = False
|
|
||||||
|
|
||||||
|
|
||||||
__virtualname__ = "azurefs"
|
|
||||||
|
|
||||||
log = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
|
||||||
"""
|
|
||||||
Only load if defined in fileserver_backend and azure.storage is present
|
|
||||||
"""
|
|
||||||
if __virtualname__ not in __opts__["fileserver_backend"]:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not HAS_AZURE:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if "azurefs" not in __opts__:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not _validate_config():
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def find_file(path, saltenv="base", **kwargs):
|
|
||||||
"""
|
|
||||||
Search the environment for the relative path
|
|
||||||
"""
|
|
||||||
fnd = {"path": "", "rel": ""}
|
|
||||||
for container in __opts__.get("azurefs", []):
|
|
||||||
if container.get("saltenv", "base") != saltenv:
|
|
||||||
continue
|
|
||||||
full = os.path.join(_get_container_path(container), path)
|
|
||||||
if os.path.isfile(full) and not salt.fileserver.is_file_ignored(__opts__, path):
|
|
||||||
fnd["path"] = full
|
|
||||||
fnd["rel"] = path
|
|
||||||
try:
|
|
||||||
# Converting the stat result to a list, the elements of the
|
|
||||||
# list correspond to the following stat_result params:
|
|
||||||
# 0 => st_mode=33188
|
|
||||||
# 1 => st_ino=10227377
|
|
||||||
# 2 => st_dev=65026
|
|
||||||
# 3 => st_nlink=1
|
|
||||||
# 4 => st_uid=1000
|
|
||||||
# 5 => st_gid=1000
|
|
||||||
# 6 => st_size=1056233
|
|
||||||
# 7 => st_atime=1468284229
|
|
||||||
# 8 => st_mtime=1456338235
|
|
||||||
# 9 => st_ctime=1456338235
|
|
||||||
fnd["stat"] = list(os.stat(full))
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
pass
|
|
||||||
return fnd
|
|
||||||
return fnd
|
|
||||||
|
|
||||||
|
|
||||||
def envs():
|
|
||||||
"""
|
|
||||||
Each container configuration can have an environment setting, or defaults
|
|
||||||
to base
|
|
||||||
"""
|
|
||||||
saltenvs = []
|
|
||||||
for container in __opts__.get("azurefs", []):
|
|
||||||
saltenvs.append(container.get("saltenv", "base"))
|
|
||||||
# Remove duplicates
|
|
||||||
return list(set(saltenvs))
|
|
||||||
|
|
||||||
|
|
||||||
def serve_file(load, fnd):
|
|
||||||
"""
|
|
||||||
Return a chunk from a file based on the data received
|
|
||||||
"""
|
|
||||||
ret = {"data": "", "dest": ""}
|
|
||||||
required_load_keys = ("path", "loc", "saltenv")
|
|
||||||
if not all(x in load for x in required_load_keys):
|
|
||||||
log.debug(
|
|
||||||
"Not all of the required keys present in payload. Missing: %s",
|
|
||||||
", ".join(required_load_keys.difference(load)),
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
if not fnd["path"]:
|
|
||||||
return ret
|
|
||||||
ret["dest"] = fnd["rel"]
|
|
||||||
gzip = load.get("gzip", None)
|
|
||||||
fpath = os.path.normpath(fnd["path"])
|
|
||||||
with salt.utils.files.fopen(fpath, "rb") as fp_:
|
|
||||||
fp_.seek(load["loc"])
|
|
||||||
data = fp_.read(__opts__["file_buffer_size"])
|
|
||||||
if data and not salt.utils.files.is_binary(fpath):
|
|
||||||
data = data.decode(__salt_system_encoding__)
|
|
||||||
if gzip and data:
|
|
||||||
data = salt.utils.gzip_util.compress(data, gzip)
|
|
||||||
ret["gzip"] = gzip
|
|
||||||
ret["data"] = data
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def update():
|
|
||||||
"""
|
|
||||||
Update caches of the storage containers.
|
|
||||||
|
|
||||||
Compares the md5 of the files on disk to the md5 of the blobs in the
|
|
||||||
container, and only updates if necessary.
|
|
||||||
|
|
||||||
Also processes deletions by walking the container caches and comparing
|
|
||||||
with the list of blobs in the container
|
|
||||||
"""
|
|
||||||
for container in __opts__["azurefs"]:
|
|
||||||
path = _get_container_path(container)
|
|
||||||
try:
|
|
||||||
if not os.path.exists(path):
|
|
||||||
os.makedirs(path)
|
|
||||||
elif not os.path.isdir(path):
|
|
||||||
shutil.rmtree(path)
|
|
||||||
os.makedirs(path)
|
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
|
||||||
log.exception("Error occurred creating cache directory for azurefs")
|
|
||||||
continue
|
|
||||||
blob_service = _get_container_service(container)
|
|
||||||
name = container["container_name"]
|
|
||||||
try:
|
|
||||||
blob_list = blob_service.list_blobs(name)
|
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
|
||||||
log.exception("Error occurred fetching blob list for azurefs")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Walk the cache directory searching for deletions
|
|
||||||
blob_names = [blob.name for blob in blob_list]
|
|
||||||
blob_set = set(blob_names)
|
|
||||||
for root, dirs, files in salt.utils.path.os_walk(path):
|
|
||||||
for f in files:
|
|
||||||
fname = os.path.join(root, f)
|
|
||||||
relpath = os.path.relpath(fname, path)
|
|
||||||
if relpath not in blob_set:
|
|
||||||
salt.fileserver.wait_lock(fname + ".lk", fname)
|
|
||||||
try:
|
|
||||||
os.unlink(fname)
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
pass
|
|
||||||
if not dirs and not files:
|
|
||||||
shutil.rmtree(root)
|
|
||||||
|
|
||||||
for blob in blob_list:
|
|
||||||
fname = os.path.join(path, blob.name)
|
|
||||||
update = False
|
|
||||||
if os.path.exists(fname):
|
|
||||||
# File exists, check the hashes
|
|
||||||
source_md5 = blob.properties.content_settings.content_md5
|
|
||||||
local_md5 = base64.b64encode(
|
|
||||||
salt.utils.hashutils.get_hash(fname, "md5").decode("hex")
|
|
||||||
)
|
|
||||||
if local_md5 != source_md5:
|
|
||||||
update = True
|
|
||||||
else:
|
|
||||||
update = True
|
|
||||||
|
|
||||||
if update:
|
|
||||||
if not os.path.exists(os.path.dirname(fname)):
|
|
||||||
os.makedirs(os.path.dirname(fname))
|
|
||||||
# Lock writes
|
|
||||||
lk_fn = fname + ".lk"
|
|
||||||
salt.fileserver.wait_lock(lk_fn, fname)
|
|
||||||
with salt.utils.files.fopen(lk_fn, "w"):
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
blob_service.get_blob_to_path(name, blob.name, fname)
|
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
|
||||||
log.exception("Error occurred fetching blob from azurefs")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Unlock writes
|
|
||||||
try:
|
|
||||||
os.unlink(lk_fn)
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Write out file list
|
|
||||||
container_list = path + ".list"
|
|
||||||
lk_fn = container_list + ".lk"
|
|
||||||
salt.fileserver.wait_lock(lk_fn, container_list)
|
|
||||||
with salt.utils.files.fopen(lk_fn, "w"):
|
|
||||||
pass
|
|
||||||
with salt.utils.files.fopen(container_list, "w") as fp_:
|
|
||||||
salt.utils.json.dump(blob_names, fp_)
|
|
||||||
try:
|
|
||||||
os.unlink(lk_fn)
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
hash_cachedir = os.path.join(__opts__["cachedir"], "azurefs", "hashes")
|
|
||||||
shutil.rmtree(hash_cachedir)
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
log.exception("Problem occurred trying to invalidate hash cach for azurefs")
|
|
||||||
|
|
||||||
|
|
||||||
def file_hash(load, fnd):
|
|
||||||
"""
|
|
||||||
Return a file hash based on the hash type set in the master config
|
|
||||||
"""
|
|
||||||
if not all(x in load for x in ("path", "saltenv")):
|
|
||||||
return "", None
|
|
||||||
ret = {"hash_type": __opts__["hash_type"]}
|
|
||||||
relpath = fnd["rel"]
|
|
||||||
path = fnd["path"]
|
|
||||||
hash_cachedir = os.path.join(__opts__["cachedir"], "azurefs", "hashes")
|
|
||||||
hashdest = salt.utils.path.join(
|
|
||||||
hash_cachedir,
|
|
||||||
load["saltenv"],
|
|
||||||
"{}.hash.{}".format(relpath, __opts__["hash_type"]),
|
|
||||||
)
|
|
||||||
if not os.path.isfile(hashdest):
|
|
||||||
if not os.path.exists(os.path.dirname(hashdest)):
|
|
||||||
os.makedirs(os.path.dirname(hashdest))
|
|
||||||
ret["hsum"] = salt.utils.hashutils.get_hash(path, __opts__["hash_type"])
|
|
||||||
with salt.utils.files.fopen(hashdest, "w+") as fp_:
|
|
||||||
fp_.write(salt.utils.stringutils.to_str(ret["hsum"]))
|
|
||||||
return ret
|
|
||||||
else:
|
|
||||||
with salt.utils.files.fopen(hashdest, "rb") as fp_:
|
|
||||||
ret["hsum"] = salt.utils.stringutils.to_unicode(fp_.read())
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def file_list(load):
|
|
||||||
"""
|
|
||||||
Return a list of all files in a specified environment
|
|
||||||
"""
|
|
||||||
ret = set()
|
|
||||||
try:
|
|
||||||
for container in __opts__["azurefs"]:
|
|
||||||
if container.get("saltenv", "base") != load["saltenv"]:
|
|
||||||
continue
|
|
||||||
container_list = _get_container_path(container) + ".list"
|
|
||||||
lk = container_list + ".lk"
|
|
||||||
salt.fileserver.wait_lock(lk, container_list, 5)
|
|
||||||
if not os.path.exists(container_list):
|
|
||||||
continue
|
|
||||||
with salt.utils.files.fopen(container_list, "r") as fp_:
|
|
||||||
ret.update(set(salt.utils.json.load(fp_)))
|
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
|
||||||
log.error(
|
|
||||||
"azurefs: an error ocurred retrieving file lists. "
|
|
||||||
"It should be resolved next time the fileserver "
|
|
||||||
"updates. Please do not manually modify the azurefs "
|
|
||||||
"cache directory."
|
|
||||||
)
|
|
||||||
return list(ret)
|
|
||||||
|
|
||||||
|
|
||||||
def dir_list(load):
|
|
||||||
"""
|
|
||||||
Return a list of all directories in a specified environment
|
|
||||||
"""
|
|
||||||
ret = set()
|
|
||||||
files = file_list(load)
|
|
||||||
for f in files:
|
|
||||||
dirname = f
|
|
||||||
while dirname:
|
|
||||||
dirname = os.path.dirname(dirname)
|
|
||||||
if dirname:
|
|
||||||
ret.add(dirname)
|
|
||||||
return list(ret)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_container_path(container):
|
|
||||||
"""
|
|
||||||
Get the cache path for the container in question
|
|
||||||
|
|
||||||
Cache paths are generate by combining the account name, container name,
|
|
||||||
and saltenv, separated by underscores
|
|
||||||
"""
|
|
||||||
root = os.path.join(__opts__["cachedir"], "azurefs")
|
|
||||||
container_dir = "{}_{}_{}".format(
|
|
||||||
container.get("account_name", ""),
|
|
||||||
container.get("container_name", ""),
|
|
||||||
container.get("saltenv", "base"),
|
|
||||||
)
|
|
||||||
return os.path.join(root, container_dir)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_container_service(container):
|
|
||||||
"""
|
|
||||||
Get the azure block blob service for the container in question
|
|
||||||
|
|
||||||
Try account_key, sas_token, and no auth in that order
|
|
||||||
"""
|
|
||||||
if "account_key" in container:
|
|
||||||
account = azure.storage.CloudStorageAccount(
|
|
||||||
container["account_name"], account_key=container["account_key"]
|
|
||||||
)
|
|
||||||
elif "sas_token" in container:
|
|
||||||
account = azure.storage.CloudStorageAccount(
|
|
||||||
container["account_name"], sas_token=container["sas_token"]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
account = azure.storage.CloudStorageAccount(container["account_name"])
|
|
||||||
blob_service = account.create_block_blob_service()
|
|
||||||
return blob_service
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_config():
|
|
||||||
"""
|
|
||||||
Validate azurefs config, return False if it doesn't validate
|
|
||||||
"""
|
|
||||||
if not isinstance(__opts__["azurefs"], list):
|
|
||||||
log.error("azurefs configuration is not formed as a list, skipping azurefs")
|
|
||||||
return False
|
|
||||||
for container in __opts__["azurefs"]:
|
|
||||||
if not isinstance(container, dict):
|
|
||||||
log.error(
|
|
||||||
"One or more entries in the azurefs configuration list are "
|
|
||||||
"not formed as a dict. Skipping azurefs: %s",
|
|
||||||
container,
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
if "account_name" not in container or "container_name" not in container:
|
|
||||||
log.error(
|
|
||||||
"An azurefs container configuration is missing either an "
|
|
||||||
"account_name or a container_name: %s",
|
|
||||||
container,
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
return True
|
|
|
@ -1,45 +0,0 @@
|
||||||
"""
|
|
||||||
Grains from cloud metadata servers at 169.254.169.254 in Azure Virtual Machine
|
|
||||||
|
|
||||||
.. versionadded:: 3006.0
|
|
||||||
|
|
||||||
:depends: requests
|
|
||||||
|
|
||||||
To enable these grains that pull from the http://169.254.169.254/metadata/instance?api-version=2020-09-01
|
|
||||||
metadata server set `metadata_server_grains: True` in the minion config.
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
metadata_server_grains: True
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import salt.utils.http as http
|
|
||||||
import salt.utils.json
|
|
||||||
|
|
||||||
HOST = "http://169.254.169.254"
|
|
||||||
URL = f"{HOST}/metadata/instance?api-version=2020-09-01"
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
|
||||||
# Check if metadata_server_grains minion option is enabled
|
|
||||||
if __opts__.get("metadata_server_grains", False) is False:
|
|
||||||
return False
|
|
||||||
azuretest = http.query(
|
|
||||||
URL, status=True, headers=True, header_list=["Metadata: true"]
|
|
||||||
)
|
|
||||||
if azuretest.get("status", 404) != 200:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def metadata():
|
|
||||||
"""Takes no arguments, returns a dictionary of metadata values from Azure."""
|
|
||||||
log.debug("All checks true - loading azure metadata")
|
|
||||||
result = http.query(URL, headers=True, header_list=["Metadata: true"])
|
|
||||||
metadata = salt.utils.json.loads(result.get("body", {}))
|
|
||||||
|
|
||||||
return metadata
|
|
|
@ -1,754 +0,0 @@
|
||||||
"""
|
|
||||||
Azure (ARM) Compute Execution Module
|
|
||||||
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This cloud provider will be removed from Salt in version 3007 in favor of
|
|
||||||
the `saltext.azurerm Salt Extension
|
|
||||||
<https://github.com/salt-extensions/saltext-azurerm>`_
|
|
||||||
|
|
||||||
:maintainer: <devops@eitr.tech>
|
|
||||||
:maturity: new
|
|
||||||
:depends:
|
|
||||||
* `azure <https://pypi.python.org/pypi/azure>`_ >= 2.0.0
|
|
||||||
* `azure-common <https://pypi.python.org/pypi/azure-common>`_ >= 1.1.8
|
|
||||||
* `azure-mgmt <https://pypi.python.org/pypi/azure-mgmt>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-compute <https://pypi.python.org/pypi/azure-mgmt-compute>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-network <https://pypi.python.org/pypi/azure-mgmt-network>`_ >= 1.7.1
|
|
||||||
* `azure-mgmt-resource <https://pypi.python.org/pypi/azure-mgmt-resource>`_ >= 1.1.0
|
|
||||||
* `azure-mgmt-storage <https://pypi.python.org/pypi/azure-mgmt-storage>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-web <https://pypi.python.org/pypi/azure-mgmt-web>`_ >= 0.32.0
|
|
||||||
* `azure-storage <https://pypi.python.org/pypi/azure-storage>`_ >= 0.34.3
|
|
||||||
* `msrestazure <https://pypi.python.org/pypi/msrestazure>`_ >= 0.4.21
|
|
||||||
:platform: linux
|
|
||||||
|
|
||||||
:configuration: This module requires Azure Resource Manager credentials to be passed as keyword arguments
|
|
||||||
to every function in order to work properly.
|
|
||||||
|
|
||||||
Required provider parameters:
|
|
||||||
|
|
||||||
if using username and password:
|
|
||||||
* ``subscription_id``
|
|
||||||
* ``username``
|
|
||||||
* ``password``
|
|
||||||
|
|
||||||
if using a service principal:
|
|
||||||
* ``subscription_id``
|
|
||||||
* ``tenant``
|
|
||||||
* ``client_id``
|
|
||||||
* ``secret``
|
|
||||||
|
|
||||||
Optional provider parameters:
|
|
||||||
|
|
||||||
**cloud_environment**: Used to point the cloud driver to different API endpoints, such as Azure GovCloud.
|
|
||||||
Possible values:
|
|
||||||
* ``AZURE_PUBLIC_CLOUD`` (default)
|
|
||||||
* ``AZURE_CHINA_CLOUD``
|
|
||||||
* ``AZURE_US_GOV_CLOUD``
|
|
||||||
* ``AZURE_GERMAN_CLOUD``
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Python libs
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
import salt.utils.azurearm
|
|
||||||
|
|
||||||
# Azure libs
|
|
||||||
HAS_LIBS = False
|
|
||||||
try:
|
|
||||||
import azure.mgmt.compute.models # pylint: disable=unused-import
|
|
||||||
from msrest.exceptions import SerializationError
|
|
||||||
from msrestazure.azure_exceptions import CloudError
|
|
||||||
|
|
||||||
HAS_LIBS = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
__virtualname__ = "azurearm_compute"
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
|
||||||
if not HAS_LIBS:
|
|
||||||
return (
|
|
||||||
False,
|
|
||||||
"The following dependencies are required to use the AzureARM modules: "
|
|
||||||
"Microsoft Azure SDK for Python >= 2.0rc6, "
|
|
||||||
"MS REST Azure (msrestazure) >= 0.4",
|
|
||||||
)
|
|
||||||
|
|
||||||
return __virtualname__
|
|
||||||
|
|
||||||
|
|
||||||
def _deprecation_message(function):
|
|
||||||
"""
|
|
||||||
Decorator wrapper to warn about azurearm deprecation
|
|
||||||
"""
|
|
||||||
|
|
||||||
@wraps(function)
|
|
||||||
def wrapped(*args, **kwargs):
|
|
||||||
salt.utils.versions.warn_until(
|
|
||||||
"Chlorine",
|
|
||||||
"The 'azurearm' functionality in Salt has been deprecated and its "
|
|
||||||
"functionality will be removed in version 3007 in favor of the "
|
|
||||||
"saltext.azurerm Salt Extension. "
|
|
||||||
"(https://github.com/salt-extensions/saltext-azurerm)",
|
|
||||||
category=FutureWarning,
|
|
||||||
)
|
|
||||||
ret = function(*args, **salt.utils.args.clean_kwargs(**kwargs))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def availability_set_create_or_update(
|
|
||||||
name, resource_group, **kwargs
|
|
||||||
): # pylint: disable=invalid-name
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Create or update an availability set.
|
|
||||||
|
|
||||||
:param name: The availability set to create.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
availability set.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.availability_set_create_or_update testset testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
if "location" not in kwargs:
|
|
||||||
rg_props = __salt__["azurearm_resource.resource_group_get"](
|
|
||||||
resource_group, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" in rg_props:
|
|
||||||
log.error("Unable to determine location from resource group specified.")
|
|
||||||
return False
|
|
||||||
kwargs["location"] = rg_props["location"]
|
|
||||||
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
|
|
||||||
# Use VM names to link to the IDs of existing VMs.
|
|
||||||
if isinstance(kwargs.get("virtual_machines"), list):
|
|
||||||
vm_list = []
|
|
||||||
for vm_name in kwargs.get("virtual_machines"):
|
|
||||||
vm_instance = __salt__["azurearm_compute.virtual_machine_get"](
|
|
||||||
name=vm_name, resource_group=resource_group, **kwargs
|
|
||||||
)
|
|
||||||
if "error" not in vm_instance:
|
|
||||||
vm_list.append({"id": str(vm_instance["id"])})
|
|
||||||
kwargs["virtual_machines"] = vm_list
|
|
||||||
|
|
||||||
try:
|
|
||||||
setmodel = __utils__["azurearm.create_object_model"](
|
|
||||||
"compute", "AvailabilitySet", **kwargs
|
|
||||||
)
|
|
||||||
except TypeError as exc:
|
|
||||||
result = {"error": "The object model could not be built. ({})".format(str(exc))}
|
|
||||||
return result
|
|
||||||
|
|
||||||
try:
|
|
||||||
av_set = compconn.availability_sets.create_or_update(
|
|
||||||
resource_group_name=resource_group,
|
|
||||||
availability_set_name=name,
|
|
||||||
parameters=setmodel,
|
|
||||||
)
|
|
||||||
result = av_set.as_dict()
|
|
||||||
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
except SerializationError as exc:
|
|
||||||
result = {
|
|
||||||
"error": "The object model could not be parsed. ({})".format(str(exc))
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def availability_set_delete(name, resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Delete an availability set.
|
|
||||||
|
|
||||||
:param name: The availability set to delete.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
availability set.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.availability_set_delete testset testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = False
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
compconn.availability_sets.delete(
|
|
||||||
resource_group_name=resource_group, availability_set_name=name
|
|
||||||
)
|
|
||||||
result = True
|
|
||||||
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def availability_set_get(name, resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Get a dictionary representing an availability set's properties.
|
|
||||||
|
|
||||||
:param name: The availability set to get.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
availability set.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.availability_set_get testset testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
av_set = compconn.availability_sets.get(
|
|
||||||
resource_group_name=resource_group, availability_set_name=name
|
|
||||||
)
|
|
||||||
result = av_set.as_dict()
|
|
||||||
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def availability_sets_list(resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
List all availability sets within a resource group.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name to list availability
|
|
||||||
sets within.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.availability_sets_list testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = {}
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
avail_sets = __utils__["azurearm.paged_object_to_list"](
|
|
||||||
compconn.availability_sets.list(resource_group_name=resource_group)
|
|
||||||
)
|
|
||||||
|
|
||||||
for avail_set in avail_sets:
|
|
||||||
result[avail_set["name"]] = avail_set
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def availability_sets_list_available_sizes(
|
|
||||||
name, resource_group, **kwargs
|
|
||||||
): # pylint: disable=invalid-name
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
List all available virtual machine sizes that can be used to
|
|
||||||
to create a new virtual machine in an existing availability set.
|
|
||||||
|
|
||||||
:param name: The availability set name to list available
|
|
||||||
virtual machine sizes within.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name to list available
|
|
||||||
availability set sizes within.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.availability_sets_list_available_sizes testset testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = {}
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
sizes = __utils__["azurearm.paged_object_to_list"](
|
|
||||||
compconn.availability_sets.list_available_sizes(
|
|
||||||
resource_group_name=resource_group, availability_set_name=name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for size in sizes:
|
|
||||||
result[size["name"]] = size
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def virtual_machine_capture(
|
|
||||||
name, destination_name, resource_group, prefix="capture-", overwrite=False, **kwargs
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Captures the VM by copying virtual hard disks of the VM and outputs
|
|
||||||
a template that can be used to create similar VMs.
|
|
||||||
|
|
||||||
:param name: The name of the virtual machine.
|
|
||||||
|
|
||||||
:param destination_name: The destination container name.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
virtual machine.
|
|
||||||
|
|
||||||
:param prefix: (Default: 'capture-') The captured virtual hard disk's name prefix.
|
|
||||||
|
|
||||||
:param overwrite: (Default: False) Overwrite the destination disk in case of conflict.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.virtual_machine_capture testvm testcontainer testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
VirtualMachineCaptureParameters = getattr(
|
|
||||||
azure.mgmt.compute.models, "VirtualMachineCaptureParameters"
|
|
||||||
)
|
|
||||||
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
vm = compconn.virtual_machines.capture(
|
|
||||||
resource_group_name=resource_group,
|
|
||||||
vm_name=name,
|
|
||||||
parameters=VirtualMachineCaptureParameters(
|
|
||||||
vhd_prefix=prefix,
|
|
||||||
destination_container_name=destination_name,
|
|
||||||
overwrite_vhds=overwrite,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
vm.wait()
|
|
||||||
vm_result = vm.result()
|
|
||||||
result = vm_result.as_dict()
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def virtual_machine_get(name, resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Retrieves information about the model view or the instance view of a
|
|
||||||
virtual machine.
|
|
||||||
|
|
||||||
:param name: The name of the virtual machine.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
virtual machine.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.virtual_machine_get testvm testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
expand = kwargs.get("expand")
|
|
||||||
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
vm = compconn.virtual_machines.get(
|
|
||||||
resource_group_name=resource_group, vm_name=name, expand=expand
|
|
||||||
)
|
|
||||||
result = vm.as_dict()
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def virtual_machine_convert_to_managed_disks(
|
|
||||||
name, resource_group, **kwargs
|
|
||||||
): # pylint: disable=invalid-name
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Converts virtual machine disks from blob-based to managed disks. Virtual
|
|
||||||
machine must be stop-deallocated before invoking this operation.
|
|
||||||
|
|
||||||
:param name: The name of the virtual machine to convert.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
virtual machine.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.virtual_machine_convert_to_managed_disks testvm testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
vm = compconn.virtual_machines.convert_to_managed_disks(
|
|
||||||
resource_group_name=resource_group, vm_name=name
|
|
||||||
)
|
|
||||||
vm.wait()
|
|
||||||
vm_result = vm.result()
|
|
||||||
result = vm_result.as_dict()
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def virtual_machine_deallocate(name, resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Power off a virtual machine and deallocate compute resources.
|
|
||||||
|
|
||||||
:param name: The name of the virtual machine to deallocate.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
virtual machine.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.virtual_machine_deallocate testvm testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
vm = compconn.virtual_machines.deallocate(
|
|
||||||
resource_group_name=resource_group, vm_name=name
|
|
||||||
)
|
|
||||||
vm.wait()
|
|
||||||
vm_result = vm.result()
|
|
||||||
result = vm_result.as_dict()
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def virtual_machine_generalize(name, resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Set the state of a virtual machine to 'generalized'.
|
|
||||||
|
|
||||||
:param name: The name of the virtual machine.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
virtual machine.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.virtual_machine_generalize testvm testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = False
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
compconn.virtual_machines.generalize(
|
|
||||||
resource_group_name=resource_group, vm_name=name
|
|
||||||
)
|
|
||||||
result = True
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def virtual_machines_list(resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
List all virtual machines within a resource group.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name to list virtual
|
|
||||||
machines within.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.virtual_machines_list testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = {}
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
vms = __utils__["azurearm.paged_object_to_list"](
|
|
||||||
compconn.virtual_machines.list(resource_group_name=resource_group)
|
|
||||||
)
|
|
||||||
for vm in vms: # pylint: disable=invalid-name
|
|
||||||
result[vm["name"]] = vm
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def virtual_machines_list_all(**kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
List all virtual machines within a subscription.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.virtual_machines_list_all
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = {}
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
vms = __utils__["azurearm.paged_object_to_list"](
|
|
||||||
compconn.virtual_machines.list_all()
|
|
||||||
)
|
|
||||||
for vm in vms: # pylint: disable=invalid-name
|
|
||||||
result[vm["name"]] = vm
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def virtual_machines_list_available_sizes(
|
|
||||||
name, resource_group, **kwargs
|
|
||||||
): # pylint: disable=invalid-name
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Lists all available virtual machine sizes to which the specified virtual
|
|
||||||
machine can be resized.
|
|
||||||
|
|
||||||
:param name: The name of the virtual machine.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
virtual machine.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.virtual_machines_list_available_sizes testvm testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = {}
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
sizes = __utils__["azurearm.paged_object_to_list"](
|
|
||||||
compconn.virtual_machines.list_available_sizes(
|
|
||||||
resource_group_name=resource_group, vm_name=name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for size in sizes:
|
|
||||||
result[size["name"]] = size
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def virtual_machine_power_off(name, resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Power off (stop) a virtual machine.
|
|
||||||
|
|
||||||
:param name: The name of the virtual machine to stop.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
virtual machine.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.virtual_machine_power_off testvm testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
vm = compconn.virtual_machines.power_off(
|
|
||||||
resource_group_name=resource_group, vm_name=name
|
|
||||||
)
|
|
||||||
vm.wait()
|
|
||||||
vm_result = vm.result()
|
|
||||||
result = vm_result.as_dict()
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def virtual_machine_restart(name, resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Restart a virtual machine.
|
|
||||||
|
|
||||||
:param name: The name of the virtual machine to restart.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
virtual machine.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.virtual_machine_restart testvm testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
vm = compconn.virtual_machines.restart(
|
|
||||||
resource_group_name=resource_group, vm_name=name
|
|
||||||
)
|
|
||||||
vm.wait()
|
|
||||||
vm_result = vm.result()
|
|
||||||
result = vm_result.as_dict()
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def virtual_machine_start(name, resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Power on (start) a virtual machine.
|
|
||||||
|
|
||||||
:param name: The name of the virtual machine to start.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
virtual machine.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.virtual_machine_start testvm testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
vm = compconn.virtual_machines.start(
|
|
||||||
resource_group_name=resource_group, vm_name=name
|
|
||||||
)
|
|
||||||
vm.wait()
|
|
||||||
vm_result = vm.result()
|
|
||||||
result = vm_result.as_dict()
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def virtual_machine_redeploy(name, resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Redeploy a virtual machine.
|
|
||||||
|
|
||||||
:param name: The name of the virtual machine to redeploy.
|
|
||||||
|
|
||||||
:param resource_group: The resource group name assigned to the
|
|
||||||
virtual machine.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_compute.virtual_machine_redeploy testvm testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
compconn = __utils__["azurearm.get_client"]("compute", **kwargs)
|
|
||||||
try:
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
vm = compconn.virtual_machines.redeploy(
|
|
||||||
resource_group_name=resource_group, vm_name=name
|
|
||||||
)
|
|
||||||
vm.wait()
|
|
||||||
vm_result = vm.result()
|
|
||||||
result = vm_result.as_dict()
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("compute", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
|
@ -1,552 +0,0 @@
|
||||||
"""
|
|
||||||
Azure (ARM) DNS Execution Module
|
|
||||||
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This cloud provider will be removed from Salt in version 3007 in favor of
|
|
||||||
the `saltext.azurerm Salt Extension
|
|
||||||
<https://github.com/salt-extensions/saltext-azurerm>`_
|
|
||||||
|
|
||||||
:maintainer: <devops@eitr.tech>
|
|
||||||
:maturity: new
|
|
||||||
:depends:
|
|
||||||
* `azure <https://pypi.python.org/pypi/azure>`_ >= 2.0.0
|
|
||||||
* `azure-common <https://pypi.python.org/pypi/azure-common>`_ >= 1.1.8
|
|
||||||
* `azure-mgmt <https://pypi.python.org/pypi/azure-mgmt>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-compute <https://pypi.python.org/pypi/azure-mgmt-compute>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-dns <https://pypi.python.org/pypi/azure-mgmt-dns>`_ >= 2.0.0rc1
|
|
||||||
* `azure-mgmt-network <https://pypi.python.org/pypi/azure-mgmt-network>`_ >= 1.7.1
|
|
||||||
* `azure-mgmt-resource <https://pypi.python.org/pypi/azure-mgmt-resource>`_ >= 1.1.0
|
|
||||||
* `azure-mgmt-storage <https://pypi.python.org/pypi/azure-mgmt-storage>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-web <https://pypi.python.org/pypi/azure-mgmt-web>`_ >= 0.32.0
|
|
||||||
* `azure-storage <https://pypi.python.org/pypi/azure-storage>`_ >= 0.34.3
|
|
||||||
* `msrestazure <https://pypi.python.org/pypi/msrestazure>`_ >= 0.4.21
|
|
||||||
|
|
||||||
:platform: linux
|
|
||||||
:configuration:
|
|
||||||
This module requires Azure Resource Manager credentials to be passed as keyword arguments
|
|
||||||
to every function in order to work properly.
|
|
||||||
|
|
||||||
Required provider parameters:
|
|
||||||
|
|
||||||
if using username and password:
|
|
||||||
|
|
||||||
* ``subscription_id``
|
|
||||||
* ``username``
|
|
||||||
* ``password``
|
|
||||||
|
|
||||||
if using a service principal:
|
|
||||||
|
|
||||||
* ``subscription_id``
|
|
||||||
* ``tenant``
|
|
||||||
* ``client_id``
|
|
||||||
* ``secret``
|
|
||||||
|
|
||||||
Optional provider parameters:
|
|
||||||
|
|
||||||
**cloud_environment**: Used to point the cloud driver to different API endpoints, such as Azure GovCloud.
|
|
||||||
|
|
||||||
Possible values:
|
|
||||||
|
|
||||||
* ``AZURE_PUBLIC_CLOUD`` (default)
|
|
||||||
* ``AZURE_CHINA_CLOUD``
|
|
||||||
* ``AZURE_US_GOV_CLOUD``
|
|
||||||
* ``AZURE_GERMAN_CLOUD``
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Python libs
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
import salt.utils.azurearm
|
|
||||||
|
|
||||||
# Azure libs
|
|
||||||
HAS_LIBS = False
|
|
||||||
try:
|
|
||||||
import azure.mgmt.dns.models # pylint: disable=unused-import
|
|
||||||
from msrest.exceptions import SerializationError
|
|
||||||
from msrestazure.azure_exceptions import CloudError
|
|
||||||
|
|
||||||
HAS_LIBS = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
__virtualname__ = "azurearm_dns"
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
|
||||||
if not HAS_LIBS:
|
|
||||||
return (
|
|
||||||
False,
|
|
||||||
"The following dependencies are required to use the AzureARM modules: "
|
|
||||||
"Microsoft Azure SDK for Python >= 2.0rc6, "
|
|
||||||
"MS REST Azure (msrestazure) >= 0.4",
|
|
||||||
)
|
|
||||||
|
|
||||||
return __virtualname__
|
|
||||||
|
|
||||||
|
|
||||||
def _deprecation_message(function):
|
|
||||||
"""
|
|
||||||
Decorator wrapper to warn about azurearm deprecation
|
|
||||||
"""
|
|
||||||
|
|
||||||
@wraps(function)
|
|
||||||
def wrapped(*args, **kwargs):
|
|
||||||
salt.utils.versions.warn_until(
|
|
||||||
"Chlorine",
|
|
||||||
"The 'azurearm' functionality in Salt has been deprecated and its "
|
|
||||||
"functionality will be removed in version 3007 in favor of the "
|
|
||||||
"saltext.azurerm Salt Extension. "
|
|
||||||
"(https://github.com/salt-extensions/saltext-azurerm)",
|
|
||||||
category=FutureWarning,
|
|
||||||
)
|
|
||||||
ret = function(*args, **salt.utils.args.clean_kwargs(**kwargs))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def record_set_create_or_update(name, zone_name, resource_group, record_type, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Creates or updates a record set within a DNS zone.
|
|
||||||
|
|
||||||
:param name: The name of the record set, relative to the name of the zone.
|
|
||||||
|
|
||||||
:param zone_name: The name of the DNS zone (without a terminating dot).
|
|
||||||
|
|
||||||
:param resource_group: The name of the resource group.
|
|
||||||
|
|
||||||
:param record_type:
|
|
||||||
The type of DNS record in this record set. Record sets of type SOA can be
|
|
||||||
updated but not created (they are created when the DNS zone is created).
|
|
||||||
Possible values include: 'A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT'
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_dns.record_set_create_or_update myhost myzone testgroup A
|
|
||||||
arecords='[{ipv4_address: 10.0.0.1}]' ttl=300
|
|
||||||
|
|
||||||
"""
|
|
||||||
dnsconn = __utils__["azurearm.get_client"]("dns", **kwargs)
|
|
||||||
|
|
||||||
try:
|
|
||||||
record_set_model = __utils__["azurearm.create_object_model"](
|
|
||||||
"dns", "RecordSet", **kwargs
|
|
||||||
)
|
|
||||||
except TypeError as exc:
|
|
||||||
result = {"error": "The object model could not be built. ({})".format(str(exc))}
|
|
||||||
return result
|
|
||||||
|
|
||||||
try:
|
|
||||||
record_set = dnsconn.record_sets.create_or_update(
|
|
||||||
relative_record_set_name=name,
|
|
||||||
zone_name=zone_name,
|
|
||||||
resource_group_name=resource_group,
|
|
||||||
record_type=record_type,
|
|
||||||
parameters=record_set_model,
|
|
||||||
if_match=kwargs.get("if_match"),
|
|
||||||
if_none_match=kwargs.get("if_none_match"),
|
|
||||||
)
|
|
||||||
result = record_set.as_dict()
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("dns", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
except SerializationError as exc:
|
|
||||||
result = {
|
|
||||||
"error": "The object model could not be parsed. ({})".format(str(exc))
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def record_set_delete(name, zone_name, resource_group, record_type, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Deletes a record set from a DNS zone. This operation cannot be undone.
|
|
||||||
|
|
||||||
:param name: The name of the record set, relative to the name of the zone.
|
|
||||||
|
|
||||||
:param zone_name: The name of the DNS zone (without a terminating dot).
|
|
||||||
|
|
||||||
:param resource_group: The name of the resource group.
|
|
||||||
|
|
||||||
:param record_type:
|
|
||||||
The type of DNS record in this record set. Record sets of type SOA cannot be
|
|
||||||
deleted (they are deleted when the DNS zone is deleted).
|
|
||||||
Possible values include: 'A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT'
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_dns.record_set_delete myhost myzone testgroup A
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = False
|
|
||||||
dnsconn = __utils__["azurearm.get_client"]("dns", **kwargs)
|
|
||||||
try:
|
|
||||||
record_set = dnsconn.record_sets.delete(
|
|
||||||
relative_record_set_name=name,
|
|
||||||
zone_name=zone_name,
|
|
||||||
resource_group_name=resource_group,
|
|
||||||
record_type=record_type,
|
|
||||||
if_match=kwargs.get("if_match"),
|
|
||||||
)
|
|
||||||
result = True
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("dns", str(exc), **kwargs)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def record_set_get(name, zone_name, resource_group, record_type, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Get a dictionary representing a record set's properties.
|
|
||||||
|
|
||||||
:param name: The name of the record set, relative to the name of the zone.
|
|
||||||
|
|
||||||
:param zone_name: The name of the DNS zone (without a terminating dot).
|
|
||||||
|
|
||||||
:param resource_group: The name of the resource group.
|
|
||||||
|
|
||||||
:param record_type:
|
|
||||||
The type of DNS record in this record set.
|
|
||||||
Possible values include: 'A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT'
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_dns.record_set_get '@' myzone testgroup SOA
|
|
||||||
|
|
||||||
"""
|
|
||||||
dnsconn = __utils__["azurearm.get_client"]("dns", **kwargs)
|
|
||||||
try:
|
|
||||||
record_set = dnsconn.record_sets.get(
|
|
||||||
relative_record_set_name=name,
|
|
||||||
zone_name=zone_name,
|
|
||||||
resource_group_name=resource_group,
|
|
||||||
record_type=record_type,
|
|
||||||
)
|
|
||||||
result = record_set.as_dict()
|
|
||||||
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("dns", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def record_sets_list_by_type(
|
|
||||||
zone_name, resource_group, record_type, top=None, recordsetnamesuffix=None, **kwargs
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Lists the record sets of a specified type in a DNS zone.
|
|
||||||
|
|
||||||
:param zone_name: The name of the DNS zone (without a terminating dot).
|
|
||||||
|
|
||||||
:param resource_group: The name of the resource group.
|
|
||||||
|
|
||||||
:param record_type:
|
|
||||||
The type of record sets to enumerate.
|
|
||||||
Possible values include: 'A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT'
|
|
||||||
|
|
||||||
:param top:
|
|
||||||
The maximum number of record sets to return. If not specified,
|
|
||||||
returns up to 100 record sets.
|
|
||||||
|
|
||||||
:param recordsetnamesuffix:
|
|
||||||
The suffix label of the record set name that has
|
|
||||||
to be used to filter the record set enumerations.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_dns.record_sets_list_by_type myzone testgroup SOA
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = {}
|
|
||||||
dnsconn = __utils__["azurearm.get_client"]("dns", **kwargs)
|
|
||||||
try:
|
|
||||||
record_sets = __utils__["azurearm.paged_object_to_list"](
|
|
||||||
dnsconn.record_sets.list_by_type(
|
|
||||||
zone_name=zone_name,
|
|
||||||
resource_group_name=resource_group,
|
|
||||||
record_type=record_type,
|
|
||||||
top=top,
|
|
||||||
recordsetnamesuffix=recordsetnamesuffix,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for record_set in record_sets:
|
|
||||||
result[record_set["name"]] = record_set
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("dns", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def record_sets_list_by_dns_zone(
|
|
||||||
zone_name, resource_group, top=None, recordsetnamesuffix=None, **kwargs
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Lists all record sets in a DNS zone.
|
|
||||||
|
|
||||||
:param zone_name: The name of the DNS zone (without a terminating dot).
|
|
||||||
|
|
||||||
:param resource_group: The name of the resource group.
|
|
||||||
|
|
||||||
:param top:
|
|
||||||
The maximum number of record sets to return. If not specified,
|
|
||||||
returns up to 100 record sets.
|
|
||||||
|
|
||||||
:param recordsetnamesuffix:
|
|
||||||
The suffix label of the record set name that has
|
|
||||||
to be used to filter the record set enumerations.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_dns.record_sets_list_by_dns_zone myzone testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = {}
|
|
||||||
dnsconn = __utils__["azurearm.get_client"]("dns", **kwargs)
|
|
||||||
try:
|
|
||||||
record_sets = __utils__["azurearm.paged_object_to_list"](
|
|
||||||
dnsconn.record_sets.list_by_dns_zone(
|
|
||||||
zone_name=zone_name,
|
|
||||||
resource_group_name=resource_group,
|
|
||||||
top=top,
|
|
||||||
recordsetnamesuffix=recordsetnamesuffix,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for record_set in record_sets:
|
|
||||||
result[record_set["name"]] = record_set
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("dns", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def zone_create_or_update(name, resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Creates or updates a DNS zone. Does not modify DNS records within the zone.
|
|
||||||
|
|
||||||
:param name: The name of the DNS zone to create (without a terminating dot).
|
|
||||||
|
|
||||||
:param resource_group: The name of the resource group.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_dns.zone_create_or_update myzone testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
# DNS zones are global objects
|
|
||||||
kwargs["location"] = "global"
|
|
||||||
|
|
||||||
dnsconn = __utils__["azurearm.get_client"]("dns", **kwargs)
|
|
||||||
|
|
||||||
# Convert list of ID strings to list of dictionaries with id key.
|
|
||||||
if isinstance(kwargs.get("registration_virtual_networks"), list):
|
|
||||||
kwargs["registration_virtual_networks"] = [
|
|
||||||
{"id": vnet} for vnet in kwargs["registration_virtual_networks"]
|
|
||||||
]
|
|
||||||
|
|
||||||
if isinstance(kwargs.get("resolution_virtual_networks"), list):
|
|
||||||
kwargs["resolution_virtual_networks"] = [
|
|
||||||
{"id": vnet} for vnet in kwargs["resolution_virtual_networks"]
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
zone_model = __utils__["azurearm.create_object_model"]("dns", "Zone", **kwargs)
|
|
||||||
except TypeError as exc:
|
|
||||||
result = {"error": "The object model could not be built. ({})".format(str(exc))}
|
|
||||||
return result
|
|
||||||
|
|
||||||
try:
|
|
||||||
zone = dnsconn.zones.create_or_update(
|
|
||||||
zone_name=name,
|
|
||||||
resource_group_name=resource_group,
|
|
||||||
parameters=zone_model,
|
|
||||||
if_match=kwargs.get("if_match"),
|
|
||||||
if_none_match=kwargs.get("if_none_match"),
|
|
||||||
)
|
|
||||||
result = zone.as_dict()
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("dns", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
except SerializationError as exc:
|
|
||||||
result = {
|
|
||||||
"error": "The object model could not be parsed. ({})".format(str(exc))
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def zone_delete(name, resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Delete a DNS zone within a resource group.
|
|
||||||
|
|
||||||
:param name: The name of the DNS zone to delete.
|
|
||||||
|
|
||||||
:param resource_group: The name of the resource group.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_dns.zone_delete myzone testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = False
|
|
||||||
dnsconn = __utils__["azurearm.get_client"]("dns", **kwargs)
|
|
||||||
try:
|
|
||||||
zone = dnsconn.zones.delete(
|
|
||||||
zone_name=name,
|
|
||||||
resource_group_name=resource_group,
|
|
||||||
if_match=kwargs.get("if_match"),
|
|
||||||
)
|
|
||||||
zone.wait()
|
|
||||||
result = True
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("dns", str(exc), **kwargs)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def zone_get(name, resource_group, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Get a dictionary representing a DNS zone's properties, but not the
|
|
||||||
record sets within the zone.
|
|
||||||
|
|
||||||
:param name: The DNS zone to get.
|
|
||||||
|
|
||||||
:param resource_group: The name of the resource group.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_dns.zone_get myzone testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
dnsconn = __utils__["azurearm.get_client"]("dns", **kwargs)
|
|
||||||
try:
|
|
||||||
zone = dnsconn.zones.get(zone_name=name, resource_group_name=resource_group)
|
|
||||||
result = zone.as_dict()
|
|
||||||
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("dns", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def zones_list_by_resource_group(resource_group, top=None, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Lists the DNS zones in a resource group.
|
|
||||||
|
|
||||||
:param resource_group: The name of the resource group.
|
|
||||||
|
|
||||||
:param top:
|
|
||||||
The maximum number of DNS zones to return. If not specified,
|
|
||||||
returns up to 100 zones.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_dns.zones_list_by_resource_group testgroup
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = {}
|
|
||||||
dnsconn = __utils__["azurearm.get_client"]("dns", **kwargs)
|
|
||||||
try:
|
|
||||||
zones = __utils__["azurearm.paged_object_to_list"](
|
|
||||||
dnsconn.zones.list_by_resource_group(
|
|
||||||
resource_group_name=resource_group, top=top
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for zone in zones:
|
|
||||||
result[zone["name"]] = zone
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("dns", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def zones_list(top=None, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Lists the DNS zones in all resource groups in a subscription.
|
|
||||||
|
|
||||||
:param top:
|
|
||||||
The maximum number of DNS zones to return. If not specified,
|
|
||||||
eturns up to 100 zones.
|
|
||||||
|
|
||||||
CLI Example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
salt-call azurearm_dns.zones_list
|
|
||||||
|
|
||||||
"""
|
|
||||||
result = {}
|
|
||||||
dnsconn = __utils__["azurearm.get_client"]("dns", **kwargs)
|
|
||||||
try:
|
|
||||||
zones = __utils__["azurearm.paged_object_to_list"](dnsconn.zones.list(top=top))
|
|
||||||
|
|
||||||
for zone in zones:
|
|
||||||
result[zone["name"]] = zone
|
|
||||||
except CloudError as exc:
|
|
||||||
__utils__["azurearm.log_cloud_error"]("dns", str(exc), **kwargs)
|
|
||||||
result = {"error": str(exc)}
|
|
||||||
|
|
||||||
return result
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,465 +0,0 @@
|
||||||
"""
|
|
||||||
Use Azure Blob as a Pillar source.
|
|
||||||
|
|
||||||
.. versionadded:: 3001
|
|
||||||
|
|
||||||
:maintainer: <devops@eitr.tech>
|
|
||||||
:maturity: new
|
|
||||||
:depends:
|
|
||||||
* `azure-storage-blob <https://pypi.org/project/azure-storage-blob/>`_ >= 12.0.0
|
|
||||||
|
|
||||||
The Azure Blob ext_pillar can be configured with the following parameters:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
ext_pillar:
|
|
||||||
- azureblob:
|
|
||||||
container: 'test_container'
|
|
||||||
connection_string: 'connection_string'
|
|
||||||
multiple_env: False
|
|
||||||
environment: 'base'
|
|
||||||
blob_cache_expire: 30
|
|
||||||
blob_sync_on_update: True
|
|
||||||
|
|
||||||
:param container: The name of the target Azure Blob Container.
|
|
||||||
|
|
||||||
:param connection_string: The connection string to use to access the specified Azure Blob Container.
|
|
||||||
|
|
||||||
:param multiple_env: Specifies whether the pillar should interpret top level folders as pillar environments.
|
|
||||||
Defaults to false.
|
|
||||||
|
|
||||||
:param environment: Specifies which environment the container represents when in single environment mode. Defaults
|
|
||||||
to 'base' and is ignored if multiple_env is set as True.
|
|
||||||
|
|
||||||
:param blob_cache_expire: Specifies expiration time of the Azure Blob metadata cache file. Defaults to 30s.
|
|
||||||
|
|
||||||
:param blob_sync_on_update: Specifies if the cache is synced on update. Defaults to True.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import pickle
|
|
||||||
import time
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
import salt.utils.files
|
|
||||||
import salt.utils.hashutils
|
|
||||||
from salt.pillar import Pillar
|
|
||||||
|
|
||||||
HAS_LIBS = False
|
|
||||||
try:
|
|
||||||
# pylint: disable=no-name-in-module
|
|
||||||
from azure.storage.blob import BlobServiceClient
|
|
||||||
|
|
||||||
# pylint: enable=no-name-in-module
|
|
||||||
HAS_LIBS = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
__virtualname__ = "azureblob"
|
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
|
||||||
if not HAS_LIBS:
|
|
||||||
return (
|
|
||||||
False,
|
|
||||||
"The following dependency is required to use the Azure Blob ext_pillar: "
|
|
||||||
"Microsoft Azure Storage Blob >= 12.0.0 ",
|
|
||||||
)
|
|
||||||
|
|
||||||
return __virtualname__
|
|
||||||
|
|
||||||
|
|
||||||
def ext_pillar(
|
|
||||||
minion_id,
|
|
||||||
pillar, # pylint: disable=W0613
|
|
||||||
container,
|
|
||||||
connection_string,
|
|
||||||
multiple_env=False,
|
|
||||||
environment="base",
|
|
||||||
blob_cache_expire=30,
|
|
||||||
blob_sync_on_update=True,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Execute a command and read the output as YAML.
|
|
||||||
|
|
||||||
:param container: The name of the target Azure Blob Container.
|
|
||||||
|
|
||||||
:param connection_string: The connection string to use to access the specified Azure Blob Container.
|
|
||||||
|
|
||||||
:param multiple_env: Specifies whether the pillar should interpret top level folders as pillar environments.
|
|
||||||
Defaults to false.
|
|
||||||
|
|
||||||
:param environment: Specifies which environment the container represents when in single environment mode. Defaults
|
|
||||||
to 'base' and is ignored if multiple_env is set as True.
|
|
||||||
|
|
||||||
:param blob_cache_expire: Specifies expiration time of the Azure Blob metadata cache file. Defaults to 30s.
|
|
||||||
|
|
||||||
:param blob_sync_on_update: Specifies if the cache is synced on update. Defaults to True.
|
|
||||||
|
|
||||||
"""
|
|
||||||
# normpath is needed to remove appended '/' if root is empty string.
|
|
||||||
pillar_dir = os.path.normpath(
|
|
||||||
os.path.join(_get_cache_dir(), environment, container)
|
|
||||||
)
|
|
||||||
|
|
||||||
if __opts__["pillar_roots"].get(environment, []) == [pillar_dir]:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
metadata = _init(
|
|
||||||
connection_string, container, multiple_env, environment, blob_cache_expire
|
|
||||||
)
|
|
||||||
|
|
||||||
log.debug("Blob metadata: %s", metadata)
|
|
||||||
|
|
||||||
if blob_sync_on_update:
|
|
||||||
# sync the containers to the local cache
|
|
||||||
log.info("Syncing local pillar cache from Azure Blob...")
|
|
||||||
for saltenv, env_meta in metadata.items():
|
|
||||||
for container, files in _find_files(env_meta).items():
|
|
||||||
for file_path in files:
|
|
||||||
cached_file_path = _get_cached_file_name(
|
|
||||||
container, saltenv, file_path
|
|
||||||
)
|
|
||||||
log.info("%s - %s : %s", container, saltenv, file_path)
|
|
||||||
# load the file from Azure Blob if not in the cache or too old
|
|
||||||
_get_file_from_blob(
|
|
||||||
connection_string,
|
|
||||||
metadata,
|
|
||||||
saltenv,
|
|
||||||
container,
|
|
||||||
file_path,
|
|
||||||
cached_file_path,
|
|
||||||
)
|
|
||||||
|
|
||||||
log.info("Sync local pillar cache from Azure Blob completed.")
|
|
||||||
|
|
||||||
opts = deepcopy(__opts__)
|
|
||||||
opts["pillar_roots"][environment] = (
|
|
||||||
[os.path.join(pillar_dir, environment)] if multiple_env else [pillar_dir]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Avoid recursively re-adding this same pillar
|
|
||||||
opts["ext_pillar"] = [x for x in opts["ext_pillar"] if "azureblob" not in x]
|
|
||||||
|
|
||||||
pil = Pillar(opts, __grains__, minion_id, environment)
|
|
||||||
|
|
||||||
compiled_pillar = pil.compile_pillar(ext=False)
|
|
||||||
|
|
||||||
return compiled_pillar
|
|
||||||
|
|
||||||
|
|
||||||
def _init(connection_string, container, multiple_env, environment, blob_cache_expire):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3001
|
|
||||||
|
|
||||||
Connect to Blob Storage and download the metadata for each file in all containers specified and
|
|
||||||
cache the data to disk.
|
|
||||||
|
|
||||||
:param connection_string: The connection string to use to access the specified Azure Blob Container.
|
|
||||||
|
|
||||||
:param container: The name of the target Azure Blob Container.
|
|
||||||
|
|
||||||
:param multiple_env: Specifies whether the pillar should interpret top level folders as pillar environments.
|
|
||||||
Defaults to false.
|
|
||||||
|
|
||||||
:param environment: Specifies which environment the container represents when in single environment mode. Defaults
|
|
||||||
to 'base' and is ignored if multiple_env is set as True.
|
|
||||||
|
|
||||||
:param blob_cache_expire: Specifies expiration time of the Azure Blob metadata cache file. Defaults to 30s.
|
|
||||||
|
|
||||||
"""
|
|
||||||
cache_file = _get_containers_cache_filename(container)
|
|
||||||
exp = time.time() - blob_cache_expire
|
|
||||||
|
|
||||||
# Check if cache_file exists and its mtime
|
|
||||||
if os.path.isfile(cache_file):
|
|
||||||
cache_file_mtime = os.path.getmtime(cache_file)
|
|
||||||
else:
|
|
||||||
# If the file does not exist then set mtime to 0 (aka epoch)
|
|
||||||
cache_file_mtime = 0
|
|
||||||
|
|
||||||
expired = cache_file_mtime <= exp
|
|
||||||
|
|
||||||
log.debug(
|
|
||||||
"Blob storage container cache file %s is %sexpired, mtime_diff=%ss,"
|
|
||||||
" expiration=%ss",
|
|
||||||
cache_file,
|
|
||||||
"" if expired else "not ",
|
|
||||||
cache_file_mtime - exp,
|
|
||||||
blob_cache_expire,
|
|
||||||
)
|
|
||||||
|
|
||||||
if expired:
|
|
||||||
pillars = _refresh_containers_cache_file(
|
|
||||||
connection_string, container, cache_file, multiple_env, environment
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
pillars = _read_containers_cache_file(cache_file)
|
|
||||||
|
|
||||||
log.debug("Blob container retrieved pillars %s", pillars)
|
|
||||||
|
|
||||||
return pillars
|
|
||||||
|
|
||||||
|
|
||||||
def _get_cache_dir():
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3001
|
|
||||||
|
|
||||||
Get pillar cache directory. Initialize it if it does not exist.
|
|
||||||
|
|
||||||
"""
|
|
||||||
cache_dir = os.path.join(__opts__["cachedir"], "pillar_azureblob")
|
|
||||||
|
|
||||||
if not os.path.isdir(cache_dir):
|
|
||||||
log.debug("Initializing Azure Blob Pillar Cache")
|
|
||||||
os.makedirs(cache_dir)
|
|
||||||
|
|
||||||
return cache_dir
|
|
||||||
|
|
||||||
|
|
||||||
def _get_cached_file_name(container, saltenv, path):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3001
|
|
||||||
|
|
||||||
Return the cached file name for a container path file.
|
|
||||||
|
|
||||||
:param container: The name of the target Azure Blob Container.
|
|
||||||
|
|
||||||
:param saltenv: Specifies which environment the container represents.
|
|
||||||
|
|
||||||
:param path: The path of the file in the container.
|
|
||||||
|
|
||||||
"""
|
|
||||||
file_path = os.path.join(_get_cache_dir(), saltenv, container, path)
|
|
||||||
|
|
||||||
# make sure container and saltenv directories exist
|
|
||||||
if not os.path.exists(os.path.dirname(file_path)):
|
|
||||||
os.makedirs(os.path.dirname(file_path))
|
|
||||||
|
|
||||||
return file_path
|
|
||||||
|
|
||||||
|
|
||||||
def _get_containers_cache_filename(container):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3001
|
|
||||||
|
|
||||||
Return the filename of the cache for container contents. Create the path if it does not exist.
|
|
||||||
|
|
||||||
:param container: The name of the target Azure Blob Container.
|
|
||||||
|
|
||||||
"""
|
|
||||||
cache_dir = _get_cache_dir()
|
|
||||||
if not os.path.exists(cache_dir):
|
|
||||||
os.makedirs(cache_dir)
|
|
||||||
|
|
||||||
return os.path.join(cache_dir, "{}-files.cache".format(container))
|
|
||||||
|
|
||||||
|
|
||||||
def _refresh_containers_cache_file(
|
|
||||||
connection_string, container, cache_file, multiple_env=False, environment="base"
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3001
|
|
||||||
|
|
||||||
Downloads the entire contents of an Azure storage container to the local filesystem.
|
|
||||||
|
|
||||||
:param connection_string: The connection string to use to access the specified Azure Blob Container.
|
|
||||||
|
|
||||||
:param container: The name of the target Azure Blob Container.
|
|
||||||
|
|
||||||
:param cache_file: The path of where the file will be cached.
|
|
||||||
|
|
||||||
:param multiple_env: Specifies whether the pillar should interpret top level folders as pillar environments.
|
|
||||||
|
|
||||||
:param environment: Specifies which environment the container represents when in single environment mode. This is
|
|
||||||
ignored if multiple_env is set as True.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Create the BlobServiceClient object which will be used to create a container client
|
|
||||||
blob_service_client = BlobServiceClient.from_connection_string(
|
|
||||||
connection_string
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create the ContainerClient object
|
|
||||||
container_client = blob_service_client.get_container_client(container)
|
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
|
||||||
log.error("Exception: %s", exc)
|
|
||||||
return False
|
|
||||||
|
|
||||||
metadata = {}
|
|
||||||
|
|
||||||
def _walk_blobs(saltenv="base", prefix=None):
|
|
||||||
# Walk the blobs in the container with a generator
|
|
||||||
blob_list = container_client.walk_blobs(name_starts_with=prefix)
|
|
||||||
|
|
||||||
# Iterate over the generator
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
blob = next(blob_list)
|
|
||||||
except StopIteration:
|
|
||||||
break
|
|
||||||
|
|
||||||
log.debug("Raw blob attributes: %s", blob)
|
|
||||||
|
|
||||||
# Directories end with "/".
|
|
||||||
if blob.name.endswith("/"):
|
|
||||||
# Recurse into the directory
|
|
||||||
_walk_blobs(prefix=blob.name)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if multiple_env:
|
|
||||||
saltenv = "base" if (not prefix or prefix == ".") else prefix[:-1]
|
|
||||||
|
|
||||||
if saltenv not in metadata:
|
|
||||||
metadata[saltenv] = {}
|
|
||||||
|
|
||||||
if container not in metadata[saltenv]:
|
|
||||||
metadata[saltenv][container] = []
|
|
||||||
|
|
||||||
metadata[saltenv][container].append(blob)
|
|
||||||
|
|
||||||
_walk_blobs(saltenv=environment)
|
|
||||||
|
|
||||||
# write the metadata to disk
|
|
||||||
if os.path.isfile(cache_file):
|
|
||||||
os.remove(cache_file)
|
|
||||||
|
|
||||||
log.debug("Writing Azure blobs pillar cache file")
|
|
||||||
|
|
||||||
with salt.utils.files.fopen(cache_file, "wb") as fp_:
|
|
||||||
pickle.dump(metadata, fp_)
|
|
||||||
|
|
||||||
return metadata
|
|
||||||
|
|
||||||
|
|
||||||
def _read_containers_cache_file(cache_file):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3001
|
|
||||||
|
|
||||||
Return the contents of the containers cache file.
|
|
||||||
|
|
||||||
:param cache_file: The path for where the file will be cached.
|
|
||||||
|
|
||||||
"""
|
|
||||||
log.debug("Reading containers cache file")
|
|
||||||
|
|
||||||
with salt.utils.files.fopen(cache_file, "rb") as fp_:
|
|
||||||
data = pickle.load(fp_)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def _find_files(metadata):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3001
|
|
||||||
|
|
||||||
Looks for all the files in the Azure Blob container cache metadata.
|
|
||||||
|
|
||||||
:param metadata: The metadata for the container files.
|
|
||||||
|
|
||||||
"""
|
|
||||||
ret = {}
|
|
||||||
|
|
||||||
for container, data in metadata.items():
|
|
||||||
if container not in ret:
|
|
||||||
ret[container] = []
|
|
||||||
|
|
||||||
# grab the paths from the metadata
|
|
||||||
file_paths = [k["name"] for k in data]
|
|
||||||
# filter out the dirs
|
|
||||||
ret[container] += [k for k in file_paths if not k.endswith("/")]
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def _find_file_meta(metadata, container, saltenv, path):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3001
|
|
||||||
|
|
||||||
Looks for a file's metadata in the Azure Blob Container cache file.
|
|
||||||
|
|
||||||
:param metadata: The metadata for the container files.
|
|
||||||
|
|
||||||
:param container: The name of the target Azure Blob Container.
|
|
||||||
|
|
||||||
:param saltenv: Specifies which environment the container represents.
|
|
||||||
|
|
||||||
:param path: The path of the file in the container.
|
|
||||||
|
|
||||||
"""
|
|
||||||
env_meta = metadata[saltenv] if saltenv in metadata else {}
|
|
||||||
container_meta = env_meta[container] if container in env_meta else {}
|
|
||||||
|
|
||||||
for item_meta in container_meta:
|
|
||||||
item_meta = dict(item_meta)
|
|
||||||
if "name" in item_meta and item_meta["name"] == path:
|
|
||||||
return item_meta
|
|
||||||
|
|
||||||
|
|
||||||
def _get_file_from_blob(
|
|
||||||
connection_string, metadata, saltenv, container, path, cached_file_path
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3001
|
|
||||||
|
|
||||||
Downloads the entire contents of an Azure storage container to the local filesystem.
|
|
||||||
|
|
||||||
:param connection_string: The connection string to use to access the specified Azure Blob Container.
|
|
||||||
|
|
||||||
:param metadata: The metadata for the container files.
|
|
||||||
|
|
||||||
:param saltenv: Specifies which environment the container represents when in single environment mode. This is
|
|
||||||
ignored if multiple_env is set as True.
|
|
||||||
|
|
||||||
:param container: The name of the target Azure Blob Container.
|
|
||||||
|
|
||||||
:param path: The path of the file in the container.
|
|
||||||
|
|
||||||
:param cached_file_path: The path of where the file will be cached.
|
|
||||||
|
|
||||||
"""
|
|
||||||
# check the local cache...
|
|
||||||
if os.path.isfile(cached_file_path):
|
|
||||||
file_meta = _find_file_meta(metadata, container, saltenv, path)
|
|
||||||
file_md5 = (
|
|
||||||
"".join(list(filter(str.isalnum, file_meta["etag"]))) if file_meta else None
|
|
||||||
)
|
|
||||||
|
|
||||||
cached_md5 = salt.utils.hashutils.get_hash(cached_file_path, "md5")
|
|
||||||
|
|
||||||
# hashes match we have a cache hit
|
|
||||||
log.debug(
|
|
||||||
"Cached file: path=%s, md5=%s, etag=%s",
|
|
||||||
cached_file_path,
|
|
||||||
cached_md5,
|
|
||||||
file_md5,
|
|
||||||
)
|
|
||||||
if cached_md5 == file_md5:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Create the BlobServiceClient object which will be used to create a container client
|
|
||||||
blob_service_client = BlobServiceClient.from_connection_string(
|
|
||||||
connection_string
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create the ContainerClient object
|
|
||||||
container_client = blob_service_client.get_container_client(container)
|
|
||||||
|
|
||||||
# Create the BlobClient object
|
|
||||||
blob_client = container_client.get_blob_client(path)
|
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
|
||||||
log.error("Exception: %s", exc)
|
|
||||||
return False
|
|
||||||
|
|
||||||
with salt.utils.files.fopen(cached_file_path, "wb") as outfile:
|
|
||||||
outfile.write(blob_client.download_blob().readall())
|
|
||||||
|
|
||||||
return
|
|
|
@ -1,362 +0,0 @@
|
||||||
"""
|
|
||||||
Azure (ARM) Compute State Module
|
|
||||||
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This cloud provider will be removed from Salt in version 3007 in favor of
|
|
||||||
the `saltext.azurerm Salt Extension
|
|
||||||
<https://github.com/salt-extensions/saltext-azurerm>`_
|
|
||||||
|
|
||||||
:maintainer: <devops@eitr.tech>
|
|
||||||
:maturity: new
|
|
||||||
:depends:
|
|
||||||
* `azure <https://pypi.python.org/pypi/azure>`_ >= 2.0.0
|
|
||||||
* `azure-common <https://pypi.python.org/pypi/azure-common>`_ >= 1.1.8
|
|
||||||
* `azure-mgmt <https://pypi.python.org/pypi/azure-mgmt>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-compute <https://pypi.python.org/pypi/azure-mgmt-compute>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-network <https://pypi.python.org/pypi/azure-mgmt-network>`_ >= 1.7.1
|
|
||||||
* `azure-mgmt-resource <https://pypi.python.org/pypi/azure-mgmt-resource>`_ >= 1.1.0
|
|
||||||
* `azure-mgmt-storage <https://pypi.python.org/pypi/azure-mgmt-storage>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-web <https://pypi.python.org/pypi/azure-mgmt-web>`_ >= 0.32.0
|
|
||||||
* `azure-storage <https://pypi.python.org/pypi/azure-storage>`_ >= 0.34.3
|
|
||||||
* `msrestazure <https://pypi.python.org/pypi/msrestazure>`_ >= 0.4.21
|
|
||||||
:platform: linux
|
|
||||||
|
|
||||||
:configuration: This module requires Azure Resource Manager credentials to be passed as a dictionary of
|
|
||||||
keyword arguments to the ``connection_auth`` parameter in order to work properly. Since the authentication
|
|
||||||
parameters are sensitive, it's recommended to pass them to the states via pillar.
|
|
||||||
|
|
||||||
Required provider parameters:
|
|
||||||
|
|
||||||
if using username and password:
|
|
||||||
* ``subscription_id``
|
|
||||||
* ``username``
|
|
||||||
* ``password``
|
|
||||||
|
|
||||||
if using a service principal:
|
|
||||||
* ``subscription_id``
|
|
||||||
* ``tenant``
|
|
||||||
* ``client_id``
|
|
||||||
* ``secret``
|
|
||||||
|
|
||||||
Optional provider parameters:
|
|
||||||
|
|
||||||
**cloud_environment**: Used to point the cloud driver to different API endpoints, such as Azure GovCloud. Possible values:
|
|
||||||
* ``AZURE_PUBLIC_CLOUD`` (default)
|
|
||||||
* ``AZURE_CHINA_CLOUD``
|
|
||||||
* ``AZURE_US_GOV_CLOUD``
|
|
||||||
* ``AZURE_GERMAN_CLOUD``
|
|
||||||
|
|
||||||
Example Pillar for Azure Resource Manager authentication:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
azurearm:
|
|
||||||
user_pass_auth:
|
|
||||||
subscription_id: 3287abc8-f98a-c678-3bde-326766fd3617
|
|
||||||
username: fletch
|
|
||||||
password: 123pass
|
|
||||||
mysubscription:
|
|
||||||
subscription_id: 3287abc8-f98a-c678-3bde-326766fd3617
|
|
||||||
tenant: ABCDEFAB-1234-ABCD-1234-ABCDEFABCDEF
|
|
||||||
client_id: ABCDEFAB-1234-ABCD-1234-ABCDEFABCDEF
|
|
||||||
secret: XXXXXXXXXXXXXXXXXXXXXXXX
|
|
||||||
cloud_environment: AZURE_PUBLIC_CLOUD
|
|
||||||
|
|
||||||
Example states using Azure Resource Manager authentication:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set profile = salt['pillar.get']('azurearm:mysubscription') %}
|
|
||||||
Ensure availability set exists:
|
|
||||||
azurearm_compute.availability_set_present:
|
|
||||||
- name: my_avail_set
|
|
||||||
- resource_group: my_rg
|
|
||||||
- virtual_machines:
|
|
||||||
- my_vm1
|
|
||||||
- my_vm2
|
|
||||||
- tags:
|
|
||||||
how_awesome: very
|
|
||||||
contact_name: Elmer Fudd Gantry
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
Ensure availability set is absent:
|
|
||||||
azurearm_compute.availability_set_absent:
|
|
||||||
- name: other_avail_set
|
|
||||||
- resource_group: my_rg
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Python libs
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
import salt.utils.azurearm
|
|
||||||
|
|
||||||
__virtualname__ = "azurearm_compute"
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
|
||||||
"""
|
|
||||||
Only make this state available if the azurearm_compute module is available.
|
|
||||||
"""
|
|
||||||
if "azurearm_compute.availability_set_create_or_update" in __salt__:
|
|
||||||
return __virtualname__
|
|
||||||
return (False, "azurearm module could not be loaded")
|
|
||||||
|
|
||||||
|
|
||||||
def _deprecation_message(function):
|
|
||||||
"""
|
|
||||||
Decorator wrapper to warn about azurearm deprecation
|
|
||||||
"""
|
|
||||||
|
|
||||||
@wraps(function)
|
|
||||||
def wrapped(*args, **kwargs):
|
|
||||||
salt.utils.versions.warn_until(
|
|
||||||
"Chlorine",
|
|
||||||
"The 'azurearm' functionality in Salt has been deprecated and its "
|
|
||||||
"functionality will be removed in version 3007 in favor of the "
|
|
||||||
"saltext.azurerm Salt Extension. "
|
|
||||||
"(https://github.com/salt-extensions/saltext-azurerm)",
|
|
||||||
category=FutureWarning,
|
|
||||||
)
|
|
||||||
ret = function(*args, **salt.utils.args.clean_kwargs(**kwargs))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def availability_set_present(
|
|
||||||
name,
|
|
||||||
resource_group,
|
|
||||||
tags=None,
|
|
||||||
platform_update_domain_count=None,
|
|
||||||
platform_fault_domain_count=None,
|
|
||||||
virtual_machines=None,
|
|
||||||
sku=None,
|
|
||||||
connection_auth=None,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Ensure an availability set exists.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
Name of the availability set.
|
|
||||||
|
|
||||||
:param resource_group:
|
|
||||||
The resource group assigned to the availability set.
|
|
||||||
|
|
||||||
:param tags:
|
|
||||||
A dictionary of strings can be passed as tag metadata to the availability set object.
|
|
||||||
|
|
||||||
:param platform_update_domain_count:
|
|
||||||
An optional parameter which indicates groups of virtual machines and underlying physical hardware that can be
|
|
||||||
rebooted at the same time.
|
|
||||||
|
|
||||||
:param platform_fault_domain_count:
|
|
||||||
An optional parameter which defines the group of virtual machines that share a common power source and network
|
|
||||||
switch.
|
|
||||||
|
|
||||||
:param virtual_machines:
|
|
||||||
A list of names of existing virtual machines to be included in the availability set.
|
|
||||||
|
|
||||||
:param sku:
|
|
||||||
The availability set SKU, which specifies whether the availability set is managed or not. Possible values are
|
|
||||||
'Aligned' or 'Classic'. An 'Aligned' availability set is managed, 'Classic' is not.
|
|
||||||
|
|
||||||
:param connection_auth:
|
|
||||||
A dict with subscription and authentication parameters to be used in connecting to the
|
|
||||||
Azure Resource Manager API.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
Ensure availability set exists:
|
|
||||||
azurearm_compute.availability_set_present:
|
|
||||||
- name: aset1
|
|
||||||
- resource_group: group1
|
|
||||||
- platform_update_domain_count: 5
|
|
||||||
- platform_fault_domain_count: 3
|
|
||||||
- sku: aligned
|
|
||||||
- tags:
|
|
||||||
contact_name: Elmer Fudd Gantry
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
- require:
|
|
||||||
- azurearm_resource: Ensure resource group exists
|
|
||||||
|
|
||||||
"""
|
|
||||||
ret = {"name": name, "result": False, "comment": "", "changes": {}}
|
|
||||||
|
|
||||||
if not isinstance(connection_auth, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "Connection information must be specified via connection_auth dictionary!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if sku:
|
|
||||||
sku = {"name": sku.capitalize()}
|
|
||||||
|
|
||||||
aset = __salt__["azurearm_compute.availability_set_get"](
|
|
||||||
name, resource_group, azurearm_log_level="info", **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" not in aset:
|
|
||||||
tag_changes = __utils__["dictdiffer.deep_diff"](
|
|
||||||
aset.get("tags", {}), tags or {}
|
|
||||||
)
|
|
||||||
if tag_changes:
|
|
||||||
ret["changes"]["tags"] = tag_changes
|
|
||||||
|
|
||||||
if platform_update_domain_count and (
|
|
||||||
int(platform_update_domain_count)
|
|
||||||
!= aset.get("platform_update_domain_count")
|
|
||||||
):
|
|
||||||
ret["changes"]["platform_update_domain_count"] = {
|
|
||||||
"old": aset.get("platform_update_domain_count"),
|
|
||||||
"new": platform_update_domain_count,
|
|
||||||
}
|
|
||||||
|
|
||||||
if platform_fault_domain_count and (
|
|
||||||
int(platform_fault_domain_count) != aset.get("platform_fault_domain_count")
|
|
||||||
):
|
|
||||||
ret["changes"]["platform_fault_domain_count"] = {
|
|
||||||
"old": aset.get("platform_fault_domain_count"),
|
|
||||||
"new": platform_fault_domain_count,
|
|
||||||
}
|
|
||||||
|
|
||||||
if sku and (sku["name"] != aset.get("sku", {}).get("name")):
|
|
||||||
ret["changes"]["sku"] = {"old": aset.get("sku"), "new": sku}
|
|
||||||
|
|
||||||
if virtual_machines:
|
|
||||||
if not isinstance(virtual_machines, list):
|
|
||||||
ret["comment"] = "Virtual machines must be supplied as a list!"
|
|
||||||
return ret
|
|
||||||
aset_vms = aset.get("virtual_machines", [])
|
|
||||||
remote_vms = sorted(
|
|
||||||
vm["id"].split("/")[-1].lower() for vm in aset_vms if "id" in aset_vms
|
|
||||||
)
|
|
||||||
local_vms = sorted(vm.lower() for vm in virtual_machines or [])
|
|
||||||
if local_vms != remote_vms:
|
|
||||||
ret["changes"]["virtual_machines"] = {
|
|
||||||
"old": aset_vms,
|
|
||||||
"new": virtual_machines,
|
|
||||||
}
|
|
||||||
|
|
||||||
if not ret["changes"]:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Availability set {} is already present.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if __opts__["test"]:
|
|
||||||
ret["result"] = None
|
|
||||||
ret["comment"] = "Availability set {} would be updated.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
else:
|
|
||||||
ret["changes"] = {
|
|
||||||
"old": {},
|
|
||||||
"new": {
|
|
||||||
"name": name,
|
|
||||||
"virtual_machines": virtual_machines,
|
|
||||||
"platform_update_domain_count": platform_update_domain_count,
|
|
||||||
"platform_fault_domain_count": platform_fault_domain_count,
|
|
||||||
"sku": sku,
|
|
||||||
"tags": tags,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if __opts__["test"]:
|
|
||||||
ret["comment"] = "Availability set {} would be created.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
return ret
|
|
||||||
|
|
||||||
aset_kwargs = kwargs.copy()
|
|
||||||
aset_kwargs.update(connection_auth)
|
|
||||||
|
|
||||||
aset = __salt__["azurearm_compute.availability_set_create_or_update"](
|
|
||||||
name=name,
|
|
||||||
resource_group=resource_group,
|
|
||||||
virtual_machines=virtual_machines,
|
|
||||||
platform_update_domain_count=platform_update_domain_count,
|
|
||||||
platform_fault_domain_count=platform_fault_domain_count,
|
|
||||||
sku=sku,
|
|
||||||
tags=tags,
|
|
||||||
**aset_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" not in aset:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Availability set {} has been created.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ret["comment"] = "Failed to create availability set {}! ({})".format(
|
|
||||||
name, aset.get("error")
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def availability_set_absent(name, resource_group, connection_auth=None):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Ensure an availability set does not exist in a resource group.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
Name of the availability set.
|
|
||||||
|
|
||||||
:param resource_group:
|
|
||||||
Name of the resource group containing the availability set.
|
|
||||||
|
|
||||||
:param connection_auth:
|
|
||||||
A dict with subscription and authentication parameters to be used in connecting to the
|
|
||||||
Azure Resource Manager API.
|
|
||||||
"""
|
|
||||||
ret = {"name": name, "result": False, "comment": "", "changes": {}}
|
|
||||||
|
|
||||||
if not isinstance(connection_auth, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "Connection information must be specified via connection_auth dictionary!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
aset = __salt__["azurearm_compute.availability_set_get"](
|
|
||||||
name, resource_group, azurearm_log_level="info", **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" in aset:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Availability set {} was not found.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
elif __opts__["test"]:
|
|
||||||
ret["comment"] = "Availability set {} would be deleted.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
ret["changes"] = {
|
|
||||||
"old": aset,
|
|
||||||
"new": {},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
deleted = __salt__["azurearm_compute.availability_set_delete"](
|
|
||||||
name, resource_group, **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if deleted:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Availability set {} has been deleted.".format(name)
|
|
||||||
ret["changes"] = {"old": aset, "new": {}}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ret["comment"] = "Failed to delete availability set {}!".format(name)
|
|
||||||
return ret
|
|
|
@ -1,762 +0,0 @@
|
||||||
"""
|
|
||||||
Azure (ARM) DNS State Module
|
|
||||||
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This cloud provider will be removed from Salt in version 3007 in favor of
|
|
||||||
the `saltext.azurerm Salt Extension
|
|
||||||
<https://github.com/salt-extensions/saltext-azurerm>`_
|
|
||||||
|
|
||||||
:maintainer: <devops@eitr.tech>
|
|
||||||
:maturity: new
|
|
||||||
:depends:
|
|
||||||
* `azure <https://pypi.python.org/pypi/azure>`_ >= 2.0.0
|
|
||||||
* `azure-common <https://pypi.python.org/pypi/azure-common>`_ >= 1.1.8
|
|
||||||
* `azure-mgmt <https://pypi.python.org/pypi/azure-mgmt>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-compute <https://pypi.python.org/pypi/azure-mgmt-compute>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-dns <https://pypi.python.org/pypi/azure-mgmt-dns>`_ >= 1.0.1
|
|
||||||
* `azure-mgmt-network <https://pypi.python.org/pypi/azure-mgmt-network>`_ >= 1.7.1
|
|
||||||
* `azure-mgmt-resource <https://pypi.python.org/pypi/azure-mgmt-resource>`_ >= 1.1.0
|
|
||||||
* `azure-mgmt-storage <https://pypi.python.org/pypi/azure-mgmt-storage>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-web <https://pypi.python.org/pypi/azure-mgmt-web>`_ >= 0.32.0
|
|
||||||
* `azure-storage <https://pypi.python.org/pypi/azure-storage>`_ >= 0.34.3
|
|
||||||
* `msrestazure <https://pypi.python.org/pypi/msrestazure>`_ >= 0.4.21
|
|
||||||
|
|
||||||
:platform: linux
|
|
||||||
|
|
||||||
:configuration:
|
|
||||||
This module requires Azure Resource Manager credentials to be passed as a dictionary of
|
|
||||||
keyword arguments to the ``connection_auth`` parameter in order to work properly. Since the authentication
|
|
||||||
parameters are sensitive, it's recommended to pass them to the states via pillar.
|
|
||||||
|
|
||||||
Required provider parameters:
|
|
||||||
|
|
||||||
if using username and password:
|
|
||||||
|
|
||||||
* ``subscription_id``
|
|
||||||
* ``username``
|
|
||||||
* ``password``
|
|
||||||
|
|
||||||
if using a service principal:
|
|
||||||
|
|
||||||
* ``subscription_id``
|
|
||||||
* ``tenant``
|
|
||||||
* ``client_id``
|
|
||||||
* ``secret``
|
|
||||||
|
|
||||||
Optional provider parameters:
|
|
||||||
|
|
||||||
**cloud_environment**: Used to point the cloud driver to different API endpoints, such as Azure GovCloud. Possible values:
|
|
||||||
|
|
||||||
Possible values:
|
|
||||||
|
|
||||||
* ``AZURE_PUBLIC_CLOUD`` (default)
|
|
||||||
* ``AZURE_CHINA_CLOUD``
|
|
||||||
* ``AZURE_US_GOV_CLOUD``
|
|
||||||
* ``AZURE_GERMAN_CLOUD``
|
|
||||||
|
|
||||||
Example Pillar for Azure Resource Manager authentication:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
azurearm:
|
|
||||||
user_pass_auth:
|
|
||||||
subscription_id: 3287abc8-f98a-c678-3bde-326766fd3617
|
|
||||||
username: fletch
|
|
||||||
password: 123pass
|
|
||||||
mysubscription:
|
|
||||||
subscription_id: 3287abc8-f98a-c678-3bde-326766fd3617
|
|
||||||
tenant: ABCDEFAB-1234-ABCD-1234-ABCDEFABCDEF
|
|
||||||
client_id: ABCDEFAB-1234-ABCD-1234-ABCDEFABCDEF
|
|
||||||
secret: XXXXXXXXXXXXXXXXXXXXXXXX
|
|
||||||
cloud_environment: AZURE_PUBLIC_CLOUD
|
|
||||||
|
|
||||||
Example states using Azure Resource Manager authentication:
|
|
||||||
|
|
||||||
.. code-block:: none
|
|
||||||
|
|
||||||
{% set profile = salt['pillar.get']('azurearm:mysubscription') %}
|
|
||||||
Ensure DNS zone exists:
|
|
||||||
azurearm_dns.zone_present:
|
|
||||||
- name: contoso.com
|
|
||||||
- resource_group: my_rg
|
|
||||||
- tags:
|
|
||||||
how_awesome: very
|
|
||||||
contact_name: Elmer Fudd Gantry
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
Ensure DNS record set exists:
|
|
||||||
azurearm_dns.record_set_present:
|
|
||||||
- name: web
|
|
||||||
- zone_name: contoso.com
|
|
||||||
- resource_group: my_rg
|
|
||||||
- record_type: A
|
|
||||||
- ttl: 300
|
|
||||||
- arecords:
|
|
||||||
- ipv4_address: 10.0.0.1
|
|
||||||
- tags:
|
|
||||||
how_awesome: very
|
|
||||||
contact_name: Elmer Fudd Gantry
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
Ensure DNS record set is absent:
|
|
||||||
azurearm_dns.record_set_absent:
|
|
||||||
- name: web
|
|
||||||
- zone_name: contoso.com
|
|
||||||
- resource_group: my_rg
|
|
||||||
- record_type: A
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
Ensure DNS zone is absent:
|
|
||||||
azurearm_dns.zone_absent:
|
|
||||||
- name: contoso.com
|
|
||||||
- resource_group: my_rg
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
import salt.utils.azurearm
|
|
||||||
|
|
||||||
__virtualname__ = "azurearm_dns"
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
|
||||||
"""
|
|
||||||
Only make this state available if the azurearm_dns module is available.
|
|
||||||
"""
|
|
||||||
if "azurearm_dns.zones_list_by_resource_group" in __salt__:
|
|
||||||
return __virtualname__
|
|
||||||
return (False, "azurearm_dns module could not be loaded")
|
|
||||||
|
|
||||||
|
|
||||||
def _deprecation_message(function):
|
|
||||||
"""
|
|
||||||
Decorator wrapper to warn about azurearm deprecation
|
|
||||||
"""
|
|
||||||
|
|
||||||
@wraps(function)
|
|
||||||
def wrapped(*args, **kwargs):
|
|
||||||
salt.utils.versions.warn_until(
|
|
||||||
"Chlorine",
|
|
||||||
"The 'azurearm' functionality in Salt has been deprecated and its "
|
|
||||||
"functionality will be removed in version 3007 in favor of the "
|
|
||||||
"saltext.azurerm Salt Extension. "
|
|
||||||
"(https://github.com/salt-extensions/saltext-azurerm)",
|
|
||||||
category=FutureWarning,
|
|
||||||
)
|
|
||||||
ret = function(*args, **salt.utils.args.clean_kwargs(**kwargs))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def zone_present(
|
|
||||||
name,
|
|
||||||
resource_group,
|
|
||||||
etag=None,
|
|
||||||
if_match=None,
|
|
||||||
if_none_match=None,
|
|
||||||
registration_virtual_networks=None,
|
|
||||||
resolution_virtual_networks=None,
|
|
||||||
tags=None,
|
|
||||||
zone_type="Public",
|
|
||||||
connection_auth=None,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Ensure a DNS zone exists.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
Name of the DNS zone (without a terminating dot).
|
|
||||||
|
|
||||||
:param resource_group:
|
|
||||||
The resource group assigned to the DNS zone.
|
|
||||||
|
|
||||||
:param etag:
|
|
||||||
The etag of the zone. `Etags <https://docs.microsoft.com/en-us/azure/dns/dns-zones-records#etags>`_ are used
|
|
||||||
to handle concurrent changes to the same resource safely.
|
|
||||||
|
|
||||||
:param if_match:
|
|
||||||
The etag of the DNS zone. Omit this value to always overwrite the current zone. Specify the last-seen etag
|
|
||||||
value to prevent accidentally overwritting any concurrent changes.
|
|
||||||
|
|
||||||
:param if_none_match:
|
|
||||||
Set to '*' to allow a new DNS zone to be created, but to prevent updating an existing zone. Other values will
|
|
||||||
be ignored.
|
|
||||||
|
|
||||||
:param registration_virtual_networks:
|
|
||||||
A list of references to virtual networks that register hostnames in this DNS zone. This is only when zone_type
|
|
||||||
is Private. (requires `azure-mgmt-dns <https://pypi.python.org/pypi/azure-mgmt-dns>`_ >= 2.0.0rc1)
|
|
||||||
|
|
||||||
:param resolution_virtual_networks:
|
|
||||||
A list of references to virtual networks that resolve records in this DNS zone. This is only when zone_type is
|
|
||||||
Private. (requires `azure-mgmt-dns <https://pypi.python.org/pypi/azure-mgmt-dns>`_ >= 2.0.0rc1)
|
|
||||||
|
|
||||||
:param tags:
|
|
||||||
A dictionary of strings can be passed as tag metadata to the DNS zone object.
|
|
||||||
|
|
||||||
:param zone_type:
|
|
||||||
The type of this DNS zone (Public or Private). Possible values include: 'Public', 'Private'. Default value: 'Public'
|
|
||||||
(requires `azure-mgmt-dns <https://pypi.python.org/pypi/azure-mgmt-dns>`_ >= 2.0.0rc1)
|
|
||||||
|
|
||||||
:param connection_auth:
|
|
||||||
A dict with subscription and authentication parameters to be used in connecting to the
|
|
||||||
Azure Resource Manager API.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
Ensure DNS zone exists:
|
|
||||||
azurearm_dns.zone_present:
|
|
||||||
- name: contoso.com
|
|
||||||
- resource_group: my_rg
|
|
||||||
- zone_type: Private
|
|
||||||
- registration_virtual_networks:
|
|
||||||
- /subscriptions/{{ sub }}/resourceGroups/my_rg/providers/Microsoft.Network/virtualNetworks/test_vnet
|
|
||||||
- tags:
|
|
||||||
how_awesome: very
|
|
||||||
contact_name: Elmer Fudd Gantry
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
"""
|
|
||||||
ret = {"name": name, "result": False, "comment": "", "changes": {}}
|
|
||||||
|
|
||||||
if not isinstance(connection_auth, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "Connection information must be specified via connection_auth dictionary!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
zone = __salt__["azurearm_dns.zone_get"](
|
|
||||||
name, resource_group, azurearm_log_level="info", **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" not in zone:
|
|
||||||
tag_changes = __utils__["dictdiffer.deep_diff"](
|
|
||||||
zone.get("tags", {}), tags or {}
|
|
||||||
)
|
|
||||||
if tag_changes:
|
|
||||||
ret["changes"]["tags"] = tag_changes
|
|
||||||
|
|
||||||
# The zone_type parameter is only accessible in azure-mgmt-dns >=2.0.0rc1
|
|
||||||
if zone.get("zone_type"):
|
|
||||||
if zone.get("zone_type").lower() != zone_type.lower():
|
|
||||||
ret["changes"]["zone_type"] = {
|
|
||||||
"old": zone["zone_type"],
|
|
||||||
"new": zone_type,
|
|
||||||
}
|
|
||||||
|
|
||||||
if zone_type.lower() == "private":
|
|
||||||
# The registration_virtual_networks parameter is only accessible in azure-mgmt-dns >=2.0.0rc1
|
|
||||||
if registration_virtual_networks and not isinstance(
|
|
||||||
registration_virtual_networks, list
|
|
||||||
):
|
|
||||||
ret["comment"] = (
|
|
||||||
"registration_virtual_networks must be supplied as a list of"
|
|
||||||
" VNET ID paths!"
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
reg_vnets = zone.get("registration_virtual_networks", [])
|
|
||||||
remote_reg_vnets = sorted(
|
|
||||||
vnet["id"].lower() for vnet in reg_vnets if "id" in vnet
|
|
||||||
)
|
|
||||||
local_reg_vnets = sorted(
|
|
||||||
vnet.lower() for vnet in registration_virtual_networks or []
|
|
||||||
)
|
|
||||||
if local_reg_vnets != remote_reg_vnets:
|
|
||||||
ret["changes"]["registration_virtual_networks"] = {
|
|
||||||
"old": remote_reg_vnets,
|
|
||||||
"new": local_reg_vnets,
|
|
||||||
}
|
|
||||||
|
|
||||||
# The resolution_virtual_networks parameter is only accessible in azure-mgmt-dns >=2.0.0rc1
|
|
||||||
if resolution_virtual_networks and not isinstance(
|
|
||||||
resolution_virtual_networks, list
|
|
||||||
):
|
|
||||||
ret["comment"] = (
|
|
||||||
"resolution_virtual_networks must be supplied as a list of VNET"
|
|
||||||
" ID paths!"
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
res_vnets = zone.get("resolution_virtual_networks", [])
|
|
||||||
remote_res_vnets = sorted(
|
|
||||||
vnet["id"].lower() for vnet in res_vnets if "id" in vnet
|
|
||||||
)
|
|
||||||
local_res_vnets = sorted(
|
|
||||||
vnet.lower() for vnet in resolution_virtual_networks or []
|
|
||||||
)
|
|
||||||
if local_res_vnets != remote_res_vnets:
|
|
||||||
ret["changes"]["resolution_virtual_networks"] = {
|
|
||||||
"old": remote_res_vnets,
|
|
||||||
"new": local_res_vnets,
|
|
||||||
}
|
|
||||||
|
|
||||||
if not ret["changes"]:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "DNS zone {} is already present.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if __opts__["test"]:
|
|
||||||
ret["result"] = None
|
|
||||||
ret["comment"] = "DNS zone {} would be updated.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
else:
|
|
||||||
ret["changes"] = {
|
|
||||||
"old": {},
|
|
||||||
"new": {
|
|
||||||
"name": name,
|
|
||||||
"resource_group": resource_group,
|
|
||||||
"etag": etag,
|
|
||||||
"registration_virtual_networks": registration_virtual_networks,
|
|
||||||
"resolution_virtual_networks": resolution_virtual_networks,
|
|
||||||
"tags": tags,
|
|
||||||
"zone_type": zone_type,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if __opts__["test"]:
|
|
||||||
ret["comment"] = "DNS zone {} would be created.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
return ret
|
|
||||||
|
|
||||||
zone_kwargs = kwargs.copy()
|
|
||||||
zone_kwargs.update(connection_auth)
|
|
||||||
|
|
||||||
zone = __salt__["azurearm_dns.zone_create_or_update"](
|
|
||||||
name=name,
|
|
||||||
resource_group=resource_group,
|
|
||||||
etag=etag,
|
|
||||||
if_match=if_match,
|
|
||||||
if_none_match=if_none_match,
|
|
||||||
registration_virtual_networks=registration_virtual_networks,
|
|
||||||
resolution_virtual_networks=resolution_virtual_networks,
|
|
||||||
tags=tags,
|
|
||||||
zone_type=zone_type,
|
|
||||||
**zone_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" not in zone:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "DNS zone {} has been created.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ret["comment"] = "Failed to create DNS zone {}! ({})".format(
|
|
||||||
name, zone.get("error")
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def zone_absent(name, resource_group, connection_auth=None):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Ensure a DNS zone does not exist in the resource group.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
Name of the DNS zone.
|
|
||||||
|
|
||||||
:param resource_group:
|
|
||||||
The resource group assigned to the DNS zone.
|
|
||||||
|
|
||||||
:param connection_auth:
|
|
||||||
A dict with subscription and authentication parameters to be used in connecting to the
|
|
||||||
Azure Resource Manager API.
|
|
||||||
"""
|
|
||||||
ret = {"name": name, "result": False, "comment": "", "changes": {}}
|
|
||||||
|
|
||||||
if not isinstance(connection_auth, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "Connection information must be specified via connection_auth dictionary!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
zone = __salt__["azurearm_dns.zone_get"](
|
|
||||||
name, resource_group, azurearm_log_level="info", **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" in zone:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "DNS zone {} was not found.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
elif __opts__["test"]:
|
|
||||||
ret["comment"] = "DNS zone {} would be deleted.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
ret["changes"] = {
|
|
||||||
"old": zone,
|
|
||||||
"new": {},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
deleted = __salt__["azurearm_dns.zone_delete"](
|
|
||||||
name, resource_group, **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if deleted:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "DNS zone {} has been deleted.".format(name)
|
|
||||||
ret["changes"] = {"old": zone, "new": {}}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ret["comment"] = "Failed to delete DNS zone {}!".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def record_set_present(
|
|
||||||
name,
|
|
||||||
zone_name,
|
|
||||||
resource_group,
|
|
||||||
record_type,
|
|
||||||
if_match=None,
|
|
||||||
if_none_match=None,
|
|
||||||
etag=None,
|
|
||||||
metadata=None,
|
|
||||||
ttl=None,
|
|
||||||
arecords=None,
|
|
||||||
aaaa_records=None,
|
|
||||||
mx_records=None,
|
|
||||||
ns_records=None,
|
|
||||||
ptr_records=None,
|
|
||||||
srv_records=None,
|
|
||||||
txt_records=None,
|
|
||||||
cname_record=None,
|
|
||||||
soa_record=None,
|
|
||||||
caa_records=None,
|
|
||||||
connection_auth=None,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Ensure a record set exists in a DNS zone.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
The name of the record set, relative to the name of the zone.
|
|
||||||
|
|
||||||
:param zone_name:
|
|
||||||
Name of the DNS zone (without a terminating dot).
|
|
||||||
|
|
||||||
:param resource_group:
|
|
||||||
The resource group assigned to the DNS zone.
|
|
||||||
|
|
||||||
:param record_type:
|
|
||||||
The type of DNS record in this record set. Record sets of type SOA can be updated but not created
|
|
||||||
(they are created when the DNS zone is created). Possible values include: 'A', 'AAAA', 'CAA', 'CNAME',
|
|
||||||
'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT'
|
|
||||||
|
|
||||||
:param if_match:
|
|
||||||
The etag of the record set. Omit this value to always overwrite the current record set. Specify the last-seen
|
|
||||||
etag value to prevent accidentally overwritting any concurrent changes.
|
|
||||||
|
|
||||||
:param if_none_match:
|
|
||||||
Set to '*' to allow a new record set to be created, but to prevent updating an existing record set. Other values
|
|
||||||
will be ignored.
|
|
||||||
|
|
||||||
:param etag:
|
|
||||||
The etag of the record set. `Etags <https://docs.microsoft.com/en-us/azure/dns/dns-zones-records#etags>`__ are
|
|
||||||
used to handle concurrent changes to the same resource safely.
|
|
||||||
|
|
||||||
:param metadata:
|
|
||||||
A dictionary of strings can be passed as tag metadata to the record set object.
|
|
||||||
|
|
||||||
:param ttl:
|
|
||||||
The TTL (time-to-live) of the records in the record set. Required when specifying record information.
|
|
||||||
|
|
||||||
:param arecords:
|
|
||||||
The list of A records in the record set. View the
|
|
||||||
`Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.arecord?view=azure-python>`__
|
|
||||||
to create a list of dictionaries representing the record objects.
|
|
||||||
|
|
||||||
:param aaaa_records:
|
|
||||||
The list of AAAA records in the record set. View the
|
|
||||||
`Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.aaaarecord?view=azure-python>`__
|
|
||||||
to create a list of dictionaries representing the record objects.
|
|
||||||
|
|
||||||
:param mx_records:
|
|
||||||
The list of MX records in the record set. View the
|
|
||||||
`Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.mxrecord?view=azure-python>`__
|
|
||||||
to create a list of dictionaries representing the record objects.
|
|
||||||
|
|
||||||
:param ns_records:
|
|
||||||
The list of NS records in the record set. View the
|
|
||||||
`Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.nsrecord?view=azure-python>`__
|
|
||||||
to create a list of dictionaries representing the record objects.
|
|
||||||
|
|
||||||
:param ptr_records:
|
|
||||||
The list of PTR records in the record set. View the
|
|
||||||
`Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.ptrrecord?view=azure-python>`__
|
|
||||||
to create a list of dictionaries representing the record objects.
|
|
||||||
|
|
||||||
:param srv_records:
|
|
||||||
The list of SRV records in the record set. View the
|
|
||||||
`Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.srvrecord?view=azure-python>`__
|
|
||||||
to create a list of dictionaries representing the record objects.
|
|
||||||
|
|
||||||
:param txt_records:
|
|
||||||
The list of TXT records in the record set. View the
|
|
||||||
`Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.txtrecord?view=azure-python>`__
|
|
||||||
to create a list of dictionaries representing the record objects.
|
|
||||||
|
|
||||||
:param cname_record:
|
|
||||||
The CNAME record in the record set. View the
|
|
||||||
`Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.cnamerecord?view=azure-python>`__
|
|
||||||
to create a dictionary representing the record object.
|
|
||||||
|
|
||||||
:param soa_record:
|
|
||||||
The SOA record in the record set. View the
|
|
||||||
`Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.soarecord?view=azure-python>`__
|
|
||||||
to create a dictionary representing the record object.
|
|
||||||
|
|
||||||
:param caa_records:
|
|
||||||
The list of CAA records in the record set. View the
|
|
||||||
`Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.caarecord?view=azure-python>`__
|
|
||||||
to create a list of dictionaries representing the record objects.
|
|
||||||
|
|
||||||
:param connection_auth:
|
|
||||||
A dict with subscription and authentication parameters to be used in connecting to the
|
|
||||||
Azure Resource Manager API.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
Ensure record set exists:
|
|
||||||
azurearm_dns.record_set_present:
|
|
||||||
- name: web
|
|
||||||
- zone_name: contoso.com
|
|
||||||
- resource_group: my_rg
|
|
||||||
- record_type: A
|
|
||||||
- ttl: 300
|
|
||||||
- arecords:
|
|
||||||
- ipv4_address: 10.0.0.1
|
|
||||||
- metadata:
|
|
||||||
how_awesome: very
|
|
||||||
contact_name: Elmer Fudd Gantry
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
"""
|
|
||||||
ret = {"name": name, "result": False, "comment": "", "changes": {}}
|
|
||||||
|
|
||||||
record_vars = [
|
|
||||||
"arecords",
|
|
||||||
"aaaa_records",
|
|
||||||
"mx_records",
|
|
||||||
"ns_records",
|
|
||||||
"ptr_records",
|
|
||||||
"srv_records",
|
|
||||||
"txt_records",
|
|
||||||
"cname_record",
|
|
||||||
"soa_record",
|
|
||||||
"caa_records",
|
|
||||||
]
|
|
||||||
|
|
||||||
if not isinstance(connection_auth, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "Connection information must be specified via connection_auth dictionary!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
rec_set = __salt__["azurearm_dns.record_set_get"](
|
|
||||||
name,
|
|
||||||
zone_name,
|
|
||||||
resource_group,
|
|
||||||
record_type,
|
|
||||||
azurearm_log_level="info",
|
|
||||||
**connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" not in rec_set:
|
|
||||||
metadata_changes = __utils__["dictdiffer.deep_diff"](
|
|
||||||
rec_set.get("metadata", {}), metadata or {}
|
|
||||||
)
|
|
||||||
if metadata_changes:
|
|
||||||
ret["changes"]["metadata"] = metadata_changes
|
|
||||||
|
|
||||||
for record_str in record_vars:
|
|
||||||
# pylint: disable=eval-used
|
|
||||||
record = eval(record_str)
|
|
||||||
if record:
|
|
||||||
if not ttl:
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "TTL is required when specifying record information!"
|
|
||||||
return ret
|
|
||||||
if not rec_set.get(record_str):
|
|
||||||
ret["changes"] = {"new": {record_str: record}}
|
|
||||||
continue
|
|
||||||
if record_str[-1] != "s":
|
|
||||||
if not isinstance(record, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "{} record information must be specified as a dictionary!".format(
|
|
||||||
record_str
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
for k, v in record.items():
|
|
||||||
if v != rec_set[record_str].get(k):
|
|
||||||
ret["changes"] = {"new": {record_str: record}}
|
|
||||||
elif record_str[-1] == "s":
|
|
||||||
if not isinstance(record, list):
|
|
||||||
ret["comment"] = (
|
|
||||||
"{} record information must be specified as a list of"
|
|
||||||
" dictionaries!".format(record_str)
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
local, remote = (
|
|
||||||
sorted(config) for config in (record, rec_set[record_str])
|
|
||||||
)
|
|
||||||
for val in local:
|
|
||||||
for key in val:
|
|
||||||
local_val = val[key]
|
|
||||||
remote_val = remote.get(key)
|
|
||||||
if isinstance(local_val, str):
|
|
||||||
local_val = local_val.lower()
|
|
||||||
if isinstance(remote_val, str):
|
|
||||||
remote_val = remote_val.lower()
|
|
||||||
if local_val != remote_val:
|
|
||||||
ret["changes"] = {"new": {record_str: record}}
|
|
||||||
|
|
||||||
if not ret["changes"]:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Record set {} is already present.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if __opts__["test"]:
|
|
||||||
ret["result"] = None
|
|
||||||
ret["comment"] = "Record set {} would be updated.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
else:
|
|
||||||
ret["changes"] = {
|
|
||||||
"old": {},
|
|
||||||
"new": {
|
|
||||||
"name": name,
|
|
||||||
"zone_name": zone_name,
|
|
||||||
"resource_group": resource_group,
|
|
||||||
"record_type": record_type,
|
|
||||||
"etag": etag,
|
|
||||||
"metadata": metadata,
|
|
||||||
"ttl": ttl,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for record in record_vars:
|
|
||||||
# pylint: disable=eval-used
|
|
||||||
if eval(record):
|
|
||||||
# pylint: disable=eval-used
|
|
||||||
ret["changes"]["new"][record] = eval(record)
|
|
||||||
|
|
||||||
if __opts__["test"]:
|
|
||||||
ret["comment"] = "Record set {} would be created.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
return ret
|
|
||||||
|
|
||||||
rec_set_kwargs = kwargs.copy()
|
|
||||||
rec_set_kwargs.update(connection_auth)
|
|
||||||
|
|
||||||
rec_set = __salt__["azurearm_dns.record_set_create_or_update"](
|
|
||||||
name=name,
|
|
||||||
zone_name=zone_name,
|
|
||||||
resource_group=resource_group,
|
|
||||||
record_type=record_type,
|
|
||||||
if_match=if_match,
|
|
||||||
if_none_match=if_none_match,
|
|
||||||
etag=etag,
|
|
||||||
ttl=ttl,
|
|
||||||
metadata=metadata,
|
|
||||||
arecords=arecords,
|
|
||||||
aaaa_records=aaaa_records,
|
|
||||||
mx_records=mx_records,
|
|
||||||
ns_records=ns_records,
|
|
||||||
ptr_records=ptr_records,
|
|
||||||
srv_records=srv_records,
|
|
||||||
txt_records=txt_records,
|
|
||||||
cname_record=cname_record,
|
|
||||||
soa_record=soa_record,
|
|
||||||
caa_records=caa_records,
|
|
||||||
**rec_set_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" not in rec_set:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Record set {} has been created.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ret["comment"] = "Failed to create record set {}! ({})".format(
|
|
||||||
name, rec_set.get("error")
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def record_set_absent(name, zone_name, resource_group, connection_auth=None):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 3000
|
|
||||||
|
|
||||||
Ensure a record set does not exist in the DNS zone.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
Name of the record set.
|
|
||||||
|
|
||||||
:param zone_name:
|
|
||||||
Name of the DNS zone.
|
|
||||||
|
|
||||||
:param resource_group:
|
|
||||||
The resource group assigned to the DNS zone.
|
|
||||||
|
|
||||||
:param connection_auth:
|
|
||||||
A dict with subscription and authentication parameters to be used in connecting to the
|
|
||||||
Azure Resource Manager API.
|
|
||||||
"""
|
|
||||||
ret = {"name": name, "result": False, "comment": "", "changes": {}}
|
|
||||||
|
|
||||||
if not isinstance(connection_auth, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "Connection information must be specified via connection_auth dictionary!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
rec_set = __salt__["azurearm_dns.record_set_get"](
|
|
||||||
name, zone_name, resource_group, azurearm_log_level="info", **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" in rec_set:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Record set {} was not found in zone {}.".format(
|
|
||||||
name, zone_name
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
elif __opts__["test"]:
|
|
||||||
ret["comment"] = "Record set {} would be deleted.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
ret["changes"] = {
|
|
||||||
"old": rec_set,
|
|
||||||
"new": {},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
deleted = __salt__["azurearm_dns.record_set_delete"](
|
|
||||||
name, zone_name, resource_group, **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if deleted:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Record set {} has been deleted.".format(name)
|
|
||||||
ret["changes"] = {"old": rec_set, "new": {}}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ret["comment"] = "Failed to delete record set {}!".format(name)
|
|
||||||
return ret
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,880 +0,0 @@
|
||||||
"""
|
|
||||||
Azure (ARM) Resource State Module
|
|
||||||
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This cloud provider will be removed from Salt in version 3007 in favor of
|
|
||||||
the `saltext.azurerm Salt Extension
|
|
||||||
<https://github.com/salt-extensions/saltext-azurerm>`_
|
|
||||||
|
|
||||||
:maintainer: <devops@eitr.tech>
|
|
||||||
:maturity: new
|
|
||||||
:depends:
|
|
||||||
* `azure <https://pypi.python.org/pypi/azure>`_ >= 2.0.0
|
|
||||||
* `azure-common <https://pypi.python.org/pypi/azure-common>`_ >= 1.1.8
|
|
||||||
* `azure-mgmt <https://pypi.python.org/pypi/azure-mgmt>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-compute <https://pypi.python.org/pypi/azure-mgmt-compute>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-network <https://pypi.python.org/pypi/azure-mgmt-network>`_ >= 1.7.1
|
|
||||||
* `azure-mgmt-resource <https://pypi.python.org/pypi/azure-mgmt-resource>`_ >= 1.1.0
|
|
||||||
* `azure-mgmt-storage <https://pypi.python.org/pypi/azure-mgmt-storage>`_ >= 1.0.0
|
|
||||||
* `azure-mgmt-web <https://pypi.python.org/pypi/azure-mgmt-web>`_ >= 0.32.0
|
|
||||||
* `azure-storage <https://pypi.python.org/pypi/azure-storage>`_ >= 0.34.3
|
|
||||||
* `msrestazure <https://pypi.python.org/pypi/msrestazure>`_ >= 0.4.21
|
|
||||||
:platform: linux
|
|
||||||
|
|
||||||
:configuration: This module requires Azure Resource Manager credentials to be passed as a dictionary of
|
|
||||||
keyword arguments to the ``connection_auth`` parameter in order to work properly. Since the authentication
|
|
||||||
parameters are sensitive, it's recommended to pass them to the states via pillar.
|
|
||||||
|
|
||||||
Required provider parameters:
|
|
||||||
|
|
||||||
if using username and password:
|
|
||||||
* ``subscription_id``
|
|
||||||
* ``username``
|
|
||||||
* ``password``
|
|
||||||
|
|
||||||
if using a service principal:
|
|
||||||
* ``subscription_id``
|
|
||||||
* ``tenant``
|
|
||||||
* ``client_id``
|
|
||||||
* ``secret``
|
|
||||||
|
|
||||||
Optional provider parameters:
|
|
||||||
|
|
||||||
**cloud_environment**: Used to point the cloud driver to different API endpoints, such as Azure GovCloud. Possible values:
|
|
||||||
* ``AZURE_PUBLIC_CLOUD`` (default)
|
|
||||||
* ``AZURE_CHINA_CLOUD``
|
|
||||||
* ``AZURE_US_GOV_CLOUD``
|
|
||||||
* ``AZURE_GERMAN_CLOUD``
|
|
||||||
|
|
||||||
Example Pillar for Azure Resource Manager authentication:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
azurearm:
|
|
||||||
user_pass_auth:
|
|
||||||
subscription_id: 3287abc8-f98a-c678-3bde-326766fd3617
|
|
||||||
username: fletch
|
|
||||||
password: 123pass
|
|
||||||
mysubscription:
|
|
||||||
subscription_id: 3287abc8-f98a-c678-3bde-326766fd3617
|
|
||||||
tenant: ABCDEFAB-1234-ABCD-1234-ABCDEFABCDEF
|
|
||||||
client_id: ABCDEFAB-1234-ABCD-1234-ABCDEFABCDEF
|
|
||||||
secret: XXXXXXXXXXXXXXXXXXXXXXXX
|
|
||||||
cloud_environment: AZURE_PUBLIC_CLOUD
|
|
||||||
|
|
||||||
Example states using Azure Resource Manager authentication:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set profile = salt['pillar.get']('azurearm:mysubscription') %}
|
|
||||||
Ensure resource group exists:
|
|
||||||
azurearm_resource.resource_group_present:
|
|
||||||
- name: my_rg
|
|
||||||
- location: westus
|
|
||||||
- tags:
|
|
||||||
how_awesome: very
|
|
||||||
contact_name: Elmer Fudd Gantry
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
Ensure resource group is absent:
|
|
||||||
azurearm_resource.resource_group_absent:
|
|
||||||
- name: other_rg
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
import salt.utils.azurearm
|
|
||||||
import salt.utils.files
|
|
||||||
|
|
||||||
__virtualname__ = "azurearm_resource"
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
|
||||||
"""
|
|
||||||
Only make this state available if the azurearm_resource module is available.
|
|
||||||
"""
|
|
||||||
if "azurearm_resource.resource_group_check_existence" in __salt__:
|
|
||||||
return __virtualname__
|
|
||||||
return (False, "azurearm_resource module could not be loaded")
|
|
||||||
|
|
||||||
|
|
||||||
def _deprecation_message(function):
|
|
||||||
"""
|
|
||||||
Decorator wrapper to warn about azurearm deprecation
|
|
||||||
"""
|
|
||||||
|
|
||||||
@wraps(function)
|
|
||||||
def wrapped(*args, **kwargs):
|
|
||||||
salt.utils.versions.warn_until(
|
|
||||||
"Chlorine",
|
|
||||||
"The 'azurearm' functionality in Salt has been deprecated and its "
|
|
||||||
"functionality will be removed in version 3007 in favor of the "
|
|
||||||
"saltext.azurerm Salt Extension. "
|
|
||||||
"(https://github.com/salt-extensions/saltext-azurerm)",
|
|
||||||
category=FutureWarning,
|
|
||||||
)
|
|
||||||
ret = function(*args, **salt.utils.args.clean_kwargs(**kwargs))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def resource_group_present(
|
|
||||||
name, location, managed_by=None, tags=None, connection_auth=None, **kwargs
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Ensure a resource group exists.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
Name of the resource group.
|
|
||||||
|
|
||||||
:param location:
|
|
||||||
The Azure location in which to create the resource group. This value cannot be updated once
|
|
||||||
the resource group is created.
|
|
||||||
|
|
||||||
:param managed_by:
|
|
||||||
The ID of the resource that manages this resource group. This value cannot be updated once
|
|
||||||
the resource group is created.
|
|
||||||
|
|
||||||
:param tags:
|
|
||||||
A dictionary of strings can be passed as tag metadata to the resource group object.
|
|
||||||
|
|
||||||
:param connection_auth:
|
|
||||||
A dict with subscription and authentication parameters to be used in connecting to the
|
|
||||||
Azure Resource Manager API.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
Ensure resource group exists:
|
|
||||||
azurearm_resource.resource_group_present:
|
|
||||||
- name: group1
|
|
||||||
- location: eastus
|
|
||||||
- tags:
|
|
||||||
contact_name: Elmer Fudd Gantry
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
"""
|
|
||||||
ret = {"name": name, "result": False, "comment": "", "changes": {}}
|
|
||||||
|
|
||||||
if not isinstance(connection_auth, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "Connection information must be specified via connection_auth dictionary!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
group = {}
|
|
||||||
|
|
||||||
present = __salt__["azurearm_resource.resource_group_check_existence"](
|
|
||||||
name, **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if present:
|
|
||||||
group = __salt__["azurearm_resource.resource_group_get"](
|
|
||||||
name, **connection_auth
|
|
||||||
)
|
|
||||||
ret["changes"] = __utils__["dictdiffer.deep_diff"](
|
|
||||||
group.get("tags", {}), tags or {}
|
|
||||||
)
|
|
||||||
|
|
||||||
if not ret["changes"]:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Resource group {} is already present.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if __opts__["test"]:
|
|
||||||
ret["comment"] = "Resource group {} tags would be updated.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
ret["changes"] = {"old": group.get("tags", {}), "new": tags}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
elif __opts__["test"]:
|
|
||||||
ret["comment"] = "Resource group {} would be created.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
ret["changes"] = {
|
|
||||||
"old": {},
|
|
||||||
"new": {
|
|
||||||
"name": name,
|
|
||||||
"location": location,
|
|
||||||
"managed_by": managed_by,
|
|
||||||
"tags": tags,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
group_kwargs = kwargs.copy()
|
|
||||||
group_kwargs.update(connection_auth)
|
|
||||||
|
|
||||||
group = __salt__["azurearm_resource.resource_group_create_or_update"](
|
|
||||||
name, location, managed_by=managed_by, tags=tags, **group_kwargs
|
|
||||||
)
|
|
||||||
present = __salt__["azurearm_resource.resource_group_check_existence"](
|
|
||||||
name, **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if present:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Resource group {} has been created.".format(name)
|
|
||||||
ret["changes"] = {"old": {}, "new": group}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ret["comment"] = "Failed to create resource group {}! ({})".format(
|
|
||||||
name, group.get("error")
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def resource_group_absent(name, connection_auth=None):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Ensure a resource group does not exist in the current subscription.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
Name of the resource group.
|
|
||||||
|
|
||||||
:param connection_auth:
|
|
||||||
A dict with subscription and authentication parameters to be used in connecting to the
|
|
||||||
Azure Resource Manager API.
|
|
||||||
"""
|
|
||||||
ret = {"name": name, "result": False, "comment": "", "changes": {}}
|
|
||||||
|
|
||||||
if not isinstance(connection_auth, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "Connection information must be specified via connection_auth dictionary!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
group = {}
|
|
||||||
|
|
||||||
present = __salt__["azurearm_resource.resource_group_check_existence"](
|
|
||||||
name, **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if not present:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Resource group {} is already absent.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
elif __opts__["test"]:
|
|
||||||
group = __salt__["azurearm_resource.resource_group_get"](
|
|
||||||
name, **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
ret["comment"] = "Resource group {} would be deleted.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
ret["changes"] = {
|
|
||||||
"old": group,
|
|
||||||
"new": {},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
group = __salt__["azurearm_resource.resource_group_get"](name, **connection_auth)
|
|
||||||
deleted = __salt__["azurearm_resource.resource_group_delete"](
|
|
||||||
name, **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if deleted:
|
|
||||||
present = False
|
|
||||||
else:
|
|
||||||
present = __salt__["azurearm_resource.resource_group_check_existence"](
|
|
||||||
name, **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if not present:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Resource group {} has been deleted.".format(name)
|
|
||||||
ret["changes"] = {"old": group, "new": {}}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ret["comment"] = "Failed to delete resource group {}!".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def policy_definition_present(
|
|
||||||
name,
|
|
||||||
policy_rule=None,
|
|
||||||
policy_type=None,
|
|
||||||
mode=None,
|
|
||||||
display_name=None,
|
|
||||||
description=None,
|
|
||||||
metadata=None,
|
|
||||||
parameters=None,
|
|
||||||
policy_rule_json=None,
|
|
||||||
policy_rule_file=None,
|
|
||||||
template="jinja",
|
|
||||||
source_hash=None,
|
|
||||||
source_hash_name=None,
|
|
||||||
skip_verify=False,
|
|
||||||
connection_auth=None,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Ensure a security policy definition exists.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
Name of the policy definition.
|
|
||||||
|
|
||||||
:param policy_rule:
|
|
||||||
A YAML dictionary defining the policy rule. See `Azure Policy Definition documentation
|
|
||||||
<https://docs.microsoft.com/en-us/azure/azure-policy/policy-definition#policy-rule>`_ for details on the
|
|
||||||
structure. One of ``policy_rule``, ``policy_rule_json``, or ``policy_rule_file`` is required, in that order of
|
|
||||||
precedence for use if multiple parameters are used.
|
|
||||||
|
|
||||||
:param policy_rule_json:
|
|
||||||
A text field defining the entirety of a policy definition in JSON. See `Azure Policy Definition documentation
|
|
||||||
<https://docs.microsoft.com/en-us/azure/azure-policy/policy-definition#policy-rule>`_ for details on the
|
|
||||||
structure. One of ``policy_rule``, ``policy_rule_json``, or ``policy_rule_file`` is required, in that order of
|
|
||||||
precedence for use if multiple parameters are used. Note that the `name` field in the JSON will override the
|
|
||||||
``name`` parameter in the state.
|
|
||||||
|
|
||||||
:param policy_rule_file:
|
|
||||||
The source of a JSON file defining the entirety of a policy definition. See `Azure Policy Definition
|
|
||||||
documentation <https://docs.microsoft.com/en-us/azure/azure-policy/policy-definition#policy-rule>`_ for
|
|
||||||
details on the structure. One of ``policy_rule``, ``policy_rule_json``, or ``policy_rule_file`` is required,
|
|
||||||
in that order of precedence for use if multiple parameters are used. Note that the `name` field in the JSON
|
|
||||||
will override the ``name`` parameter in the state.
|
|
||||||
|
|
||||||
:param skip_verify:
|
|
||||||
Used for the ``policy_rule_file`` parameter. If ``True``, hash verification of remote file sources
|
|
||||||
(``http://``, ``https://``, ``ftp://``) will be skipped, and the ``source_hash`` argument will be ignored.
|
|
||||||
|
|
||||||
:param source_hash:
|
|
||||||
This can be a source hash string or the URI of a file that contains source hash strings.
|
|
||||||
|
|
||||||
:param source_hash_name:
|
|
||||||
When ``source_hash`` refers to a hash file, Salt will try to find the correct hash by matching the
|
|
||||||
filename/URI associated with that hash.
|
|
||||||
|
|
||||||
:param policy_type:
|
|
||||||
The type of policy definition. Possible values are NotSpecified, BuiltIn, and Custom. Only used with the
|
|
||||||
``policy_rule`` parameter.
|
|
||||||
|
|
||||||
:param mode:
|
|
||||||
The policy definition mode. Possible values are NotSpecified, Indexed, and All. Only used with the
|
|
||||||
``policy_rule`` parameter.
|
|
||||||
|
|
||||||
:param display_name:
|
|
||||||
The display name of the policy definition. Only used with the ``policy_rule`` parameter.
|
|
||||||
|
|
||||||
:param description:
|
|
||||||
The policy definition description. Only used with the ``policy_rule`` parameter.
|
|
||||||
|
|
||||||
:param metadata:
|
|
||||||
The policy definition metadata defined as a dictionary. Only used with the ``policy_rule`` parameter.
|
|
||||||
|
|
||||||
:param parameters:
|
|
||||||
Required dictionary if a parameter is used in the policy rule. Only used with the ``policy_rule`` parameter.
|
|
||||||
|
|
||||||
:param connection_auth:
|
|
||||||
A dict with subscription and authentication parameters to be used in connecting to the
|
|
||||||
Azure Resource Manager API.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
Ensure policy definition exists:
|
|
||||||
azurearm_resource.policy_definition_present:
|
|
||||||
- name: testpolicy
|
|
||||||
- display_name: Test Policy
|
|
||||||
- description: Test policy for testing policies.
|
|
||||||
- policy_rule:
|
|
||||||
if:
|
|
||||||
allOf:
|
|
||||||
- equals: Microsoft.Compute/virtualMachines/write
|
|
||||||
source: action
|
|
||||||
- field: location
|
|
||||||
in:
|
|
||||||
- eastus
|
|
||||||
- eastus2
|
|
||||||
- centralus
|
|
||||||
then:
|
|
||||||
effect: deny
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
"""
|
|
||||||
ret = {"name": name, "result": False, "comment": "", "changes": {}}
|
|
||||||
|
|
||||||
if not isinstance(connection_auth, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "Connection information must be specified via connection_auth dictionary!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if not policy_rule and not policy_rule_json and not policy_rule_file:
|
|
||||||
ret["comment"] = (
|
|
||||||
'One of "policy_rule", "policy_rule_json", or "policy_rule_file" is'
|
|
||||||
" required!"
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if (
|
|
||||||
sum(x is not None for x in [policy_rule, policy_rule_json, policy_rule_file])
|
|
||||||
> 1
|
|
||||||
):
|
|
||||||
ret["comment"] = (
|
|
||||||
'Only one of "policy_rule", "policy_rule_json", or "policy_rule_file" is'
|
|
||||||
" allowed!"
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if (policy_rule_json or policy_rule_file) and (
|
|
||||||
policy_type or mode or display_name or description or metadata or parameters
|
|
||||||
):
|
|
||||||
ret["comment"] = (
|
|
||||||
'Policy definitions cannot be passed when "policy_rule_json" or'
|
|
||||||
' "policy_rule_file" is defined!'
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
temp_rule = {}
|
|
||||||
if policy_rule_json:
|
|
||||||
try:
|
|
||||||
temp_rule = json.loads(policy_rule_json)
|
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
|
||||||
ret["comment"] = "Unable to load policy rule json! ({})".format(exc)
|
|
||||||
return ret
|
|
||||||
elif policy_rule_file:
|
|
||||||
try:
|
|
||||||
# pylint: disable=unused-variable
|
|
||||||
sfn, source_sum, comment_ = __salt__["file.get_managed"](
|
|
||||||
None,
|
|
||||||
template,
|
|
||||||
policy_rule_file,
|
|
||||||
source_hash,
|
|
||||||
source_hash_name,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
__env__,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
skip_verify=skip_verify,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
|
||||||
ret["comment"] = 'Unable to locate policy rule file "{}"! ({})'.format(
|
|
||||||
policy_rule_file, exc
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if not sfn:
|
|
||||||
ret["comment"] = 'Unable to locate policy rule file "{}"!)'.format(
|
|
||||||
policy_rule_file
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
try:
|
|
||||||
with salt.utils.files.fopen(sfn, "r") as prf:
|
|
||||||
temp_rule = json.load(prf)
|
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
|
||||||
ret["comment"] = 'Unable to load policy rule file "{}"! ({})'.format(
|
|
||||||
policy_rule_file, exc
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if sfn:
|
|
||||||
salt.utils.files.remove(sfn)
|
|
||||||
|
|
||||||
policy_name = name
|
|
||||||
if policy_rule_json or policy_rule_file:
|
|
||||||
if temp_rule.get("name"):
|
|
||||||
policy_name = temp_rule.get("name")
|
|
||||||
policy_rule = temp_rule.get("properties", {}).get("policyRule")
|
|
||||||
policy_type = temp_rule.get("properties", {}).get("policyType")
|
|
||||||
mode = temp_rule.get("properties", {}).get("mode")
|
|
||||||
display_name = temp_rule.get("properties", {}).get("displayName")
|
|
||||||
description = temp_rule.get("properties", {}).get("description")
|
|
||||||
metadata = temp_rule.get("properties", {}).get("metadata")
|
|
||||||
parameters = temp_rule.get("properties", {}).get("parameters")
|
|
||||||
|
|
||||||
policy = __salt__["azurearm_resource.policy_definition_get"](
|
|
||||||
name, azurearm_log_level="info", **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" not in policy:
|
|
||||||
if policy_type and policy_type.lower() != policy.get("policy_type", "").lower():
|
|
||||||
ret["changes"]["policy_type"] = {
|
|
||||||
"old": policy.get("policy_type"),
|
|
||||||
"new": policy_type,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode or "").lower() != policy.get("mode", "").lower():
|
|
||||||
ret["changes"]["mode"] = {"old": policy.get("mode"), "new": mode}
|
|
||||||
|
|
||||||
if (display_name or "").lower() != policy.get("display_name", "").lower():
|
|
||||||
ret["changes"]["display_name"] = {
|
|
||||||
"old": policy.get("display_name"),
|
|
||||||
"new": display_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (description or "").lower() != policy.get("description", "").lower():
|
|
||||||
ret["changes"]["description"] = {
|
|
||||||
"old": policy.get("description"),
|
|
||||||
"new": description,
|
|
||||||
}
|
|
||||||
|
|
||||||
rule_changes = __utils__["dictdiffer.deep_diff"](
|
|
||||||
policy.get("policy_rule", {}), policy_rule or {}
|
|
||||||
)
|
|
||||||
if rule_changes:
|
|
||||||
ret["changes"]["policy_rule"] = rule_changes
|
|
||||||
|
|
||||||
meta_changes = __utils__["dictdiffer.deep_diff"](
|
|
||||||
policy.get("metadata", {}), metadata or {}
|
|
||||||
)
|
|
||||||
if meta_changes:
|
|
||||||
ret["changes"]["metadata"] = meta_changes
|
|
||||||
|
|
||||||
param_changes = __utils__["dictdiffer.deep_diff"](
|
|
||||||
policy.get("parameters", {}), parameters or {}
|
|
||||||
)
|
|
||||||
if param_changes:
|
|
||||||
ret["changes"]["parameters"] = param_changes
|
|
||||||
|
|
||||||
if not ret["changes"]:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Policy definition {} is already present.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if __opts__["test"]:
|
|
||||||
ret["comment"] = "Policy definition {} would be updated.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
return ret
|
|
||||||
|
|
||||||
else:
|
|
||||||
ret["changes"] = {
|
|
||||||
"old": {},
|
|
||||||
"new": {
|
|
||||||
"name": policy_name,
|
|
||||||
"policy_type": policy_type,
|
|
||||||
"mode": mode,
|
|
||||||
"display_name": display_name,
|
|
||||||
"description": description,
|
|
||||||
"metadata": metadata,
|
|
||||||
"parameters": parameters,
|
|
||||||
"policy_rule": policy_rule,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if __opts__["test"]:
|
|
||||||
ret["comment"] = "Policy definition {} would be created.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
return ret
|
|
||||||
|
|
||||||
# Convert OrderedDict to dict
|
|
||||||
if isinstance(metadata, dict):
|
|
||||||
metadata = json.loads(json.dumps(metadata))
|
|
||||||
if isinstance(parameters, dict):
|
|
||||||
parameters = json.loads(json.dumps(parameters))
|
|
||||||
|
|
||||||
policy_kwargs = kwargs.copy()
|
|
||||||
policy_kwargs.update(connection_auth)
|
|
||||||
|
|
||||||
policy = __salt__["azurearm_resource.policy_definition_create_or_update"](
|
|
||||||
name=policy_name,
|
|
||||||
policy_rule=policy_rule,
|
|
||||||
policy_type=policy_type,
|
|
||||||
mode=mode,
|
|
||||||
display_name=display_name,
|
|
||||||
description=description,
|
|
||||||
metadata=metadata,
|
|
||||||
parameters=parameters,
|
|
||||||
**policy_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" not in policy:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Policy definition {} has been created.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ret["comment"] = "Failed to create policy definition {}! ({})".format(
|
|
||||||
name, policy.get("error")
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def policy_definition_absent(name, connection_auth=None):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Ensure a policy definition does not exist in the current subscription.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
Name of the policy definition.
|
|
||||||
|
|
||||||
:param connection_auth:
|
|
||||||
A dict with subscription and authentication parameters to be used in connecting to the
|
|
||||||
Azure Resource Manager API.
|
|
||||||
"""
|
|
||||||
ret = {"name": name, "result": False, "comment": "", "changes": {}}
|
|
||||||
|
|
||||||
if not isinstance(connection_auth, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "Connection information must be specified via connection_auth dictionary!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
policy = __salt__["azurearm_resource.policy_definition_get"](
|
|
||||||
name, azurearm_log_level="info", **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" in policy:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Policy definition {} is already absent.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
elif __opts__["test"]:
|
|
||||||
ret["comment"] = "Policy definition {} would be deleted.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
ret["changes"] = {
|
|
||||||
"old": policy,
|
|
||||||
"new": {},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
deleted = __salt__["azurearm_resource.policy_definition_delete"](
|
|
||||||
name, **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if deleted:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Policy definition {} has been deleted.".format(name)
|
|
||||||
ret["changes"] = {"old": policy, "new": {}}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ret["comment"] = "Failed to delete policy definition {}!".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def policy_assignment_present(
|
|
||||||
name,
|
|
||||||
scope,
|
|
||||||
definition_name,
|
|
||||||
display_name=None,
|
|
||||||
description=None,
|
|
||||||
assignment_type=None,
|
|
||||||
parameters=None,
|
|
||||||
connection_auth=None,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Ensure a security policy assignment exists.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
Name of the policy assignment.
|
|
||||||
|
|
||||||
:param scope:
|
|
||||||
The scope of the policy assignment.
|
|
||||||
|
|
||||||
:param definition_name:
|
|
||||||
The name of the policy definition to assign.
|
|
||||||
|
|
||||||
:param display_name:
|
|
||||||
The display name of the policy assignment.
|
|
||||||
|
|
||||||
:param description:
|
|
||||||
The policy assignment description.
|
|
||||||
|
|
||||||
:param assignment_type:
|
|
||||||
The type of policy assignment.
|
|
||||||
|
|
||||||
:param parameters:
|
|
||||||
Required dictionary if a parameter is used in the policy rule.
|
|
||||||
|
|
||||||
:param connection_auth:
|
|
||||||
A dict with subscription and authentication parameters to be used in connecting to the
|
|
||||||
Azure Resource Manager API.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
Ensure policy assignment exists:
|
|
||||||
azurearm_resource.policy_assignment_present:
|
|
||||||
- name: testassign
|
|
||||||
- scope: /subscriptions/bc75htn-a0fhsi-349b-56gh-4fghti-f84852
|
|
||||||
- definition_name: testpolicy
|
|
||||||
- display_name: Test Assignment
|
|
||||||
- description: Test assignment for testing assignments.
|
|
||||||
- connection_auth: {{ profile }}
|
|
||||||
|
|
||||||
"""
|
|
||||||
ret = {"name": name, "result": False, "comment": "", "changes": {}}
|
|
||||||
|
|
||||||
if not isinstance(connection_auth, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "Connection information must be specified via connection_auth dictionary!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
policy = __salt__["azurearm_resource.policy_assignment_get"](
|
|
||||||
name, scope, azurearm_log_level="info", **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" not in policy:
|
|
||||||
if (
|
|
||||||
assignment_type
|
|
||||||
and assignment_type.lower() != policy.get("type", "").lower()
|
|
||||||
):
|
|
||||||
ret["changes"]["type"] = {"old": policy.get("type"), "new": assignment_type}
|
|
||||||
|
|
||||||
if scope.lower() != policy["scope"].lower():
|
|
||||||
ret["changes"]["scope"] = {"old": policy["scope"], "new": scope}
|
|
||||||
|
|
||||||
pa_name = policy["policy_definition_id"].split("/")[-1]
|
|
||||||
if definition_name.lower() != pa_name.lower():
|
|
||||||
ret["changes"]["definition_name"] = {"old": pa_name, "new": definition_name}
|
|
||||||
|
|
||||||
if (display_name or "").lower() != policy.get("display_name", "").lower():
|
|
||||||
ret["changes"]["display_name"] = {
|
|
||||||
"old": policy.get("display_name"),
|
|
||||||
"new": display_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (description or "").lower() != policy.get("description", "").lower():
|
|
||||||
ret["changes"]["description"] = {
|
|
||||||
"old": policy.get("description"),
|
|
||||||
"new": description,
|
|
||||||
}
|
|
||||||
|
|
||||||
param_changes = __utils__["dictdiffer.deep_diff"](
|
|
||||||
policy.get("parameters", {}), parameters or {}
|
|
||||||
)
|
|
||||||
if param_changes:
|
|
||||||
ret["changes"]["parameters"] = param_changes
|
|
||||||
|
|
||||||
if not ret["changes"]:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Policy assignment {} is already present.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if __opts__["test"]:
|
|
||||||
ret["comment"] = "Policy assignment {} would be updated.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
return ret
|
|
||||||
|
|
||||||
else:
|
|
||||||
ret["changes"] = {
|
|
||||||
"old": {},
|
|
||||||
"new": {
|
|
||||||
"name": name,
|
|
||||||
"scope": scope,
|
|
||||||
"definition_name": definition_name,
|
|
||||||
"type": assignment_type,
|
|
||||||
"display_name": display_name,
|
|
||||||
"description": description,
|
|
||||||
"parameters": parameters,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if __opts__["test"]:
|
|
||||||
ret["comment"] = "Policy assignment {} would be created.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if isinstance(parameters, dict):
|
|
||||||
parameters = json.loads(json.dumps(parameters))
|
|
||||||
|
|
||||||
policy_kwargs = kwargs.copy()
|
|
||||||
policy_kwargs.update(connection_auth)
|
|
||||||
policy = __salt__["azurearm_resource.policy_assignment_create"](
|
|
||||||
name=name,
|
|
||||||
scope=scope,
|
|
||||||
definition_name=definition_name,
|
|
||||||
type=assignment_type,
|
|
||||||
display_name=display_name,
|
|
||||||
description=description,
|
|
||||||
parameters=parameters,
|
|
||||||
**policy_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" not in policy:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Policy assignment {} has been created.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ret["comment"] = "Failed to create policy assignment {}! ({})".format(
|
|
||||||
name, policy.get("error")
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
@_deprecation_message
|
|
||||||
def policy_assignment_absent(name, scope, connection_auth=None):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
Ensure a policy assignment does not exist in the provided scope.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
Name of the policy assignment.
|
|
||||||
|
|
||||||
:param scope:
|
|
||||||
The scope of the policy assignment.
|
|
||||||
|
|
||||||
connection_auth
|
|
||||||
A dict with subscription and authentication parameters to be used in connecting to the
|
|
||||||
Azure Resource Manager API.
|
|
||||||
"""
|
|
||||||
ret = {"name": name, "result": False, "comment": "", "changes": {}}
|
|
||||||
|
|
||||||
if not isinstance(connection_auth, dict):
|
|
||||||
ret[
|
|
||||||
"comment"
|
|
||||||
] = "Connection information must be specified via connection_auth dictionary!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
policy = __salt__["azurearm_resource.policy_assignment_get"](
|
|
||||||
name, scope, azurearm_log_level="info", **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" in policy:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Policy assignment {} is already absent.".format(name)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
elif __opts__["test"]:
|
|
||||||
ret["comment"] = "Policy assignment {} would be deleted.".format(name)
|
|
||||||
ret["result"] = None
|
|
||||||
ret["changes"] = {
|
|
||||||
"old": policy,
|
|
||||||
"new": {},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
deleted = __salt__["azurearm_resource.policy_assignment_delete"](
|
|
||||||
name, scope, **connection_auth
|
|
||||||
)
|
|
||||||
|
|
||||||
if deleted:
|
|
||||||
ret["result"] = True
|
|
||||||
ret["comment"] = "Policy assignment {} has been deleted.".format(name)
|
|
||||||
ret["changes"] = {"old": policy, "new": {}}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ret["comment"] = "Failed to delete policy assignment {}!".format(name)
|
|
||||||
return ret
|
|
|
@ -1,338 +0,0 @@
|
||||||
"""
|
|
||||||
Azure (ARM) Utilities
|
|
||||||
|
|
||||||
.. versionadded:: 2019.2.0
|
|
||||||
|
|
||||||
:maintainer: <devops@eitr.tech>
|
|
||||||
:maturity: new
|
|
||||||
:depends:
|
|
||||||
* `azure <https://pypi.python.org/pypi/azure>`_ >= 2.0.0rc6
|
|
||||||
* `azure-common <https://pypi.python.org/pypi/azure-common>`_ >= 1.1.4
|
|
||||||
* `azure-mgmt <https://pypi.python.org/pypi/azure-mgmt>`_ >= 0.30.0rc6
|
|
||||||
* `azure-mgmt-compute <https://pypi.python.org/pypi/azure-mgmt-compute>`_ >= 0.33.0
|
|
||||||
* `azure-mgmt-network <https://pypi.python.org/pypi/azure-mgmt-network>`_ >= 0.30.0rc6
|
|
||||||
* `azure-mgmt-resource <https://pypi.python.org/pypi/azure-mgmt-resource>`_ >= 0.30.0
|
|
||||||
* `azure-mgmt-storage <https://pypi.python.org/pypi/azure-mgmt-storage>`_ >= 0.30.0rc6
|
|
||||||
* `azure-mgmt-web <https://pypi.python.org/pypi/azure-mgmt-web>`_ >= 0.30.0rc6
|
|
||||||
* `azure-storage <https://pypi.python.org/pypi/azure-storage>`_ >= 0.32.0
|
|
||||||
* `msrestazure <https://pypi.python.org/pypi/msrestazure>`_ >= 0.4.21
|
|
||||||
:platform: linux
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import importlib
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
from operator import itemgetter
|
|
||||||
|
|
||||||
import salt.config
|
|
||||||
import salt.loader
|
|
||||||
import salt.utils.args
|
|
||||||
import salt.utils.stringutils
|
|
||||||
import salt.utils.versions
|
|
||||||
import salt.version
|
|
||||||
from salt.exceptions import SaltInvocationError, SaltSystemExit
|
|
||||||
|
|
||||||
try:
|
|
||||||
from azure.common.credentials import (
|
|
||||||
ServicePrincipalCredentials,
|
|
||||||
UserPassCredentials,
|
|
||||||
)
|
|
||||||
from msrestazure.azure_cloud import (
|
|
||||||
MetadataEndpointError,
|
|
||||||
get_cloud_from_metadata_endpoint,
|
|
||||||
)
|
|
||||||
|
|
||||||
HAS_AZURE = True
|
|
||||||
except ImportError:
|
|
||||||
HAS_AZURE = False
|
|
||||||
|
|
||||||
__opts__ = salt.config.minion_config("/etc/salt/minion")
|
|
||||||
__salt__ = salt.loader.minion_mods(__opts__)
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
|
||||||
if not HAS_AZURE:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def _determine_auth(**kwargs):
|
|
||||||
"""
|
|
||||||
Acquire Azure ARM Credentials
|
|
||||||
"""
|
|
||||||
if "profile" in kwargs:
|
|
||||||
azure_credentials = __salt__["config.option"](kwargs["profile"])
|
|
||||||
kwargs.update(azure_credentials)
|
|
||||||
|
|
||||||
service_principal_creds_kwargs = ["client_id", "secret", "tenant"]
|
|
||||||
user_pass_creds_kwargs = ["username", "password"]
|
|
||||||
|
|
||||||
try:
|
|
||||||
if kwargs.get("cloud_environment") and kwargs.get(
|
|
||||||
"cloud_environment"
|
|
||||||
).startswith("http"):
|
|
||||||
cloud_env = get_cloud_from_metadata_endpoint(kwargs["cloud_environment"])
|
|
||||||
else:
|
|
||||||
cloud_env_module = importlib.import_module("msrestazure.azure_cloud")
|
|
||||||
cloud_env = getattr(
|
|
||||||
cloud_env_module, kwargs.get("cloud_environment", "AZURE_PUBLIC_CLOUD")
|
|
||||||
)
|
|
||||||
except (AttributeError, ImportError, MetadataEndpointError):
|
|
||||||
raise sys.exit(
|
|
||||||
"The Azure cloud environment {} is not available.".format(
|
|
||||||
kwargs["cloud_environment"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if set(service_principal_creds_kwargs).issubset(kwargs):
|
|
||||||
if not (kwargs["client_id"] and kwargs["secret"] and kwargs["tenant"]):
|
|
||||||
raise SaltInvocationError(
|
|
||||||
"The client_id, secret, and tenant parameters must all be "
|
|
||||||
"populated if using service principals."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
credentials = ServicePrincipalCredentials(
|
|
||||||
kwargs["client_id"],
|
|
||||||
kwargs["secret"],
|
|
||||||
tenant=kwargs["tenant"],
|
|
||||||
cloud_environment=cloud_env,
|
|
||||||
)
|
|
||||||
elif set(user_pass_creds_kwargs).issubset(kwargs):
|
|
||||||
if not (kwargs["username"] and kwargs["password"]):
|
|
||||||
raise SaltInvocationError(
|
|
||||||
"The username and password parameters must both be "
|
|
||||||
"populated if using username/password authentication."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
credentials = UserPassCredentials(
|
|
||||||
kwargs["username"], kwargs["password"], cloud_environment=cloud_env
|
|
||||||
)
|
|
||||||
elif "subscription_id" in kwargs:
|
|
||||||
try:
|
|
||||||
from msrestazure.azure_active_directory import MSIAuthentication
|
|
||||||
|
|
||||||
credentials = MSIAuthentication(cloud_environment=cloud_env)
|
|
||||||
except ImportError:
|
|
||||||
raise SaltSystemExit(
|
|
||||||
msg=(
|
|
||||||
"MSI authentication support not availabe (requires msrestazure >="
|
|
||||||
" 0.4.14)"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise SaltInvocationError(
|
|
||||||
"Unable to determine credentials. "
|
|
||||||
"A subscription_id with username and password, "
|
|
||||||
"or client_id, secret, and tenant or a profile with the "
|
|
||||||
"required parameters populated"
|
|
||||||
)
|
|
||||||
|
|
||||||
if "subscription_id" not in kwargs:
|
|
||||||
raise SaltInvocationError("A subscription_id must be specified")
|
|
||||||
|
|
||||||
subscription_id = salt.utils.stringutils.to_str(kwargs["subscription_id"])
|
|
||||||
|
|
||||||
return credentials, subscription_id, cloud_env
|
|
||||||
|
|
||||||
|
|
||||||
def get_client(client_type, **kwargs):
|
|
||||||
"""
|
|
||||||
Dynamically load the selected client and return a management client object
|
|
||||||
"""
|
|
||||||
client_map = {
|
|
||||||
"compute": "ComputeManagement",
|
|
||||||
"authorization": "AuthorizationManagement",
|
|
||||||
"dns": "DnsManagement",
|
|
||||||
"storage": "StorageManagement",
|
|
||||||
"managementlock": "ManagementLock",
|
|
||||||
"monitor": "MonitorManagement",
|
|
||||||
"network": "NetworkManagement",
|
|
||||||
"policy": "Policy",
|
|
||||||
"resource": "ResourceManagement",
|
|
||||||
"subscription": "Subscription",
|
|
||||||
"web": "WebSiteManagement",
|
|
||||||
}
|
|
||||||
|
|
||||||
if client_type not in client_map:
|
|
||||||
raise SaltSystemExit(
|
|
||||||
msg="The Azure ARM client_type {} specified can not be found.".format(
|
|
||||||
client_type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
map_value = client_map[client_type]
|
|
||||||
|
|
||||||
if client_type in ["policy", "subscription"]:
|
|
||||||
module_name = "resource"
|
|
||||||
elif client_type in ["managementlock"]:
|
|
||||||
module_name = "resource.locks"
|
|
||||||
else:
|
|
||||||
module_name = client_type
|
|
||||||
|
|
||||||
try:
|
|
||||||
client_module = importlib.import_module("azure.mgmt." + module_name)
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
Client = getattr(client_module, "{}Client".format(map_value))
|
|
||||||
except ImportError:
|
|
||||||
raise sys.exit("The azure {} client is not available.".format(client_type))
|
|
||||||
|
|
||||||
credentials, subscription_id, cloud_env = _determine_auth(**kwargs)
|
|
||||||
|
|
||||||
if client_type == "subscription":
|
|
||||||
client = Client(
|
|
||||||
credentials=credentials,
|
|
||||||
base_url=cloud_env.endpoints.resource_manager,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
client = Client(
|
|
||||||
credentials=credentials,
|
|
||||||
subscription_id=subscription_id,
|
|
||||||
base_url=cloud_env.endpoints.resource_manager,
|
|
||||||
)
|
|
||||||
|
|
||||||
client.config.add_user_agent("Salt/{}".format(salt.version.__version__))
|
|
||||||
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
def log_cloud_error(client, message, **kwargs):
|
|
||||||
"""
|
|
||||||
Log an azurearm cloud error exception
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
cloud_logger = getattr(log, kwargs.get("azurearm_log_level"))
|
|
||||||
except (AttributeError, TypeError):
|
|
||||||
cloud_logger = getattr(log, "error")
|
|
||||||
|
|
||||||
cloud_logger(
|
|
||||||
"An AzureARM %s CloudError has occurred: %s", client.capitalize(), message
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def paged_object_to_list(paged_object):
|
|
||||||
"""
|
|
||||||
Extract all pages within a paged object as a list of dictionaries
|
|
||||||
"""
|
|
||||||
paged_return = []
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
page = next(paged_object)
|
|
||||||
paged_return.append(page.as_dict())
|
|
||||||
except StopIteration:
|
|
||||||
break
|
|
||||||
|
|
||||||
return paged_return
|
|
||||||
|
|
||||||
|
|
||||||
def create_object_model(module_name, object_name, **kwargs):
|
|
||||||
"""
|
|
||||||
Assemble an object from incoming parameters.
|
|
||||||
"""
|
|
||||||
object_kwargs = {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
model_module = importlib.import_module(
|
|
||||||
"azure.mgmt.{}.models".format(module_name)
|
|
||||||
)
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
Model = getattr(model_module, object_name)
|
|
||||||
except ImportError:
|
|
||||||
raise sys.exit(
|
|
||||||
"The {} model in the {} Azure module is not available.".format(
|
|
||||||
object_name, module_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if "_attribute_map" in dir(Model):
|
|
||||||
for attr, items in Model._attribute_map.items():
|
|
||||||
param = kwargs.get(attr)
|
|
||||||
if param is not None:
|
|
||||||
if items["type"][0].isupper() and isinstance(param, dict):
|
|
||||||
object_kwargs[attr] = create_object_model(
|
|
||||||
module_name, items["type"], **param
|
|
||||||
)
|
|
||||||
elif items["type"][0] == "{" and isinstance(param, dict):
|
|
||||||
object_kwargs[attr] = param
|
|
||||||
elif items["type"][0] == "[" and isinstance(param, list):
|
|
||||||
obj_list = []
|
|
||||||
for list_item in param:
|
|
||||||
if items["type"][1].isupper() and isinstance(list_item, dict):
|
|
||||||
obj_list.append(
|
|
||||||
create_object_model(
|
|
||||||
module_name,
|
|
||||||
items["type"][
|
|
||||||
items["type"].index("[")
|
|
||||||
+ 1 : items["type"].rindex("]")
|
|
||||||
],
|
|
||||||
**list_item
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif items["type"][1] == "{" and isinstance(list_item, dict):
|
|
||||||
obj_list.append(list_item)
|
|
||||||
elif not items["type"][1].isupper() and items["type"][1] != "{":
|
|
||||||
obj_list.append(list_item)
|
|
||||||
object_kwargs[attr] = obj_list
|
|
||||||
else:
|
|
||||||
object_kwargs[attr] = param
|
|
||||||
|
|
||||||
# wrap calls to this function to catch TypeError exceptions
|
|
||||||
return Model(**object_kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def compare_list_of_dicts(old, new, convert_id_to_name=None):
|
|
||||||
"""
|
|
||||||
Compare lists of dictionaries representing Azure objects. Only keys found in the "new" dictionaries are compared to
|
|
||||||
the "old" dictionaries, since getting Azure objects from the API returns some read-only data which should not be
|
|
||||||
used in the comparison. A list of parameter names can be passed in order to compare a bare object name to a full
|
|
||||||
Azure ID path for brevity. If string types are found in values, comparison is case insensitive. Return comment
|
|
||||||
should be used to trigger exit from the calling function.
|
|
||||||
"""
|
|
||||||
ret = {}
|
|
||||||
|
|
||||||
if not convert_id_to_name:
|
|
||||||
convert_id_to_name = []
|
|
||||||
|
|
||||||
if not isinstance(new, list):
|
|
||||||
ret["comment"] = "must be provided as a list of dictionaries!"
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if len(new) != len(old):
|
|
||||||
ret["changes"] = {"old": old, "new": new}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
try:
|
|
||||||
local_configs, remote_configs = (
|
|
||||||
sorted(config, key=itemgetter("name")) for config in (new, old)
|
|
||||||
)
|
|
||||||
except TypeError:
|
|
||||||
ret["comment"] = "configurations must be provided as a list of dictionaries!"
|
|
||||||
return ret
|
|
||||||
except KeyError:
|
|
||||||
ret["comment"] = 'configuration dictionaries must contain the "name" key!'
|
|
||||||
return ret
|
|
||||||
|
|
||||||
for idx, val in enumerate(local_configs):
|
|
||||||
for key in val:
|
|
||||||
local_val = val[key]
|
|
||||||
if key in convert_id_to_name:
|
|
||||||
remote_val = (
|
|
||||||
remote_configs[idx].get(key, {}).get("id", "").split("/")[-1]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
remote_val = remote_configs[idx].get(key)
|
|
||||||
if isinstance(local_val, str):
|
|
||||||
local_val = local_val.lower()
|
|
||||||
if isinstance(remote_val, str):
|
|
||||||
remote_val = remote_val.lower()
|
|
||||||
if local_val != remote_val:
|
|
||||||
ret["changes"] = {"old": remote_configs, "new": local_configs}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
return ret
|
|
|
@ -1,189 +0,0 @@
|
||||||
"""
|
|
||||||
.. versionadded:: 2015.8.0
|
|
||||||
|
|
||||||
Utilities for accessing storage container blobs on Azure
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from salt.exceptions import SaltSystemExit
|
|
||||||
|
|
||||||
HAS_LIBS = False
|
|
||||||
try:
|
|
||||||
import azure
|
|
||||||
|
|
||||||
HAS_LIBS = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def get_storage_conn(storage_account=None, storage_key=None, opts=None):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2015.8.0
|
|
||||||
|
|
||||||
Return a storage_conn object for the storage account
|
|
||||||
"""
|
|
||||||
if opts is None:
|
|
||||||
opts = {}
|
|
||||||
|
|
||||||
if not storage_account:
|
|
||||||
storage_account = opts.get("storage_account", None)
|
|
||||||
if not storage_key:
|
|
||||||
storage_key = opts.get("storage_key", None)
|
|
||||||
|
|
||||||
return azure.storage.BlobService(storage_account, storage_key)
|
|
||||||
|
|
||||||
|
|
||||||
def list_blobs(storage_conn=None, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2015.8.0
|
|
||||||
|
|
||||||
List blobs associated with the container
|
|
||||||
"""
|
|
||||||
if not storage_conn:
|
|
||||||
storage_conn = get_storage_conn(opts=kwargs)
|
|
||||||
|
|
||||||
if "container" not in kwargs:
|
|
||||||
raise SaltSystemExit(
|
|
||||||
code=42, msg='An storage container name must be specified as "container"'
|
|
||||||
)
|
|
||||||
|
|
||||||
data = storage_conn.list_blobs(
|
|
||||||
container_name=kwargs["container"],
|
|
||||||
prefix=kwargs.get("prefix", None),
|
|
||||||
marker=kwargs.get("marker", None),
|
|
||||||
maxresults=kwargs.get("maxresults", None),
|
|
||||||
include=kwargs.get("include", None),
|
|
||||||
delimiter=kwargs.get("delimiter", None),
|
|
||||||
)
|
|
||||||
|
|
||||||
ret = {}
|
|
||||||
for item in data.blobs:
|
|
||||||
ret[item.name] = object_to_dict(item)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def put_blob(storage_conn=None, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2015.8.0
|
|
||||||
|
|
||||||
Upload a blob
|
|
||||||
"""
|
|
||||||
if not storage_conn:
|
|
||||||
storage_conn = get_storage_conn(opts=kwargs)
|
|
||||||
|
|
||||||
if "container" not in kwargs:
|
|
||||||
raise SaltSystemExit(
|
|
||||||
code=42, msg='The blob container name must be specified as "container"'
|
|
||||||
)
|
|
||||||
|
|
||||||
if "name" not in kwargs:
|
|
||||||
raise SaltSystemExit(code=42, msg='The blob name must be specified as "name"')
|
|
||||||
|
|
||||||
if "blob_path" not in kwargs and "blob_content" not in kwargs:
|
|
||||||
raise SaltSystemExit(
|
|
||||||
code=42,
|
|
||||||
msg=(
|
|
||||||
'Either a path to a file needs to be passed in as "blob_path" '
|
|
||||||
'or the contents of a blob as "blob_content."'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
blob_kwargs = {
|
|
||||||
"container_name": kwargs["container"],
|
|
||||||
"blob_name": kwargs["name"],
|
|
||||||
"cache_control": kwargs.get("cache_control", None),
|
|
||||||
"content_language": kwargs.get("content_language", None),
|
|
||||||
"content_md5": kwargs.get("content_md5", None),
|
|
||||||
"x_ms_blob_content_type": kwargs.get("blob_content_type", None),
|
|
||||||
"x_ms_blob_content_encoding": kwargs.get("blob_content_encoding", None),
|
|
||||||
"x_ms_blob_content_language": kwargs.get("blob_content_language", None),
|
|
||||||
"x_ms_blob_content_md5": kwargs.get("blob_content_md5", None),
|
|
||||||
"x_ms_blob_cache_control": kwargs.get("blob_cache_control", None),
|
|
||||||
"x_ms_meta_name_values": kwargs.get("meta_name_values", None),
|
|
||||||
"x_ms_lease_id": kwargs.get("lease_id", None),
|
|
||||||
}
|
|
||||||
if "blob_path" in kwargs:
|
|
||||||
data = storage_conn.put_block_blob_from_path(
|
|
||||||
file_path=kwargs["blob_path"], **blob_kwargs
|
|
||||||
)
|
|
||||||
elif "blob_content" in kwargs:
|
|
||||||
data = storage_conn.put_block_blob_from_bytes(
|
|
||||||
blob=kwargs["blob_content"], **blob_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def get_blob(storage_conn=None, **kwargs):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2015.8.0
|
|
||||||
|
|
||||||
Download a blob
|
|
||||||
"""
|
|
||||||
if not storage_conn:
|
|
||||||
storage_conn = get_storage_conn(opts=kwargs)
|
|
||||||
|
|
||||||
if "container" not in kwargs:
|
|
||||||
raise SaltSystemExit(
|
|
||||||
code=42, msg='The blob container name must be specified as "container"'
|
|
||||||
)
|
|
||||||
|
|
||||||
if "name" not in kwargs:
|
|
||||||
raise SaltSystemExit(code=42, msg='The blob name must be specified as "name"')
|
|
||||||
|
|
||||||
if "local_path" not in kwargs and "return_content" not in kwargs:
|
|
||||||
raise SaltSystemExit(
|
|
||||||
code=42,
|
|
||||||
msg=(
|
|
||||||
'Either a local path needs to be passed in as "local_path", '
|
|
||||||
'or "return_content" to return the blob contents directly'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
blob_kwargs = {
|
|
||||||
"container_name": kwargs["container"],
|
|
||||||
"blob_name": kwargs["name"],
|
|
||||||
"snapshot": kwargs.get("snapshot", None),
|
|
||||||
"x_ms_lease_id": kwargs.get("lease_id", None),
|
|
||||||
"progress_callback": kwargs.get("progress_callback", None),
|
|
||||||
"max_connections": kwargs.get("max_connections", 1),
|
|
||||||
"max_retries": kwargs.get("max_retries", 5),
|
|
||||||
"retry_wait": kwargs.get("retry_wait", 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
if "local_path" in kwargs:
|
|
||||||
data = storage_conn.get_blob_to_path(
|
|
||||||
file_path=kwargs["local_path"],
|
|
||||||
open_mode=kwargs.get("open_mode", "wb"),
|
|
||||||
**blob_kwargs
|
|
||||||
)
|
|
||||||
elif "return_content" in kwargs:
|
|
||||||
data = storage_conn.get_blob_to_bytes(**blob_kwargs)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def object_to_dict(obj):
|
|
||||||
"""
|
|
||||||
.. versionadded:: 2015.8.0
|
|
||||||
|
|
||||||
Convert an object to a dictionary
|
|
||||||
"""
|
|
||||||
if isinstance(obj, list) or isinstance(obj, tuple):
|
|
||||||
ret = []
|
|
||||||
for item in obj:
|
|
||||||
ret.append(object_to_dict(item))
|
|
||||||
elif hasattr(obj, "__dict__"):
|
|
||||||
ret = {}
|
|
||||||
for item in obj.__dict__:
|
|
||||||
if item.startswith("_"):
|
|
||||||
continue
|
|
||||||
ret[item] = object_to_dict(obj.__dict__[item])
|
|
||||||
else:
|
|
||||||
ret = obj
|
|
||||||
return ret
|
|
|
@ -1,66 +0,0 @@
|
||||||
"""
|
|
||||||
:codeauthor: Nicole Thomas <nicole@saltstack.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from salt.utils.versions import Version
|
|
||||||
from tests.integration.cloud.helpers.cloud_test_base import CloudTest
|
|
||||||
|
|
||||||
try:
|
|
||||||
import azure # pylint: disable=unused-import
|
|
||||||
|
|
||||||
HAS_AZURE = True
|
|
||||||
except ImportError:
|
|
||||||
HAS_AZURE = False
|
|
||||||
|
|
||||||
if HAS_AZURE and not hasattr(azure, "__version__"):
|
|
||||||
import azure.common
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
TIMEOUT = 1000
|
|
||||||
REQUIRED_AZURE = "1.1.0"
|
|
||||||
|
|
||||||
|
|
||||||
def __has_required_azure():
|
|
||||||
"""
|
|
||||||
Returns True/False if the required version of the Azure SDK is installed.
|
|
||||||
"""
|
|
||||||
if HAS_AZURE:
|
|
||||||
if hasattr(azure, "__version__"):
|
|
||||||
version = Version(azure.__version__)
|
|
||||||
else:
|
|
||||||
version = Version(azure.common.__version__)
|
|
||||||
if Version(REQUIRED_AZURE) <= version:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
not HAS_AZURE, reason="These tests require the Azure Python SDK to be installed."
|
|
||||||
)
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
not __has_required_azure(),
|
|
||||||
reason="The Azure Python SDK must be >= {}.".format(REQUIRED_AZURE),
|
|
||||||
)
|
|
||||||
class AzureTest(CloudTest):
|
|
||||||
"""
|
|
||||||
Integration tests for the Azure cloud provider in Salt-Cloud
|
|
||||||
"""
|
|
||||||
|
|
||||||
PROVIDER = "azurearm"
|
|
||||||
REQUIRED_PROVIDER_CONFIG_ITEMS = ("subscription_id",)
|
|
||||||
|
|
||||||
def test_instance(self):
|
|
||||||
"""
|
|
||||||
Test creating an instance on Azure
|
|
||||||
"""
|
|
||||||
# check if instance with salt installed returned
|
|
||||||
ret_val = self.run_cloud(
|
|
||||||
"-p azure-test {}".format(self.instance_name), timeout=TIMEOUT
|
|
||||||
)
|
|
||||||
self.assertInstanceExists(ret_val)
|
|
||||||
self.assertDestroyInstance(timeout=TIMEOUT)
|
|
|
@ -1,8 +0,0 @@
|
||||||
azure-test:
|
|
||||||
provider: azurearm-config
|
|
||||||
image: 'b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04-LTS-amd64-server-20140724-en-us-30GB'
|
|
||||||
size: Standard_D1
|
|
||||||
slot: production
|
|
||||||
ssh_username: ''
|
|
||||||
ssh_password: ''
|
|
||||||
script_args: '-P'
|
|
|
@ -1,16 +0,0 @@
|
||||||
azurearm-config:
|
|
||||||
driver: azurearm
|
|
||||||
subscription_id: ''
|
|
||||||
cleanup_disks: True
|
|
||||||
cleanup_interfaces: True
|
|
||||||
cleanup_vhds: True
|
|
||||||
cleanup_services: True
|
|
||||||
minion:
|
|
||||||
master_type: str
|
|
||||||
username: ''
|
|
||||||
password: ''
|
|
||||||
location: ''
|
|
||||||
network_resource_group: ''
|
|
||||||
network: ''
|
|
||||||
subnet: ''
|
|
||||||
resource_group: ''
|
|
|
@ -1,161 +0,0 @@
|
||||||
import types
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from salt.cloud.clouds import azurearm as azure
|
|
||||||
from tests.support.mock import MagicMock, create_autospec, patch
|
|
||||||
|
|
||||||
|
|
||||||
def copy_func(func, globals=None):
|
|
||||||
# I do not know that this is complete, but it's sufficient for now.
|
|
||||||
# The key to "moving" the function to another module (or stubbed module)
|
|
||||||
# is to update __globals__.
|
|
||||||
|
|
||||||
copied_func = types.FunctionType(
|
|
||||||
func.__code__, globals, func.__name__, func.__defaults__, func.__closure__
|
|
||||||
)
|
|
||||||
copied_func.__module__ = func.__module__
|
|
||||||
copied_func.__doc__ = func.__doc__
|
|
||||||
copied_func.__kwdefaults__ = func.__kwdefaults__
|
|
||||||
copied_func.__dict__.update(func.__dict__)
|
|
||||||
return copied_func
|
|
||||||
|
|
||||||
|
|
||||||
def mock_module(mod, sut=None):
|
|
||||||
if sut is None:
|
|
||||||
sut = [None]
|
|
||||||
|
|
||||||
mock = create_autospec(mod)
|
|
||||||
|
|
||||||
# we need to provide a '__globals__' so functions being tested behave correctly.
|
|
||||||
mock_globals = {}
|
|
||||||
|
|
||||||
# exclude the system under test
|
|
||||||
for name in sut:
|
|
||||||
attr = getattr(mod, name)
|
|
||||||
if isinstance(attr, types.FunctionType):
|
|
||||||
attr = copy_func(attr, mock_globals)
|
|
||||||
setattr(mock, name, attr)
|
|
||||||
|
|
||||||
# fully populate our mock_globals
|
|
||||||
for name in mod.__dict__:
|
|
||||||
if name in mock.__dict__:
|
|
||||||
mock_globals[name] = mock.__dict__[name]
|
|
||||||
elif type(getattr(mod, name)) is type(types): # is a module
|
|
||||||
mock_globals[name] = getattr(mock, name)
|
|
||||||
else:
|
|
||||||
mock_globals[name] = mod.__dict__[name]
|
|
||||||
|
|
||||||
return mock
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def configure_loader_modules():
|
|
||||||
return {azure: {"__opts__": {}, "__active_provider_name__": None}}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not azure.HAS_LIBS, reason="azure not available")
|
|
||||||
def test_function_signatures():
|
|
||||||
mock_azure = mock_module(azure, sut=["request_instance", "__opts__", "__utils__"])
|
|
||||||
mock_azure.create_network_interface.return_value = [
|
|
||||||
MagicMock(),
|
|
||||||
MagicMock(),
|
|
||||||
MagicMock(),
|
|
||||||
]
|
|
||||||
mock_azure.salt.utils.stringutils.to_str.return_value = "P4ssw0rd"
|
|
||||||
mock_azure.salt.utils.cloud.gen_keys.return_value = [MagicMock(), MagicMock()]
|
|
||||||
mock_azure.__opts__["pki_dir"] = None
|
|
||||||
|
|
||||||
mock_azure.request_instance.__globals__[
|
|
||||||
"__builtins__"
|
|
||||||
] = mock_azure.request_instance.__globals__["__builtins__"].copy()
|
|
||||||
mock_azure.request_instance.__globals__["__builtins__"]["getattr"] = MagicMock()
|
|
||||||
|
|
||||||
mock_azure.__utils__["cloud.fire_event"] = mock_azure.salt.utils.cloud.fire_event
|
|
||||||
mock_azure.__utils__[
|
|
||||||
"cloud.filter_event"
|
|
||||||
] = mock_azure.salt.utils.cloud.filter_event
|
|
||||||
mock_azure.__opts__["sock_dir"] = MagicMock()
|
|
||||||
mock_azure.__opts__["transport"] = MagicMock()
|
|
||||||
|
|
||||||
mock_azure.request_instance(
|
|
||||||
{"image": "http://img", "storage_account": "blah", "size": ""}
|
|
||||||
)
|
|
||||||
|
|
||||||
# we literally only check that a final creation call occurred.
|
|
||||||
mock_azure.get_conn.return_value.virtual_machines.create_or_update.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_configured_provider():
|
|
||||||
mock_azure = mock_module(
|
|
||||||
azure, sut=["get_configured_provider", "__opts__", "__utils__"]
|
|
||||||
)
|
|
||||||
|
|
||||||
good_combos = [
|
|
||||||
{
|
|
||||||
"subscription_id": "3287abc8-f98a-c678-3bde-326766fd3617",
|
|
||||||
"tenant": "ABCDEFAB-1234-ABCD-1234-ABCDEFABCDEF",
|
|
||||||
"client_id": "ABCDEFAB-1234-ABCD-1234-ABCDEFABCDEF",
|
|
||||||
"secret": "XXXXXXXXXXXXXXXXXXXXXXXX",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"subscription_id": "3287abc8-f98a-c678-3bde-326766fd3617",
|
|
||||||
"username": "larry",
|
|
||||||
"password": "123pass",
|
|
||||||
},
|
|
||||||
{"subscription_id": "3287abc8-f98a-c678-3bde-326766fd3617"},
|
|
||||||
]
|
|
||||||
|
|
||||||
for combo in good_combos:
|
|
||||||
mock_azure.__opts__["providers"] = {"azure_test": {"azurearm": combo}}
|
|
||||||
assert azure.get_configured_provider() == combo
|
|
||||||
|
|
||||||
bad_combos = [
|
|
||||||
{"subscrption": "3287abc8-f98a-c678-3bde-326766fd3617"},
|
|
||||||
{},
|
|
||||||
]
|
|
||||||
|
|
||||||
for combo in bad_combos:
|
|
||||||
mock_azure.__opts__["providers"] = {"azure_test": {"azurearm": combo}}
|
|
||||||
assert not azure.get_configured_provider()
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_conn():
|
|
||||||
mock_azure = mock_module(azure, sut=["get_conn", "__opts__", "__utils__"])
|
|
||||||
|
|
||||||
mock_azure.__opts__["providers"] = {
|
|
||||||
"azure_test": {
|
|
||||||
"azurearm": {
|
|
||||||
"subscription_id": "3287abc8-f98a-c678-3bde-326766fd3617",
|
|
||||||
"driver": "azurearm",
|
|
||||||
"password": "monkeydonkey",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# password is stripped if username not provided
|
|
||||||
expected = {"subscription_id": "3287abc8-f98a-c678-3bde-326766fd3617"}
|
|
||||||
with patch(
|
|
||||||
"salt.utils.azurearm.get_client", side_effect=lambda client_type, **kw: kw
|
|
||||||
):
|
|
||||||
assert azure.get_conn(client_type="compute") == expected
|
|
||||||
|
|
||||||
mock_azure.__opts__["providers"] = {
|
|
||||||
"azure_test": {
|
|
||||||
"azurearm": {
|
|
||||||
"subscription_id": "3287abc8-f98a-c678-3bde-326766fd3617",
|
|
||||||
"driver": "azurearm",
|
|
||||||
"username": "donkeymonkey",
|
|
||||||
"password": "monkeydonkey",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# username and password via provider config
|
|
||||||
expected = {
|
|
||||||
"subscription_id": "3287abc8-f98a-c678-3bde-326766fd3617",
|
|
||||||
"username": "donkeymonkey",
|
|
||||||
"password": "monkeydonkey",
|
|
||||||
}
|
|
||||||
with patch(
|
|
||||||
"salt.utils.azurearm.get_client", side_effect=lambda client_type, **kw: kw
|
|
||||||
):
|
|
||||||
assert azure.get_conn(client_type="compute") == expected
|
|
|
@ -1,96 +0,0 @@
|
||||||
"""
|
|
||||||
Unit test for salt.grains.metadata_azure
|
|
||||||
|
|
||||||
|
|
||||||
:codeauthor: :email" `Vishal Gupta <guvishal@vmware.com>
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import salt.grains.metadata_azure as metadata
|
|
||||||
import salt.utils.http as http
|
|
||||||
from tests.support.mock import create_autospec, patch
|
|
||||||
|
|
||||||
# from Exception import Exception, ValueError
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def configure_loader_modules():
|
|
||||||
return {metadata: {"__opts__": {"metadata_server_grains": "True"}}}
|
|
||||||
|
|
||||||
|
|
||||||
def test_metadata_azure_search():
|
|
||||||
def mock_http(url="", headers=False, header_list=None):
|
|
||||||
metadata_vals = {
|
|
||||||
"http://169.254.169.254/metadata/instance?api-version=2020-09-01": {
|
|
||||||
"body": '{"compute": {"test": "fulltest"}}',
|
|
||||||
"headers": {"Content-Type": "application/json; charset=utf-8"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadata_vals[url]
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"salt.utils.http.query",
|
|
||||||
create_autospec(http.query, autospec=True, side_effect=mock_http),
|
|
||||||
):
|
|
||||||
assert metadata.metadata() == {"compute": {"test": "fulltest"}}
|
|
||||||
|
|
||||||
|
|
||||||
def test_metadata_virtual():
|
|
||||||
print("running 1st")
|
|
||||||
with patch(
|
|
||||||
"salt.utils.http.query",
|
|
||||||
create_autospec(
|
|
||||||
http.query,
|
|
||||||
autospec=True,
|
|
||||||
return_value={
|
|
||||||
"error": "Bad request: . Required metadata header not specified"
|
|
||||||
},
|
|
||||||
),
|
|
||||||
):
|
|
||||||
assert metadata.__virtual__() is False
|
|
||||||
with patch(
|
|
||||||
"salt.utils.http.query",
|
|
||||||
create_autospec(
|
|
||||||
http.query,
|
|
||||||
autospec=True,
|
|
||||||
return_value={
|
|
||||||
"body": '{"compute": {"test": "fulltest"}}',
|
|
||||||
"headers": {"Content-Type": "application/json; charset=utf-8"},
|
|
||||||
"status": 200,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
):
|
|
||||||
assert metadata.__virtual__() is True
|
|
||||||
with patch(
|
|
||||||
"salt.utils.http.query",
|
|
||||||
create_autospec(
|
|
||||||
http.query,
|
|
||||||
autospec=True,
|
|
||||||
return_value={
|
|
||||||
"body": "test",
|
|
||||||
"headers": {"Content-Type": "application/json; charset=utf-8"},
|
|
||||||
"status": 404,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
):
|
|
||||||
assert metadata.__virtual__() is False
|
|
||||||
with patch(
|
|
||||||
"salt.utils.http.query",
|
|
||||||
create_autospec(
|
|
||||||
http.query,
|
|
||||||
autospec=True,
|
|
||||||
return_value={
|
|
||||||
"body": "test",
|
|
||||||
"headers": {"Content-Type": "application/json; charset=utf-8"},
|
|
||||||
"status": 400,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
):
|
|
||||||
assert metadata.__virtual__() is False
|
|
|
@ -1,182 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import salt.config
|
|
||||||
import salt.loader
|
|
||||||
import salt.modules.azurearm_dns as azurearm_dns
|
|
||||||
from tests.support.mock import MagicMock
|
|
||||||
from tests.support.sminion import create_sminion
|
|
||||||
|
|
||||||
HAS_LIBS = False
|
|
||||||
try:
|
|
||||||
import azure.mgmt.dns.models # pylint: disable=import-error
|
|
||||||
|
|
||||||
HAS_LIBS = True
|
|
||||||
except ImportError:
|
|
||||||
HAS_LIBS = False
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
pytestmark = [
|
|
||||||
pytest.mark.skipif(
|
|
||||||
HAS_LIBS is False, reason="The azure.mgmt.dns module must be installed."
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class AzureObjMock:
|
|
||||||
"""
|
|
||||||
mock azure object for as_dict calls
|
|
||||||
"""
|
|
||||||
|
|
||||||
args = None
|
|
||||||
kwargs = None
|
|
||||||
|
|
||||||
def __init__(self, args, kwargs, return_value=None):
|
|
||||||
self.args = args
|
|
||||||
self.kwargs = kwargs
|
|
||||||
self.__return_value = return_value
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
|
||||||
return MagicMock(return_value=self.__return_value)()
|
|
||||||
|
|
||||||
def as_dict(self, *args, **kwargs):
|
|
||||||
return self.args, self.kwargs
|
|
||||||
|
|
||||||
|
|
||||||
class AzureFuncMock:
|
|
||||||
"""
|
|
||||||
mock azure client function calls
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, return_value=None):
|
|
||||||
self.__return_value = return_value
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
|
||||||
return MagicMock(return_value=self.__return_value)()
|
|
||||||
|
|
||||||
def create_or_update(self, *args, **kwargs):
|
|
||||||
azure_obj = AzureObjMock(args, kwargs)
|
|
||||||
return azure_obj
|
|
||||||
|
|
||||||
|
|
||||||
class AzureSubMock:
|
|
||||||
"""
|
|
||||||
mock azure client sub-modules
|
|
||||||
"""
|
|
||||||
|
|
||||||
record_sets = AzureFuncMock()
|
|
||||||
zones = AzureFuncMock()
|
|
||||||
|
|
||||||
def __init__(self, return_value=None):
|
|
||||||
self.__return_value = return_value
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
|
||||||
return MagicMock(return_value=self.__return_value)()
|
|
||||||
|
|
||||||
|
|
||||||
class AzureClientMock:
|
|
||||||
"""
|
|
||||||
mock azure client
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, return_value=AzureSubMock):
|
|
||||||
self.__return_value = return_value
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
|
||||||
return MagicMock(return_value=self.__return_value)()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def credentials():
|
|
||||||
azurearm_dns.__virtual__()
|
|
||||||
return {
|
|
||||||
"client_id": "CLIENT_ID",
|
|
||||||
"secret": "SECRET",
|
|
||||||
"subscription_id": "SUBSCRIPTION_ID",
|
|
||||||
"tenant": "TENANT",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def configure_loader_modules():
|
|
||||||
"""
|
|
||||||
setup loader modules and override the azurearm.get_client utility
|
|
||||||
"""
|
|
||||||
minion_config = create_sminion().opts.copy()
|
|
||||||
utils = salt.loader.utils(minion_config)
|
|
||||||
funcs = salt.loader.minion_mods(
|
|
||||||
minion_config, utils=utils, whitelist=["azurearm_dns", "config"]
|
|
||||||
)
|
|
||||||
utils["azurearm.get_client"] = AzureClientMock()
|
|
||||||
return {
|
|
||||||
azurearm_dns: {"__utils__": utils, "__salt__": funcs},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_record_set_create_or_update(credentials):
|
|
||||||
"""
|
|
||||||
tests record set object creation
|
|
||||||
"""
|
|
||||||
expected = {
|
|
||||||
"if_match": None,
|
|
||||||
"if_none_match": None,
|
|
||||||
"parameters": {"arecords": [{"ipv4_address": "10.0.0.1"}], "ttl": 300},
|
|
||||||
"record_type": "A",
|
|
||||||
"relative_record_set_name": "myhost",
|
|
||||||
"resource_group_name": "testgroup",
|
|
||||||
"zone_name": "myzone",
|
|
||||||
}
|
|
||||||
|
|
||||||
record_set_args, record_set_kwargs = azurearm_dns.record_set_create_or_update(
|
|
||||||
"myhost",
|
|
||||||
"myzone",
|
|
||||||
"testgroup",
|
|
||||||
"A",
|
|
||||||
arecords=[{"ipv4_address": "10.0.0.1"}],
|
|
||||||
ttl=300,
|
|
||||||
**credentials
|
|
||||||
)
|
|
||||||
|
|
||||||
for key, val in record_set_kwargs.items():
|
|
||||||
if isinstance(val, azure.mgmt.dns.models.RecordSet):
|
|
||||||
record_set_kwargs[key] = val.as_dict()
|
|
||||||
|
|
||||||
assert record_set_kwargs == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_zone_create_or_update(credentials):
|
|
||||||
"""
|
|
||||||
tests zone object creation
|
|
||||||
"""
|
|
||||||
expected = {
|
|
||||||
"if_match": None,
|
|
||||||
"if_none_match": None,
|
|
||||||
"parameters": {"location": "global", "zone_type": "Public"},
|
|
||||||
"resource_group_name": "testgroup",
|
|
||||||
"zone_name": "myzone",
|
|
||||||
}
|
|
||||||
|
|
||||||
zone_args, zone_kwargs = azurearm_dns.zone_create_or_update(
|
|
||||||
"myzone", "testgroup", **credentials
|
|
||||||
)
|
|
||||||
|
|
||||||
for key, val in zone_kwargs.items():
|
|
||||||
if isinstance(val, azure.mgmt.dns.models.Zone):
|
|
||||||
zone_kwargs[key] = val.as_dict()
|
|
||||||
|
|
||||||
assert zone_kwargs == expected
|
|
|
@ -1,333 +0,0 @@
|
||||||
"""
|
|
||||||
Tests for the Azure Blob External Pillar.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pickle
|
|
||||||
import time
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import salt.config
|
|
||||||
import salt.loader
|
|
||||||
import salt.pillar.azureblob as azureblob
|
|
||||||
import salt.utils.files
|
|
||||||
from tests.support.mock import MagicMock, patch
|
|
||||||
|
|
||||||
HAS_LIBS = False
|
|
||||||
try:
|
|
||||||
# pylint: disable=no-name-in-module
|
|
||||||
from azure.storage.blob import BlobServiceClient
|
|
||||||
|
|
||||||
# pylint: enable=no-name-in-module
|
|
||||||
|
|
||||||
HAS_LIBS = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
pytestmark = [
|
|
||||||
pytest.mark.skipif(
|
|
||||||
HAS_LIBS is False,
|
|
||||||
reason="The azure.storage.blob module must be installed.",
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class MockBlob(dict):
|
|
||||||
"""
|
|
||||||
Creates a Mock Blob object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = ""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__(
|
|
||||||
{
|
|
||||||
"container": None,
|
|
||||||
"name": "test.sls",
|
|
||||||
"prefix": None,
|
|
||||||
"delimiter": "/",
|
|
||||||
"results_per_page": None,
|
|
||||||
"location_mode": None,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MockContainerClient:
|
|
||||||
"""
|
|
||||||
Creates a Mock ContainerClient.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def walk_blobs(self, *args, **kwargs):
|
|
||||||
yield MockBlob()
|
|
||||||
|
|
||||||
def get_blob_client(self, *args, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MockBlobServiceClient:
|
|
||||||
"""
|
|
||||||
Creates a Mock BlobServiceClient.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_container_client(self, *args, **kwargs):
|
|
||||||
container_client = MockContainerClient()
|
|
||||||
return container_client
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def cachedir(tmp_path):
|
|
||||||
dirname = tmp_path / "cachedir"
|
|
||||||
dirname.mkdir(parents=True, exist_ok=True)
|
|
||||||
return dirname
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def configure_loader_modules(cachedir, tmp_path):
|
|
||||||
base_pillar = tmp_path / "base"
|
|
||||||
prod_pillar = tmp_path / "prod"
|
|
||||||
base_pillar.mkdir(parents=True, exist_ok=True)
|
|
||||||
prod_pillar.mkdir(parents=True, exist_ok=True)
|
|
||||||
pillar_roots = {
|
|
||||||
"base": [str(base_pillar)],
|
|
||||||
"prod": [str(prod_pillar)],
|
|
||||||
}
|
|
||||||
opts = {
|
|
||||||
"cachedir": cachedir,
|
|
||||||
"pillar_roots": pillar_roots,
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
azureblob: {"__opts__": opts},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test__init_expired(tmp_path):
|
|
||||||
"""
|
|
||||||
Tests the result of _init when the cache is expired.
|
|
||||||
"""
|
|
||||||
container = "test"
|
|
||||||
multiple_env = False
|
|
||||||
environment = "base"
|
|
||||||
blob_cache_expire = 0 # The cache will be expired
|
|
||||||
blob_client = MockBlobServiceClient()
|
|
||||||
cache_file = tmp_path / "cache_file"
|
|
||||||
# Patches the _get_containers_cache_filename module so that it returns the name of the new tempfile that
|
|
||||||
# represents the cache file
|
|
||||||
with patch.object(
|
|
||||||
azureblob,
|
|
||||||
"_get_containers_cache_filename",
|
|
||||||
MagicMock(return_value=str(cache_file)),
|
|
||||||
):
|
|
||||||
# Patches the from_connection_string module of the BlobServiceClient class so that a connection string does
|
|
||||||
# not need to be given. Additionally it returns example blob data used by the ext_pillar.
|
|
||||||
with patch.object(
|
|
||||||
BlobServiceClient,
|
|
||||||
"from_connection_string",
|
|
||||||
MagicMock(return_value=blob_client),
|
|
||||||
):
|
|
||||||
ret = azureblob._init(
|
|
||||||
"", container, multiple_env, environment, blob_cache_expire
|
|
||||||
)
|
|
||||||
|
|
||||||
expected = {
|
|
||||||
"base": {
|
|
||||||
"test": [
|
|
||||||
{
|
|
||||||
"container": None,
|
|
||||||
"name": "test.sls",
|
|
||||||
"prefix": None,
|
|
||||||
"delimiter": "/",
|
|
||||||
"results_per_page": None,
|
|
||||||
"location_mode": None,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert ret == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test__init_not_expired(tmp_path):
|
|
||||||
"""
|
|
||||||
Tests the result of _init when the cache is not expired.
|
|
||||||
"""
|
|
||||||
container = "test"
|
|
||||||
multiple_env = False
|
|
||||||
environment = "base"
|
|
||||||
blob_cache_expire = (time.time()) * (time.time()) # The cache will not be expired
|
|
||||||
metadata = {
|
|
||||||
"base": {
|
|
||||||
"test": [
|
|
||||||
{"name": "base/secret.sls", "relevant": "include.sls"},
|
|
||||||
{"name": "blobtest.sls", "irrelevant": "ignore.sls"},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cache_file = tmp_path / "cache_file"
|
|
||||||
# Pickles the metadata and stores it in cache_file
|
|
||||||
with salt.utils.files.fopen(str(cache_file), "wb") as fp_:
|
|
||||||
pickle.dump(metadata, fp_)
|
|
||||||
# Patches the _get_containers_cache_filename module so that it returns the name of the new tempfile that
|
|
||||||
# represents the cache file
|
|
||||||
with patch.object(
|
|
||||||
azureblob,
|
|
||||||
"_get_containers_cache_filename",
|
|
||||||
MagicMock(return_value=str(cache_file)),
|
|
||||||
):
|
|
||||||
# Patches the _read_containers_cache_file module so that it returns what it normally would if the new
|
|
||||||
# tempfile representing the cache file was passed to it
|
|
||||||
plugged = azureblob._read_containers_cache_file(str(cache_file))
|
|
||||||
with patch.object(
|
|
||||||
azureblob,
|
|
||||||
"_read_containers_cache_file",
|
|
||||||
MagicMock(return_value=plugged),
|
|
||||||
):
|
|
||||||
ret = azureblob._init(
|
|
||||||
"", container, multiple_env, environment, blob_cache_expire
|
|
||||||
)
|
|
||||||
assert ret == metadata
|
|
||||||
|
|
||||||
|
|
||||||
def test__get_cache_dir(cachedir):
|
|
||||||
"""
|
|
||||||
Tests the result of _get_cache_dir.
|
|
||||||
"""
|
|
||||||
ret = azureblob._get_cache_dir()
|
|
||||||
assert ret == str(cachedir / "pillar_azureblob")
|
|
||||||
|
|
||||||
|
|
||||||
def test__get_cached_file_name(cachedir):
|
|
||||||
"""
|
|
||||||
Tests the result of _get_cached_file_name.
|
|
||||||
"""
|
|
||||||
container = "test"
|
|
||||||
saltenv = "base"
|
|
||||||
path = "base/secret.sls"
|
|
||||||
ret = azureblob._get_cached_file_name(container, saltenv, path)
|
|
||||||
assert ret == str(cachedir / "pillar_azureblob" / saltenv / container / path)
|
|
||||||
|
|
||||||
|
|
||||||
def test__get_containers_cache_filename(cachedir):
|
|
||||||
"""
|
|
||||||
Tests the result of _get_containers_cache_filename.
|
|
||||||
"""
|
|
||||||
container = "test"
|
|
||||||
ret = azureblob._get_containers_cache_filename(container)
|
|
||||||
assert ret == str(cachedir / "pillar_azureblob" / "test-files.cache")
|
|
||||||
|
|
||||||
|
|
||||||
def test__refresh_containers_cache_file(tmp_path):
|
|
||||||
"""
|
|
||||||
Tests the result of _refresh_containers_cache_file to ensure that it successfully copies blob data into a
|
|
||||||
cache file.
|
|
||||||
"""
|
|
||||||
blob_client = MockBlobServiceClient()
|
|
||||||
container = "test"
|
|
||||||
cache_file = tmp_path / "cache_file"
|
|
||||||
with patch.object(
|
|
||||||
BlobServiceClient,
|
|
||||||
"from_connection_string",
|
|
||||||
MagicMock(return_value=blob_client),
|
|
||||||
):
|
|
||||||
ret = azureblob._refresh_containers_cache_file("", container, str(cache_file))
|
|
||||||
expected = {
|
|
||||||
"base": {
|
|
||||||
"test": [
|
|
||||||
{
|
|
||||||
"container": None,
|
|
||||||
"name": "test.sls",
|
|
||||||
"prefix": None,
|
|
||||||
"delimiter": "/",
|
|
||||||
"results_per_page": None,
|
|
||||||
"location_mode": None,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert ret == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test__read_containers_cache_file(tmp_path):
|
|
||||||
"""
|
|
||||||
Tests the result of _read_containers_cache_file to make sure that it successfully loads in pickled metadata.
|
|
||||||
"""
|
|
||||||
metadata = {
|
|
||||||
"base": {
|
|
||||||
"test": [
|
|
||||||
{"name": "base/secret.sls", "relevant": "include.sls"},
|
|
||||||
{"name": "blobtest.sls", "irrelevant": "ignore.sls"},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cache_file = tmp_path / "cache_file"
|
|
||||||
# Pickles the metadata and stores it in cache_file
|
|
||||||
with salt.utils.files.fopen(str(cache_file), "wb") as fp_:
|
|
||||||
pickle.dump(metadata, fp_)
|
|
||||||
# Checks to see if _read_containers_cache_file can successfully read the pickled metadata from the cache file
|
|
||||||
ret = azureblob._read_containers_cache_file(str(cache_file))
|
|
||||||
assert ret == metadata
|
|
||||||
|
|
||||||
|
|
||||||
def test__find_files():
|
|
||||||
"""
|
|
||||||
Tests the result of _find_files. Ensures it only finds files and not directories. Ensures it also ignore
|
|
||||||
irrelevant files.
|
|
||||||
"""
|
|
||||||
metadata = {
|
|
||||||
"test": [
|
|
||||||
{"name": "base/secret.sls"},
|
|
||||||
{"name": "blobtest.sls", "irrelevant": "ignore.sls"},
|
|
||||||
{"name": "base/"},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
ret = azureblob._find_files(metadata)
|
|
||||||
assert ret == {"test": ["base/secret.sls", "blobtest.sls"]}
|
|
||||||
|
|
||||||
|
|
||||||
def test__find_file_meta1():
|
|
||||||
"""
|
|
||||||
Tests the result of _find_file_meta when the metadata contains a blob with the specified path and a blob
|
|
||||||
without the specified path.
|
|
||||||
"""
|
|
||||||
metadata = {
|
|
||||||
"base": {
|
|
||||||
"test": [
|
|
||||||
{"name": "base/secret.sls", "relevant": "include.sls"},
|
|
||||||
{"name": "blobtest.sls", "irrelevant": "ignore.sls"},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
container = "test"
|
|
||||||
saltenv = "base"
|
|
||||||
path = "base/secret.sls"
|
|
||||||
ret = azureblob._find_file_meta(metadata, container, saltenv, path)
|
|
||||||
assert ret == {"name": "base/secret.sls", "relevant": "include.sls"}
|
|
||||||
|
|
||||||
|
|
||||||
def test__find_file_meta2():
|
|
||||||
"""
|
|
||||||
Tests the result of _find_file_meta when the saltenv in metadata does not match the specified saltenv.
|
|
||||||
"""
|
|
||||||
metadata = {"wrong": {"test": [{"name": "base/secret.sls"}]}}
|
|
||||||
container = "test"
|
|
||||||
saltenv = "base"
|
|
||||||
path = "base/secret.sls"
|
|
||||||
ret = azureblob._find_file_meta(metadata, container, saltenv, path)
|
|
||||||
assert ret is None
|
|
||||||
|
|
||||||
|
|
||||||
def test__find_file_meta3():
|
|
||||||
"""
|
|
||||||
Tests the result of _find_file_meta when the container in metadata does not match the specified metadata.
|
|
||||||
"""
|
|
||||||
metadata = {"base": {"wrong": [{"name": "base/secret.sls"}]}}
|
|
||||||
container = "test"
|
|
||||||
saltenv = "base"
|
|
||||||
path = "base/secret.sls"
|
|
||||||
ret = azureblob._find_file_meta(metadata, container, saltenv, path)
|
|
||||||
assert ret is None
|
|
|
@ -1,55 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import salt.utils.azurearm as azurearm
|
|
||||||
from tests.support.unit import TestCase
|
|
||||||
|
|
||||||
# Azure libs
|
|
||||||
# pylint: disable=import-error
|
|
||||||
HAS_LIBS = False
|
|
||||||
try:
|
|
||||||
import azure.mgmt.compute.models # pylint: disable=unused-import
|
|
||||||
import azure.mgmt.network.models # pylint: disable=unused-import
|
|
||||||
|
|
||||||
HAS_LIBS = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# pylint: enable=import-error
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
MOCK_CREDENTIALS = {
|
|
||||||
"client_id": "CLIENT_ID",
|
|
||||||
"secret": "SECRET",
|
|
||||||
"subscription_id": "SUBSCRIPTION_ID",
|
|
||||||
"tenant": "TENANT",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
HAS_LIBS is False, reason="The azure.mgmt.network module must be installed."
|
|
||||||
)
|
|
||||||
class AzureRmUtilsTestCase(TestCase):
|
|
||||||
def test_create_object_model_vnet(self):
|
|
||||||
module_name = "network"
|
|
||||||
object_name = "VirtualNetwork"
|
|
||||||
vnet = {
|
|
||||||
"address_space": {"address_prefixes": ["10.0.0.0/8"]},
|
|
||||||
"enable_ddos_protection": False,
|
|
||||||
"enable_vm_protection": True,
|
|
||||||
"tags": {"contact_name": "Elmer Fudd Gantry"},
|
|
||||||
}
|
|
||||||
model = azurearm.create_object_model(module_name, object_name, **vnet)
|
|
||||||
self.assertEqual(vnet, model.as_dict())
|
|
||||||
|
|
||||||
def test_create_object_model_nic_ref(self):
|
|
||||||
module_name = "compute"
|
|
||||||
object_name = "NetworkInterfaceReference"
|
|
||||||
ref = {
|
|
||||||
"id": "/subscriptions/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic",
|
|
||||||
"primary": False,
|
|
||||||
}
|
|
||||||
model = azurearm.create_object_model(module_name, object_name, **ref)
|
|
||||||
self.assertEqual(ref, model.as_dict())
|
|
Loading…
Add table
Reference in a new issue