mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-28 17:36:49 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c67a5bda9 | ||
|
|
4ae436a8cc | ||
|
|
5f5c07a942 | ||
|
|
1cef1359d0 | ||
|
|
0d28bfb67e | ||
|
|
ef304ed824 | ||
|
|
bf17f289b3 | ||
|
|
0eff87d0be | ||
|
|
f00fabfa48 | ||
|
|
426cbafa06 | ||
|
|
93fe1f9a3e | ||
|
|
4e944772d5 | ||
|
|
50abeee579 | ||
|
|
eccc8d88b6 | ||
|
|
6d2d364a00 | ||
|
|
e781dd3c9b | ||
|
|
362f899a99 |
@@ -36,7 +36,7 @@ variables:
|
||||
resources:
|
||||
containers:
|
||||
- container: default
|
||||
image: quay.io/ansible/azure-pipelines-test-container:1.8.0
|
||||
image: quay.io/ansible/azure-pipelines-test-container:1.9.0
|
||||
|
||||
pool: Standard
|
||||
|
||||
|
||||
8
.github/BOTMETA.yml
vendored
8
.github/BOTMETA.yml
vendored
@@ -708,6 +708,8 @@ files:
|
||||
labels: cisco
|
||||
$modules/remote_management/ipmi/:
|
||||
maintainers: bgaifullin cloudnull
|
||||
$modules/remote_management/lenovoxcc/:
|
||||
maintainers: panyy3 renxulei
|
||||
$modules/remote_management/lxca/:
|
||||
maintainers: navalkp prabhosa
|
||||
$modules/remote_management/manageiq/:
|
||||
@@ -728,7 +730,7 @@ files:
|
||||
$modules/remote_management/oneview/oneview_fcoe_network.py:
|
||||
maintainers: fgbulsoni
|
||||
$modules/remote_management/redfish/:
|
||||
maintainers: $team_redfish billdodd
|
||||
maintainers: $team_redfish
|
||||
ignore: jose-delarosa
|
||||
$modules/remote_management/stacki/stacki_host.py:
|
||||
maintainers: bsanders bbyhuy
|
||||
@@ -919,7 +921,7 @@ files:
|
||||
maintainers: ahtik ovcharenko pyykkis
|
||||
labels: ufw
|
||||
$modules/system/vdo.py:
|
||||
maintainers: bgurney-rh
|
||||
maintainers: rhawalsh
|
||||
$modules/system/xfconf.py:
|
||||
maintainers: russoz jbenden
|
||||
labels: xfconf
|
||||
@@ -1022,7 +1024,7 @@ macros:
|
||||
team_opennebula: ilicmilan meerkampdvv rsmontero xorel
|
||||
team_oracle: manojmeda mross22 nalsaber
|
||||
team_purestorage: bannaych dnix101 genegr lionmax opslounge raekins sdodsley sile16
|
||||
team_redfish: billdodd mraineri tomasg2012 xmadsen renxulei
|
||||
team_redfish: mraineri tomasg2012 xmadsen renxulei
|
||||
team_rhn: FlossWare alikins barnabycourt vritant
|
||||
team_scaleway: QuentinBrosse abarbare jerome-quere kindermoumoute remyleone sieben
|
||||
team_solaris: bcoca fishman jasperla jpdasma mator scathatheworm troy2914 xen0l
|
||||
|
||||
@@ -6,6 +6,57 @@ Community General Release Notes
|
||||
|
||||
This changelog describes changes after version 1.0.0.
|
||||
|
||||
v2.4.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular feature and bugfix release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- vdo - add ``force`` option (https://github.com/ansible-collections/community.general/issues/2101).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- git_config - fixed scope ``file`` behaviour and added integraton test for it (https://github.com/ansible-collections/community.general/issues/2117).
|
||||
- zypper, zypper_repository - respect ``PATH`` environment variable when resolving zypper executable path (https://github.com/ansible-collections/community.general/pull/2094).
|
||||
|
||||
New Plugins
|
||||
-----------
|
||||
|
||||
Become
|
||||
~~~~~~
|
||||
|
||||
- sudosu - Run tasks using sudo su -
|
||||
|
||||
Callback
|
||||
~~~~~~~~
|
||||
|
||||
- loganalytics - Posts task results to Azure Log Analytics
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
Cloud
|
||||
~~~~~
|
||||
|
||||
opennebula
|
||||
^^^^^^^^^^
|
||||
|
||||
- one_template - Manages OpenNebula templates
|
||||
|
||||
Remote Management
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
lenovoxcc
|
||||
^^^^^^^^^
|
||||
|
||||
- xcc_redfish_command - Manages Lenovo Out-Of-Band controllers using Redfish APIs
|
||||
|
||||
v2.3.0
|
||||
======
|
||||
|
||||
@@ -432,7 +483,7 @@ Minor Changes
|
||||
- The collection is now actively tested in CI with the latest Ansible 2.9 release.
|
||||
- airbrake_deployment - add ``version`` param; clarified docs on ``revision`` param (https://github.com/ansible-collections/community.general/pull/583).
|
||||
- apk - added ``no_cache`` option (https://github.com/ansible-collections/community.general/pull/548).
|
||||
- archive - fix paramater types (https://github.com/ansible-collections/community.general/pull/1039).
|
||||
- archive - fix parameter types (https://github.com/ansible-collections/community.general/pull/1039).
|
||||
- cloudflare_dns - add support for environment variable ``CLOUDFLARE_TOKEN`` (https://github.com/ansible-collections/community.general/pull/1238).
|
||||
- consul - added support for tcp checks (https://github.com/ansible-collections/community.general/issues/1128).
|
||||
- datadog - mark ``notification_message`` as ``no_log`` (https://github.com/ansible-collections/community.general/pull/1338).
|
||||
@@ -579,7 +630,7 @@ Breaking Changes / Porting Guide
|
||||
If you use ansible-base 2.10 or newer and did not install Ansible 3.0.0, but installed (and/or upgraded) community.general manually, you need to make sure to also install ``community.postgresql`` if you are using any of the ``postgresql`` modules.
|
||||
While ansible-base 2.10 or newer can use the redirects that community.general 2.0.0 adds, the collection they point to (community.postgresql) must be installed for them to work.
|
||||
- The Google cloud inventory script ``gce.py`` has been migrated to the ``community.google`` collection. Install the ``community.google`` collection in order to continue using it.
|
||||
- archive - remove path folder itself when ``remove`` paramater is true (https://github.com/ansible-collections/community.general/issues/1041).
|
||||
- archive - remove path folder itself when ``remove`` parameter is true (https://github.com/ansible-collections/community.general/issues/1041).
|
||||
- log_plays callback - add missing information to the logs generated by the callback plugin. This changes the log message format (https://github.com/ansible-collections/community.general/pull/442).
|
||||
- passwordstore lookup plugin - now parsing a password store entry as YAML if possible, skipping the first line (which by convention only contains the password and nothing else). If it cannot be parsed as YAML, the old ``key: value`` parser will be used to process the entry. Can break backwards compatibility if YAML formatted code was parsed in a non-YAML interpreted way, e.g. ``foo: [bar, baz]`` will become a list with two elements in the new version, but a string ``'[bar, baz]'`` in the old (https://github.com/ansible-collections/community.general/issues/1673).
|
||||
- pkgng - passing ``name: *`` with ``state: absent`` will no longer remove every installed package from the system. It is now a noop. (https://github.com/ansible-collections/community.general/pull/569).
|
||||
|
||||
@@ -145,7 +145,7 @@ releases:
|
||||
- The Google cloud inventory script ``gce.py`` has been migrated to the ``community.google``
|
||||
collection. Install the ``community.google`` collection in order to continue
|
||||
using it.
|
||||
- archive - remove path folder itself when ``remove`` paramater is true (https://github.com/ansible-collections/community.general/issues/1041).
|
||||
- archive - remove path folder itself when ``remove`` parameter is true (https://github.com/ansible-collections/community.general/issues/1041).
|
||||
- log_plays callback - add missing information to the logs generated by the
|
||||
callback plugin. This changes the log message format (https://github.com/ansible-collections/community.general/pull/442).
|
||||
- 'passwordstore lookup plugin - now parsing a password store entry as YAML
|
||||
@@ -414,7 +414,7 @@ releases:
|
||||
- airbrake_deployment - add ``version`` param; clarified docs on ``revision``
|
||||
param (https://github.com/ansible-collections/community.general/pull/583).
|
||||
- apk - added ``no_cache`` option (https://github.com/ansible-collections/community.general/pull/548).
|
||||
- archive - fix paramater types (https://github.com/ansible-collections/community.general/pull/1039).
|
||||
- archive - fix parameter types (https://github.com/ansible-collections/community.general/pull/1039).
|
||||
- cloudflare_dns - add support for environment variable ``CLOUDFLARE_TOKEN``
|
||||
(https://github.com/ansible-collections/community.general/pull/1238).
|
||||
- consul - added support for tcp checks (https://github.com/ansible-collections/community.general/issues/1128).
|
||||
@@ -1629,3 +1629,35 @@ releases:
|
||||
name: from_csv
|
||||
namespace: null
|
||||
release_date: '2021-03-23'
|
||||
2.4.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- git_config - fixed scope ``file`` behaviour and added integraton test for
|
||||
it (https://github.com/ansible-collections/community.general/issues/2117).
|
||||
- zypper, zypper_repository - respect ``PATH`` environment variable when resolving
|
||||
zypper executable path (https://github.com/ansible-collections/community.general/pull/2094).
|
||||
minor_changes:
|
||||
- vdo - add ``force`` option (https://github.com/ansible-collections/community.general/issues/2101).
|
||||
release_summary: Regular feature and bugfix release.
|
||||
fragments:
|
||||
- 2.4.0.yml
|
||||
- 2094-bugfix-respect-PATH-env-variable-in-zypper-modules.yaml
|
||||
- 2110-vdo-add_force_option.yaml
|
||||
- 2125-git-config-scope-file.yml
|
||||
modules:
|
||||
- description: Manages OpenNebula templates
|
||||
name: one_template
|
||||
namespace: cloud.opennebula
|
||||
- description: Manages Lenovo Out-Of-Band controllers using Redfish APIs
|
||||
name: xcc_redfish_command
|
||||
namespace: remote_management.lenovoxcc
|
||||
plugins:
|
||||
become:
|
||||
- description: Run tasks using sudo su -
|
||||
name: sudosu
|
||||
namespace: null
|
||||
callback:
|
||||
- description: Posts task results to Azure Log Analytics
|
||||
name: loganalytics
|
||||
namespace: null
|
||||
release_date: '2021-03-30'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace: community
|
||||
name: general
|
||||
version: 2.3.0
|
||||
version: 2.4.0
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
|
||||
91
plugins/become/sudosu.py
Normal file
91
plugins/become/sudosu.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2021, Ansible Project
|
||||
# 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
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: sudosu
|
||||
short_description: Run tasks using sudo su -
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the C(sudo) and C(su) utilities combined.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: 2.4.0
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
default: root
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: sudo_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_sudo_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_SUDO_USER
|
||||
become_flags:
|
||||
description: Options to pass to C(sudo).
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: sudo_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_sudo_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_SUDO_FLAGS
|
||||
become_pass:
|
||||
description: Password to pass to C(sudo).
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_sudo_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_SUDO_PASS
|
||||
ini:
|
||||
- section: sudo_become_plugin
|
||||
key: password
|
||||
"""
|
||||
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = 'community.general.sudosu'
|
||||
|
||||
# messages for detecting prompted password issues
|
||||
fail = ('Sorry, try again.',)
|
||||
missing = ('Sorry, a password is required to run sudo', 'sudo: a password is required')
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
super(BecomeModule, self).build_become_command(cmd, shell)
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
becomecmd = 'sudo'
|
||||
|
||||
flags = self.get_option('become_flags') or ''
|
||||
prompt = ''
|
||||
if self.get_option('become_pass'):
|
||||
self.prompt = '[sudo via ansible, key=%s] password:' % self._id
|
||||
if flags: # this could be simplified, but kept as is for now for backwards string matching
|
||||
flags = flags.replace('-n', '')
|
||||
prompt = '-p "%s"' % (self.prompt)
|
||||
|
||||
user = self.get_option('become_user') or ''
|
||||
if user:
|
||||
user = '%s' % (user)
|
||||
|
||||
return ' '.join([becomecmd, flags, prompt, 'su -l', user, self._build_success_command(cmd, shell)])
|
||||
234
plugins/callback/loganalytics.py
Normal file
234
plugins/callback/loganalytics.py
Normal file
@@ -0,0 +1,234 @@
|
||||
# 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
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: loganalytics
|
||||
type: aggregate
|
||||
short_description: Posts task results to Azure Log Analytics
|
||||
author: "Cyrus Li (@zhcli) <cyrus1006@gmail.com>"
|
||||
description:
|
||||
- This callback plugin will post task results in JSON formatted to an Azure Log Analytics workspace.
|
||||
- Credits to authors of splunk callback plugin.
|
||||
version_added: "2.4.0"
|
||||
requirements:
|
||||
- Whitelisting this callback plugin.
|
||||
- An Azure log analytics work space has been established.
|
||||
options:
|
||||
workspace_id:
|
||||
description: Workspace ID of the Azure log analytics workspace.
|
||||
required: true
|
||||
env:
|
||||
- name: WORKSPACE_ID
|
||||
ini:
|
||||
- section: callback_loganalytics
|
||||
key: workspace_id
|
||||
shared_key:
|
||||
description: Shared key to connect to Azure log analytics workspace.
|
||||
required: true
|
||||
env:
|
||||
- name: WORKSPACE_SHARED_KEY
|
||||
ini:
|
||||
- section: callback_loganalytics
|
||||
key: shared_key
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
examples: |
|
||||
Whitelist the plugin in ansible.cfg:
|
||||
[defaults]
|
||||
callback_whitelist = community.general.loganalytics
|
||||
Set the environment variable:
|
||||
export WORKSPACE_ID=01234567-0123-0123-0123-01234567890a
|
||||
export WORKSPACE_SHARED_KEY=dZD0kCbKl3ehZG6LHFMuhtE0yHiFCmetzFMc2u+roXIUQuatqU924SsAAAAPemhjbGlAemhjbGktTUJQAQIDBA==
|
||||
Or configure the plugin in ansible.cfg in the callback_loganalytics block:
|
||||
[callback_loganalytics]
|
||||
workspace_id = 01234567-0123-0123-0123-01234567890a
|
||||
shared_key = dZD0kCbKl3ehZG6LHFMuhtE0yHiFCmetzFMc2u+roXIUQuatqU924SsAAAAPemhjbGlAemhjbGktTUJQAQIDBA==
|
||||
'''
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import base64
|
||||
import logging
|
||||
import json
|
||||
import uuid
|
||||
import socket
|
||||
import getpass
|
||||
|
||||
from datetime import datetime
|
||||
from os.path import basename
|
||||
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.parsing.ajson import AnsibleJSONEncoder
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
|
||||
|
||||
class AzureLogAnalyticsSource(object):
|
||||
def __init__(self):
|
||||
self.ansible_check_mode = False
|
||||
self.ansible_playbook = ""
|
||||
self.ansible_version = ""
|
||||
self.session = str(uuid.uuid4())
|
||||
self.host = socket.gethostname()
|
||||
self.user = getpass.getuser()
|
||||
self.extra_vars = ""
|
||||
|
||||
def __build_signature(self, date, workspace_id, shared_key, content_length):
|
||||
# Build authorisation signature for Azure log analytics API call
|
||||
sigs = "POST\n{0}\napplication/json\nx-ms-date:{1}\n/api/logs".format(
|
||||
str(content_length), date)
|
||||
utf8_sigs = sigs.encode('utf-8')
|
||||
decoded_shared_key = base64.b64decode(shared_key)
|
||||
hmac_sha256_sigs = hmac.new(
|
||||
decoded_shared_key, utf8_sigs, digestmod=hashlib.sha256).digest()
|
||||
encoded_hash = base64.b64encode(hmac_sha256_sigs).decode('utf-8')
|
||||
signature = "SharedKey {0}:{1}".format(workspace_id, encoded_hash)
|
||||
return signature
|
||||
|
||||
def __build_workspace_url(self, workspace_id):
|
||||
return "https://{0}.ods.opinsights.azure.com/api/logs?api-version=2016-04-01".format(workspace_id)
|
||||
|
||||
def __rfc1123date(self):
|
||||
return datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
|
||||
def send_event(self, workspace_id, shared_key, state, result, runtime):
|
||||
if result._task_fields['args'].get('_ansible_check_mode') is True:
|
||||
self.ansible_check_mode = True
|
||||
|
||||
if result._task_fields['args'].get('_ansible_version'):
|
||||
self.ansible_version = \
|
||||
result._task_fields['args'].get('_ansible_version')
|
||||
|
||||
if result._task._role:
|
||||
ansible_role = str(result._task._role)
|
||||
else:
|
||||
ansible_role = None
|
||||
|
||||
data = {}
|
||||
data['uuid'] = result._task._uuid
|
||||
data['session'] = self.session
|
||||
data['status'] = state
|
||||
data['timestamp'] = self.__rfc1123date()
|
||||
data['host'] = self.host
|
||||
data['user'] = self.user
|
||||
data['runtime'] = runtime
|
||||
data['ansible_version'] = self.ansible_version
|
||||
data['ansible_check_mode'] = self.ansible_check_mode
|
||||
data['ansible_host'] = result._host.name
|
||||
data['ansible_playbook'] = self.ansible_playbook
|
||||
data['ansible_role'] = ansible_role
|
||||
data['ansible_task'] = result._task_fields
|
||||
# Removing args since it can contain sensitive data
|
||||
if 'args' in data['ansible_task']:
|
||||
data['ansible_task'].pop('args')
|
||||
data['ansible_result'] = result._result
|
||||
if 'content' in data['ansible_result']:
|
||||
data['ansible_result'].pop('content')
|
||||
|
||||
# Adding extra vars info
|
||||
data['extra_vars'] = self.extra_vars
|
||||
|
||||
# Preparing the playbook logs as JSON format and send to Azure log analytics
|
||||
jsondata = json.dumps({'event': data}, cls=AnsibleJSONEncoder, sort_keys=True)
|
||||
content_length = len(jsondata)
|
||||
rfc1123date = self.__rfc1123date()
|
||||
signature = self.__build_signature(rfc1123date, workspace_id, shared_key, content_length)
|
||||
workspace_url = self.__build_workspace_url(workspace_id)
|
||||
|
||||
open_url(
|
||||
workspace_url,
|
||||
jsondata,
|
||||
headers={
|
||||
'content-type': 'application/json',
|
||||
'Authorization': signature,
|
||||
'Log-Type': 'ansible_playbook',
|
||||
'x-ms-date': rfc1123date
|
||||
},
|
||||
method='POST'
|
||||
)
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'aggregate'
|
||||
CALLBACK_NAME = 'loganalytics'
|
||||
CALLBACK_NEEDS_WHITELIST = True
|
||||
|
||||
def __init__(self, display=None):
|
||||
super(CallbackModule, self).__init__(display=display)
|
||||
self.start_datetimes = {} # Collect task start times
|
||||
self.workspace_id = None
|
||||
self.shared_key = None
|
||||
self.loganalytics = AzureLogAnalyticsSource()
|
||||
|
||||
def _seconds_since_start(self, result):
|
||||
return (
|
||||
datetime.utcnow() -
|
||||
self.start_datetimes[result._task._uuid]
|
||||
).total_seconds()
|
||||
|
||||
def set_options(self, task_keys=None, var_options=None, direct=None):
|
||||
super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
||||
self.workspace_id = self.get_option('workspace_id')
|
||||
self.shared_key = self.get_option('shared_key')
|
||||
|
||||
def v2_playbook_on_play_start(self, play):
|
||||
vm = play.get_variable_manager()
|
||||
extra_vars = vm.extra_vars
|
||||
self.loganalytics.extra_vars = extra_vars
|
||||
|
||||
def v2_playbook_on_start(self, playbook):
|
||||
self.loganalytics.ansible_playbook = basename(playbook._file_name)
|
||||
|
||||
def v2_playbook_on_task_start(self, task, is_conditional):
|
||||
self.start_datetimes[task._uuid] = datetime.utcnow()
|
||||
|
||||
def v2_playbook_on_handler_task_start(self, task):
|
||||
self.start_datetimes[task._uuid] = datetime.utcnow()
|
||||
|
||||
def v2_runner_on_ok(self, result, **kwargs):
|
||||
self.loganalytics.send_event(
|
||||
self.workspace_id,
|
||||
self.shared_key,
|
||||
'OK',
|
||||
result,
|
||||
self._seconds_since_start(result)
|
||||
)
|
||||
|
||||
def v2_runner_on_skipped(self, result, **kwargs):
|
||||
self.loganalytics.send_event(
|
||||
self.workspace_id,
|
||||
self.shared_key,
|
||||
'SKIPPED',
|
||||
result,
|
||||
self._seconds_since_start(result)
|
||||
)
|
||||
|
||||
def v2_runner_on_failed(self, result, **kwargs):
|
||||
self.loganalytics.send_event(
|
||||
self.workspace_id,
|
||||
self.shared_key,
|
||||
'FAILED',
|
||||
result,
|
||||
self._seconds_since_start(result)
|
||||
)
|
||||
|
||||
def runner_on_async_failed(self, result, **kwargs):
|
||||
self.loganalytics.send_event(
|
||||
self.workspace_id,
|
||||
self.shared_key,
|
||||
'FAILED',
|
||||
result,
|
||||
self._seconds_since_start(result)
|
||||
)
|
||||
|
||||
def v2_runner_on_unreachable(self, result, **kwargs):
|
||||
self.loganalytics.send_event(
|
||||
self.workspace_id,
|
||||
self.shared_key,
|
||||
'UNREACHABLE',
|
||||
result,
|
||||
self._seconds_since_start(result)
|
||||
)
|
||||
@@ -13,12 +13,32 @@ class ModuleDocFragment(object):
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
config:
|
||||
description:
|
||||
description:
|
||||
- Path to a .json configuration file containing the OneView client configuration.
|
||||
The configuration file is optional and when used should be present in the host running the ansible commands.
|
||||
If the file path is not provided, the configuration will be loaded from environment variables.
|
||||
For links to example configuration files or how to use the environment variables verify the notes section.
|
||||
type: path
|
||||
type: path
|
||||
api_version:
|
||||
description:
|
||||
- OneView API Version.
|
||||
type: int
|
||||
image_streamer_hostname:
|
||||
description:
|
||||
- IP address or hostname for the HPE Image Streamer REST API.
|
||||
type: str
|
||||
hostname:
|
||||
description:
|
||||
- IP address or hostname for the appliance.
|
||||
type: str
|
||||
username:
|
||||
description:
|
||||
- Username for API authentication.
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- Password for API authentication.
|
||||
type: str
|
||||
|
||||
requirements:
|
||||
- python >= 2.7.9
|
||||
|
||||
@@ -39,14 +39,16 @@ class OpenNebulaModule:
|
||||
wait_timeout=dict(type='int', default=300),
|
||||
)
|
||||
|
||||
def __init__(self, argument_spec, supports_check_mode=False, mutually_exclusive=None):
|
||||
def __init__(self, argument_spec, supports_check_mode=False, mutually_exclusive=None, required_one_of=None, required_if=None):
|
||||
|
||||
module_args = OpenNebulaModule.common_args
|
||||
module_args = OpenNebulaModule.common_args.copy()
|
||||
module_args.update(argument_spec)
|
||||
|
||||
self.module = AnsibleModule(argument_spec=module_args,
|
||||
supports_check_mode=supports_check_mode,
|
||||
mutually_exclusive=mutually_exclusive)
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
required_one_of=required_one_of,
|
||||
required_if=required_if)
|
||||
self.result = dict(changed=False,
|
||||
original_message='',
|
||||
message='')
|
||||
|
||||
276
plugins/modules/cloud/opennebula/one_template.py
Normal file
276
plugins/modules/cloud/opennebula/one_template.py
Normal file
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright: (c) 2021, Georg Gadinger <nilsding@nilsding.org>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: one_template
|
||||
|
||||
short_description: Manages OpenNebula templates
|
||||
|
||||
version_added: 2.4.0
|
||||
|
||||
requirements:
|
||||
- pyone
|
||||
|
||||
description:
|
||||
- "Manages OpenNebula templates."
|
||||
|
||||
options:
|
||||
id:
|
||||
description:
|
||||
- A I(id) of the template you would like to manage. If not set then a
|
||||
- new template will be created with the given I(name).
|
||||
type: int
|
||||
name:
|
||||
description:
|
||||
- A I(name) of the template you would like to manage. If a template with
|
||||
- the given name does not exist it will be created, otherwise it will be
|
||||
- managed by this module.
|
||||
type: str
|
||||
template:
|
||||
description:
|
||||
- A string containing the template contents.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- C(present) - state that is used to manage the template.
|
||||
- C(absent) - delete the template.
|
||||
choices: ["present", "absent"]
|
||||
default: present
|
||||
type: str
|
||||
|
||||
notes:
|
||||
- Supports C(check_mode). Note that check mode always returns C(changed=true) for existing templates, even if the template would not actually change.
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.opennebula
|
||||
|
||||
author:
|
||||
- "Georg Gadinger (@nilsding)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Fetch the TEMPLATE by id
|
||||
community.general.one_template:
|
||||
id: 6459
|
||||
register: result
|
||||
|
||||
- name: Print the TEMPLATE properties
|
||||
ansible.builtin.debug:
|
||||
var: result
|
||||
|
||||
- name: Fetch the TEMPLATE by name
|
||||
community.general.one_template:
|
||||
name: tf-prd-users-workerredis-p6379a
|
||||
register: result
|
||||
|
||||
- name: Create a new or update an existing TEMPLATE
|
||||
community.general.one_template:
|
||||
name: generic-opensuse
|
||||
template: |
|
||||
CONTEXT = [
|
||||
HOSTNAME = "generic-opensuse"
|
||||
]
|
||||
CPU = "1"
|
||||
CUSTOM_ATTRIBUTE = ""
|
||||
DISK = [
|
||||
CACHE = "writeback",
|
||||
DEV_PREFIX = "sd",
|
||||
DISCARD = "unmap",
|
||||
IMAGE = "opensuse-leap-15.2",
|
||||
IMAGE_UNAME = "oneadmin",
|
||||
IO = "threads",
|
||||
SIZE = "" ]
|
||||
MEMORY = "2048"
|
||||
NIC = [
|
||||
MODEL = "virtio",
|
||||
NETWORK = "testnet",
|
||||
NETWORK_UNAME = "oneadmin" ]
|
||||
OS = [
|
||||
ARCH = "x86_64",
|
||||
BOOT = "disk0" ]
|
||||
SCHED_REQUIREMENTS = "CLUSTER_ID=\\"100\\""
|
||||
VCPU = "2"
|
||||
|
||||
- name: Delete the TEMPLATE by id
|
||||
community.general.one_template:
|
||||
id: 6459
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
id:
|
||||
description: template id
|
||||
type: int
|
||||
returned: when I(state=present)
|
||||
sample: 153
|
||||
name:
|
||||
description: template name
|
||||
type: str
|
||||
returned: when I(state=present)
|
||||
sample: app1
|
||||
template:
|
||||
description: the parsed template
|
||||
type: dict
|
||||
returned: when I(state=present)
|
||||
group_id:
|
||||
description: template's group id
|
||||
type: int
|
||||
returned: when I(state=present)
|
||||
sample: 1
|
||||
group_name:
|
||||
description: template's group name
|
||||
type: str
|
||||
returned: when I(state=present)
|
||||
sample: one-users
|
||||
owner_id:
|
||||
description: template's owner id
|
||||
type: int
|
||||
returned: when I(state=present)
|
||||
sample: 143
|
||||
owner_name:
|
||||
description: template's owner name
|
||||
type: str
|
||||
returned: when I(state=present)
|
||||
sample: ansible-test
|
||||
'''
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.opennebula import OpenNebulaModule
|
||||
|
||||
|
||||
class TemplateModule(OpenNebulaModule):
|
||||
def __init__(self):
|
||||
argument_spec = dict(
|
||||
id=dict(type='int', required=False),
|
||||
name=dict(type='str', required=False),
|
||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
template=dict(type='str', required=False),
|
||||
)
|
||||
|
||||
mutually_exclusive = [
|
||||
['id', 'name']
|
||||
]
|
||||
|
||||
required_one_of = [('id', 'name')]
|
||||
|
||||
required_if = [
|
||||
['state', 'present', ['template']]
|
||||
]
|
||||
|
||||
OpenNebulaModule.__init__(self,
|
||||
argument_spec,
|
||||
supports_check_mode=True,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
required_one_of=required_one_of,
|
||||
required_if=required_if)
|
||||
|
||||
def run(self, one, module, result):
|
||||
params = module.params
|
||||
id = params.get('id')
|
||||
name = params.get('name')
|
||||
desired_state = params.get('state')
|
||||
template_data = params.get('template')
|
||||
|
||||
self.result = {}
|
||||
|
||||
template = self.get_template_instance(id, name)
|
||||
needs_creation = False
|
||||
if not template and desired_state != 'absent':
|
||||
if id:
|
||||
module.fail_json(msg="There is no template with id=" + str(id))
|
||||
else:
|
||||
needs_creation = True
|
||||
|
||||
if desired_state == 'absent':
|
||||
self.result = self.delete_template(template)
|
||||
else:
|
||||
if needs_creation:
|
||||
self.result = self.create_template(name, template_data)
|
||||
else:
|
||||
self.result = self.update_template(template, template_data)
|
||||
|
||||
self.exit()
|
||||
|
||||
def get_template(self, predicate):
|
||||
# -3 means "Resources belonging to the user"
|
||||
# the other two parameters are used for pagination, -1 for both essentially means "return all"
|
||||
pool = self.one.templatepool.info(-3, -1, -1)
|
||||
|
||||
for template in pool.VMTEMPLATE:
|
||||
if predicate(template):
|
||||
return template
|
||||
|
||||
return None
|
||||
|
||||
def get_template_by_id(self, template_id):
|
||||
return self.get_template(lambda template: (template.ID == template_id))
|
||||
|
||||
def get_template_by_name(self, template_name):
|
||||
return self.get_template(lambda template: (template.NAME == template_name))
|
||||
|
||||
def get_template_instance(self, requested_id, requested_name):
|
||||
if requested_id:
|
||||
return self.get_template_by_id(requested_id)
|
||||
else:
|
||||
return self.get_template_by_name(requested_name)
|
||||
|
||||
def get_template_info(self, template):
|
||||
info = {
|
||||
'id': template.ID,
|
||||
'name': template.NAME,
|
||||
'template': template.TEMPLATE,
|
||||
'user_name': template.UNAME,
|
||||
'user_id': template.UID,
|
||||
'group_name': template.GNAME,
|
||||
'group_id': template.GID,
|
||||
}
|
||||
|
||||
return info
|
||||
|
||||
def create_template(self, name, template_data):
|
||||
if not self.module.check_mode:
|
||||
self.one.template.allocate("NAME = \"" + name + "\"\n" + template_data)
|
||||
|
||||
result = self.get_template_info(self.get_template_by_name(name))
|
||||
result['changed'] = True
|
||||
|
||||
return result
|
||||
|
||||
def update_template(self, template, template_data):
|
||||
if not self.module.check_mode:
|
||||
# 0 = replace the whole template
|
||||
self.one.template.update(template.ID, template_data, 0)
|
||||
|
||||
result = self.get_template_info(self.get_template_by_id(template.ID))
|
||||
if self.module.check_mode:
|
||||
# Unfortunately it is not easy to detect if the template would have changed, therefore always report a change here.
|
||||
result['changed'] = True
|
||||
else:
|
||||
# if the previous parsed template data is not equal to the updated one, this has changed
|
||||
result['changed'] = template.TEMPLATE != result['template']
|
||||
|
||||
return result
|
||||
|
||||
def delete_template(self, template):
|
||||
if not template:
|
||||
return {'changed': False}
|
||||
|
||||
if not self.module.check_mode:
|
||||
self.one.template.delete(template.ID)
|
||||
|
||||
return {'changed': True}
|
||||
|
||||
|
||||
def main():
|
||||
TemplateModule().run_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -63,7 +63,7 @@ EXAMPLES = r'''
|
||||
- name: Changing Managing hosts list
|
||||
community.general.ipa_service:
|
||||
name: http/host01.example.com
|
||||
host:
|
||||
hosts:
|
||||
- host01.example.com
|
||||
- host02.example.com
|
||||
ipa_host: ipa.example.com
|
||||
|
||||
1
plugins/modules/one_template.py
Symbolic link
1
plugins/modules/one_template.py
Symbolic link
@@ -0,0 +1 @@
|
||||
./cloud/opennebula/one_template.py
|
||||
@@ -16,6 +16,7 @@ author: "Joe Adams (@sysadmind)"
|
||||
short_description: Add or remove Pulp repos from a remote host.
|
||||
description:
|
||||
- Add or remove Pulp repos from a remote host.
|
||||
- Note, this is for Pulp 2 only.
|
||||
options:
|
||||
add_export_distributor:
|
||||
description:
|
||||
|
||||
@@ -336,7 +336,7 @@ def get_cmd(m, subcommand):
|
||||
"puts together the basic zypper command arguments with those passed to the module"
|
||||
is_install = subcommand in ['install', 'update', 'patch', 'dist-upgrade']
|
||||
is_refresh = subcommand == 'refresh'
|
||||
cmd = ['/usr/bin/zypper', '--quiet', '--non-interactive', '--xmlout']
|
||||
cmd = [m.get_bin_path('zypper', required=True), '--quiet', '--non-interactive', '--xmlout']
|
||||
if m.params['extra_args_precommand']:
|
||||
args_list = m.params['extra_args_precommand'].split()
|
||||
cmd.extend(args_list)
|
||||
|
||||
@@ -141,9 +141,9 @@ from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
REPO_OPTS = ['alias', 'name', 'priority', 'enabled', 'autorefresh', 'gpgcheck']
|
||||
|
||||
|
||||
def _get_cmd(*args):
|
||||
def _get_cmd(module, *args):
|
||||
"""Combines the non-interactive zypper command with arguments/subcommands"""
|
||||
cmd = ['/usr/bin/zypper', '--quiet', '--non-interactive']
|
||||
cmd = [module.get_bin_path('zypper', required=True), '--quiet', '--non-interactive']
|
||||
cmd.extend(args)
|
||||
|
||||
return cmd
|
||||
@@ -151,7 +151,7 @@ def _get_cmd(*args):
|
||||
|
||||
def _parse_repos(module):
|
||||
"""parses the output of zypper --xmlout repos and return a parse repo dictionary"""
|
||||
cmd = _get_cmd('--xmlout', 'repos')
|
||||
cmd = _get_cmd(module, '--xmlout', 'repos')
|
||||
|
||||
if not HAS_XML:
|
||||
module.fail_json(msg=missing_required_lib("python-xml"), exception=XML_IMP_ERR)
|
||||
@@ -230,7 +230,7 @@ def repo_exists(module, repodata, overwrite_multiple):
|
||||
def addmodify_repo(module, repodata, old_repos, zypper_version, warnings):
|
||||
"Adds the repo, removes old repos before, that would conflict."
|
||||
repo = repodata['url']
|
||||
cmd = _get_cmd('addrepo', '--check')
|
||||
cmd = _get_cmd(module, 'addrepo', '--check')
|
||||
if repodata['name']:
|
||||
cmd.extend(['--name', repodata['name']])
|
||||
|
||||
@@ -274,14 +274,14 @@ def addmodify_repo(module, repodata, old_repos, zypper_version, warnings):
|
||||
|
||||
def remove_repo(module, repo):
|
||||
"Removes the repo."
|
||||
cmd = _get_cmd('removerepo', repo)
|
||||
cmd = _get_cmd(module, 'removerepo', repo)
|
||||
|
||||
rc, stdout, stderr = module.run_command(cmd, check_rc=True)
|
||||
return rc, stdout, stderr
|
||||
|
||||
|
||||
def get_zypper_version(module):
|
||||
rc, stdout, stderr = module.run_command(['/usr/bin/zypper', '--version'])
|
||||
rc, stdout, stderr = module.run_command([module.get_bin_path('zypper', required=True), '--version'])
|
||||
if rc != 0 or not stdout.startswith('zypper '):
|
||||
return LooseVersion('1.0')
|
||||
return LooseVersion(stdout.split()[1])
|
||||
@@ -290,9 +290,9 @@ def get_zypper_version(module):
|
||||
def runrefreshrepo(module, auto_import_keys=False, shortname=None):
|
||||
"Forces zypper to refresh repo metadata."
|
||||
if auto_import_keys:
|
||||
cmd = _get_cmd('--gpg-auto-import-keys', 'refresh', '--force')
|
||||
cmd = _get_cmd(module, '--gpg-auto-import-keys', 'refresh', '--force')
|
||||
else:
|
||||
cmd = _get_cmd('refresh', '--force')
|
||||
cmd = _get_cmd(module, 'refresh', '--force')
|
||||
if shortname is not None:
|
||||
cmd.extend(['-r', shortname])
|
||||
|
||||
|
||||
@@ -0,0 +1,673 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: xcc_redfish_command
|
||||
short_description: Manages Lenovo Out-Of-Band controllers using Redfish APIs
|
||||
version_added: 2.4.0
|
||||
description:
|
||||
- Builds Redfish URIs locally and sends them to remote OOB controllers to
|
||||
perform an action or get information back or update a configuration attribute.
|
||||
- Manages virtual media.
|
||||
- Supports getting information back via GET method.
|
||||
- Supports updating a configuration attribute via PATCH method.
|
||||
- Supports performing an action via POST method.
|
||||
options:
|
||||
category:
|
||||
required: true
|
||||
description:
|
||||
- Category to execute on OOB controller.
|
||||
type: str
|
||||
command:
|
||||
required: true
|
||||
description:
|
||||
- List of commands to execute on OOB controller.
|
||||
type: list
|
||||
elements: str
|
||||
baseuri:
|
||||
required: true
|
||||
description:
|
||||
- Base URI of OOB controller.
|
||||
type: str
|
||||
username:
|
||||
description:
|
||||
- Username for authentication with OOB controller.
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- Password for authentication with OOB controller.
|
||||
type: str
|
||||
auth_token:
|
||||
description:
|
||||
- Security token for authentication with OOB controller
|
||||
type: str
|
||||
timeout:
|
||||
description:
|
||||
- Timeout in seconds for URL requests to OOB controller.
|
||||
default: 10
|
||||
type: int
|
||||
resource_id:
|
||||
required: false
|
||||
description:
|
||||
- The ID of the System, Manager or Chassis to modify.
|
||||
type: str
|
||||
virtual_media:
|
||||
required: false
|
||||
description:
|
||||
- The options for VirtualMedia commands.
|
||||
type: dict
|
||||
suboptions:
|
||||
media_types:
|
||||
description:
|
||||
- The list of media types appropriate for the image.
|
||||
type: list
|
||||
elements: str
|
||||
image_url:
|
||||
description:
|
||||
- The URL of the image to insert or eject.
|
||||
type: str
|
||||
inserted:
|
||||
description:
|
||||
- Indicates if the image is treated as inserted on command completion.
|
||||
type: bool
|
||||
default: true
|
||||
write_protected:
|
||||
description:
|
||||
- Indicates if the media is treated as write-protected.
|
||||
type: bool
|
||||
default: true
|
||||
username:
|
||||
description:
|
||||
- The username for accessing the image URL.
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- The password for accessing the image URL.
|
||||
type: str
|
||||
transfer_protocol_type:
|
||||
description:
|
||||
- The network protocol to use with the image.
|
||||
type: str
|
||||
transfer_method:
|
||||
description:
|
||||
- The transfer method to use with the image.
|
||||
type: str
|
||||
resource_uri:
|
||||
required: false
|
||||
description:
|
||||
- The resource uri to get or patch or post.
|
||||
type: str
|
||||
request_body:
|
||||
required: false
|
||||
description:
|
||||
- The request body to patch or post.
|
||||
type: dict
|
||||
|
||||
author: "Yuyan Pan (@panyy3)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Insert Virtual Media
|
||||
community.general.xcc_redfish_command:
|
||||
category: Manager
|
||||
command: VirtualMediaInsert
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
virtual_media:
|
||||
image_url: "http://example.com/images/SomeLinux-current.iso"
|
||||
media_types:
|
||||
- CD
|
||||
- DVD
|
||||
resource_id: "1"
|
||||
|
||||
- name: Eject Virtual Media
|
||||
community.general.xcc_redfish_command:
|
||||
category: Manager
|
||||
command: VirtualMediaEject
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
virtual_media:
|
||||
image_url: "http://example.com/images/SomeLinux-current.iso"
|
||||
resource_id: "1"
|
||||
|
||||
- name: Eject all Virtual Media
|
||||
community.general.xcc_redfish_command:
|
||||
category: Manager
|
||||
command: VirtualMediaEject
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
resource_id: "1"
|
||||
|
||||
- name: Get ComputeSystem Oem property SystemStatus via GetResource command
|
||||
community.general.xcc_redfish_command:
|
||||
category: Raw
|
||||
command: GetResource
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
resource_uri: "/redfish/v1/Systems/1"
|
||||
register: result
|
||||
- ansible.builtin.debug:
|
||||
msg: "{{ result.redfish_facts.data.Oem.Lenovo.SystemStatus }}"
|
||||
|
||||
- name: Get Oem DNS setting via GetResource command
|
||||
community.general.xcc_redfish_command:
|
||||
category: Raw
|
||||
command: GetResource
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
resource_uri: "/redfish/v1/Managers/1/NetworkProtocol/Oem/Lenovo/DNS"
|
||||
register: result
|
||||
- ansible.builtin.debug:
|
||||
msg: "{{ result.redfish_facts.data }}"
|
||||
|
||||
- name: Get Lenovo FoD key collection resource via GetCollectionResource command
|
||||
community.general.xcc_redfish_command:
|
||||
category: Raw
|
||||
command: GetCollectionResource
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
resource_uri: "/redfish/v1/Managers/1/Oem/Lenovo/FoD/Keys"
|
||||
register: result
|
||||
- ansible.builtin.debug:
|
||||
msg: "{{ result.redfish_facts.data_list }}"
|
||||
|
||||
- name: Update ComputeSystem property AssetTag via PatchResource command
|
||||
community.general.xcc_redfish_command:
|
||||
category: Raw
|
||||
command: PatchResource
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
resource_uri: "/redfish/v1/Systems/1"
|
||||
request_body:
|
||||
AssetTag: "new_asset_tag"
|
||||
|
||||
- name: Perform BootToBIOSSetup action via PostResource command
|
||||
community.general.xcc_redfish_command:
|
||||
category: Raw
|
||||
command: PostResource
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
resource_uri: "/redfish/v1/Systems/1/Actions/Oem/LenovoComputerSystem.BootToBIOSSetup"
|
||||
request_body: {}
|
||||
|
||||
- name: Perform SecureBoot.ResetKeys action via PostResource command
|
||||
community.general.xcc_redfish_command:
|
||||
category: Raw
|
||||
command: PostResource
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
resource_uri: "/redfish/v1/Systems/1/SecureBoot/Actions/SecureBoot.ResetKeys"
|
||||
request_body:
|
||||
ResetKeysType: DeleteAllKeys
|
||||
|
||||
- name: Create session
|
||||
community.general.redfish_command:
|
||||
category: Sessions
|
||||
command: CreateSession
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
register: result
|
||||
|
||||
- name: Update Manager DateTimeLocalOffset property using security token for auth
|
||||
community.general.xcc_redfish_command:
|
||||
category: Raw
|
||||
command: PatchResource
|
||||
baseuri: "{{ baseuri }}"
|
||||
auth_token: "{{ result.session.token }}"
|
||||
resource_uri: "/redfish/v1/Managers/1"
|
||||
request_body:
|
||||
DateTimeLocalOffset: "+08:00"
|
||||
|
||||
- name: Delete session using security token created by CreateSesssion above
|
||||
community.general.redfish_command:
|
||||
category: Sessions
|
||||
command: DeleteSession
|
||||
baseuri: "{{ baseuri }}"
|
||||
auth_token: "{{ result.session.token }}"
|
||||
session_uri: "{{ result.session.uri }}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
msg:
|
||||
description: A message related to the performed action(s).
|
||||
returned: when failure or action/update success
|
||||
type: str
|
||||
sample: "Action was successful"
|
||||
redfish_facts:
|
||||
description: Resource content.
|
||||
returned: when command == GetResource or command == GetCollectionResource
|
||||
type: dict
|
||||
sample: '{
|
||||
"redfish_facts": {
|
||||
"data": {
|
||||
"@odata.etag": "\"3179bf00d69f25a8b3c\"",
|
||||
"@odata.id": "/redfish/v1/Managers/1/NetworkProtocol/Oem/Lenovo/DNS",
|
||||
"@odata.type": "#LenovoDNS.v1_0_0.LenovoDNS",
|
||||
"DDNS": [
|
||||
{
|
||||
"DDNSEnable": true,
|
||||
"DomainName": "",
|
||||
"DomainNameSource": "DHCP"
|
||||
}
|
||||
],
|
||||
"DNSEnable": true,
|
||||
"Description": "This resource is used to represent a DNS resource for a Redfish implementation.",
|
||||
"IPv4Address1": "10.103.62.178",
|
||||
"IPv4Address2": "0.0.0.0",
|
||||
"IPv4Address3": "0.0.0.0",
|
||||
"IPv6Address1": "::",
|
||||
"IPv6Address2": "::",
|
||||
"IPv6Address3": "::",
|
||||
"Id": "LenovoDNS",
|
||||
"PreferredAddresstype": "IPv4"
|
||||
},
|
||||
"ret": true
|
||||
}
|
||||
}'
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible_collections.community.general.plugins.module_utils.redfish_utils import RedfishUtils
|
||||
|
||||
|
||||
class XCCRedfishUtils(RedfishUtils):
|
||||
@staticmethod
|
||||
def _find_empty_virt_media_slot(resources, media_types,
|
||||
media_match_strict=True):
|
||||
for uri, data in resources.items():
|
||||
# check MediaTypes
|
||||
if 'MediaTypes' in data and media_types:
|
||||
if not set(media_types).intersection(set(data['MediaTypes'])):
|
||||
continue
|
||||
else:
|
||||
if media_match_strict:
|
||||
continue
|
||||
if 'RDOC' in uri:
|
||||
continue
|
||||
# if ejected, 'Inserted' should be False and 'ImageName' cleared
|
||||
if (not data.get('Inserted', False) and
|
||||
not data.get('ImageName')):
|
||||
return uri, data
|
||||
return None, None
|
||||
|
||||
def virtual_media_eject_one(self, image_url):
|
||||
# locate and read the VirtualMedia resources
|
||||
response = self.get_request(self.root_uri + self.manager_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
data = response['data']
|
||||
if 'VirtualMedia' not in data:
|
||||
return {'ret': False, 'msg': "VirtualMedia resource not found"}
|
||||
virt_media_uri = data["VirtualMedia"]["@odata.id"]
|
||||
response = self.get_request(self.root_uri + virt_media_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
data = response['data']
|
||||
virt_media_list = []
|
||||
for member in data[u'Members']:
|
||||
virt_media_list.append(member[u'@odata.id'])
|
||||
resources, headers = self._read_virt_media_resources(virt_media_list)
|
||||
|
||||
# find the VirtualMedia resource to eject
|
||||
uri, data, eject = self._find_virt_media_to_eject(resources, image_url)
|
||||
if uri and eject:
|
||||
if ('Actions' not in data or
|
||||
'#VirtualMedia.EjectMedia' not in data['Actions']):
|
||||
# try to eject via PATCH if no EjectMedia action found
|
||||
h = headers[uri]
|
||||
if 'allow' in h:
|
||||
methods = [m.strip() for m in h.get('allow').split(',')]
|
||||
if 'PATCH' not in methods:
|
||||
# if Allow header present and PATCH missing, return error
|
||||
return {'ret': False,
|
||||
'msg': "%s action not found and PATCH not allowed"
|
||||
% '#VirtualMedia.EjectMedia'}
|
||||
return self.virtual_media_eject_via_patch(uri)
|
||||
else:
|
||||
# POST to the EjectMedia Action
|
||||
action = data['Actions']['#VirtualMedia.EjectMedia']
|
||||
if 'target' not in action:
|
||||
return {'ret': False,
|
||||
'msg': "target URI property missing from Action "
|
||||
"#VirtualMedia.EjectMedia"}
|
||||
action_uri = action['target']
|
||||
# empty payload for Eject action
|
||||
payload = {}
|
||||
# POST to action
|
||||
response = self.post_request(self.root_uri + action_uri,
|
||||
payload)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
return {'ret': True, 'changed': True,
|
||||
'msg': "VirtualMedia ejected"}
|
||||
elif uri and not eject:
|
||||
# already ejected: return success but changed=False
|
||||
return {'ret': True, 'changed': False,
|
||||
'msg': "VirtualMedia image '%s' already ejected" %
|
||||
image_url}
|
||||
else:
|
||||
# return failure (no resources matching image_url found)
|
||||
return {'ret': False, 'changed': False,
|
||||
'msg': "No VirtualMedia resource found with image '%s' "
|
||||
"inserted" % image_url}
|
||||
|
||||
def virtual_media_eject(self, options):
|
||||
if options:
|
||||
image_url = options.get('image_url')
|
||||
if image_url: # eject specified one media
|
||||
return self.virtual_media_eject_one(image_url)
|
||||
|
||||
# eject all inserted media when no image_url specified
|
||||
# read all the VirtualMedia resources
|
||||
response = self.get_request(self.root_uri + self.manager_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
data = response['data']
|
||||
if 'VirtualMedia' not in data:
|
||||
return {'ret': False, 'msg': "VirtualMedia resource not found"}
|
||||
virt_media_uri = data["VirtualMedia"]["@odata.id"]
|
||||
response = self.get_request(self.root_uri + virt_media_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
data = response['data']
|
||||
virt_media_list = []
|
||||
for member in data[u'Members']:
|
||||
virt_media_list.append(member[u'@odata.id'])
|
||||
resources, headers = self._read_virt_media_resources(virt_media_list)
|
||||
|
||||
# eject all inserted media one by one
|
||||
ejected_media_list = []
|
||||
for uri, data in resources.items():
|
||||
if data.get('Image') and data.get('Inserted', True):
|
||||
returndict = self.virtual_media_eject_one(data.get('Image'))
|
||||
if not returndict['ret']:
|
||||
return returndict
|
||||
ejected_media_list.append(data.get('Image'))
|
||||
|
||||
if len(ejected_media_list) == 0:
|
||||
# no media inserted: return success but changed=False
|
||||
return {'ret': True, 'changed': False,
|
||||
'msg': "No VirtualMedia image inserted"}
|
||||
else:
|
||||
return {'ret': True, 'changed': True,
|
||||
'msg': "VirtualMedia %s ejected" % str(ejected_media_list)}
|
||||
|
||||
def raw_get_resource(self, resource_uri):
|
||||
if resource_uri is None:
|
||||
return {'ret': False, 'msg': "resource_uri is missing"}
|
||||
response = self.get_request(self.root_uri + resource_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
data = response['data']
|
||||
return {'ret': True, 'data': data}
|
||||
|
||||
def raw_get_collection_resource(self, resource_uri):
|
||||
if resource_uri is None:
|
||||
return {'ret': False, 'msg': "resource_uri is missing"}
|
||||
response = self.get_request(self.root_uri + resource_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
if 'Members' not in response['data']:
|
||||
return {'ret': False, 'msg': "Specified resource_uri doesn't have Members property"}
|
||||
member_list = [i['@odata.id'] for i in response['data'].get('Members', [])]
|
||||
|
||||
# get member resource one by one
|
||||
data_list = []
|
||||
for member_uri in member_list:
|
||||
uri = self.root_uri + member_uri
|
||||
response = self.get_request(uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
data = response['data']
|
||||
data_list.append(data)
|
||||
|
||||
return {'ret': True, 'data_list': data_list}
|
||||
|
||||
def raw_patch_resource(self, resource_uri, request_body):
|
||||
if resource_uri is None:
|
||||
return {'ret': False, 'msg': "resource_uri is missing"}
|
||||
if request_body is None:
|
||||
return {'ret': False, 'msg': "request_body is missing"}
|
||||
# check whether resource_uri existing or not
|
||||
response = self.get_request(self.root_uri + resource_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
original_etag = response['data']['@odata.etag']
|
||||
|
||||
# check validity of keys in request_body
|
||||
data = response['data']
|
||||
for key in request_body.keys():
|
||||
if key not in data:
|
||||
return {'ret': False, 'msg': "Key %s not found. Supported key list: %s" % (key, str(data.keys()))}
|
||||
|
||||
# perform patch
|
||||
response = self.patch_request(self.root_uri + resource_uri, request_body)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
|
||||
# check whether changed or not
|
||||
current_etag = ''
|
||||
if 'data' in response and '@odata.etag' in response['data']:
|
||||
current_etag = response['data']['@odata.etag']
|
||||
if current_etag != original_etag:
|
||||
return {'ret': True, 'changed': True}
|
||||
else:
|
||||
return {'ret': True, 'changed': False}
|
||||
|
||||
def raw_post_resource(self, resource_uri, request_body):
|
||||
if resource_uri is None:
|
||||
return {'ret': False, 'msg': "resource_uri is missing"}
|
||||
if '/Actions/' not in resource_uri:
|
||||
return {'ret': False, 'msg': "Bad uri %s. Keyword /Actions/ should be included in uri" % resource_uri}
|
||||
if request_body is None:
|
||||
return {'ret': False, 'msg': "request_body is missing"}
|
||||
# get action base uri data for further checking
|
||||
action_base_uri = resource_uri.split('/Actions/')[0]
|
||||
response = self.get_request(self.root_uri + action_base_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
if 'Actions' not in response['data']:
|
||||
return {'ret': False, 'msg': "Actions property not found in %s" % action_base_uri}
|
||||
|
||||
# check resouce_uri with target uri found in action base uri data
|
||||
action_found = False
|
||||
action_info_uri = None
|
||||
action_target_uri_list = []
|
||||
for key in response['data']['Actions'].keys():
|
||||
if action_found:
|
||||
break
|
||||
if not key.startswith('#'):
|
||||
continue
|
||||
if 'target' in response['data']['Actions'][key]:
|
||||
if resource_uri == response['data']['Actions'][key]['target']:
|
||||
action_found = True
|
||||
if '@Redfish.ActionInfo' in response['data']['Actions'][key]:
|
||||
action_info_uri = response['data']['Actions'][key]['@Redfish.ActionInfo']
|
||||
else:
|
||||
action_target_uri_list.append(response['data']['Actions'][key]['target'])
|
||||
if not action_found and 'Oem' in response['data']['Actions']:
|
||||
for key in response['data']['Actions']['Oem'].keys():
|
||||
if action_found:
|
||||
break
|
||||
if not key.startswith('#'):
|
||||
continue
|
||||
if 'target' in response['data']['Actions']['Oem'][key]:
|
||||
if resource_uri == response['data']['Actions']['Oem'][key]['target']:
|
||||
action_found = True
|
||||
if '@Redfish.ActionInfo' in response['data']['Actions']['Oem'][key]:
|
||||
action_info_uri = response['data']['Actions']['Oem'][key]['@Redfish.ActionInfo']
|
||||
else:
|
||||
action_target_uri_list.append(response['data']['Actions']['Oem'][key]['target'])
|
||||
|
||||
if not action_found:
|
||||
return {'ret': False,
|
||||
'msg': 'Specified resource_uri is not a supported action target uri, please specify a supported target uri instead. Supported uri: %s'
|
||||
% (str(action_target_uri_list))}
|
||||
|
||||
# check request_body with parameter name defined by @Redfish.ActionInfo
|
||||
if action_info_uri is not None:
|
||||
response = self.get_request(self.root_uri + action_info_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
for key in request_body.keys():
|
||||
key_found = False
|
||||
for para in response['data']['Parameters']:
|
||||
if key == para['Name']:
|
||||
key_found = True
|
||||
break
|
||||
if not key_found:
|
||||
return {'ret': False,
|
||||
'msg': 'Invalid property %s found in request_body. Please refer to @Redfish.ActionInfo Parameters: %s'
|
||||
% (key, str(response['data']['Parameters']))}
|
||||
|
||||
# perform post
|
||||
response = self.post_request(self.root_uri + resource_uri, request_body)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
return {'ret': True, 'changed': True}
|
||||
|
||||
|
||||
# More will be added as module features are expanded
|
||||
CATEGORY_COMMANDS_ALL = {
|
||||
"Manager": ["VirtualMediaInsert",
|
||||
"VirtualMediaEject"],
|
||||
"Raw": ["GetResource",
|
||||
"GetCollectionResource",
|
||||
"PatchResource",
|
||||
"PostResource"]
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
result = {}
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
category=dict(required=True),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True),
|
||||
timeout=dict(type='int', default=10),
|
||||
resource_id=dict(),
|
||||
virtual_media=dict(
|
||||
type='dict',
|
||||
options=dict(
|
||||
media_types=dict(type='list', elements='str', default=[]),
|
||||
image_url=dict(),
|
||||
inserted=dict(type='bool', default=True),
|
||||
write_protected=dict(type='bool', default=True),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
transfer_protocol_type=dict(),
|
||||
transfer_method=dict(),
|
||||
)
|
||||
),
|
||||
resource_uri=dict(),
|
||||
request_body=dict(
|
||||
type='dict',
|
||||
),
|
||||
),
|
||||
required_together=[
|
||||
('username', 'password'),
|
||||
],
|
||||
required_one_of=[
|
||||
('username', 'auth_token'),
|
||||
],
|
||||
mutually_exclusive=[
|
||||
('username', 'auth_token'),
|
||||
],
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
category = module.params['category']
|
||||
command_list = module.params['command']
|
||||
|
||||
# admin credentials used for authentication
|
||||
creds = {'user': module.params['username'],
|
||||
'pswd': module.params['password'],
|
||||
'token': module.params['auth_token']}
|
||||
|
||||
# timeout
|
||||
timeout = module.params['timeout']
|
||||
|
||||
# System, Manager or Chassis ID to modify
|
||||
resource_id = module.params['resource_id']
|
||||
|
||||
# VirtualMedia options
|
||||
virtual_media = module.params['virtual_media']
|
||||
|
||||
# resource_uri
|
||||
resource_uri = module.params['resource_uri']
|
||||
|
||||
# request_body
|
||||
request_body = module.params['request_body']
|
||||
|
||||
# Build root URI
|
||||
root_uri = "https://" + module.params['baseuri']
|
||||
rf_utils = XCCRedfishUtils(creds, root_uri, timeout, module, resource_id=resource_id, data_modification=True)
|
||||
|
||||
# Check that Category is valid
|
||||
if category not in CATEGORY_COMMANDS_ALL:
|
||||
module.fail_json(msg=to_native("Invalid Category '%s'. Valid Categories = %s" % (category, CATEGORY_COMMANDS_ALL.keys())))
|
||||
|
||||
# Check that all commands are valid
|
||||
for cmd in command_list:
|
||||
# Fail if even one command given is invalid
|
||||
if cmd not in CATEGORY_COMMANDS_ALL[category]:
|
||||
module.fail_json(msg=to_native("Invalid Command '%s'. Valid Commands = %s" % (cmd, CATEGORY_COMMANDS_ALL[category])))
|
||||
|
||||
# Organize by Categories / Commands
|
||||
if category == "Manager":
|
||||
# execute only if we find a Manager service resource
|
||||
result = rf_utils._find_managers_resource()
|
||||
if result['ret'] is False:
|
||||
module.fail_json(msg=to_native(result['msg']))
|
||||
|
||||
for command in command_list:
|
||||
if command == 'VirtualMediaInsert':
|
||||
result = rf_utils.virtual_media_insert(virtual_media)
|
||||
elif command == 'VirtualMediaEject':
|
||||
result = rf_utils.virtual_media_eject(virtual_media)
|
||||
elif category == "Raw":
|
||||
for command in command_list:
|
||||
if command == 'GetResource':
|
||||
result = rf_utils.raw_get_resource(resource_uri)
|
||||
elif command == 'GetCollectionResource':
|
||||
result = rf_utils.raw_get_collection_resource(resource_uri)
|
||||
elif command == 'PatchResource':
|
||||
result = rf_utils.raw_patch_resource(resource_uri, request_body)
|
||||
elif command == 'PostResource':
|
||||
result = rf_utils.raw_post_resource(resource_uri, request_body)
|
||||
|
||||
# Return data back or fail with proper message
|
||||
if result['ret'] is True:
|
||||
if command == 'GetResource' or command == 'GetCollectionResource':
|
||||
module.exit_json(redfish_facts=result)
|
||||
else:
|
||||
changed = result.get('changed', True)
|
||||
msg = result.get('msg', 'Action was successful')
|
||||
module.exit_json(changed=changed, msg=msg)
|
||||
else:
|
||||
module.fail_json(msg=to_native(result['msg']))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -24,6 +24,7 @@ options:
|
||||
name:
|
||||
description:
|
||||
- Data Center name.
|
||||
type: str
|
||||
options:
|
||||
description:
|
||||
- "Retrieve additional information. Options available: 'visualContent'."
|
||||
|
||||
@@ -24,6 +24,7 @@ options:
|
||||
name:
|
||||
description:
|
||||
- Enclosure name.
|
||||
type: str
|
||||
options:
|
||||
description:
|
||||
- "List with options to gather additional information about an Enclosure and related resources.
|
||||
|
||||
@@ -24,11 +24,13 @@ options:
|
||||
- C(present) will ensure data properties are compliant with OneView.
|
||||
- C(absent) will remove the resource from OneView, if it exists.
|
||||
- C(default_bandwidth_reset) will reset the network connection template to the default.
|
||||
type: str
|
||||
default: present
|
||||
choices: [present, absent, default_bandwidth_reset]
|
||||
data:
|
||||
description:
|
||||
- List with Ethernet Network properties.
|
||||
type: dict
|
||||
required: true
|
||||
extends_documentation_fragment:
|
||||
- community.general.oneview
|
||||
|
||||
@@ -23,6 +23,7 @@ options:
|
||||
name:
|
||||
description:
|
||||
- Ethernet Network name.
|
||||
type: str
|
||||
options:
|
||||
description:
|
||||
- "List with options to gather additional information about an Ethernet Network and related resources.
|
||||
|
||||
@@ -20,11 +20,13 @@ options:
|
||||
- Indicates the desired state for the Fibre Channel Network resource.
|
||||
C(present) will ensure data properties are compliant with OneView.
|
||||
C(absent) will remove the resource from OneView, if it exists.
|
||||
type: str
|
||||
choices: ['present', 'absent']
|
||||
required: true
|
||||
data:
|
||||
description:
|
||||
- List with the Fibre Channel Network properties.
|
||||
type: dict
|
||||
required: true
|
||||
|
||||
extends_documentation_fragment:
|
||||
|
||||
@@ -23,6 +23,7 @@ options:
|
||||
name:
|
||||
description:
|
||||
- Fibre Channel Network name.
|
||||
type: str
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.oneview
|
||||
|
||||
@@ -21,11 +21,13 @@ options:
|
||||
- Indicates the desired state for the FCoE Network resource.
|
||||
C(present) will ensure data properties are compliant with OneView.
|
||||
C(absent) will remove the resource from OneView, if it exists.
|
||||
type: str
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
data:
|
||||
description:
|
||||
- List with FCoE Network properties.
|
||||
type: dict
|
||||
required: true
|
||||
|
||||
extends_documentation_fragment:
|
||||
|
||||
@@ -23,6 +23,7 @@ options:
|
||||
name:
|
||||
description:
|
||||
- FCoE Network name.
|
||||
type: str
|
||||
extends_documentation_fragment:
|
||||
- community.general.oneview
|
||||
- community.general.oneview.factsparams
|
||||
|
||||
@@ -24,11 +24,13 @@ options:
|
||||
- Indicates the desired state for the Logical Interconnect Group resource.
|
||||
C(absent) will remove the resource from OneView, if it exists.
|
||||
C(present) will ensure data properties are compliant with OneView.
|
||||
type: str
|
||||
choices: [absent, present]
|
||||
default: present
|
||||
data:
|
||||
description:
|
||||
- List with the Logical Interconnect Group properties.
|
||||
type: dict
|
||||
required: true
|
||||
extends_documentation_fragment:
|
||||
- community.general.oneview
|
||||
|
||||
@@ -24,6 +24,7 @@ options:
|
||||
name:
|
||||
description:
|
||||
- Logical Interconnect Group name.
|
||||
type: str
|
||||
extends_documentation_fragment:
|
||||
- community.general.oneview
|
||||
- community.general.oneview.factsparams
|
||||
|
||||
@@ -23,11 +23,13 @@ options:
|
||||
- Indicates the desired state for the Network Set resource.
|
||||
- C(present) will ensure data properties are compliant with OneView.
|
||||
- C(absent) will remove the resource from OneView, if it exists.
|
||||
type: str
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
data:
|
||||
description:
|
||||
- List with the Network Set properties.
|
||||
type: dict
|
||||
required: true
|
||||
|
||||
extends_documentation_fragment:
|
||||
|
||||
@@ -23,6 +23,7 @@ options:
|
||||
name:
|
||||
description:
|
||||
- Network Set name.
|
||||
type: str
|
||||
|
||||
options:
|
||||
description:
|
||||
|
||||
@@ -24,12 +24,14 @@ options:
|
||||
- C(present) ensures data properties are compliant with OneView.
|
||||
- C(absent) removes the resource from OneView, if it exists.
|
||||
- C(connection_information_set) updates the connection information for the SAN Manager. This operation is non-idempotent.
|
||||
type: str
|
||||
default: present
|
||||
choices: [present, absent, connection_information_set]
|
||||
data:
|
||||
description:
|
||||
- List with SAN Manager properties.
|
||||
required: true
|
||||
description:
|
||||
- List with SAN Manager properties.
|
||||
type: dict
|
||||
required: true
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.oneview
|
||||
|
||||
@@ -23,6 +23,7 @@ options:
|
||||
provider_display_name:
|
||||
description:
|
||||
- Provider Display Name.
|
||||
type: str
|
||||
params:
|
||||
description:
|
||||
- List of params to delimit, filter and sort the list of resources.
|
||||
@@ -31,6 +32,7 @@ options:
|
||||
- C(count): The number of resources to return.
|
||||
- C(query): A general query string to narrow the list of resources returned.
|
||||
- C(sort): The sort order of the returned data set."
|
||||
type: dict
|
||||
extends_documentation_fragment:
|
||||
- community.general.oneview
|
||||
|
||||
|
||||
@@ -216,14 +216,13 @@ def main():
|
||||
args = [git_path, "config", "--includes"]
|
||||
if params['list_all']:
|
||||
args.append('-l')
|
||||
if scope:
|
||||
args.append("--" + scope)
|
||||
if name:
|
||||
args.append(name)
|
||||
|
||||
if scope == 'file':
|
||||
args.append('-f')
|
||||
args.append(params['file'])
|
||||
elif scope:
|
||||
args.append("--" + scope)
|
||||
if name:
|
||||
args.append(name)
|
||||
|
||||
if scope == 'local':
|
||||
dir = params['repo']
|
||||
|
||||
@@ -258,6 +258,18 @@ options:
|
||||
configured setting unless a different value is specified
|
||||
in the playbook.
|
||||
type: str
|
||||
force:
|
||||
description:
|
||||
- When creating a volume, ignores any existing file system
|
||||
or VDO signature already present in the storage device.
|
||||
When stopping or removing a VDO volume, first unmounts
|
||||
the file system stored on the device if mounted.
|
||||
- "B(Warning:) Since this parameter removes all safety
|
||||
checks it is important to make sure that all parameters
|
||||
provided are accurate and intentional."
|
||||
type: bool
|
||||
default: no
|
||||
version_added: 2.4.0
|
||||
notes:
|
||||
- In general, the default thread configuration should be used.
|
||||
requirements:
|
||||
@@ -409,6 +421,9 @@ def add_vdooptions(params):
|
||||
if ('indexmode' in params) and (params['indexmode'] == 'sparse'):
|
||||
options.append("--sparseIndex=enabled")
|
||||
|
||||
if ('force' in params) and (params['force']):
|
||||
options.append("--force")
|
||||
|
||||
# Entering an invalid thread config results in a cryptic
|
||||
# 'Could not set up device mapper for %s' error from the 'vdo'
|
||||
# command execution. The dmsetup module on the system will
|
||||
@@ -465,7 +480,8 @@ def run_module():
|
||||
biothreads=dict(type='str'),
|
||||
cputhreads=dict(type='str'),
|
||||
logicalthreads=dict(type='str'),
|
||||
physicalthreads=dict(type='str')
|
||||
physicalthreads=dict(type='str'),
|
||||
force=dict(type='bool', default=False),
|
||||
)
|
||||
|
||||
# Seed the result dictionary in the object. There will be an
|
||||
|
||||
1
plugins/modules/xcc_redfish_command.py
Symbolic link
1
plugins/modules/xcc_redfish_command.py
Symbolic link
@@ -0,0 +1 @@
|
||||
remote_management/lenovoxcc/xcc_redfish_command.py
|
||||
@@ -1,2 +1,3 @@
|
||||
shippable/posix/group3
|
||||
skip/aix
|
||||
destructive
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
- import_tasks: setup_no_value.yml
|
||||
|
||||
- name: setting value with state=present
|
||||
git_config:
|
||||
name: "{{ option_name }}"
|
||||
value: "{{ option_value }}"
|
||||
scope: "file"
|
||||
file: "{{ output_dir }}/gitconfig_file"
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- name: getting value with state=present
|
||||
git_config:
|
||||
name: "{{ option_name }}"
|
||||
scope: "file"
|
||||
file: "{{ output_dir }}/gitconfig_file"
|
||||
state: present
|
||||
register: get_result
|
||||
|
||||
- name: assert set changed and value is correct with state=present
|
||||
assert:
|
||||
that:
|
||||
- set_result is changed
|
||||
- set_result.diff.before == "\n"
|
||||
- set_result.diff.after == option_value + "\n"
|
||||
- get_result is not changed
|
||||
- get_result.config_value == option_value
|
||||
...
|
||||
@@ -16,6 +16,8 @@
|
||||
- import_tasks: get_set_no_state.yml
|
||||
# testing get/set option with state=present
|
||||
- import_tasks: get_set_state_present.yml
|
||||
# testing get/set option with state=present and scope=file
|
||||
- import_tasks: get_set_state_present_file.yml
|
||||
# testing state=absent without value to delete
|
||||
- import_tasks: unset_no_value.yml
|
||||
# testing state=absent with value to delete
|
||||
|
||||
@@ -5,4 +5,9 @@
|
||||
file:
|
||||
path: ~/.gitconfig
|
||||
state: absent
|
||||
...
|
||||
|
||||
- name: set up without value (file)
|
||||
file:
|
||||
path: "{{ output_dir }}/gitconfig_file"
|
||||
state: absent
|
||||
...
|
||||
|
||||
@@ -5,4 +5,9 @@
|
||||
copy:
|
||||
src: gitconfig
|
||||
dest: ~/.gitconfig
|
||||
...
|
||||
|
||||
- name: set up with value (file)
|
||||
copy:
|
||||
src: gitconfig
|
||||
dest: "{{ output_dir }}/gitconfig_file"
|
||||
...
|
||||
|
||||
2
tests/integration/targets/one_template/aliases
Normal file
2
tests/integration/targets/one_template/aliases
Normal file
@@ -0,0 +1,2 @@
|
||||
cloud/opennebula
|
||||
shippable/cloud/group1
|
||||
Binary file not shown.
243
tests/integration/targets/one_template/tasks/main.yml
Normal file
243
tests/integration/targets/one_template/tasks/main.yml
Normal file
@@ -0,0 +1,243 @@
|
||||
####################################################################
|
||||
# WARNING: These are designed specifically for Ansible tests #
|
||||
# and should not be used as examples of how to write Ansible roles #
|
||||
####################################################################
|
||||
|
||||
# test code for the one_template module
|
||||
|
||||
|
||||
# ENVIRONMENT PREPARATION
|
||||
|
||||
- name: "copy fixtures to test host"
|
||||
copy:
|
||||
src: testhost/tmp/opennebula-fixtures.json.gz
|
||||
dest: /tmp
|
||||
when:
|
||||
- opennebula_test_fixture
|
||||
- opennebula_test_fixture_replay
|
||||
|
||||
|
||||
# Create a new template
|
||||
|
||||
- name: "Create a new TEMPLATE"
|
||||
one_template:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name: ansible-onetemplate-test
|
||||
template: |
|
||||
CONTEXT = [
|
||||
HOSTNAME = "ansible-onetemplate",
|
||||
NETWORK = "YES",
|
||||
SSH_PUBLIC_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKAQwTkU84eEnhX3r60Mn5TPh99BDxyCNJu12OB5sfMu foxy@FoxPad",
|
||||
USERNAME = "root" ]
|
||||
CPU = "1"
|
||||
CUSTOM_ATTRIBUTE = ""
|
||||
DISK = [
|
||||
CACHE = "writeback",
|
||||
DEV_PREFIX = "sd",
|
||||
DISCARD = "unmap",
|
||||
IMAGE = "ansible-onetemplate",
|
||||
IMAGE_UNAME = "oneadmin",
|
||||
IO = "threads",
|
||||
SIZE = "" ]
|
||||
FEATURES = [
|
||||
VIRTIO_SCSI_QUEUES = "2" ]
|
||||
GRAPHICS = [
|
||||
KEYMAP = "de",
|
||||
LISTEN = "0.0.0.0",
|
||||
TYPE = "VNC" ]
|
||||
MEMORY = "2048"
|
||||
NIC = [
|
||||
MODEL = "virtio",
|
||||
NETWORK = "tf-prd-centos",
|
||||
NETWORK_UNAME = "oneadmin" ]
|
||||
OS = [
|
||||
ARCH = "x86_64",
|
||||
BOOT = "disk0" ]
|
||||
SCHED_REQUIREMENTS = "CLUSTER_ID=\"100\""
|
||||
VCPU = "2"
|
||||
environment:
|
||||
PYONE_TEST_FIXTURE: "{{ opennebula_test_fixture }}"
|
||||
PYONE_TEST_FIXTURE_FILE: /tmp/opennebula-fixtures.json.gz
|
||||
PYONE_TEST_FIXTURE_REPLAY: "{{ opennebula_test_fixture_replay }}"
|
||||
PYONE_TEST_FIXTURE_UNIT: test_create_template
|
||||
register: result
|
||||
|
||||
- name: "assert that creation worked"
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
|
||||
# Updating a template
|
||||
|
||||
- name: "Update an existing TEMPLATE"
|
||||
one_template:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name: ansible-onetemplate-test
|
||||
template: |
|
||||
CONTEXT = [
|
||||
HOSTNAME = "ansible-onetemplate",
|
||||
NETWORK = "YES",
|
||||
SSH_PUBLIC_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKAQwTkU84eEnhX3r60Mn5TPh99BDxyCNJu12OB5sfMu foxy@FoxPad",
|
||||
USERNAME = "root" ]
|
||||
CPU = "1"
|
||||
CUSTOM_ATTRIBUTE = ""
|
||||
DISK = [
|
||||
CACHE = "writeback",
|
||||
DEV_PREFIX = "sd",
|
||||
DISCARD = "unmap",
|
||||
IMAGE = "ansible-onetemplate",
|
||||
IMAGE_UNAME = "oneadmin",
|
||||
IO = "threads",
|
||||
SIZE = "" ]
|
||||
FEATURES = [
|
||||
VIRTIO_SCSI_QUEUES = "2" ]
|
||||
GRAPHICS = [
|
||||
KEYMAP = "de",
|
||||
LISTEN = "0.0.0.0",
|
||||
TYPE = "VNC" ]
|
||||
MEMORY = "4096"
|
||||
NIC = [
|
||||
MODEL = "virtio",
|
||||
NETWORK = "tf-prd-centos",
|
||||
NETWORK_UNAME = "oneadmin" ]
|
||||
OS = [
|
||||
ARCH = "x86_64",
|
||||
BOOT = "disk0" ]
|
||||
SCHED_REQUIREMENTS = "CLUSTER_ID=\"100\""
|
||||
VCPU = "2"
|
||||
environment:
|
||||
PYONE_TEST_FIXTURE: "{{ opennebula_test_fixture }}"
|
||||
PYONE_TEST_FIXTURE_FILE: /tmp/opennebula-fixtures.json.gz
|
||||
PYONE_TEST_FIXTURE_REPLAY: "{{ opennebula_test_fixture_replay }}"
|
||||
PYONE_TEST_FIXTURE_UNIT: test_update_existing_template
|
||||
register: result
|
||||
|
||||
- name: "assert that it updated the template"
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: "Update an existing TEMPLATE with the same changes again"
|
||||
one_template:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name: ansible-onetemplate-test
|
||||
template: |
|
||||
CONTEXT = [
|
||||
HOSTNAME = "ansible-onetemplate",
|
||||
NETWORK = "YES",
|
||||
SSH_PUBLIC_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKAQwTkU84eEnhX3r60Mn5TPh99BDxyCNJu12OB5sfMu foxy@FoxPad",
|
||||
USERNAME = "root" ]
|
||||
CPU = "1"
|
||||
CUSTOM_ATTRIBUTE = ""
|
||||
DISK = [
|
||||
CACHE = "writeback",
|
||||
DEV_PREFIX = "sd",
|
||||
DISCARD = "unmap",
|
||||
IMAGE = "ansible-onetemplate",
|
||||
IMAGE_UNAME = "oneadmin",
|
||||
IO = "threads",
|
||||
SIZE = "" ]
|
||||
FEATURES = [
|
||||
VIRTIO_SCSI_QUEUES = "2" ]
|
||||
GRAPHICS = [
|
||||
KEYMAP = "de",
|
||||
LISTEN = "0.0.0.0",
|
||||
TYPE = "VNC" ]
|
||||
MEMORY = "4096"
|
||||
NIC = [
|
||||
MODEL = "virtio",
|
||||
NETWORK = "tf-prd-centos",
|
||||
NETWORK_UNAME = "oneadmin" ]
|
||||
OS = [
|
||||
ARCH = "x86_64",
|
||||
BOOT = "disk0" ]
|
||||
SCHED_REQUIREMENTS = "CLUSTER_ID=\"100\""
|
||||
VCPU = "2"
|
||||
environment:
|
||||
PYONE_TEST_FIXTURE: "{{ opennebula_test_fixture }}"
|
||||
PYONE_TEST_FIXTURE_FILE: /tmp/opennebula-fixtures.json.gz
|
||||
PYONE_TEST_FIXTURE_REPLAY: "{{ opennebula_test_fixture_replay }}"
|
||||
PYONE_TEST_FIXTURE_UNIT: test_update_existing_and_already_updated_template
|
||||
register: result
|
||||
|
||||
- name: "assert that there was no change"
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
|
||||
# Deletion of templates
|
||||
|
||||
- name: "Delete a nonexisting TEMPLATE"
|
||||
one_template:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name: ansible-onetemplate-test-nonexisting
|
||||
state: absent
|
||||
environment:
|
||||
PYONE_TEST_FIXTURE: "{{ opennebula_test_fixture }}"
|
||||
PYONE_TEST_FIXTURE_FILE: /tmp/opennebula-fixtures.json.gz
|
||||
PYONE_TEST_FIXTURE_REPLAY: "{{ opennebula_test_fixture_replay }}"
|
||||
PYONE_TEST_FIXTURE_UNIT: test_delete_nonexisting_template
|
||||
register: result
|
||||
|
||||
- name: "assert that there was no change"
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: "Delete an existing TEMPLATE"
|
||||
one_template:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name: ansible-onetemplate-test
|
||||
state: absent
|
||||
environment:
|
||||
PYONE_TEST_FIXTURE: "{{ opennebula_test_fixture }}"
|
||||
PYONE_TEST_FIXTURE_FILE: /tmp/opennebula-fixtures.json.gz
|
||||
PYONE_TEST_FIXTURE_REPLAY: "{{ opennebula_test_fixture_replay }}"
|
||||
PYONE_TEST_FIXTURE_UNIT: test_delete_existing_template
|
||||
register: result
|
||||
|
||||
- name: "assert that there was a change"
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
|
||||
# Usage without `template` parameter
|
||||
|
||||
- name: "Try to create use one_template with state=present and without the template parameter"
|
||||
one_template:
|
||||
api_url: "{{ opennebula_url }}"
|
||||
api_username: "{{ opennebula_username }}"
|
||||
api_password: "{{ opennebula_password }}"
|
||||
name: ansible-onetemplate-test
|
||||
state: present
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- name: "assert that it failed because template is missing"
|
||||
assert:
|
||||
that:
|
||||
- result is failed
|
||||
|
||||
|
||||
# TEARDOWN
|
||||
|
||||
- name: "fetch fixtures"
|
||||
fetch:
|
||||
src: /tmp/opennebula-fixtures.json.gz
|
||||
dest: targets/one_host/files
|
||||
when:
|
||||
- opennebula_test_fixture
|
||||
- not opennebula_test_fixture_replay
|
||||
@@ -134,38 +134,6 @@ plugins/modules/remote_management/manageiq/manageiq_provider.py validate-modules
|
||||
plugins/modules/remote_management/manageiq/manageiq_provider.py validate-modules:parameter-type-not-in-doc # missing docs on suboptions
|
||||
plugins/modules/remote_management/manageiq/manageiq_provider.py validate-modules:undocumented-parameter # missing docs on suboptions
|
||||
plugins/modules/remote_management/manageiq/manageiq_tags.py validate-modules:parameter-state-invalid-choice
|
||||
plugins/modules/remote_management/oneview/oneview_datacenter_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_datacenter_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_enclosure_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_enclosure_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_ethernet_network.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_ethernet_network.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_ethernet_network_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_ethernet_network_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network.py validate-modules:doc-missing-type
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network.py validate-modules:doc-missing-type
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group.py validate-modules:doc-missing-type
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_network_set.py validate-modules:doc-missing-type
|
||||
plugins/modules/remote_management/oneview/oneview_network_set.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_network_set.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_network_set_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_network_set_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_san_manager.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_san_manager.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_san_manager_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_san_manager_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/stacki/stacki_host.py validate-modules:doc-default-does-not-match-spec
|
||||
plugins/modules/remote_management/stacki/stacki_host.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/stacki/stacki_host.py validate-modules:undocumented-parameter
|
||||
|
||||
@@ -133,38 +133,6 @@ plugins/modules/remote_management/manageiq/manageiq_provider.py validate-modules
|
||||
plugins/modules/remote_management/manageiq/manageiq_provider.py validate-modules:parameter-type-not-in-doc # missing docs on suboptions
|
||||
plugins/modules/remote_management/manageiq/manageiq_provider.py validate-modules:undocumented-parameter # missing docs on suboptions
|
||||
plugins/modules/remote_management/manageiq/manageiq_tags.py validate-modules:parameter-state-invalid-choice
|
||||
plugins/modules/remote_management/oneview/oneview_datacenter_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_datacenter_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_enclosure_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_enclosure_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_ethernet_network.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_ethernet_network.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_ethernet_network_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_ethernet_network_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network.py validate-modules:doc-missing-type
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network.py validate-modules:doc-missing-type
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group.py validate-modules:doc-missing-type
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_network_set.py validate-modules:doc-missing-type
|
||||
plugins/modules/remote_management/oneview/oneview_network_set.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_network_set.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_network_set_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_network_set_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_san_manager.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_san_manager.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_san_manager_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_san_manager_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/stacki/stacki_host.py validate-modules:doc-default-does-not-match-spec
|
||||
plugins/modules/remote_management/stacki/stacki_host.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/stacki/stacki_host.py validate-modules:undocumented-parameter
|
||||
|
||||
@@ -165,38 +165,6 @@ plugins/modules/remote_management/manageiq/manageiq_provider.py validate-modules
|
||||
plugins/modules/remote_management/manageiq/manageiq_provider.py validate-modules:doc-missing-type # missing docs on suboptions
|
||||
plugins/modules/remote_management/manageiq/manageiq_provider.py validate-modules:parameter-type-not-in-doc # missing docs on suboptions
|
||||
plugins/modules/remote_management/manageiq/manageiq_provider.py validate-modules:undocumented-parameter # missing docs on suboptions
|
||||
plugins/modules/remote_management/oneview/oneview_datacenter_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_datacenter_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_enclosure_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_enclosure_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_ethernet_network.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_ethernet_network.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_ethernet_network_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_ethernet_network_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network.py validate-modules:doc-missing-type
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_fc_network_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network.py validate-modules:doc-missing-type
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_fcoe_network_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group.py validate-modules:doc-missing-type
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_logical_interconnect_group_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_network_set.py validate-modules:doc-missing-type
|
||||
plugins/modules/remote_management/oneview/oneview_network_set.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_network_set.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_network_set_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_network_set_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_san_manager.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_san_manager.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/oneview/oneview_san_manager_info.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/oneview/oneview_san_manager_info.py validate-modules:undocumented-parameter
|
||||
plugins/modules/remote_management/stacki/stacki_host.py validate-modules:doc-default-does-not-match-spec
|
||||
plugins/modules/remote_management/stacki/stacki_host.py validate-modules:parameter-type-not-in-doc
|
||||
plugins/modules/remote_management/stacki/stacki_host.py validate-modules:undocumented-parameter
|
||||
|
||||
45
tests/unit/plugins/become/test_sudosu.py
Normal file
45
tests/unit/plugins/become/test_sudosu.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# (c) 2021 Ansible Project
|
||||
#
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
|
||||
from ansible import context
|
||||
from ansible.playbook.play_context import PlayContext
|
||||
from ansible.plugins.loader import become_loader
|
||||
|
||||
|
||||
def test_sudosu(mocker, parser, reset_cli_args):
|
||||
options = parser.parse_args([])
|
||||
context._init_global_context(options)
|
||||
play_context = PlayContext()
|
||||
|
||||
default_cmd = "/bin/foo"
|
||||
default_exe = "/bin/bash"
|
||||
sudo_exe = 'sudo'
|
||||
sudo_flags = '-H -s -n'
|
||||
|
||||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable=default_exe)
|
||||
assert cmd == default_cmd
|
||||
|
||||
success = 'BECOME-SUCCESS-.+?'
|
||||
|
||||
play_context.become = True
|
||||
play_context.become_user = 'foo'
|
||||
play_context.set_become_plugin(become_loader.get('community.general.sudosu'))
|
||||
play_context.become_flags = sudo_flags
|
||||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable=default_exe)
|
||||
|
||||
assert (re.match("""%s %s su -l %s %s -c 'echo %s; %s'""" % (sudo_exe, sudo_flags, play_context.become_user,
|
||||
default_exe, success, default_cmd), cmd) is not None)
|
||||
|
||||
play_context.become_pass = 'testpass'
|
||||
cmd = play_context.make_become_cmd(cmd=default_cmd, executable=default_exe)
|
||||
assert (re.match("""%s %s -p "%s" su -l %s %s -c 'echo %s; %s'""" % (sudo_exe, sudo_flags.replace('-n', ''),
|
||||
r"\[sudo via ansible, key=.+?\] password:", play_context.become_user,
|
||||
default_exe, success, default_cmd), cmd) is not None)
|
||||
64
tests/unit/plugins/callback/test_loganalytics.py
Normal file
64
tests/unit/plugins/callback/test_loganalytics.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# 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
|
||||
|
||||
from ansible.executor.task_result import TaskResult
|
||||
from ansible_collections.community.general.tests.unit.compat import unittest
|
||||
from ansible_collections.community.general.tests.unit.compat.mock import patch, call, MagicMock, Mock
|
||||
from ansible_collections.community.general.plugins.callback.loganalytics import AzureLogAnalyticsSource
|
||||
from datetime import datetime
|
||||
|
||||
import json
|
||||
|
||||
|
||||
class TestAzureLogAnalytics(unittest.TestCase):
|
||||
@patch('ansible_collections.community.general.plugins.callback.loganalytics.socket')
|
||||
def setUp(self, mock_socket):
|
||||
mock_socket.gethostname.return_value = 'my-host'
|
||||
mock_socket.gethostbyname.return_value = '1.2.3.4'
|
||||
self.loganalytics = AzureLogAnalyticsSource()
|
||||
self.mock_task = Mock('MockTask')
|
||||
self.mock_task._role = 'myrole'
|
||||
self.mock_task._uuid = 'myuuid'
|
||||
self.task_fields = {'args': {}}
|
||||
self.mock_host = Mock('MockHost')
|
||||
self.mock_host.name = 'myhost'
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.callback.loganalytics.datetime')
|
||||
@patch('ansible_collections.community.general.plugins.callback.loganalytics.open_url')
|
||||
def test_overall(self, open_url_mock, mock_datetime):
|
||||
mock_datetime.utcnow.return_value = datetime(2020, 12, 1)
|
||||
result = TaskResult(host=self.mock_host, task=self.mock_task, return_data={}, task_fields=self.task_fields)
|
||||
|
||||
self.loganalytics.send_event(workspace_id='01234567-0123-0123-0123-01234567890a',
|
||||
shared_key='dZD0kCbKl3ehZG6LHFMuhtE0yHiFCmetzFMc2u+roXIUQuatqU924SsAAAAPemhjbGlAemhjbGktTUJQAQIDBA==',
|
||||
state='OK',
|
||||
result=result,
|
||||
runtime=100)
|
||||
|
||||
args, kwargs = open_url_mock.call_args
|
||||
sent_data = json.loads(args[1])
|
||||
|
||||
self.assertEqual(sent_data['event']['timestamp'], 'Tue, 01 Dec 2020 00:00:00 GMT')
|
||||
self.assertEqual(sent_data['event']['host'], 'my-host')
|
||||
self.assertEqual(sent_data['event']['uuid'], 'myuuid')
|
||||
self.assertEqual(args[0], 'https://01234567-0123-0123-0123-01234567890a.ods.opinsights.azure.com/api/logs?api-version=2016-04-01')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.callback.loganalytics.datetime')
|
||||
@patch('ansible_collections.community.general.plugins.callback.loganalytics.open_url')
|
||||
def test_auth_headers(self, open_url_mock, mock_datetime):
|
||||
mock_datetime.utcnow.return_value = datetime(2020, 12, 1)
|
||||
result = TaskResult(host=self.mock_host, task=self.mock_task, return_data={}, task_fields=self.task_fields)
|
||||
|
||||
self.loganalytics.send_event(workspace_id='01234567-0123-0123-0123-01234567890a',
|
||||
shared_key='dZD0kCbKl3ehZG6LHFMuhtE0yHiFCmetzFMc2u+roXIUQuatqU924SsAAAAPemhjbGlAemhjbGktTUJQAQIDBA==',
|
||||
state='OK',
|
||||
result=result,
|
||||
runtime=100)
|
||||
|
||||
args, kwargs = open_url_mock.call_args
|
||||
headers = kwargs['headers']
|
||||
|
||||
self.assertRegexpMatches(headers['Authorization'], r'^SharedKey 01234567-0123-0123-0123-01234567890a:.*=$')
|
||||
self.assertEqual(headers['Log-Type'], 'ansible_playbook')
|
||||
@@ -0,0 +1,626 @@
|
||||
# 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
|
||||
|
||||
import json
|
||||
from ansible_collections.community.general.tests.unit.compat import mock
|
||||
from ansible_collections.community.general.tests.unit.compat.mock import patch
|
||||
from ansible_collections.community.general.tests.unit.compat import unittest
|
||||
from ansible.module_utils import basic
|
||||
from ansible.module_utils._text import to_bytes
|
||||
import ansible_collections.community.general.plugins.modules.remote_management.lenovoxcc.xcc_redfish_command as module
|
||||
from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson
|
||||
from ansible_collections.community.general.tests.unit.plugins.modules.utils import set_module_args, exit_json, fail_json
|
||||
|
||||
|
||||
def get_bin_path(self, arg, required=False):
|
||||
"""Mock AnsibleModule.get_bin_path"""
|
||||
return arg
|
||||
|
||||
|
||||
class TestXCCRedfishCommand(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mock_module_helper = patch.multiple(basic.AnsibleModule,
|
||||
exit_json=exit_json,
|
||||
fail_json=fail_json,
|
||||
get_bin_path=get_bin_path)
|
||||
self.mock_module_helper.start()
|
||||
self.addCleanup(self.mock_module_helper.stop)
|
||||
|
||||
def test_module_fail_when_required_args_missing(self):
|
||||
with self.assertRaises(AnsibleFailJson):
|
||||
set_module_args({})
|
||||
module.main()
|
||||
|
||||
def test_module_fail_when_unknown_category(self):
|
||||
with self.assertRaises(AnsibleFailJson):
|
||||
set_module_args({
|
||||
'category': 'unknown',
|
||||
'command': 'VirtualMediaEject',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
})
|
||||
module.main()
|
||||
|
||||
def test_module_fail_when_unknown_command(self):
|
||||
with self.assertRaises(AnsibleFailJson):
|
||||
set_module_args({
|
||||
'category': 'Manager',
|
||||
'command': 'unknown',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
})
|
||||
module.main()
|
||||
|
||||
def test_module_command_VirtualMediaInsert_pass(self):
|
||||
set_module_args({
|
||||
'category': 'Manager',
|
||||
'command': 'VirtualMediaInsert',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'timeout': 30,
|
||||
'virtual_media': {
|
||||
'image_url': "nfs://10.245.52.18:/home/nfs/bootable-sr635-20210111-autorun.iso",
|
||||
'media_types': ['CD'],
|
||||
'inserted': True,
|
||||
'write_protected': True,
|
||||
'transfer_protocol_type': 'NFS'
|
||||
}
|
||||
})
|
||||
with patch.object(module.XCCRedfishUtils, '_find_managers_resource') as mock__find_managers_resource:
|
||||
mock__find_managers_resource.return_value = {'ret': True, 'changed': True, 'msg': 'success'}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'virtual_media_insert') as mock_virtual_media_insert:
|
||||
mock_virtual_media_insert.return_value = {'ret': True, 'changed': True, 'msg': 'success'}
|
||||
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_VirtualMediaEject_pass(self):
|
||||
set_module_args({
|
||||
'category': 'Manager',
|
||||
'command': 'VirtualMediaEject',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'timeout': 30,
|
||||
'virtual_media': {
|
||||
'image_url': "nfs://10.245.52.18:/home/nfs/bootable-sr635-20210111-autorun.iso",
|
||||
}
|
||||
})
|
||||
with patch.object(module.XCCRedfishUtils, '_find_managers_resource') as mock__find_managers_resource:
|
||||
mock__find_managers_resource.return_value = {'ret': True, 'changed': True, 'msg': 'success'}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'virtual_media_eject') as mock_virtual_media_eject:
|
||||
mock_virtual_media_eject.return_value = {'ret': True, 'changed': True, 'msg': 'success'}
|
||||
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_VirtualMediaEject_fail_when_required_args_missing(self):
|
||||
with self.assertRaises(AnsibleFailJson):
|
||||
set_module_args({
|
||||
'category': 'Manager',
|
||||
'command': 'VirtualMediaEject',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
})
|
||||
module.main()
|
||||
|
||||
def test_module_command_GetResource_fail_when_required_args_missing(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'GetResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_GetResource_fail_when_get_return_false(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'GetResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/testuri',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': False, 'msg': '404 error'}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_GetResource_pass(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'GetResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/testuri',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
|
||||
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_GetCollectionResource_fail_when_required_args_missing(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'GetCollectionResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_GetCollectionResource_fail_when_get_return_false(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'GetCollectionResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/testuri',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': False, 'msg': '404 error'}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_GetCollectionResource_fail_when_get_not_colection(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'GetCollectionResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/testuri',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_GetCollectionResource_pass_when_get_empty_collection(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'GetCollectionResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/testuri',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': True, 'data': {'Members': [], 'Members@odata.count': 0}}
|
||||
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_GetCollectionResource_pass_when_get_collection(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'GetCollectionResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/testuri',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': True, 'data': {'Members': [{'@odata.id': '/redfish/v1/testuri/1'}], 'Members@odata.count': 1}}
|
||||
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PatchResource_fail_when_required_args_missing(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PatchResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx', '@odata.etag': '27f6eb13fa1c28a2711'}}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'patch_request') as mock_patch_request:
|
||||
mock_patch_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PatchResource_fail_when_required_args_missing_no_requestbody(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PatchResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/testuri',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx', '@odata.etag': '27f6eb13fa1c28a2711'}}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'patch_request') as mock_patch_request:
|
||||
mock_patch_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PatchResource_fail_when_noexisting_property_in_requestbody(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PatchResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/testuri',
|
||||
'request_body': {'teststr': 'yyyy', 'otherkey': 'unknownkey'}
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx', '@odata.etag': '27f6eb13fa1c28a2711'}}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'patch_request') as mock_patch_request:
|
||||
mock_patch_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx'}}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PatchResource_fail_when_get_return_false(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PatchResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/testuri',
|
||||
'request_body': {'teststr': 'yyyy'}
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx', '@odata.etag': '27f6eb13fa1c28a2711'}}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'patch_request') as mock_patch_request:
|
||||
mock_patch_request.return_value = {'ret': False, 'msg': '500 internal error'}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PatchResource_pass(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PatchResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/testuri',
|
||||
'request_body': {'teststr': 'yyyy'}
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': True, 'data': {'teststr': 'xxxx', '@odata.etag': '27f6eb13fa1c28a2711'}}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'patch_request') as mock_patch_request:
|
||||
mock_patch_request.return_value = {'ret': True, 'data': {'teststr': 'yyyy', '@odata.etag': '322e0d45d9572723c98'}}
|
||||
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PostResource_fail_when_required_args_missing(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PostResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {
|
||||
'ret': True,
|
||||
'data': {
|
||||
'Actions': {
|
||||
'#Bios.ChangePassword': {
|
||||
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
|
||||
'title': "ChangePassword",
|
||||
'PasswordName@Redfish.AllowableValues': [
|
||||
"UefiAdminPassword",
|
||||
"UefiPowerOnPassword"
|
||||
]
|
||||
},
|
||||
'#Bios.ResetBios': {
|
||||
'title': "ResetBios",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
|
||||
mock_post_request.return_value = {'ret': True}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PostResource_fail_when_invalid_resourceuri(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PostResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/testuri',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {
|
||||
'ret': True,
|
||||
'data': {
|
||||
'Actions': {
|
||||
'#Bios.ChangePassword': {
|
||||
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
|
||||
'title': "ChangePassword",
|
||||
'PasswordName@Redfish.AllowableValues': [
|
||||
"UefiAdminPassword",
|
||||
"UefiPowerOnPassword"
|
||||
]
|
||||
},
|
||||
'#Bios.ResetBios': {
|
||||
'title': "ResetBios",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
|
||||
mock_post_request.return_value = {'ret': True}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PostResource_fail_when_no_requestbody(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PostResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {
|
||||
'ret': True,
|
||||
'data': {
|
||||
'Actions': {
|
||||
'#Bios.ChangePassword': {
|
||||
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
|
||||
'title': "ChangePassword",
|
||||
'PasswordName@Redfish.AllowableValues': [
|
||||
"UefiAdminPassword",
|
||||
"UefiPowerOnPassword"
|
||||
]
|
||||
},
|
||||
'#Bios.ResetBios': {
|
||||
'title': "ResetBios",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
|
||||
mock_post_request.return_value = {'ret': True}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PostResource_fail_when_no_requestbody(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PostResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword',
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {
|
||||
'ret': True,
|
||||
'data': {
|
||||
'Actions': {
|
||||
'#Bios.ChangePassword': {
|
||||
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
|
||||
'title': "ChangePassword",
|
||||
'PasswordName@Redfish.AllowableValues': [
|
||||
"UefiAdminPassword",
|
||||
"UefiPowerOnPassword"
|
||||
]
|
||||
},
|
||||
'#Bios.ResetBios': {
|
||||
'title': "ResetBios",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
|
||||
mock_post_request.return_value = {'ret': True}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PostResource_fail_when_requestbody_mismatch_with_data_from_actioninfo_uri(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PostResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword',
|
||||
'request_body': {'PasswordName': 'UefiAdminPassword', 'NewPassword': 'PASSW0RD=='}
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {
|
||||
'ret': True,
|
||||
'data': {
|
||||
'Parameters': [],
|
||||
'Actions': {
|
||||
'#Bios.ChangePassword': {
|
||||
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
|
||||
'title': "ChangePassword",
|
||||
'PasswordName@Redfish.AllowableValues': [
|
||||
"UefiAdminPassword",
|
||||
"UefiPowerOnPassword"
|
||||
]
|
||||
},
|
||||
'#Bios.ResetBios': {
|
||||
'title': "ResetBios",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
|
||||
mock_post_request.return_value = {'ret': True}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PostResource_fail_when_get_return_false(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PostResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword',
|
||||
'request_body': {'PasswordName': 'UefiAdminPassword', 'NewPassword': 'PASSW0RD=='}
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {'ret': False, 'msg': '404 error'}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
|
||||
mock_post_request.return_value = {'ret': True}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PostResource_fail_when_post_return_false(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PostResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios',
|
||||
'request_body': {}
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {
|
||||
'ret': True,
|
||||
'data': {
|
||||
'Actions': {
|
||||
'#Bios.ChangePassword': {
|
||||
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
|
||||
'title': "ChangePassword",
|
||||
'PasswordName@Redfish.AllowableValues': [
|
||||
"UefiAdminPassword",
|
||||
"UefiPowerOnPassword"
|
||||
]
|
||||
},
|
||||
'#Bios.ResetBios': {
|
||||
'title': "ResetBios",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
|
||||
mock_post_request.return_value = {'ret': False, 'msg': '500 internal error'}
|
||||
|
||||
with self.assertRaises(AnsibleFailJson) as result:
|
||||
module.main()
|
||||
|
||||
def test_module_command_PostResource_pass(self):
|
||||
set_module_args({
|
||||
'category': 'Raw',
|
||||
'command': 'PostResource',
|
||||
'baseuri': '10.245.39.251',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
'resource_uri': '/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios',
|
||||
'request_body': {}
|
||||
})
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'get_request') as mock_get_request:
|
||||
mock_get_request.return_value = {
|
||||
'ret': True,
|
||||
'data': {
|
||||
'Actions': {
|
||||
'#Bios.ChangePassword': {
|
||||
'@Redfish.ActionInfo': "/redfish/v1/Systems/1/Bios/ChangePasswordActionInfo",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ChangePassword",
|
||||
'title': "ChangePassword",
|
||||
'PasswordName@Redfish.AllowableValues': [
|
||||
"UefiAdminPassword",
|
||||
"UefiPowerOnPassword"
|
||||
]
|
||||
},
|
||||
'#Bios.ResetBios': {
|
||||
'title': "ResetBios",
|
||||
'target': "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
with patch.object(module.XCCRedfishUtils, 'post_request') as mock_post_request:
|
||||
mock_post_request.return_value = {'ret': True, 'msg': 'post success'}
|
||||
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
module.main()
|
||||
Reference in New Issue
Block a user