Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Matt Davis, <mdavis@ansible.com>
# Copyright: (c) 2016 Chris Houseknecht, <house@redhat.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class ModuleDocFragment(object):
# Azure doc fragment
DOCUMENTATION = r'''
options:
ad_user:
description:
- Active Directory username. Use when authenticating with an Active Directory user rather than service
principal.
type: str
password:
description:
- Active Directory user password. Use when authenticating with an Active Directory user rather than service
principal.
type: str
profile:
description:
- Security profile found in ~/.azure/credentials file.
type: str
subscription_id:
description:
- Your Azure subscription Id.
type: str
client_id:
description:
- Azure client ID. Use when authenticating with a Service Principal.
type: str
secret:
description:
- Azure client secret. Use when authenticating with a Service Principal.
type: str
tenant:
description:
- Azure tenant ID. Use when authenticating with a Service Principal.
type: str
cloud_environment:
description:
- For cloud environments other than the US public cloud, the environment name (as defined by Azure Python SDK, eg, C(AzureChinaCloud),
C(AzureUSGovernment)), or a metadata discovery endpoint URL (required for Azure Stack). Can also be set via credential file profile or
the C(AZURE_CLOUD_ENVIRONMENT) environment variable.
type: str
default: AzureCloud
version_added: '0.0.1'
adfs_authority_url:
description:
- Azure AD authority url. Use when authenticating with Username/password, and has your own ADFS authority.
type: str
version_added: '0.0.1'
cert_validation_mode:
description:
- Controls the certificate validation behavior for Azure endpoints. By default, all modules will validate the server certificate, but
when an HTTPS proxy is in use, or against Azure Stack, it may be necessary to disable this behavior by passing C(ignore). Can also be
set via credential file profile or the C(AZURE_CERT_VALIDATION) environment variable.
type: str
choices: [ ignore, validate ]
version_added: '0.0.1'
auth_source:
description:
- Controls the source of the credentials to use for authentication.
- Can also be set via the C(ANSIBLE_AZURE_AUTH_SOURCE) environment variable.
- When set to C(auto) (the default) the precedence is module parameters -> C(env) -> C(credential_file) -> C(cli).
- When set to C(env), the credentials will be read from the environment variables
- When set to C(credential_file), it will read the profile from C(~/.azure/credentials).
- When set to C(cli), the credentials will be sources from the Azure CLI profile. C(subscription_id) or the environment variable
C(AZURE_SUBSCRIPTION_ID) can be used to identify the subscription ID if more than one is present otherwise the default
az cli subscription is used.
- When set to C(msi), the host machine must be an azure resource with an enabled MSI extension. C(subscription_id) or the
environment variable C(AZURE_SUBSCRIPTION_ID) can be used to identify the subscription ID if the resource is granted
access to more than one subscription, otherwise the first subscription is chosen.
- The C(msi) was added in Ansible 2.6.
type: str
default: auto
choices:
- auto
- cli
- credential_file
- env
- msi
version_added: '0.0.1'
api_profile:
description:
- Selects an API profile to use when communicating with Azure services. Default value of C(latest) is appropriate for public clouds;
future values will allow use with Azure Stack.
type: str
default: latest
version_added: '0.0.1'
log_path:
description:
- Parent argument.
type: str
log_mode:
description:
- Parent argument.
type: str
requirements:
- python >= 2.7
- The host that executes this module must have the azure.azcollection collection installed via galaxy
- All python packages listed in collection's requirements-azure.txt must be installed via pip on the host that executes modules from azure.azcollection
- Full installation instructions may be found https://galaxy.ansible.com/azure/azcollection
notes:
- For authentication with Azure you can pass parameters, set environment variables, use a profile stored
in ~/.azure/credentials, or log in before you run your tasks or playbook with C(az login).
- Authentication is also possible using a service principal or Active Directory user.
- To authenticate via service principal, pass subscription_id, client_id, secret and tenant or set environment
variables AZURE_SUBSCRIPTION_ID, AZURE_CLIENT_ID, AZURE_SECRET and AZURE_TENANT.
- To authenticate via Active Directory user, pass ad_user and password, or set AZURE_AD_USER and
AZURE_PASSWORD in the environment.
- "Alternatively, credentials can be stored in ~/.azure/credentials. This is an ini file containing
a [default] section and the following keys: subscription_id, client_id, secret and tenant or
subscription_id, ad_user and password. It is also possible to add additional profiles. Specify the profile
by passing profile or setting AZURE_PROFILE in the environment."
seealso:
- name: Sign in with Azure CLI
link: https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli?view=azure-cli-latest
description: How to authenticate using the C(az login) command.
'''

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016, Matt Davis, <mdavis@ansible.com>
# Copyright: (c) 2016, Chris Houseknecht, <house@redhat.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class ModuleDocFragment(object):
# Azure doc fragment
DOCUMENTATION = r'''
options:
tags:
description:
- Dictionary of string:string pairs to assign as metadata to the object.
- Metadata tags on the object will be updated with any provided values.
- To remove tags set append_tags option to false.
- Currently, Azure DNS zones and Traffic Manager services also don't allow the use of spaces in the tag.
- Azure Front Door doesn't support the use of # in the tag name.
- Azure Automation and Azure CDN only support 15 tags on resources.
type: dict
append_tags:
description:
- Use to control if tags field is canonical or just appends to existing tags.
- When canonical, any tags not found in the tags parameter will be removed from the object's metadata.
type: bool
default: yes
'''

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, NetApp Ansible Team ng-ansibleteam@netapp.com
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = r'''
options:
- See respective platform section for more details
requirements:
- See respective platform section for more details
notes:
- Ansible modules are available for the following NetApp Storage Platforms: E-Series, ONTAP, SolidFire
'''
# Documentation fragment for Cloud Volume Services on Azure NetApp (azure_rm_netapp)
AZURE_RM_NETAPP = r'''
options:
resource_group:
description:
- Name of the resource group.
required: true
type: str
requirements:
- python >= 2.7
- azure >= 2.0.0
- Python azure-mgmt. Install using 'pip install azure-mgmt'
- Python azure-mgmt-netapp. Install using 'pip install azure-mgmt-netapp'
- For authentication with Azure NetApp log in before you run your tasks or playbook with C(az login).
notes:
- The modules prefixed with azure_rm_netapp are built to support the Cloud Volume Services for Azure NetApp Files.
seealso:
- name: Sign in with Azure CLI
link: https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli?view=azure-cli-latest
description: How to authenticate using the C(az login) command.
'''

View File

