Compare commits

...

17 Commits
2.3.0 ... 2.4.0

Author SHA1 Message Date
Felix Fontein
8c67a5bda9 Release 2.4.0. 2021-03-30 12:39:01 +02:00
patchback[bot]
4ae436a8cc Callback plugin: Azure Log Analytics (#2091) (#2133)
* adding plugins/callback/loganalytics.py

* * fixed sanity check issues
* adjusted documentation and license sections

* added changelogs fragment

* * added unit test
* documentation updated

* updated changelogs

* further docuement update

* minor fixes

* updated unittest

* suggested updates from community

* remove AnsibleError section

(cherry picked from commit 19db6f24f7)

Co-authored-by: zhcli <49675498+zhcli@users.noreply.github.com>
2021-03-30 12:37:52 +02:00
Felix Fontein
5f5c07a942 Add release summary. 2021-03-30 12:14:33 +02:00
patchback[bot]
1cef1359d0 git_config - fixed bug with scope file (#2125) (#2132)
* fixed bug

- scope file was not working
- added test to guarantee that behaviour
- marked integration test as destructive, because it overwrites ~/.gitconfig

* added changelog fragment

* Update tests/integration/targets/git_config/tasks/setup_no_value.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update tests/integration/targets/git_config/tasks/get_set_state_present_file.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update tests/integration/targets/git_config/tasks/get_set_state_present_file.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update tests/integration/targets/git_config/aliases

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update changelogs/fragments/2125-git-config-scope-file.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit eb24e33666)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-03-30 09:20:04 +02:00
patchback[bot]
0d28bfb67e vdo: add force option (#2110) (#2123)
* vdo: add force option

* Add changelog

* Improve the diff the next time something is added :)

Co-authored-by: Felix Fontein <felix@fontein.de>

* Add warning text placeholder by felixfontein

Co-authored-by: Felix Fontein <felix@fontein.de>

* Add warning text

* Apply suggestion for warning text from rhawalsh

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 73bb0f1900)

