Merge "Add template for generation of artibtrary module"

This commit is contained in:
Zuul 2020-05-28 19:55:17 +00:00 committed by Gerrit Code Review
commit f89644973d
4 changed files with 345 additions and 0 deletions

5
contrib/generate_module.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
# For resource changing module
ansible localhost -c local -m template -a "src=module_template.py.j2 dest=my_module.py" -e @module_template_vars.yaml
# For resource info collection module
ansible localhost -c local -m template -a "src=module_info_template.py.j2 dest=my_module_info.py" -e @module_template_vars.yaml

View File

@ -0,0 +1,110 @@
#!/usr/bin/python
# coding: utf-8 -*-
# Copyright (c) 2020, {{ author_name }} <{{ author_mail }}>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
---
module: {{ module_name }}
short_description: {{ module_short_description }}
author: {{ author_name }} <{{ author_mail }}>
description:
- {{ module_long_description }}
options:
{{ options|to_nice_yaml(indent=2,sort_keys=false)|indent(width=2)|trim }}
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = '''
{{ module_returns_example|to_nice_yaml(indent=2,sort_keys=false) }}
'''
EXAMPLES = '''
# What modules does for example
- {{ module_name }}:
name:
- name1
- name2
timeout: 200
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class {{ module_name.split("_")|map("capitalize")|list|join("") }}Module(OpenStackModule):
argument_spec = dict(
{% for k, v in options.items() %}
{{ k | indent( width=8, indentfirst=True) }}=dict(type={{ v.type }}
{%- if 'required' in v %}, required={{ v.required }}{% endif %}
{%- if 'elements' in v %}, elements={{ v.elements }}{% endif %}
{%- if 'default' in v %}, default={% if v.type == 'str' %}"{{ v.default }}"{% else %}{{ v.default }}{% endif %}{% endif %}
{%- if 'aliases' in v %}, aliases={{ v.aliases }}{% endif %}
{%- if 'choices' in v %}, choices={{ v.choices }}{% endif %}),
{% endfor %}
),
# Optional arguments requirements
module_kwargs = dict(
required_if=[
['action', 'rebuild', ['image']], # if need to rebuild image (only), the 'image' is required
["state", "present", ["username", "user_roles"]], # for creating user 'user_roles' is required
["state", "absent", ["username"]], # for state 'absent' only username is required
],
required_by=dict( # for weather and population 'city' is required to set
weather=('city'),
population=('city'),
),
mutually_exclusive=[
['use_cloud1', 'use_cloud2'] # can't run on both, choose only one to set
],
required_together=[
['remove_image', 'image_name'] # if need to remove image, must to specify which one
],
required_one_of_args=[["password", "password_hash"]], # one of these args must be set
supports_check_mode={{ check_mode_support }}, # good practice is to support check_mode
)
# you main funciton is here
def run(self):
# do any arguments check if needed
data = self.preliminary_checks()
# check if we need to prepare various filters for results
filters = self.prepare_filters()
# run SDK call to get information about requested resource
result = self.conn.compute.resource_list(
filters=filters,
detailed=self.params['detailed'],
# any other parameters
)
# process results if they require a change
result = self.normalize_result()
self.results.update({'resource_name': result})
def preliminary_checks(self):
# you checks before running like arguments and options checks, etc
return data
def prepare_filters(self):
# process filters if they require additional checks
return filters
def normalize_result(self):
# process filters if they require additional checks
return result
def main():
module = {{ module_name.split("_")|map("capitalize")|list|join("") }}Module()
module()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,149 @@
#!/usr/bin/python
# coding: utf-8 -*-
# Copyright (c) 2020, {{ author_name }} <{{ author_mail }}>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
---
module: {{ module_name }}
short_description: {{ module_short_description }}
author: {{ author_name }} <{{ author_mail }}>
description:
- {{ module_long_description }}
options:
{{ options|to_nice_yaml(indent=2,sort_keys=false)|indent(width=2)|trim }}
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = '''
{{ module_returns_example|to_nice_yaml(indent=2,sort_keys=false) }}
'''
EXAMPLES = '''
# What modules does for example
- {{ module_name }}:
action: pause
auth:
auth_url: https://identity.example.com
username: admin
password: admin
project_name: admin
server: vm1
timeout: 200
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class {{ module_name.split("_")|map("capitalize")|list|join("") }}Module(OpenStackModule):
argument_spec = dict(
{% for k, v in options.items() %}
{{ k | indent( width=8, indentfirst=True) }}=dict(type={{ v.type }}
{%- if 'required' in v %}, required={{ v.required }}{% endif %}
{%- if 'elements' in v %}, elements={{ v.elements }}{% endif %}
{%- if 'default' in v %}, default={% if v.type == 'str' %}"{{ v.default }}"{% else %}{{ v.default }}{% endif %}{% endif %}
{%- if 'aliases' in v %}, aliases={{ v.aliases }}{% endif %}
{%- if 'choices' in v %}, choices={{ v.choices }}{% endif %}),
{% endfor %}
),
# Optional arguments requirements
module_kwargs = dict(
required_if=[
['action', 'rebuild', ['image']], # if need to rebuild image (only), the 'image' is required
["state", "present", ["username", "user_roles"]], # for creating user 'user_roles' is required
["state", "absent", ["username"]], # for state 'absent' only username is required
],
required_by=dict( # for weather and population 'city' is required to set
weather=('city'),
population=('city'),
),
mutually_exclusive=[
['use_cloud1', 'use_cloud2'] # can't run on both, choose only one to set
],
required_together=[
['remove_image', 'image_name'] # if need to remove image, must to specify which one
],
required_one_of_args=[["password", "password_hash"]], # one of these args must be set
supports_check_mode={{ check_mode_support }}, # good practice is to support check_mode
)
# you main funciton is here
def run(self):
# do any arguments check if needed
data = self.preliminary_checks()
# check if we need to run or the resource is in desired state already
must_run = self.check_mode_test()
# if the resource is good
if not must_run:
# updated returned results if need
self.results.update({"returning_data": some_data})
# returning {changed: False, ...} because we didn't change resource
self.exit_json(self.results)
# do something if must to run the module
self.execute()
def preliminary_checks(self):
# you checks before running like arguments and options checks, etc
return data
def check_mode_test(self):
# check the idempotency - does module should do anything or
# it's already in the desired state?
return must_run
def execute(self):
# doing here what should be done, using OpenstackSDK
# for example actions for resource:
# self.params['action'] = "rebuild"
action_name = self.params['action'] + "_resource" # action_name='rebuild_resource'
try:
# find a method "rebuild_resource" in openstack SDK compute:
func_name = getattr(self.conn.compute, action_name)
# found self.conn.compute.rebuild_resource
except AttributeError:
self.fail_json(
msg="Method %s wasn't found in OpenstackSDK compute" % action_name)
summary = func_name(data) # summary = self.conn.compute.rebuild_resource(data)
self.results.update({"returning_data": summary})
# that's it, exiting, results will be returned from module automatically
# another option for states
def execute_with_action_map(self):
actions_map = {
'start': self._start_resource,
'stop': self._stop_resource,
'restart': self._restart_resource,
'absent': self._absent_resource,
}
summary = actions_map(self.params['action'])() # summary = self.start_resource()
self.results.update({"changed": True, "data2return": summary})
def _start_resource(self, some_other_data):
pass
def _stop_resource(self, some_other_data):
pass
def _restart_resource(self, some_other_data):
pass
def _absent_resource(self, some_other_data):
pass
def main():
module = {{ module_name.split("_")|map("capitalize")|list|join("") }}Module()
module()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,81 @@
##### PLEASE READ BEFORE #####
# Module format and documentation
# https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_documenting.html#module-format-and-documentation
module_name: server_manage
author_name: 'Happy Ansible User'
author_mail: dontwriteme@example.com
module_short_description: "Doing something very useful"
module_long_description: "Here is the place to release your inner writer"
check_mode_support: True # good practice to support check_mode:
# https://docs.ansible.com/ansible/latest/user_guide/playbooks_checkmode.html#check-mode-dry-run
module_returns_example:
image:
description: Image inspection results for the image that was pulled, pushed, or built.
returned: always # or 'success' in case of success only
type: dict
sample:
Image Name: Sample Image
Image ID: e6471d00796a13de8142c15d7ad3a44f
Nested:
images list:
- data 1
- image 1234
boolean_1: True
options:
optional_string:
description:
- This variable is set for having string argument, for example 'action'
type: str
required: true
default: "my_lovely_action"
choices:
- allowed_option1
- allowed_option1
optional_boolean:
description:
- This variable is set for having a boolean argument, for example whether
to wait for resource creation or not
type: bool
required: false # may be omitted if false
# and no default because not required
optional_integer:
description:
- This variable is set for having a integer argument, for example how many
seconds to wait for the resource to come alive
required: true
default: 60
type: int
aliases: # sometimes we allow to pass the same option with different name
- old_optional_integer_name
- different_option_name
optional_list:
description:
- This variable is set for having a list argument, for example files need
to create with the resource
type: list
elements: str # type of elements of the list, can be dict, str, int, list
optional_dictionary:
description:
- This variable is set for having a dictionary argument, for example to
set environment variables or to pass more complex data to SDK
required: true
default: {}
type: dict
suboptions:
suboption_1:
description:
- suboption_1 description, what it does
type: str
aliases:
- suboption_1_another_name
suboption_2:
description:
- suboption_2 description, what it does
type: list
elements: str
default: []