@@ -0,0 +1,156 @@
# (c) 2019, NetApp, Inc
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
'''
azure_rm_netapp_common
Wrapper around AzureRMModuleBase base class
'''
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import sys
HAS_AZURE_COLLECTION = True
NEW_STYLE = None
COLLECTION_VERSION = "21.10.0"
IMPORT_ERRORS = []
SDK_VERSION = "0.0.0"
if 'pytest' in sys.modules:
from ansible_collections.netapp.azure.plugins.module_utils.netapp_module import AzureRMModuleBaseMock as AzureRMModuleBase
else:
try:
from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import AzureRMModuleBase
except ImportError as exc:
IMPORT_ERRORS.append(str(exc))
HAS_AZURE_COLLECTION = False
except SyntaxError as exc:
# importing Azure collection fails with python 2.6
if sys.version_info < (2, 8):
IMPORT_ERRORS.append(str(exc))
from ansible_collections.netapp.azure.plugins.module_utils.netapp_module import AzureRMModuleBaseMock as AzureRMModuleBase
HAS_AZURE_COLLECTION = False
else:
raise
try:
from azure.mgmt.netapp import NetAppManagementClient # 1.0.0 or newer
NEW_STYLE = True
except ImportError as exc:
IMPORT_ERRORS.append(str(exc))
try:
from azure.mgmt.netapp import AzureNetAppFilesManagementClient # 0.10.0 or older
NEW_STYLE = False
except ImportError as exc:
IMPORT_ERRORS.append(str(exc))
try:
from azure.mgmt.netapp import VERSION as SDK_VERSION
except ImportError as exc:
IMPORT_ERRORS.append(str(exc))
class AzureRMNetAppModuleBase(AzureRMModuleBase):
''' Wrapper around AzureRMModuleBase base class '''
def __init__(self, derived_arg_spec, required_if=None, supports_check_mode=False, supports_tags=True):
self._netapp_client = None
self._new_style = NEW_STYLE
self._sdk_version = SDK_VERSION
super(AzureRMNetAppModuleBase, self).__init__(derived_arg_spec=derived_arg_spec,
required_if=required_if,
supports_check_mode=supports_check_mode,
supports_tags=supports_tags)
if not HAS_AZURE_COLLECTION:
self.fail_when_import_errors(IMPORT_ERRORS)
def get_mgmt_svc_client(self, client_type, base_url=None, api_version=None):
if not self._new_style:
return super(AzureRMNetAppModuleBase, self).get_mgmt_svc_client(client_type, base_url, api_version)
self.log('Getting management service client NetApp {0}'.format(client_type.__name__))
self.check_client_version(client_type)
if not base_url:
# most things are resource_manager, don't make everyone specify
base_url = self.azure_auth._cloud_environment.endpoints.resource_manager
client_kwargs = dict(credential=self.azure_auth.azure_credentials, subscription_id=self.azure_auth.subscription_id, base_url=base_url)
return client_type(**client_kwargs)
@property
def netapp_client(self):
self.log('Getting netapp client')
if self._new_style is None:
# note that we always have at least one import error
self.fail_when_import_errors(IMPORT_ERRORS)
if self._netapp_client is None:
if self._new_style:
self._netapp_client = self.get_mgmt_svc_client(NetAppManagementClient)
else:
self._netapp_client = self.get_mgmt_svc_client(AzureNetAppFilesManagementClient,
base_url=self._cloud_environment.endpoints.resource_manager,
api_version='2018-05-01')
return self._netapp_client
@property
def new_style(self):
return self._new_style
@property
def sdk_version(self):
return self._sdk_version
def get_method(self, category, name):
try:
methods = getattr(self.netapp_client, category)
except AttributeError as exc:
self.module.fail_json('Error: category %s not found for netapp_client: %s' % (category, str(exc)))
if self._new_style:
name = 'begin_' + name
try:
method = getattr(methods, name)
except AttributeError as exc:
self.module.fail_json('Error: method %s not found for netapp_client category: %s - %s' % (name, category, str(exc)))
return method
def fail_when_import_errors(self, import_errors, has_azure_mgmt_netapp=True):
if has_azure_mgmt_netapp and not import_errors:
return
msg = ''
if not has_azure_mgmt_netapp:
msg = "The python azure-mgmt-netapp package is required. "
if hasattr(self, 'module'):
msg += 'Import errors: %s' % str(import_errors)
self.module.fail_json(msg=msg)
msg += str(import_errors)
raise ImportError(msg)
def has_feature(self, feature_name):
feature = self.get_feature(feature_name)
if isinstance(feature, bool):
return feature
self.module.fail_json(msg="Error: expected bool type for feature flag: %s" % feature_name)
def get_feature(self, feature_name):
''' if the user has configured the feature, use it
otherwise, use our default
'''
default_flags = dict(
# TODO: review need for these
# trace_apis=False, # if true, append REST requests/responses to /tmp/azure_apis.log
# check_required_params_for_none=True,
# deprecation_warning=True,
# show_modified=True,
#
# preview features in ANF
ignore_change_ownership_mode=True
)
if self.parameters.get('feature_flags') is not None and feature_name in self.parameters['feature_flags']:
return self.parameters['feature_flags'][feature_name]
if feature_name in default_flags:
return default_flags[feature_name]
self.module.fail_json(msg="Internal error: unexpected feature flag: %s" % feature_name)

View File