Co-authored-by: Amin Vakil <info@aminvakil.com>
2021-03-27 15:41:58 +01:00
patchback[bot]
ef304ed824 remove billdodd from team_redfish (#2118) (#2120)
(cherry picked from commit 0de196413f)

Co-authored-by: Bill Dodd <billdodd@gmail.com>
2021-03-26 20:08:05 +01:00
patchback[bot]
bf17f289b3 AZP: update default container version (#2112) (#2114)
(cherry picked from commit 0bc76c98b0)

Co-authored-by: Andrew Klychkov <aklychko@redhat.com>
2021-03-26 13:09:43 +01:00
patchback[bot]
0eff87d0be opennebula: add one_template module (#2046) (#2111)
* opennebula: add one_template module

A basic module for maintaining VM templates which should be flexible enough
for most needs ...

* fixup! opennebula: add one_template module

* fixup! fixup! opennebula: add one_template module

(cherry picked from commit cdc415ea1f)

Co-authored-by: Georg Gadinger <nilsding@nilsding.org>
2021-03-26 07:47:38 +01:00
Abhijeet Kasurde
f00fabfa48 Typo fix in changelog (#2030)
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2021-03-26 07:24:37 +01:00
patchback[bot]
426cbafa06 ipa_service - Correct pluralisation of "hosts" in example (#2103) (#2105)
(cherry picked from commit 2558cd3f01)

Co-authored-by: Alex Willmer <al.willmer@cgi.com>
2021-03-26 07:09:53 +01:00
patchback[bot]
93fe1f9a3e Bugfix: Respect PATH env variable in zypper modules (#2094) (#2109)
* Bugfix: Respect PATH env variable in zypper modules

* Improve changelogs/fragments/2094-bugfix-respect-PATH-env-variable-in-zypper-modules.yaml

Co-authored-by: Felix Fontein <felix@fontein.de>

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit e7a0a12c3f)

Co-authored-by: Stefan Richter <sealor@users.noreply.github.com>
2021-03-25 22:57:53 +01:00
patchback[bot]
4e944772d5 Updated vdo maintainer to rhawalsh. (#2102) (#2107)
bgurney-rh does not work with VDO projects anymore.  This change re-points
maintainer pings to rhawalsh instead.

(cherry picked from commit 62cd38a9a0)

Co-authored-by: Andy Walsh <33293922+rhawalsh@users.noreply.github.com>
2021-03-25 22:46:04 +01:00
patchback[bot]
50abeee579 Add a Pulp 2 related note to pulp_repo (#2096) (#2100)
(cherry picked from commit de8e2a83e2)

Co-authored-by: Matthias Dellweg <2500@gmx.de>
2021-03-24 18:13:04 +01:00
patchback[bot]
eccc8d88b6 Add support for sudo su - using password auth (#2054) (#2097)
* Add support for `sudo su -` using password auth

Allow users to run Ansible tasks through `sudo su -` using password auth

- Feature Pull Request

sudosu

So I have been using this at various customers for bootstrapping Ansible mostly.

Often you have an existing setup where there is a user that has root-access enabled through sudo, but only to run `su` to log using the user's password.
In these specific cases the root password is unique to the system and therefore not an easy way to automate bootstrapping.

Having a `sudo su -` become option **with password prompt** is not possible with the existing become methods (neither sudo nor su can be used) by abusing `become_exe` or `become_flags`.

This fixes ansible/ansible#12686

* Fix all reported issues

* Add unit tests

* Apply suggestions from code review

* Update plugins/become/sudosu.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update tests/unit/plugins/become/test_sudosu.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update tests/unit/plugins/become/test_sudosu.py

Co-authored-by: Felix Fontein <felix@fontein.de>

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit db26514bf1)

Co-authored-by: Dag Wieers <dag@wieers.com>
2021-03-24 17:48:53 +01:00
patchback[bot]
6d2d364a00 add new module xcc_redfish_command to manage Lenovo servers using Redfish APIs (#2007) (#2095)
* add new module xcc_redfish_command to manage Lenovo servers using Redfish APIs

* Update plugins/modules/remote_management/lenovoxcc/xcc_redfish_command.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* fix some errors detected by ansible-test sanity

* end all descriptions (except short_description) with a period

* fix return definition problem and other errors detected by ansible-test sanity

* Always use true/false for booleans in YAML

* It is usually a good idea to leave away required: false

* fix errors detected by ansible-test sanity

* fix elements of command is not defined

* check whether resource_uri is specified for Raw commands

* if no Members property, return false; if empty array, return true

* get @odata.etag from patch body instead of getting again

* add request_body checking

* add unit test for the module

* fix errors detected by ansible-test sanity --test pep8

* update class name xcc_RedfishUtils to XCCRedfishUtils to follow convention; import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, exit_json, fail_json from ansible_collections.community.general.tests.unit.plugins.modules.utils instead of inline them

* support using security token for auth

* fix line too long error

* As 2.3.0 got released yesterday, move to 2.4.0

* add maintainers for lenovoxcc

* update to make sure that it's sorted alphabetically

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 04f46f0435)

Co-authored-by: panyy3 <panyy3@lenovo.com>
2021-03-24 16:43:11 +01:00
patchback[bot]
e781dd3c9b fixed documentation for oneview modules (#2092) (#2093)
(cherry picked from commit 94cf07efbf)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-03-24 16:41:03 +01:00
Felix Fontein
362f899a99 Next expected release is 2.4.0. 2021-03-23 13:30:16 +01:00
47 changed files with 2477 additions and 131 deletions

View File

@@ -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
View File

@@ -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

View File

@@ -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).

View File

@@ -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'

View File

@@ -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
View 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)])

View 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)
)

View File

@@ -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

View File

@@ -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='')

View 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()

View File

@@ -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

View File

@@ -0,0 +1 @@
./cloud/opennebula/one_template.py

View File

@@ -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:

View File

@@ -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)

View File

@@ -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])

View File

@@ -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()

View File

@@ -24,6 +24,7 @@ options:
name:
description:
- Data Center name.
type: str
options:
description:
- "Retrieve additional information. Options available: 'visualContent'."

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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:

View File

@@ -23,6 +23,7 @@ options:
name:
description:
- Fibre Channel Network name.
type: str
extends_documentation_fragment:
- community.general.oneview

View File

@@ -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:

View File

@@ -23,6 +23,7 @@ options:
name:
description:
- FCoE Network name.
type: str
extends_documentation_fragment:
- community.general.oneview
- community.general.oneview.factsparams

View File

@@ -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

View File

@@ -24,6 +24,7 @@ options:
name:
description:
- Logical Interconnect Group name.
type: str
extends_documentation_fragment:
- community.general.oneview
- community.general.oneview.factsparams

View File

@@ -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:

View File

@@ -23,6 +23,7 @@ options:
name:
description:
- Network Set name.
type: str
options:
description:

View File

@@ -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

View File

@@ -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

View File

@@ -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']

View File

@@ -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

View File

@@ -0,0 +1 @@
remote_management/lenovoxcc/xcc_redfish_command.py

View File

@@ -1,2 +1,3 @@
shippable/posix/group3
skip/aix
destructive

View File

@@ -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
...

View File

@@ -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

View File

@@ -5,4 +5,9 @@
file:
path: ~/.gitconfig
state: absent
...
- name: set up without value (file)
file:
path: "{{ output_dir }}/gitconfig_file"
state: absent
...

View File

@@ -5,4 +5,9 @@
copy:
src: gitconfig
dest: ~/.gitconfig
...
- name: set up with value (file)
copy:
src: gitconfig
dest: "{{ output_dir }}/gitconfig_file"
...

View File

@@ -0,0 +1,2 @@
cloud/opennebula
shippable/cloud/group1

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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)

View 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')

View File

@@ -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()