@@ -0,0 +1,271 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# Copyright (c) 2018, Laurent Nicolas <laurentn@netapp.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''' Support class for NetApp ansible modules '''
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.module_utils import basic
class AzureRMModuleBaseMock():
''' Mock for sanity tests when azcollection is not installed '''
def __init__(self, derived_arg_spec, required_if=None, supports_check_mode=False, supports_tags=True, **kwargs):
if supports_tags:
derived_arg_spec.update(dict(tags=dict()))
self.module = basic.AnsibleModule(
argument_spec=derived_arg_spec,
required_if=required_if,
supports_check_mode=supports_check_mode
)
self.module.warn('Running in Unit Test context!')
# the following is done in exec_module()
self.parameters = dict([item for item in self.module.params.items() if item[1] is not None])
# remove values with a default of None (not required)
self.module_arg_spec = dict([item for item in self.module_arg_spec.items() if item[0] in self.parameters])
def update_tags(self, tags):
self.module.log('update_tags called with:', tags)
return None, None
def cmp(obj1, obj2):
"""
Python 3 does not have a cmp function, this will do the cmp.
:param a: first object to check
:param b: second object to check
:return:
"""
# convert to lower case for string comparison.
if obj1 is None:
return -1
if isinstance(obj1, str) and isinstance(obj2, str):
obj1 = obj1.lower()
obj2 = obj2.lower()
# if list has string element, convert string to lower case.
if isinstance(obj1, list) and isinstance(obj2, list):
obj1 = [x.lower() if isinstance(x, str) else x for x in obj1]
obj2 = [x.lower() if isinstance(x, str) else x for x in obj2]
obj1.sort()
obj2.sort()
if isinstance(obj1, dict) and isinstance(obj2, dict):
return 0 if obj1 == obj2 else 1
return (obj1 > obj2) - (obj1 < obj2)
class NetAppModule():
'''
Common class for NetApp modules
set of support functions to derive actions based
on the current state of the system, and a desired state
'''
def __init__(self):
self.log = []
self.changed = False
self.parameters = {'name': 'not intialized'}
self.zapi_string_keys = dict()
self.zapi_bool_keys = dict()
self.zapi_list_keys = {}
self.zapi_int_keys = {}
self.zapi_required = {}
def set_parameters(self, ansible_params):
self.parameters = {}
for param in ansible_params:
if ansible_params[param] is not None:
self.parameters[param] = ansible_params[param]
return self.parameters
def get_cd_action(self, current, desired):
''' takes a desired state and a current state, and return an action:
create, delete, None
eg:
is_present = 'absent'
some_object = self.get_object(source)
if some_object is not None:
is_present = 'present'
action = cd_action(current=is_present, desired = self.desired.state())
'''
desired_state = desired['state'] if 'state' in desired else 'present'
if current is None and desired_state == 'absent':
return None
if current is not None and desired_state == 'present':
return None
# change in state
self.changed = True
if current is not None:
return 'delete'
return 'create'
def compare_and_update_values(self, current, desired, keys_to_compare):
updated_values = {}
is_changed = False
for key in keys_to_compare:
if key in current:
if key in desired and desired[key] is not None:
if current[key] != desired[key]:
updated_values[key] = desired[key]
is_changed = True
else:
updated_values[key] = current[key]
else:
updated_values[key] = current[key]
return updated_values, is_changed
@staticmethod
def check_keys(current, desired):
''' TODO: raise an error if keys do not match
with the exception of:
new_name, state in desired
'''
@staticmethod
def compare_lists(current, desired, get_list_diff):
''' compares two lists and return a list of elements that are either the desired elements or elements that are
modified from the current state depending on the get_list_diff flag
:param: current: current item attribute in ONTAP
:param: desired: attributes from playbook
:param: get_list_diff: specifies whether to have a diff of desired list w.r.t current list for an attribute
:return: list of attributes to be modified
:rtype: list
'''
desired_diff_list = [item for item in desired if item not in current] # get what in desired and not in current
current_diff_list = [item for item in current if item not in desired] # get what in current but not in desired
if desired_diff_list or current_diff_list:
# there are changes
if get_list_diff:
return desired_diff_list
else:
return desired
else:
return []
def get_modified_attributes(self, current, desired, get_list_diff=False):
''' takes two dicts of attributes and return a dict of attributes that are
not in the current state
It is expected that all attributes of interest are listed in current and
desired.
:param: current: current attributes in ONTAP
:param: desired: attributes from playbook
:param: get_list_diff: specifies whether to have a diff of desired list w.r.t current list for an attribute
:return: dict of attributes to be modified
:rtype: dict
NOTE: depending on the attribute, the caller may need to do a modify or a
different operation (eg move volume if the modified attribute is an
aggregate name)
'''
# if the object does not exist, we can't modify it
modified = {}
if current is None:
return modified
# error out if keys do not match
self.check_keys(current, desired)
# collect changed attributes
for key, value in current.items():
if key in desired and desired[key] is not None:
if isinstance(value, list):
modified_list = self.compare_lists(value, desired[key], get_list_diff) # get modified list from current and desired
if modified_list:
modified[key] = modified_list
elif cmp(value, desired[key]) != 0:
modified[key] = desired[key]
if modified:
self.changed = True
return modified
def is_rename_action(self, source, target):
''' takes a source and target object, and returns True
if a rename is required
eg:
source = self.get_object(source_name)
target = self.get_object(target_name)
action = is_rename_action(source, target)
:return: None for error, True for rename action, False otherwise
'''
if source is None and target is None:
# error, do nothing
# cannot rename an non existent resource
# alternatively we could create B
return None
if source is not None and target is not None:
# error, do nothing
# idempotency (or) new_name_is_already_in_use
# alternatively we could delete B and rename A to B
return False
if source is None:
# do nothing, maybe the rename was already done
return False
# source is not None and target is None:
# rename is in order
self.changed = True
return True
def filter_out_none_entries(self, list_or_dict):
"""take a dict or list as input and return a dict/list without keys/elements whose values are None
skip empty dicts or lists.
"""
if isinstance(list_or_dict, dict):
result = {}
for key, value in list_or_dict.items():
if isinstance(value, (list, dict)):
sub = self.filter_out_none_entries(value)
if sub:
# skip empty dict or list
result[key] = sub
elif value is not None:
# skip None value
result[key] = value
return result
if isinstance(list_or_dict, list):
alist = []
for item in list_or_dict:
if isinstance(item, (list, dict)):
sub = self.filter_out_none_entries(item)
if sub:
# skip empty dict or list
alist.append(sub)
elif item is not None:
# skip None value
alist.append(item)
return alist
raise TypeError('unexpected type %s' % type(list_or_dict))
@staticmethod
def get_not_none_values_from_dict(parameters, keys):
# python 2.6 does not support dict comprehension using k: v
return dict((key, value) for key, value in parameters.items() if key in keys and value is not None)

View File

@@ -0,0 +1,404 @@
#!/usr/bin/python
#
# (c) 2019, NetApp, Inc
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
'''
azure_rm_netapp_account
'''
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: azure_rm_netapp_account
short_description: Manage NetApp Azure Files Account
version_added: 19.10.0
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create and delete NetApp Azure account.
Provide the Resource group name for the NetApp account to be created.
extends_documentation_fragment:
- netapp.azure.azure
- netapp.azure.azure_tags
- netapp.azure.netapp.azure_rm_netapp
options:
name:
description:
- The name of the NetApp account.
required: true
type: str
location:
description:
- Resource location.
- Required for create.
type: str
active_directories:
description:
- list of active directory dictionaries.
- The list is currently limited to a single active directory (ANF or Azure limit of one AD per subscription).
type: list
elements: dict
version_added: 21.2.0
suboptions:
active_directory_id:
description: not used for create. Not needed for join.
type: str
dns:
description: list of DNS addresses. Required for create or join.
type: list
elements: str
domain:
description: Fully Qualified Active Directory DNS Domain Name. Required for create or join.
type: str
site:
description: The Active Directory site the service will limit Domain Controller discovery to.
type: str
smb_server_name:
description: Prefix for creating the SMB server's computer account name in the Active Directory domain. Required for create or join.
type: str
organizational_unit:
description: LDAP Path for the Organization Unit where SMB Server machine accounts will be created (i.e. OU=SecondLevel,OU=FirstLevel).
type: str
username:
description: Credentials that have permissions to create SMB server machine account in the AD domain. Required for create or join.
type: str
password:
description: see username. If password is present, the module is not idempotent, as we cannot check the current value. Required for create or join.
type: str
aes_encryption:
description: If enabled, AES encryption will be enabled for SMB communication.
type: bool
ldap_signing:
description: Specifies whether or not the LDAP traffic needs to be signed.
type: bool
ad_name:
description: Name of the active directory machine. Used only while creating kerberos volume.
type: str
version_added: 21.3.0
kdc_ip:
description: kdc server IP addresses for the active directory machine. Used only while creating kerberos volume.
type: str
version_added: 21.3.0
server_root_ca_certificate:
description:
- When LDAP over SSL/TLS is enabled, the LDAP client is required to have base64 encoded Active Directory Certificate Service's
self-signed root CA certificate, this optional parameter is used only for dual protocol with LDAP user-mapping volumes.
type: str
version_added: 21.3.0
state:
description:
- State C(present) will check that the NetApp account exists with the requested configuration.
- State C(absent) will delete the NetApp account.
default: present
choices:
- absent
- present
type: str
debug:
description: output details about current account if it exists.
type: bool
default: false
'''
EXAMPLES = '''
- name: Create NetApp Azure Account
netapp.azure.azure_rm_netapp_account:
resource_group: myResourceGroup
name: testaccount
location: eastus
tags: {'abc': 'xyz', 'cba': 'zyx'}
- name: Modify Azure NetApp account (Join AD)
netapp.azure.azure_rm_netapp_account:
resource_group: myResourceGroup
name: testaccount
location: eastus
active_directories:
- site: ln
dns: 10.10.10.10
domain: domain.com
smb_server_name: dummy
password: xxxxxx
username: laurentn
- name: Delete NetApp Azure Account
netapp.azure.azure_rm_netapp_account:
state: absent
resource_group: myResourceGroup
name: testaccount
location: eastus
- name: Create Azure NetApp account (with AD)
netapp.azure.azure_rm_netapp_account:
resource_group: laurentngroupnodash
name: tests-netapp11
location: eastus
tags:
creator: laurentn
use: Ansible
active_directories:
- site: ln
dns: 10.10.10.10
domain: domain.com
smb_server_name: dummy
password: xxxxxx
username: laurentn
'''
RETURN = '''
'''
import traceback
HAS_AZURE_MGMT_NETAPP = False
IMPORT_ERRORS = list()
try:
from msrestazure.azure_exceptions import CloudError
from azure.core.exceptions import AzureError, ResourceNotFoundError
except ImportError as exc:
IMPORT_ERRORS.append(str(exc))
try:
from azure.mgmt.netapp.models import NetAppAccount, NetAppAccountPatch, ActiveDirectory
HAS_AZURE_MGMT_NETAPP = True
except ImportError as exc:
IMPORT_ERRORS.append(str(exc))
from ansible.module_utils.basic import to_native
from ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common import AzureRMNetAppModuleBase
from ansible_collections.netapp.azure.plugins.module_utils.netapp_module import NetAppModule
class AzureRMNetAppAccount(AzureRMNetAppModuleBase):
''' create, modify, delete account, including joining AD domain
'''
def __init__(self):
self.module_arg_spec = dict(
resource_group=dict(type='str', required=True),
name=dict(type='str', required=True),
location=dict(type='str', required=False),
state=dict(choices=['present', 'absent'], default='present', type='str'),
active_directories=dict(type='list', elements='dict', options=dict(
active_directory_id=dict(type='str'),
dns=dict(type='list', elements='str'),
domain=dict(type='str'),
site=dict(type='str'),
smb_server_name=dict(type='str'),
organizational_unit=dict(type='str'),
username=dict(type='str'),
password=dict(type='str', no_log=True),
aes_encryption=dict(type='bool'),
ldap_signing=dict(type='bool'),
ad_name=dict(type='str'),
kdc_ip=dict(type='str'),
server_root_ca_certificate=dict(type='str', no_log=True),
)),
debug=dict(type='bool', default=False)
)
self.na_helper = NetAppModule()
self.parameters = dict()
self.debug = list()
self.warnings = list()
# import errors are handled in AzureRMModuleBase
super(AzureRMNetAppAccount, self).__init__(derived_arg_spec=self.module_arg_spec,
required_if=[('state', 'present', ['location'])],
supports_check_mode=True)
def get_azure_netapp_account(self):
"""
Returns NetApp Account object for an existing account
Return None if account does not exist
"""
try:
account_get = self.netapp_client.accounts.get(self.parameters['resource_group'], self.parameters['name'])
except (CloudError, ResourceNotFoundError): # account does not exist
return None
account = vars(account_get)
ads = None
if account.get('active_directories') is not None:
ads = list()
for each_ad in account.get('active_directories'):
ad_dict = vars(each_ad)
dns = ad_dict.get('dns')
if dns is not None:
ad_dict['dns'] = sorted(dns.split(','))
ads.append(ad_dict)
account['active_directories'] = ads
return account
def create_account_request_body(self, modify=None):
"""
Create an Azure NetApp Account Request Body
:return: None
"""
options = dict()
location = None
for attr in ('location', 'tags', 'active_directories'):
value = self.parameters.get(attr)
if attr == 'location' and modify is None:
location = value
continue
if value is not None:
if modify is None or attr in modify:
if attr == 'active_directories':
ads = list()
for ad_dict in value:
if ad_dict.get('dns') is not None:
# API expects a string of comma separated elements
ad_dict['dns'] = ','.join(ad_dict['dns'])
ads.append(ActiveDirectory(**self.na_helper.filter_out_none_entries(ad_dict)))
value = ads
options[attr] = value
if modify is None:
if location is None:
self.module.fail_json(msg="Error: 'location' is a required parameter")
return NetAppAccount(location=location, **options)
return NetAppAccountPatch(**options)
def create_azure_netapp_account(self):
"""
Create an Azure NetApp Account
:return: None
"""
account_body = self.create_account_request_body()
try:
response = self.get_method('accounts', 'create_or_update')(body=account_body,
resource_group_name=self.parameters['resource_group'],
account_name=self.parameters['name'])
while response.done() is not True:
response.result(10)
except (CloudError, AzureError) as error:
self.module.fail_json(msg='Error creating Azure NetApp account %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def update_azure_netapp_account(self, modify):
"""
Create an Azure NetApp Account
:return: None
"""
account_body = self.create_account_request_body(modify)
try:
response = self.get_method('accounts', 'update')(body=account_body,
resource_group_name=self.parameters['resource_group'],
account_name=self.parameters['name'])
while response.done() is not True:
response.result(10)
except (CloudError, AzureError) as error:
self.module.fail_json(msg='Error creating Azure NetApp account %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_azure_netapp_account(self):
"""
Delete an Azure NetApp Account
:return: None
"""
try:
response = self.get_method('accounts', 'delete')(resource_group_name=self.parameters['resource_group'],
account_name=self.parameters['name'])
while response.done() is not True:
response.result(10)
except (CloudError, AzureError) as error:
self.module.fail_json(msg='Error deleting Azure NetApp account %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def get_changes_in_ads(self, current, desired):
c_ads = current.get('active_directories')
d_ads = desired.get('active_directories')
if not c_ads:
return desired.get('active_directories'), None
if not d_ads:
return None, current.get('active_directories')
if len(c_ads) > 1 or len(d_ads) > 1:
msg = 'Error checking for AD, currently only one AD is supported.'
if len(c_ads) > 1:
msg += ' Current: %s.' % str(c_ads)
if len(d_ads) > 1:
msg += ' Desired: %s.' % str(d_ads)
self.module.fail_json(msg='Error checking for AD, currently only one AD is supported')
changed = False
d_ad = d_ads[0]
c_ad = c_ads[0]
for key, value in c_ad.items():
if key == 'password':
if d_ad.get(key) is None:
continue
self.warnings.append("module is not idempotent if 'password:' is present")
if d_ad.get(key) is None:
d_ad[key] = value
elif d_ad.get(key) != value:
changed = True
self.debug.append("key: %s, value %s" % (key, value))
if changed:
return [d_ad], None
return None, None
def exec_module(self, **kwargs):
# unlikely
self.fail_when_import_errors(IMPORT_ERRORS, HAS_AZURE_MGMT_NETAPP)
# set up parameters according to our initial list
for key in list(self.module_arg_spec):
self.parameters[key] = kwargs[key]
# and common parameter
for key in ['tags']:
if key in kwargs:
self.parameters[key] = kwargs[key]
current = self.get_azure_netapp_account()
modify = None
cd_action = self.na_helper.get_cd_action(current, self.parameters)
self.debug.append('current: %s' % str(current))
if current is not None and cd_action is None:
ads_to_add, ads_to_delete = self.get_changes_in_ads(current, self.parameters)
self.parameters.pop('active_directories', None)
if ads_to_add:
self.parameters['active_directories'] = ads_to_add
if ads_to_delete:
self.module.fail_json(msg="Error: API does not support unjoining an AD", debug=self.debug)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if 'tags' in modify:
dummy, modify['tags'] = self.update_tags(current.get('tags'))
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_azure_netapp_account()
elif cd_action == 'delete':
self.delete_azure_netapp_account()
elif modify:
self.update_azure_netapp_account(modify)
results = dict(
changed=self.na_helper.changed,
modify=modify
)
if self.warnings:
results['warnings'] = self.warnings
if self.parameters['debug']:
results['debug'] = self.debug
self.module.exit_json(**results)
def main():
AzureRMNetAppAccount()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,259 @@
#!/usr/bin/python
#
# (c) 2019, NetApp, Inc
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
azure_rm_netapp_capacity_pool
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: azure_rm_netapp_capacity_pool
short_description: Manage NetApp Azure Files capacity pool
version_added: 19.10.0
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create and delete NetApp Azure capacity pool.
Provide the Resource group name for the capacity pool to be created.
- Resize NetApp Azure capacity pool
extends_documentation_fragment:
- netapp.azure.azure
- netapp.azure.azure_tags
- netapp.azure.netapp.azure_rm_netapp
options:
name:
description:
- The name of the capacity pool.
required: true
type: str
account_name:
description:
- The name of the NetApp account.
required: true
type: str
location:
description:
- Resource location.
- Required for create.
type: str
size:
description:
- Provisioned size of the pool (in chunks). Allowed values are in 4TiB chunks.
- Provide number to be multiplied to 4TiB.
- Required for create.
default: 1
type: int
service_level:
description:
- The service level of the file system.
- Required for create.
choices: ['Standard', 'Premium', 'Ultra']
type: str
version_added: "20.5.0"
state:
description:
- State C(present) will check that the capacity pool exists with the requested configuration.
- State C(absent) will delete the capacity pool.
default: present
choices: ['present', 'absent']
type: str
'''
EXAMPLES = '''
- name: Create Azure NetApp capacity pool
netapp.azure.azure_rm_netapp_capacity_pool:
resource_group: myResourceGroup
account_name: tests-netapp
name: tests-pool
location: eastus
size: 2
service_level: Standard
- name: Resize Azure NetApp capacity pool
netapp.azure.azure_rm_netapp_capacity_pool:
resource_group: myResourceGroup
account_name: tests-netapp
name: tests-pool
location: eastus
size: 3
service_level: Standard
- name: Delete Azure NetApp capacity pool
netapp.azure.azure_rm_netapp_capacity_pool:
state: absent
resource_group: myResourceGroup
account_name: tests-netapp
name: tests-pool
'''
RETURN = '''
'''
import traceback
AZURE_OBJECT_CLASS = 'NetAppAccount'
HAS_AZURE_MGMT_NETAPP = False
IMPORT_ERRORS = list()
SIZE_POOL = 4398046511104
try:
from msrestazure.azure_exceptions import CloudError
from azure.core.exceptions import AzureError, ResourceNotFoundError
except ImportError as exc:
IMPORT_ERRORS.append(str(exc))
try:
from azure.mgmt.netapp.models import CapacityPool
HAS_AZURE_MGMT_NETAPP = True
except ImportError as exc:
IMPORT_ERRORS.append(str(exc))
from ansible.module_utils.basic import to_native
from ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common import AzureRMNetAppModuleBase
from ansible_collections.netapp.azure.plugins.module_utils.netapp_module import NetAppModule
class AzureRMNetAppCapacityPool(AzureRMNetAppModuleBase):
""" create, modify, delete a capacity pool """
def __init__(self):
self.module_arg_spec = dict(
resource_group=dict(type='str', required=True),
name=dict(type='str', required=True),
account_name=dict(type='str', required=True),
location=dict(type='str', required=False),
state=dict(choices=['present', 'absent'], default='present', type='str'),
size=dict(type='int', required=False, default=1),
service_level=dict(type='str', required=False, choices=['Standard', 'Premium', 'Ultra']),
)
self.na_helper = NetAppModule()
self.parameters = dict()
# import errors are handled in AzureRMModuleBase
super(AzureRMNetAppCapacityPool, self).__init__(derived_arg_spec=self.module_arg_spec,
required_if=[('state', 'present', ['location', 'service_level'])],
supports_check_mode=True)
def get_azure_netapp_capacity_pool(self):
"""
Returns capacity pool object for an existing pool
Return None if capacity pool does not exist
"""
try:
capacity_pool_get = self.netapp_client.pools.get(self.parameters['resource_group'],
self.parameters['account_name'], self.parameters['name'])
except (CloudError, ResourceNotFoundError): # capacity pool does not exist
return None
return capacity_pool_get
def create_azure_netapp_capacity_pool(self):
"""
Create a capacity pool for the given Azure NetApp Account
:return: None
"""
options = self.na_helper.get_not_none_values_from_dict(self.parameters, ['location', 'service_level', 'size', 'tags'])
capacity_pool_body = CapacityPool(**options)
try:
response = self.get_method('pools', 'create_or_update')(body=capacity_pool_body, resource_group_name=self.parameters['resource_group'],
account_name=self.parameters['account_name'],
pool_name=self.parameters['name'])
while response.done() is not True:
response.result(10)
except (CloudError, AzureError) as error:
self.module.fail_json(msg='Error creating capacity pool %s for Azure NetApp account %s: %s'
% (self.parameters['name'], self.parameters['account_name'], to_native(error)),
exception=traceback.format_exc())
def modify_azure_netapp_capacity_pool(self, modify):
"""
Modify a capacity pool for the given Azure NetApp Account
:return: None
"""
options = self.na_helper.get_not_none_values_from_dict(self.parameters, ['location', 'service_level', 'size', 'tags'])
capacity_pool_body = CapacityPool(**options)
try:
response = self.get_method('pools', 'update')(body=capacity_pool_body, resource_group_name=self.parameters['resource_group'],
account_name=self.parameters['account_name'],
pool_name=self.parameters['name'])
while response.done() is not True:
response.result(10)
except (CloudError, AzureError) as error:
self.module.fail_json(msg='Error modifying capacity pool %s for Azure NetApp account %s: %s'
% (self.parameters['name'], self.parameters['account_name'], to_native(error)),
exception=traceback.format_exc())
def delete_azure_netapp_capacity_pool(self):
"""
Delete a capacity pool for the given Azure NetApp Account
:return: None
"""
try:
response = self.get_method('pools', 'delete')(resource_group_name=self.parameters['resource_group'],
account_name=self.parameters['account_name'], pool_name=self.parameters['name'])
while response.done() is not True:
response.result(10)
except (CloudError, AzureError) as error:
self.module.fail_json(msg='Error deleting capacity pool %s for Azure NetApp account %s: %s'
% (self.parameters['name'], self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def exec_module(self, **kwargs):
# unlikely
self.fail_when_import_errors(IMPORT_ERRORS, HAS_AZURE_MGMT_NETAPP)
# set up parameters according to our initial list
for key in list(self.module_arg_spec):
self.parameters[key] = kwargs[key]
# and common parameter
for key in ['tags']:
if key in kwargs:
self.parameters[key] = kwargs[key]
if 'size' in self.parameters:
self.parameters['size'] *= SIZE_POOL
modify = {}
current = self.get_azure_netapp_capacity_pool()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None and self.parameters['state'] == 'present':
current = vars(current)
# get_azure_netapp_capacity_pool() returns pool name with account name appended in front of it like 'account/pool'
current['name'] = self.parameters['name']
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if 'tags' in modify:
dummy, modify['tags'] = self.update_tags(current.get('tags'))
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_azure_netapp_capacity_pool()
elif cd_action == 'delete':
self.delete_azure_netapp_capacity_pool()
elif modify:
self.modify_azure_netapp_capacity_pool(modify)
self.module.exit_json(changed=self.na_helper.changed, modify=modify)
def main():
AzureRMNetAppCapacityPool()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,226 @@
#!/usr/bin/python
#
# (c) 2019, NetApp, Inc
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
azure_rm_netapp_snapshot
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: azure_rm_netapp_snapshot
short_description: Manage NetApp Azure Files Snapshot
version_added: 19.10.0
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create and delete NetApp Azure Snapshot.
extends_documentation_fragment:
- netapp.azure.azure
- netapp.azure.netapp.azure_rm_netapp
options:
name:
description:
- The name of the snapshot.
required: true
type: str
volume_name:
description:
- The name of the volume.
required: true
type: str
pool_name:
description:
- The name of the capacity pool.
required: true
type: str
account_name:
description:
- The name of the NetApp account.
required: true
type: str
location:
description:
- Resource location.
- Required for create.
type: str
state:
description:
- State C(present) will check that the snapshot exists with the requested configuration.
- State C(absent) will delete the snapshot.
default: present
choices:
- absent
- present
type: str
'''
EXAMPLES = '''
- name: Create Azure NetApp Snapshot
netapp.azure.azure_rm_netapp_snapshot:
resource_group: myResourceGroup
account_name: tests-netapp
pool_name: tests-pool
volume_name: tests-volume2
name: tests-snapshot
location: eastus
- name: Delete Azure NetApp Snapshot
netapp.azure.azure_rm_netapp_snapshot:
state: absent
resource_group: myResourceGroup
account_name: tests-netapp
pool_name: tests-pool
volume_name: tests-volume2
name: tests-snapshot
'''
RETURN = '''
'''
import traceback
AZURE_OBJECT_CLASS = 'NetAppAccount'
HAS_AZURE_MGMT_NETAPP = False
IMPORT_ERRORS = list()
try:
from msrestazure.azure_exceptions import CloudError
from azure.core.exceptions import AzureError, ResourceNotFoundError
except ImportError as exc:
IMPORT_ERRORS.append(str(exc))
try:
from azure.mgmt.netapp.models import Snapshot
HAS_AZURE_MGMT_NETAPP = True
except ImportError as exc:
IMPORT_ERRORS.append(str(exc))
from ansible.module_utils.basic import to_native
from ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common import AzureRMNetAppModuleBase
from ansible_collections.netapp.azure.plugins.module_utils.netapp_module import NetAppModule
class AzureRMNetAppSnapshot(AzureRMNetAppModuleBase):
""" crate or delete snapshots """
def __init__(self):
self.module_arg_spec = dict(
resource_group=dict(type='str', required=True),
name=dict(type='str', required=True),
volume_name=dict(type='str', required=True),
pool_name=dict(type='str', required=True),
account_name=dict(type='str', required=True),
location=dict(type='str', required=False),
state=dict(choices=['present', 'absent'], default='present', type='str')
)
self.na_helper = NetAppModule()
self.parameters = dict()
# import errors are handled in AzureRMModuleBase
super(AzureRMNetAppSnapshot, self).__init__(derived_arg_spec=self.module_arg_spec,
required_if=[('state', 'present', ['location'])],
supports_check_mode=True,
supports_tags=False)
def get_azure_netapp_snapshot(self):
"""
Returns snapshot object for an existing snapshot
Return None if snapshot does not exist
"""
try:
snapshot_get = self.netapp_client.snapshots.get(self.parameters['resource_group'], self.parameters['account_name'],
self.parameters['pool_name'], self.parameters['volume_name'],
self.parameters['name'])
except (CloudError, ResourceNotFoundError): # snapshot does not exist
return None
return snapshot_get
def create_azure_netapp_snapshot(self):
"""
Create a snapshot for the given Azure NetApp Account
:return: None
"""
kw_args = dict(
resource_group_name=self.parameters['resource_group'],
account_name=self.parameters['account_name'],
pool_name=self.parameters['pool_name'],
volume_name=self.parameters['volume_name'],
snapshot_name=self.parameters['name']
)
if self.new_style:
kw_args['body'] = Snapshot(
location=self.parameters['location']
)
else:
kw_args['location'] = self.parameters['location']
try:
result = self.get_method('snapshots', 'create')(**kw_args)
# waiting till the status turns Succeeded
while result.done() is not True:
result.result(10)
except (CloudError, AzureError) as error:
self.module.fail_json(msg='Error creating snapshot %s for Azure NetApp account %s: %s'
% (self.parameters['name'], self.parameters['account_name'], to_native(error)),
exception=traceback.format_exc())
def delete_azure_netapp_snapshot(self):
"""
Delete a snapshot for the given Azure NetApp Account
:return: None
"""
try:
result = self.get_method('snapshots', 'delete')(resource_group_name=self.parameters['resource_group'],
account_name=self.parameters['account_name'],
pool_name=self.parameters['pool_name'],
volume_name=self.parameters['volume_name'],
snapshot_name=self.parameters['name'])
# waiting till the status turns Succeeded
while result.done() is not True:
result.result(10)
except (CloudError, AzureError) as error:
self.module.fail_json(msg='Error deleting snapshot %s for Azure NetApp account %s: %s'
% (self.parameters['name'], self.parameters['account_name'], to_native(error)),
exception=traceback.format_exc())
def exec_module(self, **kwargs):
# unlikely
self.fail_when_import_errors(IMPORT_ERRORS, HAS_AZURE_MGMT_NETAPP)
# set up parameters according to our initial list
for key in list(self.module_arg_spec):
self.parameters[key] = kwargs[key]
current = self.get_azure_netapp_snapshot()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_azure_netapp_snapshot()
elif cd_action == 'delete':
self.delete_azure_netapp_snapshot()
self.module.exit_json(changed=self.na_helper.changed)
def main():
AzureRMNetAppSnapshot()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,399 @@
#!/usr/bin/python
#
# (c) 2019, NetApp, Inc
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
'''
azure_rm_netapp_volume
'''
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: azure_rm_netapp_volume
short_description: Manage NetApp Azure Files Volume
version_added: 19.10.0
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create and delete NetApp Azure volume.
extends_documentation_fragment:
- netapp.azure.azure
- netapp.azure.azure_tags
- netapp.azure.netapp.azure_rm_netapp
options:
name:
description:
- The name of the volume.
required: true
type: str
file_path:
description:
- A unique file path for the volume. Used when creating mount targets.
type: str
pool_name:
description:
- The name of the capacity pool.
required: true
type: str
account_name:
description:
- The name of the NetApp account.
required: true
type: str
location:
description:
- Resource location.
- Required for create.
type: str
subnet_name:
description:
- Azure resource name for a delegated subnet. Must have the delegation Microsoft.NetApp/volumes.
- Provide name of the subnet ID.
- Required for create.
type: str
aliases: ['subnet_id']
version_added: 21.1.0
virtual_network:
description:
- The name of the virtual network required for the subnet to create a volume.
- Required for create.
type: str
service_level:
description:
- The service level of the file system.
- default is Premium.
type: str
choices: ['Premium', 'Standard', 'Ultra']
vnet_resource_group_for_subnet:
description:
- Only required if virtual_network to be used is of different resource_group.
- Name of the resource group for virtual_network and subnet_name to be used.
type: str
version_added: "20.5.0"
size:
description:
- Provisioned size of the volume (in GiB).
- Minimum size is 100 GiB. Upper limit is 100TiB
- default is 100GiB.
version_added: "20.5.0"
type: int
protocol_types:
description:
- Protocol types - NFSv3, NFSv4.1, CIFS (for SMB).
type: list
elements: str
version_added: 21.2.0
state:
description:
- State C(present) will check that the volume exists with the requested configuration.
- State C(absent) will delete the volume.
default: present
choices: ['present', 'absent']
type: str
feature_flags:
description:
- Enable or disable a new feature.
- This can be used to enable an experimental feature or disable a new feature that breaks backward compatibility.
- Supported keys and values are subject to change without notice. Unknown keys are ignored.
type: dict
version_added: 21.9.0
notes:
- feature_flags is setting ignore_change_ownership_mode to true by default to bypass a 'change ownership mode' issue with azure-mgmt-netapp 4.0.0.
'''
EXAMPLES = '''
- name: Create Azure NetApp volume
netapp.azure.azure_rm_netapp_volume:
resource_group: myResourceGroup
account_name: tests-netapp
pool_name: tests-pool
name: tests-volume2
location: eastus
file_path: tests-volume2
virtual_network: myVirtualNetwork
vnet_resource_group_for_subnet: myVirtualNetworkResourceGroup
subnet_name: test
service_level: Ultra
size: 100
- name: Delete Azure NetApp volume
netapp.azure.azure_rm_netapp_volume:
state: absent
resource_group: myResourceGroup
account_name: tests-netapp
pool_name: tests-pool
name: tests-volume2
'''
RETURN = '''
mount_path:
description: Returns mount_path of the Volume
returned: always
type: str
'''
import traceback
AZURE_OBJECT_CLASS = 'NetAppAccount'
HAS_AZURE_MGMT_NETAPP = False
IMPORT_ERRORS = []
ONE_GIB = 1073741824
try:
from msrestazure.azure_exceptions import CloudError
from msrest.exceptions import ValidationError
from azure.core.exceptions import AzureError, ResourceNotFoundError
except ImportError as exc:
IMPORT_ERRORS.append(str(exc))
try:
from azure.mgmt.netapp.models import Volume, ExportPolicyRule, VolumePropertiesExportPolicy, VolumePatch
HAS_AZURE_MGMT_NETAPP = True
except ImportError as exc:
IMPORT_ERRORS.append(str(exc))
from ansible.module_utils.basic import to_native
from ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common import AzureRMNetAppModuleBase
from ansible_collections.netapp.azure.plugins.module_utils.netapp_module import NetAppModule
class AzureRMNetAppVolume(AzureRMNetAppModuleBase):
''' create or delete a volume '''
def __init__(self):
self.module_arg_spec = dict(
resource_group=dict(type='str', required=True),
name=dict(type='str', required=True),
file_path=dict(type='str', required=False),
pool_name=dict(type='str', required=True),
account_name=dict(type='str', required=True),
location=dict(type='str', required=False),
state=dict(choices=['present', 'absent'], default='present', type='str'),
subnet_name=dict(type='str', required=False, aliases=['subnet_id']),
virtual_network=dict(type='str', required=False),
size=dict(type='int', required=False),
vnet_resource_group_for_subnet=dict(type='str', required=False),
service_level=dict(type='str', required=False, choices=['Premium', 'Standard', 'Ultra']),
protocol_types=dict(type='list', elements='str'),
feature_flags=dict(type='dict')
)
self.na_helper = NetAppModule()
self.parameters = {}
# import errors are handled in AzureRMModuleBase
super(AzureRMNetAppVolume, self).__init__(derived_arg_spec=self.module_arg_spec,
required_if=[('state', 'present', ['location', 'file_path', 'subnet_name', 'virtual_network']),
],
supports_check_mode=True)
@staticmethod
def dict_from_volume_object(volume_object):
def replace_list_of_objects_with_list_of_dicts(adict, key):
if adict.get(key):
adict[key] = [vars(x) for x in adict[key]]
current_dict = vars(volume_object)
attr = 'subnet_id'
if attr in current_dict:
current_dict['subnet_name'] = current_dict.pop(attr).split('/')[-1]
attr = 'mount_targets'
replace_list_of_objects_with_list_of_dicts(current_dict, attr)
attr = 'export_policy'
if current_dict.get(attr):
attr_dict = vars(current_dict[attr])
replace_list_of_objects_with_list_of_dicts(attr_dict, 'rules')
current_dict[attr] = attr_dict
return current_dict
def get_azure_netapp_volume(self):
"""
Returns volume object for an existing volume
Return None if volume does not exist
"""
try:
volume_get = self.netapp_client.volumes.get(self.parameters['resource_group'], self.parameters['account_name'],
self.parameters['pool_name'], self.parameters['name'])
except (CloudError, ResourceNotFoundError): # volume does not exist
return None
return self.dict_from_volume_object(volume_get)
def get_export_policy_rules(self):
# ExportPolicyRule(rule_index: int=None, unix_read_only: bool=None, unix_read_write: bool=None,
# kerberos5_read_only: bool=False, kerberos5_read_write: bool=False, kerberos5i_read_only: bool=False,
# kerberos5i_read_write: bool=False, kerberos5p_read_only: bool=False, kerberos5p_read_write: bool=False,
# cifs: bool=None, nfsv3: bool=None, nfsv41: bool=None, allowed_clients: str=None, has_root_access: bool=True
ptypes = self.parameters.get('protocol_types')
if ptypes is None:
return None
ptypes = [x.lower() for x in ptypes]
if 'nfsv4.1' in ptypes:
ptypes.append('nfsv41')
# only create a policy when NFSv4 is used (for now)
if 'nfsv41' not in ptypes:
return None
options = dict(
rule_index=1,
allowed_clients='0.0.0.0/0',
unix_read_write=True)
if self.has_feature('ignore_change_ownership_mode') and self.sdk_version >= '4.0.0':
# https://github.com/Azure/azure-sdk-for-python/issues/20356
options['chown_mode'] = None
for protocol in ('cifs', 'nfsv3', 'nfsv41'):
options[protocol] = protocol in ptypes
return VolumePropertiesExportPolicy(rules=[ExportPolicyRule(**options)])
def create_azure_netapp_volume(self):
"""
Create a volume for the given Azure NetApp Account
:return: None
"""
options = self.na_helper.get_not_none_values_from_dict(self.parameters, ['protocol_types', 'service_level', 'tags', 'usage_threshold'])
rules = self.get_export_policy_rules()
if rules is not None:
# TODO: other options to expose ?
# options['throughput_mibps'] = 1.6
# options['encryption_key_source'] = 'Microsoft.NetApp'
# options['security_style'] = 'Unix'
# options['unix_permissions'] = '0770'
# required for NFSv4
options['export_policy'] = rules
subnet_id = '/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s'\
% (self.azure_auth.subscription_id,
self.parameters['resource_group'] if self.parameters.get('vnet_resource_group_for_subnet') is None
else self.parameters['vnet_resource_group_for_subnet'],
self.parameters['virtual_network'],
self.parameters['subnet_name'])
volume_body = Volume(
location=self.parameters['location'],
creation_token=self.parameters['file_path'],
subnet_id=subnet_id,
**options
)
try:
result = self.get_method('volumes', 'create_or_update')(body=volume_body, resource_group_name=self.parameters['resource_group'],
account_name=self.parameters['account_name'],
pool_name=self.parameters['pool_name'], volume_name=self.parameters['name'])
# waiting till the status turns Succeeded
while result.done() is not True:
result.result(10)
except (CloudError, ValidationError, AzureError) as error:
self.module.fail_json(msg='Error creating volume %s for Azure NetApp account %s and subnet ID %s: %s'
% (self.parameters['name'], self.parameters['account_name'], subnet_id, to_native(error)),
exception=traceback.format_exc())
def modify_azure_netapp_volume(self):
"""
Modify a volume for the given Azure NetApp Account
:return: None
"""
options = self.na_helper.get_not_none_values_from_dict(self.parameters, ['tags', 'usage_threshold'])
volume_body = VolumePatch(
**options
)
try:
result = self.get_method('volumes', 'update')(body=volume_body, resource_group_name=self.parameters['resource_group'],
account_name=self.parameters['account_name'],
pool_name=self.parameters['pool_name'], volume_name=self.parameters['name'])
# waiting till the status turns Succeeded
while result.done() is not True:
result.result(10)
except (CloudError, ValidationError, AzureError) as error:
self.module.fail_json(msg='Error modifying volume %s for Azure NetApp account %s: %s'
% (self.parameters['name'], self.parameters['account_name'], to_native(error)),
exception=traceback.format_exc())
def delete_azure_netapp_volume(self):
"""
Delete a volume for the given Azure NetApp Account
:return: None
"""
try:
result = self.get_method('volumes', 'delete')(resource_group_name=self.parameters['resource_group'],
account_name=self.parameters['account_name'],
pool_name=self.parameters['pool_name'], volume_name=self.parameters['name'])
# waiting till the status turns Succeeded
while result.done() is not True:
result.result(10)
except (CloudError, AzureError) as error:
self.module.fail_json(msg='Error deleting volume %s for Azure NetApp account %s: %s'
% (self.parameters['name'], self.parameters['account_name'], to_native(error)),
exception=traceback.format_exc())
def validate_modify(self, modify, current):
disallowed = dict(modify)
disallowed.pop('tags', None)
disallowed.pop('usage_threshold', None)
if disallowed:
self.module.fail_json(msg="Error: the following properties cannot be modified: %s. Current: %s" % (repr(disallowed), repr(current)))
def exec_module(self, **kwargs):
# unlikely
self.fail_when_import_errors(IMPORT_ERRORS, HAS_AZURE_MGMT_NETAPP)
# set up parameters according to our initial list
for key in list(self.module_arg_spec):
self.parameters[key] = kwargs[key]
# and common parameter
for key in ['tags']:
if key in kwargs:
self.parameters[key] = kwargs[key]
# API is using 'usage_threshold' for 'size', and the unit is bytes
if self.parameters.get('size') is not None:
self.parameters['usage_threshold'] = ONE_GIB * self.parameters.pop('size')
modify = None
current = self.get_azure_netapp_volume()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None and current:
# ignore change in name
name = current.pop('name', None)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if name is not None:
current['name'] = name
if 'tags' in modify:
dummy, modify['tags'] = self.update_tags(current.get('tags'))
self.validate_modify(modify, current)
if self.na_helper.changed and not self.module.check_mode:
if cd_action == 'create':
self.create_azure_netapp_volume()
elif cd_action == 'delete':
self.delete_azure_netapp_volume()
elif modify:
self.modify_azure_netapp_volume()
def get_mount_info(return_info):
if return_info is not None and return_info.get('mount_targets'):
return '%s:/%s' % (return_info['mount_targets'][0]['ip_address'], return_info['creation_token'])
return None
mount_info = ''
if self.parameters['state'] == 'present':
return_info = self.get_azure_netapp_volume()
if return_info is None and not self.module.check_mode:
self.module.fail_json(msg='Error: volume %s was created successfully, but cannot be found.' % self.parameters['name'])
mount_info = get_mount_info(return_info)
if mount_info is None and not self.module.check_mode:
self.module.fail_json(msg='Error: volume %s was created successfully, but mount target(s) cannot be found - volume details: %s.'
% (self.parameters['name'], str(return_info)))
self.module.exit_json(changed=self.na_helper.changed, mount_path=mount_info, modify=modify)
def main():
AzureRMNetAppVolume()
if __name__ == '__main__':
main()